馃攢 Request Middleware

KNETMiddleware i KNETMiddlewarePipeline oferuj膮 prosty pattern do kompozycji logiki przetwarzania request贸w.

馃挕 Middleware vs Interceptor

Middleware to prostsza alternatywa dla interceptor贸w. Idealne do szybkich transformacji request贸w/response bez potrzeby implementacji pe艂nego interfejsu.

馃摝 Import

import rip.nerd.kitsunenet.util.KNETMiddleware
import rip.nerd.kitsunenet.util.KNETMiddlewarePipeline
import rip.nerd.kitsunenet.util.through
import rip.nerd.kitsunenet.util.then

馃殌 Szybki start

// Definiuj middleware
val authMiddleware = KNETMiddleware.request { request ->
    request.copy(
        headers = request.headers + ("Authorization" to "Bearer $token")
    )
}

// Stw贸rz pipeline
val pipeline = KNETMiddlewarePipeline()
    .use(authMiddleware)

// Wykonaj request przez pipeline
val response = pipeline.execute(client, request)

馃敡 Typy middleware

Request middleware

Modyfikuje tylko request przed wys艂aniem.

val addHeader = KNETMiddleware.request { request ->
    request.copy(
        headers = request.headers + ("X-Custom" to "value")
    )
}

val addQuery = KNETMiddleware.request { request ->
    request.copy(
        query = request.query + ("api_version" to "v2")
    )
}

Response middleware

Modyfikuje tylko response po otrzymaniu.

val logStatus = KNETMiddleware.response { response ->
    println("Status: ${response.statusCode}")
    response  // zwr贸膰 bez modyfikacji
}

val mapErrors = KNETMiddleware.response { response ->
    if (!response.isSuccessful) {
        // Transform error response
        response.copy(bodyString = "Error: ${response.statusCode}")
    } else response
}

Full middleware

Pe艂na kontrola nad request i response.

val timingMiddleware = KNETMiddleware.full { request, next ->
    val start = System.currentTimeMillis()
    val response = next(request)
    val duration = System.currentTimeMillis() - start

    println("${request.method} ${request.url} took ${duration}ms")
    response
}

val retryMiddleware = KNETMiddleware.full { request, next ->
    var lastResponse: KNETResponse? = null

    repeat(3) { attempt ->
        val response = next(request)
        if (response.isSuccessful) return@full response
        lastResponse = response
        delay(1000L * (attempt + 1))
    }

    lastResponse!!
}

Tap middleware (side effects)

Obserwuje bez modyfikacji.

val analytics = KNETMiddleware.tap(
    onRequest = { request ->
        analytics.logRequest(request.url)
    },
    onResponse = { request, response ->
        analytics.logResponse(request.url, response.statusCode)
    }
)

馃摝 Builtin middleware

Headers

// Pojedynczy header
KNETMiddleware.Builtin.addHeader("X-Custom", "value")

// Dynamiczny header
KNETMiddleware.Builtin.addHeader("X-Timestamp") {
    System.currentTimeMillis().toString()
}

// Wiele headers
KNETMiddleware.Builtin.addHeaders(mapOf(
    "X-App-Name" to "MyApp",
    "X-App-Version" to "1.0.0"
))

Autoryzacja

// Bearer token
KNETMiddleware.Builtin.bearerToken { authRepository.getToken() }

// Basic auth
KNETMiddleware.Builtin.basicAuth("username", "password")

// API Key w header
KNETMiddleware.Builtin.apiKey("X-API-Key", "your-api-key")

// API Key w query
KNETMiddleware.Builtin.apiKeyQuery("api_key", "your-api-key")

Logging & Timing

// Simple logging
KNETMiddleware.Builtin.logging()

// Custom logger
KNETMiddleware.Builtin.logging(
    logRequest = true,
    logResponse = true,
    logger = { Log.d("KNET", it) }
)

// Timing
KNETMiddleware.Builtin.timing { request, response, duration ->
    metrics.recordLatency(request.url, duration)
}

Retry

// Simple retry
KNETMiddleware.Builtin.retry(maxAttempts = 3)

// Custom retry logic
KNETMiddleware.Builtin.retry(
    maxAttempts = 5,
    delayMs = 2000,
    shouldRetry = { response ->
        response.statusCode in listOf(502, 503, 504)
    }
)

Utility

// Request ID
KNETMiddleware.Builtin.requestId()

// Custom request ID generator
KNETMiddleware.Builtin.requestId { "req_${UUID.randomUUID()}" }

// User-Agent
KNETMiddleware.Builtin.userAgent("MyApp/1.0.0 Android/12")

// Accept header
KNETMiddleware.Builtin.accept("application/json")

// Content-Type
KNETMiddleware.Builtin.contentType("application/json")

// No cache
KNETMiddleware.Builtin.noCache()

// Cache control
KNETMiddleware.Builtin.cacheControl("max-age=3600")

馃敆 Pipeline

Podstawowe u偶ycie

val pipeline = KNETMiddlewarePipeline()
    .use(KNETMiddleware.Builtin.accept())
    .use(KNETMiddleware.Builtin.requestId())
    .use(KNETMiddleware.Builtin.bearerToken { getToken() })
    .use(KNETMiddleware.Builtin.logging())

// Wykonaj request
val response = pipeline.execute(client, request)

// Lub z extension
val response = request.through(pipeline, client)

Warunkowe middleware

pipeline.useIf(
    condition = { request -> request.url.contains("/admin") },
    middleware = KNETMiddleware.Builtin.addHeader("X-Admin-Token", adminToken)
)

// Tylko dla POST/PUT
pipeline.useForMethods("POST", "PUT") {
    KNETMiddleware.Builtin.contentType("application/json")
}

Factory methods

// Z middleware
val pipeline = KNETMiddlewarePipeline.of(
    authMiddleware,
    loggingMiddleware,
    retryMiddleware
)

// Z auth
val pipeline = KNETMiddlewarePipeline.withAuth { getToken() }

// Z logging
val pipeline = KNETMiddlewarePipeline.withLogging { Log.d("KNET", it) }

// Z retry
val pipeline = KNETMiddlewarePipeline.withRetry(maxAttempts = 3)

// Standardowy dla API
val pipeline = KNETMiddlewarePipeline.standard(
    tokenProvider = { authRepository.getToken() },
    userAgent = "MyApp/1.0.0",
    enableLogging = BuildConfig.DEBUG
)

Kompozycja

// 艁膮czenie middleware
val combined = authMiddleware.then(loggingMiddleware)

// Kopiowanie pipeline
val copy = pipeline.copy()

// Merge dw贸ch pipeline
val merged = basePipeline.merge(additionalPipeline)

馃挕 Praktyczne przyk艂ady

API Client z middleware

class ApiClient(
    private val client: KNETClient,
    private val authRepository: AuthRepository
) {
    private val pipeline = KNETMiddlewarePipeline()
        .use(KNETMiddleware.Builtin.accept("application/json"))
        .use(KNETMiddleware.Builtin.contentType("application/json"))
        .use(KNETMiddleware.Builtin.userAgent("MyApp/${BuildConfig.VERSION_NAME}"))
        .use(KNETMiddleware.Builtin.requestId())
        .use(KNETMiddleware.Builtin.bearerToken { authRepository.getToken() })
        .use(KNETMiddleware.Builtin.retry(maxAttempts = 3))
        .apply {
            if (BuildConfig.DEBUG) {
                use(KNETMiddleware.Builtin.logging())
            }
        }

    suspend fun get(url: String): KNETResponse {
        val request = KNETRequest.get(url)
        return pipeline.execute(client, request)
    }

    suspend fun post(url: String, data: Map<String, Any>): KNETResponse {
        val request = KNETRequest.post(url, data)
        return pipeline.execute(client, request)
    }
}

Multi-tenant middleware

class TenantMiddleware(private val tenantProvider: () -> String) {

    fun create() = KNETMiddleware.request { request ->
        val tenant = tenantProvider()
        request.copy(
            headers = request.headers + mapOf(
                "X-Tenant-ID" to tenant,
                "X-Tenant-Host" to "$tenant.example.com"
            )
        )
    }
}

val pipeline = KNETMiddlewarePipeline()
    .use(TenantMiddleware { currentTenant }.create())
    .use(authMiddleware)

Request signing middleware

fun signingMiddleware(secretKey: String) = KNETMiddleware.request { request ->
    val timestamp = System.currentTimeMillis()
    val payload = "${request.method}|${request.url}|$timestamp"
    val signature = hmacSha256(secretKey, payload)

    request.copy(
        headers = request.headers + mapOf(
            "X-Timestamp" to timestamp.toString(),
            "X-Signature" to signature
        )
    )
}

val pipeline = KNETMiddlewarePipeline()
    .use(signingMiddleware(BuildConfig.API_SECRET))

Feature flags middleware

class FeatureFlagMiddleware(private val featureFlags: FeatureFlags) {

    fun create() = KNETMiddleware.request { request ->
        val flags = featureFlags.getActiveFlags().joinToString(",")
        request.copy(
            headers = request.headers + ("X-Feature-Flags" to flags)
        )
    }
}

pipeline.use(FeatureFlagMiddleware(featureFlags).create())

A/B Testing middleware

fun abTestMiddleware(userId: String) = KNETMiddleware.request { request ->
    val variant = abTestService.getVariant(userId, "api_experiment")
    request.copy(
        headers = request.headers + mapOf(
            "X-Experiment-Variant" to variant,
            "X-User-Segment" to userSegmentService.getSegment(userId)
        )
    )
}

馃敆 API Reference

KNETMiddleware

Factory Opis
request { } Modyfikuje tylko request
response { } Modyfikuje tylko response
full { request, next -> } Pe艂na kontrola
tap(onRequest, onResponse) Side effects bez modyfikacji
conditional(condition, middleware) Warunkowe wykonanie
forMethods(methods, middleware) Tylko dla metod HTTP
forUrls(pattern, middleware) Tylko dla URL matching

KNETMiddlewarePipeline

Metoda Opis
use(middleware) Dodaje middleware
useBefore(middleware) Dodaje na pocz膮tek
useIf(condition, middleware) Warunkowe middleware
useForMethods(methods, middleware) Dla metod HTTP
execute(client, request) Wykonuje request
copy() Kopiuje pipeline
merge(other) 艁膮czy dwa pipeline
clear() Czy艣ci middleware

馃摎 Zobacz te偶