(fix) Umbau zu SCS

### API-Gateway erweitern
- Bestehenden API-Gateway-Service mit zusätzlichen Funktionen ausstatten:
    - Rate Limiting implementieren
    - Request/Response Logging verbessern
    - Cross-Service Tracing mit eindeutigen Request-IDs einführen
This commit is contained in:
stefan 2025-07-21 17:46:42 +02:00
parent 834c92dcb2
commit 89e1fa8b52
5 changed files with 19 additions and 31 deletions

View File

@ -1,16 +1,11 @@
package at.mocode.gateway.config
import at.mocode.gateway.config.REQUEST_ID_KEY
import at.mocode.gateway.config.REQUEST_START_TIME_KEY
import at.mocode.shared.config.AppConfig
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.plugins.calllogging.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.util.*
import org.slf4j.LoggerFactory
import org.slf4j.event.Level
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
@ -158,7 +153,7 @@ private fun extractBasePath(path: String): String {
if (parts.isEmpty()) return "/"
// For API paths, include up to the resource name (typically 3 parts: api, version, resource)
if (parts.size >= 1 && parts[0] == "api") {
if (parts[0] == "api") {
val depth = minOf(3, parts.size)
return "/" + parts.take(depth).joinToString("/")
}

View File

@ -1,8 +1,6 @@
package at.mocode.gateway.config
import at.mocode.dto.base.ApiResponse
import at.mocode.gateway.config.REQUEST_ID_KEY
import at.mocode.gateway.config.REQUEST_START_TIME_KEY
import at.mocode.shared.config.AppConfig
import io.ktor.http.*
import io.ktor.server.application.*
@ -14,9 +12,9 @@ import org.slf4j.event.Level
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import kotlin.random.Random
/**
@ -104,7 +102,7 @@ private fun extractBasePath(path: String): String {
if (parts.isEmpty()) return "/"
// For API paths, include up to the resource name (typically 3 parts: api, version, resource)
if (parts.size >= 1 && parts[0] == "api") {
if (parts[0] == "api") {
val depth = minOf(3, parts.size)
return "/" + parts.take(depth).joinToString("/")
}

View File

@ -38,7 +38,7 @@ private val cacheCleanupScheduler = java.util.Timer("token-cache-cleanup").apply
schedule(object : java.util.TimerTask() {
override fun run() {
if (tokenCache.size > TOKEN_CACHE_MAX_SIZE) {
// If cache exceeds max size, remove oldest entries (simple approach)
// If the cache exceeds max size, remove the oldest entries (simple approach)
val keysToRemove = tokenCache.keys.take(tokenCache.size - TOKEN_CACHE_MAX_SIZE / 2)
keysToRemove.forEach { tokenCache.remove(it) }
}
@ -56,15 +56,15 @@ private object AdaptiveRateLimiting {
// Thresholds for CPU usage (percentage)
const val CPU_MEDIUM_LOAD_THRESHOLD = 60.0 // Medium load threshold (60%)
const val CPU_HIGH_LOAD_THRESHOLD = 80.0 // High load threshold (80%)
const val CPU_HIGH_LOAD_THRESHOLD = 80.0 // High-load threshold (80%)
// Thresholds for memory usage (percentage)
const val MEMORY_MEDIUM_LOAD_THRESHOLD = 70.0 // Medium load threshold (70%)
const val MEMORY_HIGH_LOAD_THRESHOLD = 85.0 // High load threshold (85%)
const val MEMORY_HIGH_LOAD_THRESHOLD = 85.0 // High-load threshold (85%)
// Rate limit adjustment factors
const val MEDIUM_LOAD_FACTOR = 0.7 // Reduce limits to 70% under medium load
const val HIGH_LOAD_FACTOR = 0.4 // Reduce limits to 40% under high load
const val MEDIUM_LOAD_FACTOR = 0.7 // Reduce limits to 70% under a medium load
const val HIGH_LOAD_FACTOR = 0.4 // Reduce limits to 40% under a high load
// Monitoring interval in milliseconds
const val MONITORING_INTERVAL_MS = 5000L // Check every 5 seconds
@ -415,18 +415,18 @@ private fun extractUserIdFromToken(authHeader: String): String? {
// Get the user ID
val userId = matchResult?.groupValues?.get(1) ?: token.hashCode().toString()
// Store in cache for future use
// Store in a cache for future use
tokenCache[tokenHash] = Pair(userId, userType)
return userId
} catch (e: Exception) {
} catch (_: Exception) {
// If any error occurs during parsing, fall back to using the token hash
return authHeader.hashCode().toString()
}
}
/**
* Determine user type from JWT token.
* Determine a user type from a JWT token.
* Parses the JWT token to extract the user role from the claims.
* Uses caching to avoid repeated parsing of the same token.
*/
@ -467,24 +467,24 @@ private fun determineUserType(authHeader: String): String {
val matchResult = subjectRegex.find(payload)
val userId = matchResult?.groupValues?.get(1) ?: token.hashCode().toString()
// Store in cache for future use
// Store in a cache for future use
tokenCache[tokenHash] = Pair(userId, userType)
return userType
} catch (e: Exception) {
} catch (_: Exception) {
// If any error occurs during parsing, default to authenticated
return "authenticated"
}
}
/**
* Helper function to determine user type from JWT payload.
* Helper function to determine a user type from JWT payload.
* Extracted to avoid code duplication between extractUserIdFromToken and determineUserType.
*/
private fun determineUserTypeFromPayload(payload: String): String {
try {
// Extract the role using a simple regex
// Look for role, roles, or authorities claims
// Look for role, roles, or authority claims
val roleRegex = "\"(role|roles|authorities)\"\\s*:\\s*\"([^\"]+)\"".toRegex()
val matchResult = roleRegex.find(payload)
@ -508,9 +508,9 @@ private fun determineUserTypeFromPayload(payload: String): String {
}
}
// Default to authenticated if no role information found
// Default to authenticate if no role information found
return "authenticated"
} catch (e: Exception) {
} catch (_: Exception) {
// If any error occurs during parsing, default to authenticated
return "authenticated"
}

View File

@ -231,7 +231,7 @@ private fun generateRequestId(): String {
val uuid = UUID.randomUUID().toString()
val timestamp = System.currentTimeMillis()
// Get environment prefix safely (first 4 chars or less)
// Get environment prefix safely (first 4 chars or fewer)
val environment = AppConfig.environment.toString().let { env ->
if (env.length > 4) env.substring(0, 4) else env
}.lowercase()

View File

@ -1,17 +1,12 @@
package at.mocode.gateway
import at.mocode.gateway.config.configureMonitoring
import at.mocode.gateway.config.configureOpenApi
import at.mocode.gateway.config.configureRateLimiting
import at.mocode.gateway.config.configureRequestTracing
import at.mocode.gateway.config.configureSwagger
import at.mocode.gateway.config.*
import at.mocode.gateway.routing.docRoutes
import at.mocode.shared.config.AppConfig
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.application.*
import io.ktor.server.http.content.*
import io.ktor.server.plugins.calllogging.*
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.server.plugins.cors.routing.*
import io.ktor.server.response.*