Exemples de Fonctionnalités Web Android Kotlin

Exemples de fonctionnalités web Android Kotlin incluant le routage URL, la gestion des middlewares et le service de fichiers statiques

Key Facts

Category
Kotlin
Items
3
Format Families
audio

Sample Overview

Exemples de fonctionnalités web Android Kotlin incluant le routage URL, la gestion des middlewares et le service de fichiers statiques This sample set belongs to Kotlin and can be used to test related workflows inside Elysia Tools.

💻 Routage URL kotlin

🟡 intermediate ⭐⭐⭐⭐

Analyser et gérer les routes URL avec des paramètres, des caractères génériques et des modèles RESTful

⏱️ 35 min 🏷️ kotlin, android, web, routing
Prerequisites: Intermediate Kotlin, HTTP basics
// Android Kotlin URL Routing Examples
// Using custom routing implementation for handling URL patterns

import java.util.regex.Pattern

// 1. Basic Route Definition
data class Route(
    val path: String,
    val handler: (Map<String, String>) -> String
)

// 2. Route Parser
class RouteParser {

    // Parse route path and extract parameters
    fun parsePath(path: String): List<String> {
        return path.split("/").filter { it.isNotEmpty() }
    }

    // Convert route pattern to regex
    fun patternToRegex(pattern: String): Pattern {
        val regexPattern = pattern.replace(Regex("\{([^}]+)\}"), "([^/]+)")
        return Pattern.compile("^$regexPattern$")
    }

    // Extract parameter names from pattern
    fun extractParamNames(pattern: String): List<String> {
        val params = mutableListOf<String>()
        val matcher = Pattern.compile("\{([^}]+)\}").matcher(pattern)
        while (matcher.find()) {
            params.add(matcher.group(1))
        }
        return params
    }

    // Match path against pattern and extract parameters
    fun match(pattern: String, path: String): Map<String, String>? {
        val regex = patternToRegex(pattern)
        val matcher = regex.matcher(path)

        if (!matcher.matches()) {
            return null
        }

        val paramNames = extractParamNames(pattern)
        val params = mutableMapOf<String, String>()

        for (i in paramNames.indices) {
            params[paramNames[i]] = matcher.group(i + 1)
        }

        return params
    }
}

// 3. Simple Router
class SimpleRouter {

    private val routes = mutableListOf<Route>()
    private val parser = RouteParser()

    // Add route
    fun addRoute(path: String, handler: (Map<String, String>) -> String) {
        routes.add(Route(path, handler))
    }

    // GET route
    fun get(path: String, handler: (Map<String, String>) -> String) {
        addRoute("GET:$path", handler)
    }

    // POST route
    fun post(path: String, handler: (Map<String, String>) -> String) {
        addRoute("POST:$path", handler)
    }

    // PUT route
    fun put(path: String, handler: (Map<String, String>) -> String) {
        addRoute("PUT:$path", handler)
    }

    // DELETE route
    fun delete(path: String, handler: (Map<String, String>) -> String) {
        addRoute("DELETE:$path", handler)
    }

    // Handle incoming request
    fun handle(method: String, path: String): String? {
        val fullPath = "$method:$path"

        for (route in routes) {
            val params = parser.match(route.path, fullPath)
            if (params != null) {
                return route.handler(params)
            }
        }

        return null
    }

    // Resolve route
    fun resolve(method: String, path: String): Pair<Map<String, String>, ((Map<String, String>) -> String)>? {
        val fullPath = "$method:$path"

        for (route in routes) {
            val params = parser.match(route.path, fullPath)
            if (params != null) {
                return Pair(params, route.handler)
            }
        }

        return null
    }
}

// 4. RESTful Router
class RestfulRouter {

    private val router = SimpleRouter()

    // Resource routes
    fun resource(name: String) {
        val basePath = "/$name"
        val idPath = "/$name/{id}"

        // List all
        router.get(basePath) { params ->
            "Listing all $name"
        }

        // Get one
        router.get(idPath) { params ->
            "Getting $name with id: ${params["id"]}"
        }

        // Create
        router.post(basePath) { params ->
            "Creating new $name"
        }

        // Update
        router.put(idPath) { params ->
            "Updating $name with id: ${params["id"]}"
        }

        // Delete
        router.delete(idPath) { params ->
            "Deleting $name with id: ${params["id"]}"
        }
    }

    // Handle request
    fun handle(method: String, path: String): String? {
        return router.handle(method, path)
    }
}

// 5. Nested Router
class NestedRouter {

    private val router = SimpleRouter()

    // Group routes under prefix
    fun group(prefix: String, block: (SimpleRouter) -> Unit) {
        val originalRoutes = router.toList()

        block(router)

        // Add prefix to new routes
        val newRoutes = router.toList().drop(originalRoutes.size)
        for (route in newRoutes) {
            val prefixedPath = "$prefix${route.path.substringAfter(":")}"
            // Re-add with prefix
        }
    }

    // Add nested routes
    fun addNestedRoutes() {
        // API v1 routes
        router.get("/api/v1/users") { "API v1 users" }
        router.get("/api/v1/posts") { "API v1 posts" }

        // API v2 routes
        router.get("/api/v2/users") { "API v2 users" }
        router.get("/api/v2/posts") { "API v2 posts" }
    }

    // Handle request
    fun handle(method: String, path: String): String? {
        return router.handle(method, path)
    }

    private fun SimpleRouter.toList(): List<Route> {
        // Implementation for getting routes list
        return emptyList()
    }
}

// 6. Route Builder
class RouteBuilder {

    private val routes = mutableListOf<Route>()

    // Create route with DSL
    fun route(method: String, path: String, handler: (Map<String, String>) -> String) {
        routes.add(Route("$method:$path", handler))
    }

    // Build router
    fun build(): SimpleRouter {
        val router = SimpleRouter()
        for (route in routes) {
            val method = route.path.substringBefore(":")
            val path = route.path.substringAfter(":")
            router.addRoute(route.path, route.handler)
        }
        return router
    }
}

// 7. URL Matcher
class UrlMatcher {

    // Match with wildcards
    fun matchWildcard(pattern: String, path: String): Boolean {
        val regexPattern = pattern
            .replace(".", "\\.")
            .replace("*", ".*")
            .replace("?", ".")
        return Regex(regexPattern).matches(path)
    }

    // Match with query parameters
    fun matchWithQuery(pattern: String, path: String): Map<String, String>? {
        val (pathPart, queryPart) = if (path.contains("?")) {
            path.split("?", limit = 2)
        } else {
            listOf(path, "")
        }

        val params = mutableMapOf<String, String>()

        if (queryPart.isNotEmpty()) {
            val queryParams = queryPart.split("&")
            for (param in queryParams) {
                val parts = param.split("=", limit = 2)
                val key = parts[0]
                val value = if (parts.size > 1) parts[1] else ""
                params[key] = value
            }
        }

        return if (matchWildcard(pattern, pathPart)) params else null
    }
}

// 8. Path Utilities
class PathUtils {

    // Join path segments
    fun join(vararg segments: String): String {
        return segments
            .joinToString("/")
            .replace(Regex("/+"), "/")
            .trimEnd('/')
    }

    // Normalize path
    fun normalize(path: String): String {
        return path
            .replace(Regex("/+"), "/")
            .trimEnd('/')
            .ifEmpty { "/" }
    }

    // Get file extension from path
    fun getExtension(path: String): String {
        return path.substringAfterLast('.', "")
    }

    // Get filename from path
    fun getFilename(path: String): String {
        return path.substringAfterLast('/')
    }

    // Get directory from path
    fun getDirectory(path: String): String {
        return path.substringBeforeLast('/', "")
    }
}

// 9. Query String Parser
class QueryStringParser {

    // Parse query string
    fun parse(queryString: String): Map<String, List<String>> {
        val params = mutableMapOf<String, MutableList<String>>()

        if (queryString.isEmpty()) {
            return params
        }

        val pairs = queryString.split("&")
        for (pair in pairs) {
            val parts = pair.split("=", limit = 2)
            val key = java.net.URLDecoder.decode(parts[0], "UTF-8")
            val value = if (parts.size > 1) {
                java.net.URLDecoder.decode(parts[1], "UTF-8")
            } else {
                ""
            }
            params.getOrPut(key) { mutableListOf() }.add(
                value
            )
        }

        return params
    }

    // Build query string
    fun build(params: Map<String, Any>): String {
        return params
            .flatMap { (key, value) ->
                when (value) {
                    is List<*> -> value.map { key to it.toString() }
                    else -> listOf(key to value.toString())
                }
            }
            .joinToString("&") { (key, value) ->
                "$key=${java.net.URLEncoder.encode(value, "UTF-8")}"
            }
    }
}

// 10. Route Middleware
data class RouteMiddleware(
    val name: String,
    val handler: (Map<String, String>, () -> String) -> String
)

class MiddlewareRouter {

    private val routes = mutableListOf<Route>()
    private val middlewares = mutableListOf<RouteMiddleware>()

    // Add middleware
    fun use(middleware: RouteMiddleware) {
        middlewares.add(middleware)
    }

    // Add route
    fun addRoute(method: String, path: String, handler: (Map<String, String>) -> String) {
        routes.add(Route("$method:$path", handler))
    }

    // Handle with middleware
    fun handle(method: String, path: String, params: Map<String, String> = emptyMap()): String {
        var result: String? = null

        // Create handler chain
        val handlerChain: () -> String = {
            val matchedRoute = routes.find { route ->
                RouteParser().match(route.path, "$method:$path") != null
            }

            matchedRoute?.handler?.invoke(params) ?: "404 Not Found"
        }

        // Apply middlewares in reverse order
        var chain = handlerChain
        for (middleware in middlewares.reversed()) {
            val currentChain = chain
            chain = {
                middleware.handler(params, currentChain)
            }
        }

        return chain()
    }
}

// Main demonstration
fun demonstrateUrlRouting() {
    println("=== Android Kotlin URL Routing Examples ===\n")

    // 1. Basic routing
    println("--- 1. Basic Routing ---")
    val router = SimpleRouter()

    router.get("/users") { params -> "List all users" }
    router.get("/users/{id}") { params -> "Get user ${params["id"]}" }
    router.post("/users") { params -> "Create user" }

    println(router.handle("GET", "/users"))
    println(router.handle("GET", "/users/123"))
    println(router.handle("POST", "/users"))

    // 2. Route parameters
    println("\n--- 2. Route Parameters ---")
    val parser = RouteParser()
    val params = parser.match("/users/{id}/posts/{postId}", "/users/123/posts/456")
    println("Matched params: $params")

    // 3. RESTful routes
    println("\n--- 3. RESTful Routes ---")
    val restRouter = RestfulRouter()
    restRouter.resource("articles")
    println(restRouter.handle("GET", "/articles"))
    println(restRouter.handle("GET", "/articles/789"))
    println(restRouter.handle("POST", "/articles"))

    // 4. URL matching
    println("\n--- 4. URL Matching ---")
    val matcher = UrlMatcher()
    println("Match wildcard: ${matcher.matchWildcard("/api/*", "/api/users")}")
    println("Match with query: ${matcher.matchWithQuery("/search", "/search?q=kotlin&page=1")}")

    // 5. Path utilities
    println("\n--- 5. Path Utilities ---")
    val pathUtils = PathUtils()
    println("Join path: ${pathUtils.join("/api", "v1", "users")}")
    println("Normalize: ${pathUtils.normalize("//api//v1//")}")
    println("Get extension: ${pathUtils.getExtension("/files/image.png")}")
    println("Get filename: ${pathUtils.getFilename("/files/image.png")}")

    // 6. Query string parsing
    println("\n--- 6. Query String Parsing ---")
    val queryParser = QueryStringParser()
    val parsed = queryParser.parse("q=kotlin&page=1&sort=desc")
    println("Parsed query: $parsed")
    println("Build query: ${queryParser.build(mapOf("q" to "kotlin", "page" to 1))}")

    // 7. Middleware
    println("\n--- 7. Middleware ---")
    val middlewareRouter = MiddlewareRouter()
    middlewareRouter.use(RouteMiddleware("logger") { params, next ->
        println("Request: $params")
        next()
    })
    middlewareRouter.addRoute("GET", "/test") { "Test response" }
    println(middlewareRouter.handle("GET", "/test"))

    println("\n=== All URL Routing Examples Completed ===")
}

💻 Gestion des Middlewares kotlin

🟡 intermediate ⭐⭐⭐⭐

Implémenter des middlewares de traitement des requêtes avec chaînage, journalisation, authentification et gestion des erreurs

⏱️ 40 min 🏷️ kotlin, android, web, middleware
Prerequisites: Intermediate Kotlin, HTTP concepts
// Android Kotlin Middleware Handling Examples
// Implement middleware pattern for request processing

// 1. Context Object
data class RequestContext(
    val method: String,
    val path: String,
    val headers: Map<String, String> = emptyMap(),
    val body: String? = null,
    val queryParams: Map<String, List<String>> = emptyMap(),
    val attributes: MutableMap<String, Any> = mutableMapOf()
)

// 2. Response Object
data class ResponseContext(
    var statusCode: Int = 200,
    var headers: MutableMap<String, String> = mutableMapOf(),
    var body: String? = null
)

// 3. Middleware Interface
interface Middleware {
    fun process(ctx: RequestContext, res: ResponseContext, next: () -> Unit)
}

// 4. Logging Middleware
class LoggingMiddleware : Middleware {
    override fun process(ctx: RequestContext, res: ResponseContext, next: () -> Unit) {
        val startTime = System.currentTimeMillis()
        println("[REQUEST] ${ctx.method} ${ctx.path}")

        next()

        val duration = System.currentTimeMillis() - startTime
        println("[RESPONSE] ${res.statusCode} - ${duration}ms")
    }
}

// 5. Authentication Middleware
class AuthenticationMiddleware(private val apiKey: String) : Middleware {

    override fun process(ctx: RequestContext, res: ResponseContext, next: () -> Unit) {
        val authHeader = ctx.headers["Authorization"]

        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            res.statusCode = 401
            res.body = "Unauthorized: Missing or invalid token"
            println("[AUTH] Failed: No token provided")
            return
        }

        val token = authHeader.substringAfter(" ")

        if (token != apiKey) {
            res.statusCode = 403
            res.body = "Forbidden: Invalid token"
            println("[AUTH] Failed: Invalid token")
            return
        }

        println("[AUTH] Success: Token validated")
        ctx.attributes["userId"] = "user_123"
        next()
    }
}

// 6. CORS Middleware
class CorsMiddleware(
    private val allowedOrigins: List<String> = listOf("*"),
    private val allowedMethods: List<String> = listOf("GET", "POST", "PUT", "DELETE"),
    private val allowedHeaders: List<String> = listOf("Content-Type", "Authorization")
) : Middleware {

    override fun process(ctx: RequestContext, res: ResponseContext, next: () -> Unit) {
        val origin = ctx.headers["Origin"] ?: "*"

        // Set CORS headers
        res.headers["Access-Control-Allow-Origin"] = if (allowedOrigins.contains("*")) {
            "*"
        } else {
            if (allowedOrigins.contains(origin)) origin else allowedOrigins.first()
        }

        res.headers["Access-Control-Allow-Methods"] = allowedMethods.joinToString(", ")
        res.headers["Access-Control-Allow-Headers"] = allowedHeaders.joinToString(", ")
        res.headers["Access-Control-Max-Age"] = "86400"

        // Handle preflight request
        if (ctx.method == "OPTIONS") {
            res.statusCode = 204
            res.body = null
            println("[CORS] Preflight request handled")
            return
        }

        println("[CORS] Headers added for origin: $origin")
        next()
    }
}

// 7. Body Parser Middleware
class BodyParserMiddleware : Middleware {

    override fun process(ctx: RequestContext, res: ResponseContext, next: () -> Unit) {
        if (ctx.body != null && ctx.headers["Content-Type"]?.contains("application/json") == true) {
            try {
                // Parse JSON body
                ctx.attributes["parsedBody"] = ctx.body
                println("[BODY] JSON body parsed successfully")
            } catch (e: Exception) {
                res.statusCode = 400
                res.body = "Invalid JSON: ${e.message}"
                println("[BODY] Failed to parse JSON")
                return
            }
        }

        next()
    }
}

// 8. Rate Limiting Middleware
class RateLimitMiddleware(
    private val maxRequests: Int = 100,
    private val windowMs: Long = 60000 // 1 minute
) : Middleware {

    private val requestCounts = mutableMapOf<String, MutableList<Long>>()

    override fun process(ctx: RequestContext, res: ResponseContext, next: () -> Unit) {
        val clientId = ctx.headers["X-Client-ID"] ?: ctx.queryParams["ip"]?.firstOrNull() ?: "unknown"
        val currentTime = System.currentTimeMillis()

        // Clean old requests
        requestCounts[clientId]?.removeIf { currentTime - it > windowMs }

        // Get current request count
        val requests = requestCounts.getOrPut(clientId) { mutableListOf() }

        if (requests.size >= maxRequests) {
            res.statusCode = 429
            res.headers["Retry-After"] = "${(windowMs - (currentTime - requests.first())) / 1000}"
            res.body = "Too many requests. Please try again later."
            println("[RATE LIMIT] Blocked: $clientId (${requests.size}/$maxRequests)")
            return
        }

        requests.add(currentTime)
        println("[RATE LIMIT] Allowed: $clientId (${requests.size}/$maxRequests)")
        next()
    }
}

// 9. Error Handling Middleware
class ErrorHandlingMiddleware : Middleware {

    override fun process(ctx: RequestContext, res: ResponseContext, next: () -> Unit) {
        try {
            next()
        } catch (e: Exception) {
            res.statusCode = 500
            res.body = "Internal Server Error: ${e.message}"
            println("[ERROR] ${e.javaClass.simpleName}: ${e.message}")
            e.printStackTrace()
        }
    }
}

// 10. Compression Middleware
class CompressionMiddleware : Middleware {

    override fun process(ctx: RequestContext, res: ResponseContext, next: () -> Unit) {
        next()

        // Compress response if applicable
        val acceptEncoding = ctx.headers["Accept-Encoding"] ?: ""

        if (acceptEncoding.contains("gzip") && res.body != null && res.body!!.length > 1024) {
            // In real implementation, compress the body
            res.headers["Content-Encoding"] = "gzip"
            println("[COMPRESSION] Response compressed")
        }
    }
}

// 11. Cache Middleware
class CacheMiddleware(
    private val defaultMaxAge: Int = 3600
) : Middleware {

    private val cache = mutableMapOf<String, Pair<String, Long>>()

    override fun process(ctx: RequestContext, res: ResponseContext, next: () -> Unit) {
        val cacheKey = "${ctx.method}:${ctx.path}"
        val currentTime = System.currentTimeMillis()

        // Check cache for GET requests
        if (ctx.method == "GET") {
            val cached = cache[cacheKey]
            if (cached != null && currentTime - cached.second < defaultMaxAge * 1000) {
                res.statusCode = 200
                res.body = cached.first
                res.headers["X-Cache"] = "HIT"
                println("[CACHE] HIT: $cacheKey")
                return
            }
        }

        next()

        // Cache response for GET requests
        if (ctx.method == "GET" && res.statusCode == 200 && res.body != null) {
            cache[cacheKey] = Pair(res.body!!, currentTime)
            res.headers["X-Cache"] = "MISS"
            res.headers["Cache-Control"] = "max-age=$defaultMaxAge"
            println("[CACHE] MISS: Stored $cacheKey")
        }
    }
}

// 12. Timeout Middleware
class TimeoutMiddleware(
    private val timeoutMs: Long = 30000
) : Middleware {

    override fun process(ctx: RequestContext, res: ResponseContext, next: () -> Unit) {
        val thread = Thread {
            try {
                next()
            } catch (e: Exception) {
                res.statusCode = 504
                res.body = "Gateway Timeout: Request took too long"
                println("[TIMEOUT] Request exceeded ${timeoutMs}ms")
            }
        }

        thread.start()
        thread.join(timeoutMs)

        if (thread.isAlive) {
            thread.interrupt()
            res.statusCode = 504
            res.body = "Gateway Timeout"
            println("[TIMEOUT] Interrupted request")
        }
    }
}

// 13. Security Headers Middleware
class SecurityHeadersMiddleware : Middleware {

    override fun process(ctx: RequestContext, res: ResponseContext, next: () -> Unit) {
        next()

        // Add security headers
        res.headers["X-Content-Type-Options"] = "nosniff"
        res.headers["X-Frame-Options"] = "DENY"
        res.headers["X-XSS-Protection"] = "1; mode=block"
        res.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
        res.headers["Content-Security-Policy"] = "default-src 'self'"

        println("[SECURITY] Security headers added")
    }
}

// 14. Middleware Pipeline
class MiddlewarePipeline {

    private val middlewares = mutableListOf<Middleware>()

    // Add middleware
    fun use(middleware: Middleware): MiddlewarePipeline {
        middlewares.add(middleware)
        return this
    }

    // Process request through middleware chain
    fun process(ctx: RequestContext, handler: (RequestContext, ResponseContext) -> Unit): ResponseContext {
        val res = ResponseContext()
        var index = 0

        // Create chain
        val chain: () -> Unit = {
            if (index < middlewares.size) {
                val middleware = middlewares[index++]
                middleware.process(ctx, res, chain)
            } else {
                handler(ctx, res)
            }
        }

        chain()
        return res
    }

    // Clear all middlewares
    fun clear(): MiddlewarePipeline {
        middlewares.clear()
        return this
    }
}

// 15. Router with Middleware
class MiddlewareRouter {

    private val pipeline = MiddlewarePipeline()

    // Setup common middlewares
    fun setup() {
        pipeline
            .use(ErrorHandlingMiddleware())
            .use(LoggingMiddleware())
            .use(CorsMiddleware())
            .use(SecurityHeadersMiddleware())
    }

    // Handle request
    fun handle(
        method: String,
        path: String,
        headers: Map<String, String> = emptyMap(),
        handler: (RequestContext, ResponseContext) -> Unit
    ): ResponseContext {
        val ctx = RequestContext(
            method = method,
            path = path,
            headers = headers
        )

        return pipeline.process(ctx, handler)
    }

    // Add custom middleware
    fun use(middleware: Middleware): MiddlewareRouter {
        pipeline.use(middleware)
        return this
    }
}

// 16. Async Middleware (conceptual)
class AsyncMiddleware : Middleware {

    override fun process(ctx: RequestContext, res: ResponseContext, next: () -> Unit) {
        // Simulate async operation
        Thread {
            println("[ASYNC] Processing background task")
            Thread.sleep(100)
            println("[ASYNC] Background task completed")
        }.start()

        next()
    }
}

// 17. Conditional Middleware
class ConditionalMiddleware(
    private val condition: (RequestContext) -> Boolean,
    private val middleware: Middleware
) : Middleware {

    override fun process(ctx: RequestContext, res: ResponseContext, next: () -> Unit) {
        if (condition(ctx)) {
            middleware.process(ctx, res, next)
        } else {
            println("[CONDITIONAL] Middleware skipped")
            next()
        }
    }
}

// 18. Request Validation Middleware
class ValidationMiddleware(
    private val validator: (RequestContext) -> Boolean,
    private val errorMessage: String = "Validation failed"
) : Middleware {

    override fun process(ctx: RequestContext, res: ResponseContext, next: () -> Unit) {
        if (validator(ctx)) {
            println("[VALIDATION] Passed")
            next()
        } else {
            res.statusCode = 400
            res.body = errorMessage
            println("[VALIDATION] Failed: $errorMessage")
        }
    }
}

// Main demonstration
fun demonstrateMiddlewareHandling() {
    println("=== Android Kotlin Middleware Handling Examples ===\n")

    // 1. Basic middleware chain
    println("--- 1. Basic Middleware Chain ---")
    val pipeline = MiddlewarePipeline()

    pipeline
        .use(LoggingMiddleware())
        .use { ctx, res, next ->
            println("[CUSTOM] Processing request")
            res.statusCode = 200
            res.body = "Hello, World!"
            next()
        }

    val ctx = RequestContext("GET", "/api/test")
    val response = pipeline.process(ctx) { reqCtx, res ->
        // Handler
    }
    println("Response: ${response.statusCode} - ${response.body}")

    // 2. Authentication middleware
    println("\n--- 2. Authentication Middleware ---")
    val authPipeline = MiddlewarePipeline()
    authPipeline.use(AuthenticationMiddleware("secret_key_123"))

    val authCtxValid = RequestContext(
        "GET", "/api/protected",
        headers = mapOf("Authorization" to "Bearer secret_key_123")
    )
    val authResponseValid = authPipeline.process(authCtxValid) { _, res ->
        res.statusCode = 200
        res.body = "Protected content"
    }
    println("Valid token: ${authResponseValid.statusCode}")

    val authCtxInvalid = RequestContext(
        "GET", "/api/protected",
        headers = mapOf("Authorization" to "Bearer wrong_key")
    )
    val authResponseInvalid = authPipeline.process(authCtxInvalid) { _, res -> }
    println("Invalid token: ${authResponseInvalid.statusCode}")

    // 3. CORS middleware
    println("\n--- 3. CORS Middleware ---")
    val corsPipeline = MiddlewarePipeline()
    corsPipeline.use(CorsMiddleware(allowedOrigins = listOf("https://example.com")))

    val corsCtx = RequestContext(
        "OPTIONS", "/api/data",
        headers = mapOf("Origin" to "https://example.com")
    )
    val corsResponse = corsPipeline.process(corsCtx) { _, res -> }
    println("CORS headers: ${corsResponse.headers}")

    // 4. Rate limiting
    println("\n--- 4. Rate Limiting Middleware ---")
    val rateLimitPipeline = MiddlewarePipeline()
    rateLimitPipeline.use(RateLimitMiddleware(maxRequests = 3))

    for (i in 1..5) {
        val rateCtx = RequestContext("GET", "/api/data")
        val rateRes = rateLimitPipeline.process(rateCtx) { _, res ->
            res.statusCode = 200
            res.body = "Response $i"
        }
        println("Request $i: ${rateRes.statusCode}")
    }

    // 5. Error handling
    println("\n--- 5. Error Handling Middleware ---")
    val errorPipeline = MiddlewarePipeline()
    errorPipeline.use(ErrorHandlingMiddleware())

    val errorCtx = RequestContext("GET", "/api/error")
    val errorResponse = errorPipeline.process(errorCtx) { _, res ->
        throw RuntimeException("Something went wrong!")
    }
    println("Error response: ${errorResponse.statusCode} - ${errorResponse.body}")

    // 6. Cache middleware
    println("\n--- 6. Cache Middleware ---")
    val cachePipeline = MiddlewarePipeline()
    cachePipeline.use(CacheMiddleware(defaultMaxAge = 60))

    for (i in 1..3) {
        val cacheCtx = RequestContext("GET", "/api/data")
        val cacheRes = cachePipeline.process(cacheCtx) { _, res ->
            res.statusCode = 200
            res.body = "Cached data"
        }
        println("Request $i: ${cacheRes.headers["X-Cache"]}")
    }

    // 7. Combined middlewares
    println("\n--- 7. Combined Middlewares ---")
    val router = MiddlewareRouter()
    router.setup()
    router.use(ValidationMiddleware({ it.method == "GET" }, "Only GET allowed"))

    val combinedRes = router.handle("GET", "/api/data") { reqCtx, res ->
        res.statusCode = 200
        res.body = "Success"
    }
    println("Combined response: ${combinedRes.statusCode} - ${combinedRes.body}")

    println("\n=== All Middleware Handling Examples Completed ===")
}

💻 Service de Fichiers Statiques kotlin

🟡 intermediate ⭐⭐⭐⭐

Servir des fichiers statiques avec détection de type MIME, mise en cache et prise en charge de la compression

⏱️ 35 min 🏷️ kotlin, android, web, static files
Prerequisites: Intermediate Kotlin, File I/O
// Android Kotlin Static File Serving Examples
// Implementation for serving static files from Android assets or storage

import android.content.Context
import android.content.res.AssetManager
import java.io.File
import java.io.FileInputStream
import java.io.InputStream
import java.util.zip.GZIPOutputStream
import java.io.ByteArrayOutputStream

// 1. MIME Type Detector
class MimeTypeDetector {

    private val mimeTypes = mapOf(
        "html" to "text/html",
        "css" to "text/css",
        "js" to "application/javascript",
        "json" to "application/json",
        "xml" to "application/xml",
        "pdf" to "application/pdf",
        "zip" to "application/zip",
        "txt" to "text/plain",
        "jpg" to "image/jpeg",
        "jpeg" to "image/jpeg",
        "png" to "image/png",
        "gif" to "image/gif",
        "svg" to "image/svg+xml",
        "ico" to "image/x-icon",
        "woff" to "font/woff",
        "woff2" to "font/woff2",
        "ttf" to "font/ttf",
        "eot" to "application/vnd.ms-fontobject",
        "mp4" to "video/mp4",
        "webm" to "video/webm",
        "mp3" to "audio/mpeg",
        "wav" to "audio/wav",
        "ogg" to "audio/ogg"
    )

    // Get MIME type from file extension
    fun getMimeType(filename: String): String {
        val extension = filename.substringAfterLast('.', "").lowercase()
        return mimeTypes[extension] ?: "application/octet-stream"
    }

    // Get MIME type from file
    fun getMimeType(file: File): String {
        return getMimeType(file.name)
    }

    // Check if file is binary
    fun isBinary(filename: String): Boolean {
        val mimeType = getMimeType(filename)
        return !mimeType.startsWith("text/")
    }

    // Check if file is image
    fun isImage(filename: String): Boolean {
        val mimeType = getMimeType(filename)
        return mimeType.startsWith("image/")
    }

    // Check if file is video
    fun isVideo(filename: String): Boolean {
        val mimeType = getMimeType(filename)
        return mimeType.startsWith("video/")
    }

    // Check if file is audio
    fun isAudio(filename: String): Boolean {
        val mimeType = getMimeType(filename)
        return mimeType.startsWith("audio/")
    }
}

// 2. File Response
data class FileResponse(
    val inputStream: InputStream,
    val contentType: String,
    val contentLength: Long,
    val lastModified: Long,
    val etag: String
)

// 3. Static File Server (Assets)
class AssetFileServer(private val context: Context) {

    private val assetManager: AssetManager = context.assets
    private val mimeTypeDetector = MimeTypeDetector()

    // Serve file from assets
    fun serveFile(path: String): FileResponse? {
        return try {
            val inputStream = assetManager.open(path.removePrefix("/"))
            val fileInfo = getFileInfo(path)

            FileResponse(
                inputStream = inputStream,
                contentType = fileInfo.contentType,
                contentLength = fileInfo.size,
                lastModified = fileInfo.lastModified,
                etag = generateEtag(path, fileInfo.lastModified)
            )
        } catch (e: Exception) {
            println("Error serving asset file: ${e.message}")
            null
        }
    }

    // List files in directory
    fun listFiles(path: String = ""): List<String> {
        return try {
            assetManager.list(path.removePrefix("/"))?.toList() ?: emptyList()
        } catch (e: Exception) {
            emptyList()
        }
    }

    // Check if file exists
    fun fileExists(path: String): Boolean {
        return try {
            assetManager.open(path.removePrefix("/")).close()
            true
        } catch (e: Exception) {
            false
        }
    }

    // Get file info
    private fun getFileInfo(path: String): FileInfo {
        val extension = path.substringAfterLast('.', "")
        val contentType = mimeTypeDetector.getMimeType(extension)

        // Asset files don't provide size or last modified directly
        // In real implementation, you might cache this info
        return FileInfo(
            size = -1L,
            lastModified = System.currentTimeMillis(),
            contentType = contentType
        )
    }

    // Generate ETag
    private fun generateEtag(path: String, lastModified: Long): String {
        return "\"${path.hashCode()}-${lastModified}\""
    }

    private data class FileInfo(
        val size: Long,
        val lastModified: Long,
        val contentType: String
    )
}

// 4. Static File Server (Storage)
class StorageFileServer {

    private val mimeTypeDetector = MimeTypeDetector()

    // Serve file from storage
    fun serveFile(baseDir: File, path: String): FileResponse? {
        val file = File(baseDir, path.removePrefix("/"))

        return if (file.exists() && file.isFile) {
            try {
                FileResponse(
                    inputStream = FileInputStream(file),
                    contentType = mimeTypeDetector.getMimeType(file),
                    contentLength = file.length(),
                    lastModified = file.lastModified(),
                    etag = generateEtag(file)
                )
            } catch (e: Exception) {
                println("Error serving file: ${e.message}")
                null
            }
        } else {
            null
        }
    }

    // Serve file with range support
    fun serveFileWithRange(baseDir: File, path: String, range: String?): FileResponse? {
        val file = File(baseDir, path.removePrefix("/"))

        if (!file.exists() || !file.isFile) {
            return null
        }

        return try {
            val inputStream = if (range != null) {
                // Parse range header: "bytes=start-end"
                val parts = range.removePrefix("bytes=").split("-")
                val start = parts[0].toLongOrNull() ?: 0L
                val end = if (parts.size > 1 && parts[1].isNotEmpty()) {
                    parts[1].toLong()
                } else {
                    file.length() - 1
                }

                // In real implementation, return partial stream
                FileInputStream(file)
            } else {
                FileInputStream(file)
            }

            FileResponse(
                inputStream = inputStream,
                contentType = mimeTypeDetector.getMimeType(file),
                contentLength = file.length(),
                lastModified = file.lastModified(),
                etag = generateEtag(file)
            )
        } catch (e: Exception) {
            println("Error serving file: ${e.message}")
            null
        }
    }

    // List directory
    fun listDirectory(baseDir: File, path: String): List<File> {
        val dir = File(baseDir, path.removePrefix("/"))
        return if (dir.exists() && dir.isDirectory) {
            dir.listFiles()?.toList() ?: emptyList()
        } else {
            emptyList()
        }
    }

    // Generate ETag
    private fun generateEtag(file: File): String {
        return "\"${file.name}-${file.lastModified()}-${file.length()}\""
    }
}

// 5. Cache Control
class CacheControl {

    // Get cache control header
    fun getCacheControl(maxAge: Int = 3600, mustRevalidate: Boolean = false): String {
        val parts = mutableListOf("max-age=$maxAge")
        if (mustRevalidate) parts.add("must-revalidate")
        return parts.joinToString(", ")
    }

    // Get cache control for static assets
    fun getStaticAssetCache(): String {
        return "public, max-age=31536000, immutable"
    }

    // Get cache control for HTML files
    fun getHtmlCache(): String {
        return "public, max-age=0, must-revalidate"
    }

    // Get cache control for API responses
    fun getApiCache(): String {
        return "no-cache, no-store, must-revalidate"
    }

    // Check if client has cached version
    fun isCached(etag: String, ifNoneMatch: String?): Boolean {
        return ifNoneMatch?.contains(etag) == true
    }

    // Check if resource has been modified
    fun isModified(lastModified: Long, ifModifiedSince: Long): Boolean {
        return lastModified > ifModifiedSince
    }
}

// 6. Compression Handler
class CompressionHandler {

    // Compress data if client accepts gzip
    fun compressIfNeeded(
        data: ByteArray,
        acceptEncoding: String?
    ): Pair<ByteArray, String?> {
        if (acceptEncoding?.contains("gzip") == true && data.size > 1024) {
            val compressed = compress(data)
            return compressed to "gzip"
        }
        return data to null
    }

    // Compress data with GZIP
    private fun compress(data: ByteArray): ByteArray {
        val outputStream = ByteArrayOutputStream()
        GZIPOutputStream(outputStream).use { gzip ->
            gzip.write(data)
        }
        return outputStream.toByteArray()
    }

    // Check if should compress based on content type
    fun shouldCompress(contentType: String): Boolean {
        val compressibleTypes = listOf(
            "text/",
            "application/json",
            "application/javascript",
            "application/xml",
            "application/xhtml+xml"
        )

        return compressibleTypes.any { contentType.startsWith(it) }
    }
}

// 7. Range Request Handler
class RangeRequestHandler {

    // Parse range header
    fun parseRange(rangeHeader: String, fileSize: Long): RangeInfo {
        val range = rangeHeader.removePrefix("bytes=")
        val parts = range.split("-")

        val start = parts[0].toLongOrNull() ?: 0L
        val end = if (parts.size > 1 && parts[1].isNotEmpty()) {
            parts[1].toLong()
        } else {
            fileSize - 1
        }

        return RangeInfo(
            start = start.coerceAtLeast(0),
            end = end.coerceAtMost(fileSize - 1),
            fileSize = fileSize
        )
    }

    // Generate content range header
    fun generateContentRange(range: RangeInfo): String {
        return "bytes ${range.start}-${range.end}/${range.fileSize}"
    }

    data class RangeInfo(
        val start: Long,
        val end: Long,
        val fileSize: Long
    ) {
        val length: Long get() = end - start + 1
    }
}

// 8. Static File Configuration
data class StaticFileConfig(
    val baseDirectory: File,
    val indexFile: String = "index.html",
    val enableCompression: Boolean = true,
    val enableCaching: Boolean = true,
    val cacheMaxAge: Int = 3600,
    val enableETag: Boolean = true,
    val enableRange: Boolean = true
)

// 9. Complete Static File Server
class CompleteStaticFileServer(private val config: StaticFileConfig) {

    private val storageServer = StorageFileServer()
    private val cacheControl = CacheControl()
    private val compressionHandler = CompressionHandler()
    private val rangeHandler = RangeRequestHandler()

    // Serve file with all features
    fun serveFile(
        path: String,
        headers: Map<String, String> = emptyMap()
    ): Pair<FileResponse, Map<String, String>>? {
        // Try index file if directory
        var requestPath = path
        if (requestPath.endsWith("/")) {
            requestPath += config.indexFile
        }

        // Serve file
        val fileResponse = storageServer.serveFile(config.baseDirectory, requestPath)
            ?: return null

        // Build response headers
        val responseHeaders = mutableMapOf<String, String>()

        // Content type
        responseHeaders["Content-Type"] = fileResponse.contentType

        // Content length
        responseHeaders["Content-Length"] = fileResponse.contentLength.toString()

        // Last modified
        responseHeaders["Last-Modified"] = formatDate(fileResponse.lastModified)

        // ETag
        if (config.enableETag) {
            responseHeaders["ETag"] = fileResponse.etag

            // Check if client has cached version
            val ifNoneMatch = headers["If-None-Match"]
            if (cacheControl.isCached(fileResponse.etag, ifNoneMatch)) {
                return Pair(fileResponse.copy(inputStream = InputStream.nullInputStream()), mapOf("Status" to "304 Not Modified"))
            }
        }

        // Cache control
        if (config.enableCaching) {
            val mimeType = fileResponse.contentType
            val maxAge = if (mimeType.contains("html")) {
                0
            } else {
                config.cacheMaxAge
            }
            responseHeaders["Cache-Control"] = cacheControl.getCacheControl(maxAge)
        }

        // Accept ranges
        if (config.enableRange) {
            responseHeaders["Accept-Ranges"] = "bytes"
        }

        // Compression
        if (config.enableCompression) {
            val acceptEncoding = headers["Accept-Encoding"]
            if (compressionHandler.shouldCompress(fileResponse.contentType)) {
                // In real implementation, compress the stream
                responseHeaders["Vary"] = "Accept-Encoding"
            }
        }

        return Pair(fileResponse, responseHeaders)
    }

    // Format date for HTTP header
    private fun formatDate(timestamp: Long): String {
        val sdf = java.text.SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", java.util.Locale.US)
        sdf.timeZone = java.util.SimpleTimeZone(0, "GMT")
        return sdf.format(java.util.Date(timestamp))
    }
}

// 10. Directory Listing
class DirectoryListing {

    private val mimeTypeDetector = MimeTypeDetector()

    // Generate HTML directory listing
    fun generateListing(files: List<File>, path: String): String {
        val sb = StringBuilder()
        sb.append("<!DOCTYPE html>\n")
        sb.append("<html>\n")
        sb.append("<head><title>Index of $path</title></head>\n")
        sb.append("<body>\n")
        sb.append("<h1>Index of $path</h1>\n")
        sb.append("<hr>\n")
        sb.append("<pre>\n")

        // Parent directory link
        if (path != "/") {
            val parentPath = path.substringBeforeLast('/', "")
            sb.append("<a href=\"$parentPath\">../</a>\n")
        }

        // Sort files: directories first, then files
        val sortedFiles = files.sortedWith(compareBy({ !it.isDirectory }, { it.name }))

        for (file in sortedFiles) {
            val name = file.name
            val size = if (file.isFile) formatSize(file.length()) else ""
            val modified = formatDate(file.lastModified())

            sb.append("<a href=\"$name\">$name</a>")
            sb.append("        ".repeat(maxOf(0, 30 - name.length)))
            sb.append(modified)
            sb.append("        ".repeat(2))
            sb.append(size)
            sb.append("\n")
        }

        sb.append("</pre>\n")
        sb.append("<hr>\n")
        sb.append("</body>\n")
        sb.append("</html>")

        return sb.toString()
    }

    // Format file size
    private fun formatSize(bytes: Long): String {
        return when {
            bytes < 1024 -> "$bytes B"
            bytes < 1024 * 1024 -> "${bytes / 1024} KB"
            bytes < 1024 * 1024 * 1024 -> "${bytes / (1024 * 1024)} MB"
            else -> "${bytes / (1024 * 1024 * 1024)} GB"
        }
    }

    // Format date
    private fun formatDate(timestamp: Long): String {
        val sdf = java.text.SimpleDateFormat("dd-MMM-yyyy HH:mm", java.util.Locale.US)
        return sdf.format(java.util.Date(timestamp))
    }
}

// 11. File Upload Handler (conceptual)
class FileUploadHandler(private val uploadDir: File) {

    // Handle file upload
    fun handleUpload(
        filename: String,
        inputStream: InputStream,
        contentType: String
    ): File {
        uploadDir.mkdirs()
        val file = File(uploadDir, filename)

        file.outputStream().use { output ->
            inputStream.copyTo(output)
        }

        return file
    }

    // Generate unique filename
    fun generateUniqueFilename(originalName: String): String {
        val extension = originalName.substringAfterLast('.', "")
        val baseName = originalName.substringBeforeLast('.')
        val timestamp = System.currentTimeMillis()
        return "${baseName}_$timestamp.$extension"
    }

    // Validate file type
    fun isValidFileType(filename: String, allowedTypes: List<String>): Boolean {
        val extension = filename.substringAfterLast('.', "").lowercase()
        return allowedTypes.contains(extension)
    }
}

// 12. Resource Bundles
class ResourceBundle(private val context: Context) {

    // Serve resource from bundle
    fun serveResource(resourceName: String): InputStream? {
        return try {
            context.resources.openRawResource(
                context.resources.getIdentifier(
                    resourceName.substringBeforeLast('.'),
                    "raw",
                    context.packageName
                )
            )
        } catch (e: Exception) {
            null
        }
    }

    // List all resources
    fun listResources(): List<String> {
        val fields = R.raw::class.java.fields
        return fields.map { it.name }
    }
}

// Main demonstration
fun demonstrateStaticFileServing(context: Context) {
    println("=== Android Kotlin Static File Serving Examples ===\n")

    // 1. MIME type detection
    println("--- 1. MIME Type Detection ---")
    val mimeTypeDetector = MimeTypeDetector()
    println("HTML: ${mimeTypeDetector.getMimeType("index.html")}")
    println("CSS: ${mimeTypeDetector.getMimeType("style.css")}")
    println("JavaScript: ${mimeTypeDetector.getMimeType("app.js")}")
    println("PNG Image: ${mimeTypeDetector.getMimeType("image.png")}")
    println("Is image: ${mimeTypeDetector.isImage("photo.jpg")}")
    println("Is binary: ${mimeTypeDetector.isBinary("video.mp4")}")

    // 2. Asset file server
    println("\n--- 2. Asset File Server ---")
    val assetServer = AssetFileServer(context)
    println("File exists 'index.html': ${assetServer.fileExists("index.html")}")
    println("List files: ${assetServer.listFiles()}")

    // 3. Storage file server
    println("\n--- 3. Storage File Server ---")
    val storageServer = StorageFileServer()
    val baseDir = File(context.filesDir, "public")
    println("Base directory: ${baseDir.absolutePath}")

    // 4. Cache control
    println("\n--- 4. Cache Control ---")
    val cacheControl = CacheControl()
    println("Static asset cache: ${cacheControl.getStaticAssetCache()}")
    println("HTML cache: ${cacheControl.getHtmlCache()}")
    println("API cache: ${cacheControl.getApiCache()}")

    // 5. Compression
    println("\n--- 5. Compression ---")
    val compressionHandler = CompressionHandler()
    println("Should compress HTML: ${compressionHandler.shouldCompress("text/html")}")
    println("Should compress JSON: ${compressionHandler.shouldCompress("application/json")}")
    println("Should compress PNG: ${compressionHandler.shouldCompress("image/png")}")

    // 6. Range requests
    println("\n--- 6. Range Requests ---")
    val rangeHandler = RangeRequestHandler()
    val rangeInfo = rangeHandler.parseRange("bytes=0-1023", 10000)
    println("Range: ${rangeInfo.start}-${rangeInfo.end} (total: ${rangeInfo.fileSize})")
    println("Content-Range: ${rangeHandler.generateContentRange(rangeInfo)}")

    // 7. Complete static file server
    println("\n--- 7. Complete Static File Server ---")
    val config = StaticFileConfig(
        baseDirectory = baseDir,
        indexFile = "index.html",
        enableCompression = true,
        enableCaching = true,
        enableETag = true
    )
    val completeServer = CompleteStaticFileServer(config)
    println("Server configured with:")
    println("  - Base directory: ${config.baseDirectory}")
    println("  - Index file: ${config.indexFile}")
    println("  - Compression: ${config.enableCompression}")
    println("  - Caching: ${config.enableCaching}")

    // 8. Directory listing
    println("\n--- 8. Directory Listing ---")
    val directoryListing = DirectoryListing()
    println("Directory listing methods:")
    println("  - generateListing(): Generate HTML listing")
    println("  - formatSize(): Format file size")
    println("  - formatDate(): Format file date")

    // 9. File upload
    println("\n--- 9. File Upload ---")
    val uploadHandler = FileUploadHandler(File(context.cacheDir, "uploads"))
    println("Upload handler methods:")
    println("  - handleUpload(): Save uploaded file")
    println("  - generateUniqueFilename(): Create unique name")
    println("  - isValidFileType(): Validate file type")

    println("\n=== All Static File Serving Examples Completed ===")
}