🎯 Exemples recommandés
Balanced sample collections from various categories for you to explore
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
💻 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 (key, value) = param.split("=", limit = 2)
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 (key, value) = pair.split("=", limit = 2)
params.getOrPut(key) { mutableListOf() }.add(
java.net.URLDecoder.decode(value, "UTF-8")
)
}
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 ===")
}