(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:
@@ -1,16 +1,11 @@
|
|||||||
package at.mocode.gateway.config
|
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 at.mocode.shared.config.AppConfig
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
import io.ktor.server.application.*
|
import io.ktor.server.application.*
|
||||||
import io.ktor.server.plugins.calllogging.*
|
|
||||||
import io.ktor.server.request.*
|
import io.ktor.server.request.*
|
||||||
import io.ktor.server.response.*
|
|
||||||
import io.ktor.util.*
|
import io.ktor.util.*
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.slf4j.event.Level
|
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
@@ -158,7 +153,7 @@ private fun extractBasePath(path: String): String {
|
|||||||
if (parts.isEmpty()) return "/"
|
if (parts.isEmpty()) return "/"
|
||||||
|
|
||||||
// For API paths, include up to the resource name (typically 3 parts: api, version, resource)
|
// 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)
|
val depth = minOf(3, parts.size)
|
||||||
return "/" + parts.take(depth).joinToString("/")
|
return "/" + parts.take(depth).joinToString("/")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package at.mocode.gateway.config
|
package at.mocode.gateway.config
|
||||||
|
|
||||||
import at.mocode.dto.base.ApiResponse
|
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 at.mocode.shared.config.AppConfig
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
import io.ktor.server.application.*
|
import io.ktor.server.application.*
|
||||||
@@ -14,9 +12,9 @@ import org.slf4j.event.Level
|
|||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -104,7 +102,7 @@ private fun extractBasePath(path: String): String {
|
|||||||
if (parts.isEmpty()) return "/"
|
if (parts.isEmpty()) return "/"
|
||||||
|
|
||||||
// For API paths, include up to the resource name (typically 3 parts: api, version, resource)
|
// 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)
|
val depth = minOf(3, parts.size)
|
||||||
return "/" + parts.take(depth).joinToString("/")
|
return "/" + parts.take(depth).joinToString("/")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ private val cacheCleanupScheduler = java.util.Timer("token-cache-cleanup").apply
|
|||||||
schedule(object : java.util.TimerTask() {
|
schedule(object : java.util.TimerTask() {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
if (tokenCache.size > TOKEN_CACHE_MAX_SIZE) {
|
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)
|
val keysToRemove = tokenCache.keys.take(tokenCache.size - TOKEN_CACHE_MAX_SIZE / 2)
|
||||||
keysToRemove.forEach { tokenCache.remove(it) }
|
keysToRemove.forEach { tokenCache.remove(it) }
|
||||||
}
|
}
|
||||||
@@ -56,15 +56,15 @@ private object AdaptiveRateLimiting {
|
|||||||
|
|
||||||
// Thresholds for CPU usage (percentage)
|
// Thresholds for CPU usage (percentage)
|
||||||
const val CPU_MEDIUM_LOAD_THRESHOLD = 60.0 // Medium load threshold (60%)
|
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)
|
// Thresholds for memory usage (percentage)
|
||||||
const val MEMORY_MEDIUM_LOAD_THRESHOLD = 70.0 // Medium load threshold (70%)
|
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
|
// Rate limit adjustment factors
|
||||||
const val MEDIUM_LOAD_FACTOR = 0.7 // Reduce limits to 70% under medium 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 high load
|
const val HIGH_LOAD_FACTOR = 0.4 // Reduce limits to 40% under a high load
|
||||||
|
|
||||||
// Monitoring interval in milliseconds
|
// Monitoring interval in milliseconds
|
||||||
const val MONITORING_INTERVAL_MS = 5000L // Check every 5 seconds
|
const val MONITORING_INTERVAL_MS = 5000L // Check every 5 seconds
|
||||||
@@ -415,18 +415,18 @@ private fun extractUserIdFromToken(authHeader: String): String? {
|
|||||||
// Get the user ID
|
// Get the user ID
|
||||||
val userId = matchResult?.groupValues?.get(1) ?: token.hashCode().toString()
|
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)
|
tokenCache[tokenHash] = Pair(userId, userType)
|
||||||
|
|
||||||
return userId
|
return userId
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
// If any error occurs during parsing, fall back to using the token hash
|
// If any error occurs during parsing, fall back to using the token hash
|
||||||
return authHeader.hashCode().toString()
|
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.
|
* Parses the JWT token to extract the user role from the claims.
|
||||||
* Uses caching to avoid repeated parsing of the same token.
|
* 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 matchResult = subjectRegex.find(payload)
|
||||||
val userId = matchResult?.groupValues?.get(1) ?: token.hashCode().toString()
|
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)
|
tokenCache[tokenHash] = Pair(userId, userType)
|
||||||
|
|
||||||
return userType
|
return userType
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
// If any error occurs during parsing, default to authenticated
|
// If any error occurs during parsing, default to authenticated
|
||||||
return "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.
|
* Extracted to avoid code duplication between extractUserIdFromToken and determineUserType.
|
||||||
*/
|
*/
|
||||||
private fun determineUserTypeFromPayload(payload: String): String {
|
private fun determineUserTypeFromPayload(payload: String): String {
|
||||||
try {
|
try {
|
||||||
// Extract the role using a simple regex
|
// 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 roleRegex = "\"(role|roles|authorities)\"\\s*:\\s*\"([^\"]+)\"".toRegex()
|
||||||
val matchResult = roleRegex.find(payload)
|
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"
|
return "authenticated"
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
// If any error occurs during parsing, default to authenticated
|
// If any error occurs during parsing, default to authenticated
|
||||||
return "authenticated"
|
return "authenticated"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -231,7 +231,7 @@ private fun generateRequestId(): String {
|
|||||||
val uuid = UUID.randomUUID().toString()
|
val uuid = UUID.randomUUID().toString()
|
||||||
val timestamp = System.currentTimeMillis()
|
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 ->
|
val environment = AppConfig.environment.toString().let { env ->
|
||||||
if (env.length > 4) env.substring(0, 4) else env
|
if (env.length > 4) env.substring(0, 4) else env
|
||||||
}.lowercase()
|
}.lowercase()
|
||||||
|
|||||||
@@ -1,17 +1,12 @@
|
|||||||
package at.mocode.gateway
|
package at.mocode.gateway
|
||||||
|
|
||||||
import at.mocode.gateway.config.configureMonitoring
|
import at.mocode.gateway.config.*
|
||||||
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.routing.docRoutes
|
import at.mocode.gateway.routing.docRoutes
|
||||||
import at.mocode.shared.config.AppConfig
|
import at.mocode.shared.config.AppConfig
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
import io.ktor.serialization.kotlinx.json.*
|
import io.ktor.serialization.kotlinx.json.*
|
||||||
import io.ktor.server.application.*
|
import io.ktor.server.application.*
|
||||||
import io.ktor.server.http.content.*
|
import io.ktor.server.http.content.*
|
||||||
import io.ktor.server.plugins.calllogging.*
|
|
||||||
import io.ktor.server.plugins.contentnegotiation.*
|
import io.ktor.server.plugins.contentnegotiation.*
|
||||||
import io.ktor.server.plugins.cors.routing.*
|
import io.ktor.server.plugins.cors.routing.*
|
||||||
import io.ktor.server.response.*
|
import io.ktor.server.response.*
|
||||||
|
|||||||
Reference in New Issue
Block a user