fixing(Gateway)
This commit is contained in:
+197
@@ -0,0 +1,197 @@
|
||||
package at.mocode.infrastructure.gateway.config
|
||||
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilter
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain
|
||||
import org.springframework.cloud.gateway.filter.GlobalFilter
|
||||
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.core.Ordered
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse
|
||||
import org.springframework.stereotype.Component
|
||||
import org.springframework.web.server.ServerWebExchange
|
||||
import reactor.core.publisher.Mono
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
/**
|
||||
* Gateway-Konfiguration für erweiterte Funktionalitäten wie Logging, Rate Limiting und Security.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Global Filter für Korrelations-IDs zur Request-Verfolgung.
|
||||
*/
|
||||
@Component
|
||||
@org.springframework.context.annotation.Profile("!test")
|
||||
class CorrelationIdFilter : GlobalFilter, Ordered {
|
||||
|
||||
companion object {
|
||||
const val CORRELATION_ID_HEADER = "X-Correlation-ID"
|
||||
}
|
||||
|
||||
override fun filter(exchange: ServerWebExchange, chain: GatewayFilterChain): Mono<Void> {
|
||||
val request = exchange.request
|
||||
val correlationId = request.headers.getFirst(CORRELATION_ID_HEADER)
|
||||
?: UUID.randomUUID().toString()
|
||||
|
||||
val mutatedRequest = request.mutate()
|
||||
.header(CORRELATION_ID_HEADER, correlationId)
|
||||
.build()
|
||||
|
||||
val mutatedExchange = exchange.mutate()
|
||||
.request(mutatedRequest)
|
||||
.build()
|
||||
|
||||
// Add a response header after processing
|
||||
mutatedExchange.response.headers.add(CORRELATION_ID_HEADER, correlationId)
|
||||
|
||||
return chain.filter(mutatedExchange)
|
||||
}
|
||||
|
||||
override fun getOrder(): Int = Ordered.HIGHEST_PRECEDENCE
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhanced Logging Filter für strukturiertes Logging mit Request/Response Details.
|
||||
*/
|
||||
@Component
|
||||
@org.springframework.context.annotation.Profile("!test")
|
||||
class EnhancedLoggingFilter : GlobalFilter, Ordered {
|
||||
|
||||
override fun filter(exchange: ServerWebExchange, chain: GatewayFilterChain): Mono<Void> {
|
||||
val startTime = System.currentTimeMillis()
|
||||
val request = exchange.request
|
||||
val correlationId = request.headers.getFirst(CorrelationIdFilter.CORRELATION_ID_HEADER)
|
||||
|
||||
logRequest(request, correlationId)
|
||||
|
||||
return chain.filter(exchange)
|
||||
.doOnSuccess {
|
||||
val responseTime = System.currentTimeMillis() - startTime
|
||||
logResponse(exchange.response, correlationId, responseTime)
|
||||
}
|
||||
.doOnError { error ->
|
||||
val responseTime = System.currentTimeMillis() - startTime
|
||||
logError(error, correlationId, responseTime)
|
||||
}
|
||||
}
|
||||
|
||||
private fun logRequest(request: ServerHttpRequest, correlationId: String?) {
|
||||
println("""
|
||||
[${LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)}] [REQUEST] [${correlationId}]
|
||||
Method: ${request.method}
|
||||
URI: ${request.uri}
|
||||
RemoteAddress: ${request.remoteAddress}
|
||||
UserAgent: ${request.headers.getFirst("User-Agent")}
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
private fun logResponse(response: ServerHttpResponse, correlationId: String?, responseTime: Long) {
|
||||
println("""
|
||||
[${LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)}] [RESPONSE] [${correlationId}]
|
||||
Status: ${response.statusCode}
|
||||
ResponseTime: ${responseTime}ms
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
private fun logError(error: Throwable, correlationId: String?, responseTime: Long) {
|
||||
println("""
|
||||
[${LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)}] [ERROR] [${correlationId}]
|
||||
Error: ${error.message}
|
||||
ResponseTime: ${responseTime}ms
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
override fun getOrder(): Int = Ordered.HIGHEST_PRECEDENCE + 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Rate Limiting Filter basierend auf IP-Adresse und User-Typ.
|
||||
*/
|
||||
@Component
|
||||
@org.springframework.context.annotation.Profile("!test")
|
||||
class RateLimitingFilter : GlobalFilter, Ordered {
|
||||
|
||||
private val requestCounts = ConcurrentHashMap<String, RequestCounter>()
|
||||
|
||||
companion object {
|
||||
const val RATE_LIMIT_ENABLED_HEADER = "X-RateLimit-Enabled"
|
||||
const val RATE_LIMIT_LIMIT_HEADER = "X-RateLimit-Limit"
|
||||
const val RATE_LIMIT_REMAINING_HEADER = "X-RateLimit-Remaining"
|
||||
|
||||
// Rate Limits pro Minute
|
||||
const val ANONYMOUS_LIMIT = 50
|
||||
const val AUTHENTICATED_LIMIT = 200
|
||||
const val ADMIN_LIMIT = 500
|
||||
const val AUTH_ENDPOINT_LIMIT = 20
|
||||
const val DEFAULT_LIMIT = 100
|
||||
}
|
||||
|
||||
data class RequestCounter(
|
||||
var count: Int = 0,
|
||||
var lastReset: Long = System.currentTimeMillis()
|
||||
)
|
||||
|
||||
override fun filter(exchange: ServerWebExchange, chain: GatewayFilterChain): Mono<Void> {
|
||||
val request = exchange.request
|
||||
val response = exchange.response
|
||||
val clientIp = getClientIp(request)
|
||||
val path = request.path.value()
|
||||
|
||||
val limit = determineRateLimit(request, path)
|
||||
val counter = requestCounts.computeIfAbsent(clientIp) { RequestCounter() }
|
||||
|
||||
// Reset counter if more than a minute has passed
|
||||
val now = System.currentTimeMillis()
|
||||
if (now - counter.lastReset > 60_000) {
|
||||
counter.count = 0
|
||||
counter.lastReset = now
|
||||
}
|
||||
|
||||
counter.count++
|
||||
|
||||
// Add rate limit headers
|
||||
response.headers.add(RATE_LIMIT_ENABLED_HEADER, "true")
|
||||
response.headers.add(RATE_LIMIT_LIMIT_HEADER, limit.toString())
|
||||
response.headers.add(RATE_LIMIT_REMAINING_HEADER, maxOf(0, limit - counter.count).toString())
|
||||
|
||||
return if (counter.count > limit) {
|
||||
response.statusCode = HttpStatus.TOO_MANY_REQUESTS
|
||||
response.setComplete()
|
||||
} else {
|
||||
chain.filter(exchange)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getClientIp(request: ServerHttpRequest): String {
|
||||
return request.headers.getFirst("X-Forwarded-For")?.split(",")?.first()?.trim()
|
||||
?: request.headers.getFirst("X-Real-IP")
|
||||
?: request.remoteAddress?.address?.hostAddress
|
||||
?: "unknown"
|
||||
}
|
||||
|
||||
private fun determineRateLimit(request: ServerHttpRequest, path: String): Int {
|
||||
return when {
|
||||
path.startsWith("/api/auth") -> AUTH_ENDPOINT_LIMIT
|
||||
isAdminUser(request) -> ADMIN_LIMIT
|
||||
isAuthenticatedUser(request) -> AUTHENTICATED_LIMIT
|
||||
else -> ANONYMOUS_LIMIT
|
||||
}
|
||||
}
|
||||
|
||||
private fun isAuthenticatedUser(request: ServerHttpRequest): Boolean {
|
||||
return request.headers.getFirst("Authorization") != null
|
||||
}
|
||||
|
||||
private fun isAdminUser(request: ServerHttpRequest): Boolean {
|
||||
// This would typically decode the JWT and check for admin role
|
||||
// For now, we'll use a simple header check
|
||||
return request.headers.getFirst("X-User-Role") == "ADMIN"
|
||||
}
|
||||
|
||||
override fun getOrder(): Int = Ordered.HIGHEST_PRECEDENCE + 2
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package at.mocode.infrastructure.gateway.controller
|
||||
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RequestMethod
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import java.time.LocalDateTime
|
||||
|
||||
/**
|
||||
* Fallback Controller für Circuit Breaker Szenarien.
|
||||
* Bietet standardisierte Fehlermeldungen wenn Backend-Services nicht verfügbar sind.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/fallback")
|
||||
class FallbackController {
|
||||
|
||||
@RequestMapping(value = ["/members"], method = [RequestMethod.GET, RequestMethod.POST])
|
||||
fun membersFallback(): ResponseEntity<ErrorResponse> {
|
||||
return createFallbackResponse("members-service", "Member operations are temporarily unavailable")
|
||||
}
|
||||
|
||||
@RequestMapping(value = ["/horses"], method = [RequestMethod.GET, RequestMethod.POST])
|
||||
fun horsesFallback(): ResponseEntity<ErrorResponse> {
|
||||
return createFallbackResponse("horses-service", "Horse registry operations are temporarily unavailable")
|
||||
}
|
||||
|
||||
@RequestMapping(value = ["/events"], method = [RequestMethod.GET, RequestMethod.POST])
|
||||
fun eventsFallback(): ResponseEntity<ErrorResponse> {
|
||||
return createFallbackResponse("events-service", "Event management operations are temporarily unavailable")
|
||||
}
|
||||
|
||||
@RequestMapping(value = ["/masterdata"], method = [RequestMethod.GET, RequestMethod.POST])
|
||||
fun masterdataFallback(): ResponseEntity<ErrorResponse> {
|
||||
return createFallbackResponse("masterdata-service", "Master data operations are temporarily unavailable")
|
||||
}
|
||||
|
||||
@RequestMapping(value = ["/auth"], method = [RequestMethod.GET, RequestMethod.POST])
|
||||
fun authFallback(): ResponseEntity<ErrorResponse> {
|
||||
return createFallbackResponse("auth-service", "Authentication operations are temporarily unavailable")
|
||||
}
|
||||
|
||||
@RequestMapping(value = [""], method = [RequestMethod.GET, RequestMethod.POST])
|
||||
fun defaultFallback(): ResponseEntity<ErrorResponse> {
|
||||
return createFallbackResponse("unknown-service", "Service is temporarily unavailable")
|
||||
}
|
||||
|
||||
private fun createFallbackResponse(service: String, message: String): ResponseEntity<ErrorResponse> {
|
||||
val errorResponse = ErrorResponse(
|
||||
error = "SERVICE_UNAVAILABLE",
|
||||
message = message,
|
||||
service = service,
|
||||
timestamp = LocalDateTime.now(),
|
||||
status = HttpStatus.SERVICE_UNAVAILABLE.value(),
|
||||
suggestion = "Please try again in a few moments. If the problem persists, contact support."
|
||||
)
|
||||
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body(errorResponse)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Standardisierte Fehlerantwort für Circuit Breaker Fallbacks.
|
||||
*/
|
||||
data class ErrorResponse(
|
||||
val error: String,
|
||||
val message: String,
|
||||
val service: String,
|
||||
val timestamp: LocalDateTime,
|
||||
val status: Int,
|
||||
val suggestion: String
|
||||
)
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
package at.mocode.infrastructure.gateway.security
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilter
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain
|
||||
import org.springframework.cloud.gateway.filter.GlobalFilter
|
||||
import org.springframework.context.annotation.Profile
|
||||
import org.springframework.core.Ordered
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse
|
||||
import org.springframework.stereotype.Component
|
||||
import org.springframework.util.AntPathMatcher
|
||||
import org.springframework.web.server.ServerWebExchange
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
/**
|
||||
* JWT Authentication Filter für das Gateway.
|
||||
* Validiert JWT-Tokens für alle geschützten Endpunkte.
|
||||
*/
|
||||
@Component
|
||||
@ConditionalOnProperty(value = ["gateway.security.jwt.enabled"], havingValue = "true", matchIfMissing = true)
|
||||
class JwtAuthenticationFilter : GlobalFilter, Ordered {
|
||||
|
||||
private val pathMatcher = AntPathMatcher()
|
||||
|
||||
// Öffentliche Pfade, die keine Authentifizierung erfordern
|
||||
private val publicPaths = listOf(
|
||||
"/",
|
||||
"/health",
|
||||
"/actuator/**",
|
||||
"/api/auth/login",
|
||||
"/api/auth/register",
|
||||
"/api/auth/refresh",
|
||||
"/fallback/**",
|
||||
"/docs/**",
|
||||
"/swagger-ui/**",
|
||||
"/api/ping/**" // Ping Service für Monitoring
|
||||
)
|
||||
|
||||
override fun filter(exchange: ServerWebExchange, chain: GatewayFilterChain): Mono<Void> {
|
||||
val request = exchange.request
|
||||
val path = request.path.value()
|
||||
|
||||
// Prüfe ob der Pfad öffentlich zugänglich ist
|
||||
if (isPublicPath(path)) {
|
||||
return chain.filter(exchange)
|
||||
}
|
||||
|
||||
// Extrahiere JWT aus Authorization Header
|
||||
val authHeader = request.headers.getFirst("Authorization")
|
||||
|
||||
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
|
||||
return handleUnauthorized(exchange, "Missing or invalid Authorization header")
|
||||
}
|
||||
|
||||
val token = authHeader.substring(7)
|
||||
|
||||
// Hier würde normalerweise die JWT-Validierung mit dem auth-client erfolgen
|
||||
// Für diese Implementation verwenden wir eine vereinfachte Validierung
|
||||
return validateJwtToken(token, exchange, chain)
|
||||
}
|
||||
|
||||
private fun isPublicPath(path: String): Boolean {
|
||||
return publicPaths.any { publicPath ->
|
||||
pathMatcher.match(publicPath, path)
|
||||
}
|
||||
}
|
||||
|
||||
private fun validateJwtToken(
|
||||
token: String,
|
||||
exchange: ServerWebExchange,
|
||||
chain: GatewayFilterChain
|
||||
): Mono<Void> {
|
||||
|
||||
// Einfache Token-Validierung (in der Realität würde hier der auth-client verwendet)
|
||||
if (token.isEmpty() || token.length < 10) {
|
||||
return handleUnauthorized(exchange, "Invalid JWT token")
|
||||
}
|
||||
|
||||
// Füge User-Information zu Headers hinzu (simuliert)
|
||||
val userRole = extractUserRole(token)
|
||||
val userId = extractUserId(token)
|
||||
|
||||
val mutatedRequest = exchange.request.mutate()
|
||||
.header("X-User-ID", userId)
|
||||
.header("X-User-Role", userRole)
|
||||
.build()
|
||||
|
||||
val mutatedExchange = exchange.mutate()
|
||||
.request(mutatedRequest)
|
||||
.build()
|
||||
|
||||
return chain.filter(mutatedExchange)
|
||||
}
|
||||
|
||||
private fun extractUserRole(token: String): String {
|
||||
// Vereinfachte Rollenextraktion (normalerweise aus JWT Claims)
|
||||
return when {
|
||||
token.contains("admin") -> "ADMIN"
|
||||
token.contains("user") -> "USER"
|
||||
else -> "GUEST"
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractUserId(token: String): String {
|
||||
// Vereinfachte User-ID Extraktion (normalerweise aus JWT Subject)
|
||||
return "user-${token.hashCode()}"
|
||||
}
|
||||
|
||||
private fun handleUnauthorized(exchange: ServerWebExchange, message: String): Mono<Void> {
|
||||
val response: ServerHttpResponse = exchange.response
|
||||
response.statusCode = HttpStatus.UNAUTHORIZED
|
||||
response.headers.add("Content-Type", "application/json")
|
||||
|
||||
val errorJson = """{
|
||||
"error": "UNAUTHORIZED",
|
||||
"message": "$message",
|
||||
"timestamp": "${java.time.LocalDateTime.now()}",
|
||||
"status": 401
|
||||
}"""
|
||||
|
||||
val buffer = response.bufferFactory().wrap(errorJson.toByteArray())
|
||||
return response.writeWith(Mono.just(buffer))
|
||||
}
|
||||
|
||||
override fun getOrder(): Int = Ordered.HIGHEST_PRECEDENCE + 3
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package at.mocode.infrastructure.gateway.security
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
|
||||
/**
|
||||
* Minimal reactive security configuration for the Gateway.
|
||||
*
|
||||
* Rationale:
|
||||
* - During tests, Spring Security is on the classpath (testImplementation), which enables
|
||||
* security auto-configuration and can lock down all endpoints unless a SecurityWebFilterChain is provided.
|
||||
* - The Gateway enforces auth using a GlobalFilter (JwtAuthenticationFilter) when enabled via property,
|
||||
* so the SecurityWebFilterChain should stay permissive and let the filter do the auth work.
|
||||
*/
|
||||
@Configuration
|
||||
class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http
|
||||
.csrf { it.disable() }
|
||||
.cors { }
|
||||
.authorizeExchange { exchanges ->
|
||||
exchanges
|
||||
.anyExchange().permitAll()
|
||||
}
|
||||
.build()
|
||||
}
|
||||
}
|
||||
@@ -1,40 +1,147 @@
|
||||
# Port, auf dem das Gateway läuft
|
||||
server:
|
||||
port: 8080
|
||||
# Optimierte Netty-Konfiguration für reaktive Anwendungen
|
||||
netty:
|
||||
connection-timeout: 5s
|
||||
idle-timeout: 15s
|
||||
|
||||
# Name, unter dem sich das Gateway in Consul registriert
|
||||
spring:
|
||||
application:
|
||||
name: api-gateway
|
||||
profiles:
|
||||
active: ${SPRING_PROFILES_ACTIVE:dev}
|
||||
security:
|
||||
user:
|
||||
name: admin
|
||||
password: admin
|
||||
name: ${GATEWAY_ADMIN_USER:admin}
|
||||
password: ${GATEWAY_ADMIN_PASSWORD:admin}
|
||||
cloud:
|
||||
consul:
|
||||
host: localhost
|
||||
port: 8500
|
||||
host: ${CONSUL_HOST:localhost}
|
||||
port: ${CONSUL_PORT:8500}
|
||||
discovery:
|
||||
register: true
|
||||
health-check-path: /actuator/health
|
||||
health-check-interval: 10s
|
||||
instance-id: ${spring.application.name}-${server.port}-${random.uuid}
|
||||
gateway:
|
||||
# HTTP Client-Timeouts für stabile Upstream-Verbindungen
|
||||
httpclient:
|
||||
connect-timeout: 5000 # in Millisekunden
|
||||
response-timeout: 30s
|
||||
# Globales CORS-Setup (kann pro Umgebung überschrieben werden)
|
||||
pool:
|
||||
type: elastic
|
||||
max-idle-time: 15s
|
||||
max-life-time: 60s
|
||||
# Verbesserte CORS-Konfiguration
|
||||
globalcors:
|
||||
corsConfigurations:
|
||||
'[/**]':
|
||||
allowedOrigins: "*"
|
||||
allowedMethods: "*"
|
||||
allowedHeaders: "*"
|
||||
# Antwort-Header bereinigen (verhindert doppelte CORS-Header)
|
||||
allowedOriginPatterns:
|
||||
- "https://*.meldestelle.at"
|
||||
- "http://localhost:*"
|
||||
allowedMethods:
|
||||
- GET
|
||||
- POST
|
||||
- PUT
|
||||
- DELETE
|
||||
- PATCH
|
||||
- OPTIONS
|
||||
allowedHeaders:
|
||||
- "*"
|
||||
allowCredentials: true
|
||||
maxAge: 3600
|
||||
# Antwort-Header bereinigen und globale Filter
|
||||
default-filters:
|
||||
- DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin
|
||||
- name: CircuitBreaker
|
||||
args:
|
||||
name: defaultCircuitBreaker
|
||||
fallbackUri: forward:/fallback
|
||||
- name: Retry
|
||||
args:
|
||||
retries: 3
|
||||
statuses: BAD_GATEWAY,GATEWAY_TIMEOUT
|
||||
methods: GET,POST,PUT,DELETE
|
||||
backoff:
|
||||
firstBackoff: 50ms
|
||||
maxBackoff: 500ms
|
||||
factor: 2
|
||||
basedOnPreviousValue: false
|
||||
# Route definitions with service discovery
|
||||
routes:
|
||||
# Health Check und Gateway Info Routes
|
||||
- id: gateway-info-route
|
||||
uri: http://localhost:${server.port}
|
||||
predicates:
|
||||
- Path=/
|
||||
- Method=GET
|
||||
filters:
|
||||
- SetStatus=200
|
||||
- SetResponseHeader=Content-Type,application/json
|
||||
|
||||
# Members Service Routes
|
||||
- id: members-service-route
|
||||
uri: lb://members-service
|
||||
predicates:
|
||||
- Path=/api/members/**
|
||||
filters:
|
||||
- StripPrefix=1
|
||||
- name: CircuitBreaker
|
||||
args:
|
||||
name: membersCircuitBreaker
|
||||
fallbackUri: forward:/fallback/members
|
||||
|
||||
# Horses Service Routes
|
||||
- id: horses-service-route
|
||||
uri: lb://horses-service
|
||||
predicates:
|
||||
- Path=/api/horses/**
|
||||
filters:
|
||||
- StripPrefix=1
|
||||
- name: CircuitBreaker
|
||||
args:
|
||||
name: horsesCircuitBreaker
|
||||
fallbackUri: forward:/fallback/horses
|
||||
|
||||
# Events Service Routes
|
||||
- id: events-service-route
|
||||
uri: lb://events-service
|
||||
predicates:
|
||||
- Path=/api/events/**
|
||||
filters:
|
||||
- StripPrefix=1
|
||||
- name: CircuitBreaker
|
||||
args:
|
||||
name: eventsCircuitBreaker
|
||||
fallbackUri: forward:/fallback/events
|
||||
|
||||
# Masterdata Service Routes
|
||||
- id: masterdata-service-route
|
||||
uri: lb://masterdata-service
|
||||
predicates:
|
||||
- Path=/api/masterdata/**
|
||||
filters:
|
||||
- StripPrefix=1
|
||||
- name: CircuitBreaker
|
||||
args:
|
||||
name: masterdataCircuitBreaker
|
||||
fallbackUri: forward:/fallback/masterdata
|
||||
|
||||
# Auth Service Routes (if exists)
|
||||
- id: auth-service-route
|
||||
uri: lb://auth-service
|
||||
predicates:
|
||||
- Path=/api/auth/**
|
||||
filters:
|
||||
- StripPrefix=1
|
||||
- name: CircuitBreaker
|
||||
args:
|
||||
name: authCircuitBreaker
|
||||
fallbackUri: forward:/fallback/auth
|
||||
|
||||
# Ping Service Routes (existing)
|
||||
- id: ping-service-route
|
||||
uri: lb://ping-service
|
||||
predicates:
|
||||
@@ -42,8 +149,71 @@ spring:
|
||||
filters:
|
||||
- StripPrefix=1
|
||||
|
||||
# Circuit Breaker Configuration
|
||||
resilience4j:
|
||||
circuitbreaker:
|
||||
configs:
|
||||
default:
|
||||
registerHealthIndicator: true
|
||||
slidingWindowSize: 100
|
||||
minimumNumberOfCalls: 20
|
||||
permittedNumberOfCallsInHalfOpenState: 3
|
||||
automaticTransitionFromOpenToHalfOpenEnabled: true
|
||||
waitDurationInOpenState: 5s
|
||||
failureRateThreshold: 50
|
||||
eventConsumerBufferSize: 10
|
||||
recordExceptions:
|
||||
- org.springframework.web.client.HttpServerErrorException
|
||||
- java.util.concurrent.TimeoutException
|
||||
- java.io.IOException
|
||||
instances:
|
||||
defaultCircuitBreaker:
|
||||
baseConfig: default
|
||||
membersCircuitBreaker:
|
||||
baseConfig: default
|
||||
slidingWindowSize: 50
|
||||
horsesCircuitBreaker:
|
||||
baseConfig: default
|
||||
slidingWindowSize: 50
|
||||
eventsCircuitBreaker:
|
||||
baseConfig: default
|
||||
slidingWindowSize: 75
|
||||
masterdataCircuitBreaker:
|
||||
baseConfig: default
|
||||
slidingWindowSize: 30
|
||||
authCircuitBreaker:
|
||||
baseConfig: default
|
||||
slidingWindowSize: 20
|
||||
failureRateThreshold: 30
|
||||
|
||||
# Management und Monitoring
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: health,info
|
||||
include: health,info,metrics,prometheus,gateway
|
||||
endpoint:
|
||||
health:
|
||||
show-details: always
|
||||
show-components: always
|
||||
metrics:
|
||||
enabled: true
|
||||
metrics:
|
||||
export:
|
||||
prometheus:
|
||||
distribution:
|
||||
percentiles-histogram:
|
||||
spring.cloud.gateway.requests: true
|
||||
percentiles:
|
||||
spring.cloud.gateway.requests: 0.5,0.95,0.99
|
||||
tags:
|
||||
application: ${spring.application.name}
|
||||
|
||||
# Logging Configuration
|
||||
logging:
|
||||
level:
|
||||
org.springframework.cloud.gateway: INFO
|
||||
org.springframework.cloud.loadbalancer: DEBUG
|
||||
at.mocode.infrastructure.gateway: DEBUG
|
||||
pattern:
|
||||
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level [%X{correlationId:-}] %logger{36} - %msg%n"
|
||||
|
||||
Reference in New Issue
Block a user