Skip to content

Commit

Permalink
Add files via upload
Browse files Browse the repository at this point in the history
  • Loading branch information
dreamstar-enterprises authored Sep 9, 2024
1 parent 8d5ac6b commit 0a4bb85
Show file tree
Hide file tree
Showing 8 changed files with 466 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package com.frontiers.bff.routing

import com.frontiers.bff.props.ServerProperties
import io.github.resilience4j.timelimiter.TimeLimiterRegistry
import org.springframework.cloud.gateway.filter.factory.TokenRelayGatewayFilterFactory
import org.springframework.cloud.gateway.route.RouteLocator
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpMethod
import org.springframework.http.HttpStatus
import reactor.core.publisher.Mono
import java.time.Duration
import java.util.function.Supplier

/**********************************************************************************************************************/
/***************************************************** GATEWAY ROUTING ************************************************/
/**********************************************************************************************************************/

@Configuration
internal class RoutingConfig(
private val serverProperties: ServerProperties,
) {

@Bean
fun routeLocator(
builder: RouteLocatorBuilder,
tokenRelayGatewayFilterFactory: TokenRelayGatewayFilterFactory,
timeLimiterRegistry: TimeLimiterRegistry
): RouteLocator {
return builder.routes()

// routing for Resource Server
.route("resource-server") { r ->
r.path("/api/v1${serverProperties.resourceServerPrefix}/**")
.filters { f ->

// token relay filter
f.filter(tokenRelayGatewayFilterFactory.apply())

// circuit breaker configuration
f.circuitBreaker { circuitBreakerConfig ->
circuitBreakerConfig.setName("resourceServerCircuitBreaker")
circuitBreakerConfig.setFallbackUri("forward:/fallback")
circuitBreakerConfig.setStatusCodes(
setOf(
HttpStatus.INTERNAL_SERVER_ERROR.value().toString(), // 500
HttpStatus.NOT_IMPLEMENTED.value().toString(), // 501
HttpStatus.BAD_GATEWAY.value().toString(), // 502
HttpStatus.SERVICE_UNAVAILABLE.value().toString(), // 503
HttpStatus.GATEWAY_TIMEOUT.value().toString(), // 504
HttpStatus.HTTP_VERSION_NOT_SUPPORTED.value().toString(), // 505
HttpStatus.VARIANT_ALSO_NEGOTIATES.value().toString(), // 506
HttpStatus.INSUFFICIENT_STORAGE.value().toString(), // 507
HttpStatus.LOOP_DETECTED.value().toString(), // 508
HttpStatus.BANDWIDTH_LIMIT_EXCEEDED.value().toString(), // 509
HttpStatus.NOT_EXTENDED.value().toString(), // 510
HttpStatus.NETWORK_AUTHENTICATION_REQUIRED.value().toString() // 511
)
)
}

// time limiter configuration
f.filter { exchange, chain ->
val startTime = System.currentTimeMillis()
val timeLimiter = timeLimiterRegistry.timeLimiter("resourceServerTimeLimiter")
val futureSupplier = Supplier { chain.filter(exchange).toFuture() }
val decoratedCallable = timeLimiter.decorateFutureSupplier(futureSupplier)
val result = Mono.fromCallable(decoratedCallable)
return@filter result.doOnTerminate {
val endTime = System.currentTimeMillis()
println("Request took ${endTime - startTime} ms")
}
}

// retry configuration
f.retry { retryConfig ->
retryConfig.retries = 3
retryConfig.setMethods(HttpMethod.GET)
retryConfig.setBackoff(
Duration.ofMillis(50),
Duration.ofMillis(500),
2,
true
)
retryConfig.setStatuses(
HttpStatus.INTERNAL_SERVER_ERROR, // 500
HttpStatus.NOT_IMPLEMENTED, // 501
HttpStatus.BAD_GATEWAY, // 502
HttpStatus.SERVICE_UNAVAILABLE, // 503
HttpStatus.GATEWAY_TIMEOUT, // 504
HttpStatus.HTTP_VERSION_NOT_SUPPORTED, // 505
HttpStatus.VARIANT_ALSO_NEGOTIATES, // 506
HttpStatus.INSUFFICIENT_STORAGE, // 507
HttpStatus.LOOP_DETECTED, // 508
HttpStatus.BANDWIDTH_LIMIT_EXCEEDED, // 509
HttpStatus.NOT_EXTENDED, // 510
HttpStatus.NETWORK_AUTHENTICATION_REQUIRED // 511
)
retryConfig.validate()
}

// pass-through filter (optional, can be omitted if not needed)
f.filter { exchange, chain ->
chain.filter(exchange)
}
.removeRequestHeader("Cookie")
}
.uri(serverProperties.resourceServerUri)
}
.build()
}

}
/**********************************************************************************************************************/
/**************************************************** END OF KOTLIN ***************************************************/
/**********************************************************************************************************************/
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.example.bff.gateway.circuitbreaker

import io.github.resilience4j.circuitbreaker.CircuitBreaker
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig.SlidingWindowType
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import java.time.Duration

/**********************************************************************************************************************/
/***************************************************** CIRCUIT BREAKER ************************************************/
/**********************************************************************************************************************/

@Configuration
internal class CircuitBreakerConfig {

// default configuraiton
@Bean
fun customCircuitBreakerConfig(): CircuitBreakerConfig {
return CircuitBreakerConfig.custom()
.slidingWindowSize(10)
.slidingWindowType(SlidingWindowType.TIME_BASED)
.permittedNumberOfCallsInHalfOpenState(5)
.failureRateThreshold(50F)
.waitDurationInOpenState(Duration.ofSeconds(20))
.maxWaitDurationInHalfOpenState(Duration.ofSeconds(15))
.minimumNumberOfCalls(20)
.slowCallRateThreshold(50F)
.slowCallDurationThreshold(Duration.ofSeconds(7.5.toLong()))
.writableStackTraceEnabled(true)
.automaticTransitionFromOpenToHalfOpenEnabled(true)
.recordExceptions(Exception::class.java)
.ignoreExceptions(IllegalArgumentException::class.java)
.build()
}

@Bean
fun customCircuitBreakerRegistry(
customCircuitBreakerConfig: CircuitBreakerConfig
): CircuitBreakerRegistry {
return CircuitBreakerRegistry.of(customCircuitBreakerConfig)
}

@Bean
fun resourceServerCircuitBreaker(
circuitBreakerRegistry: CircuitBreakerRegistry,
customCircuitBreakerConfig: CircuitBreakerConfig
): CircuitBreaker {
return circuitBreakerRegistry.circuitBreaker("resourceServerCircuitBreaker", customCircuitBreakerConfig)
}

}

/**********************************************************************************************************************/
/**************************************************** END OF KOTLIN ***************************************************/
/**********************************************************************************************************************/
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.frontiers.bff.routing.circuitbreaker

import io.github.resilience4j.timelimiter.TimeLimiter
import io.github.resilience4j.timelimiter.TimeLimiterConfig
import io.github.resilience4j.timelimiter.TimeLimiterRegistry
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import java.time.Duration

/**********************************************************************************************************************/
/**************************************************** RATE LIMITER ****************************************************/
/**********************************************************************************************************************/

@Configuration
internal class TimeLimiterConfig {

// default configuraiton
@Bean
fun customTimeLimiterConfig(): TimeLimiterConfig {
return TimeLimiterConfig.custom()
.timeoutDuration(Duration.ofSeconds(10))
.cancelRunningFuture(true)
.build()
}

@Bean
fun customTimeLimiterRegistry(
customTimeLimiterConfig: TimeLimiterConfig
): TimeLimiterRegistry {
return TimeLimiterRegistry.of(customTimeLimiterConfig)
}

@Bean
fun resourceServerTimeLimiter(
timeLimiterRegistry: TimeLimiterRegistry,
customTimeLimiterConfig: TimeLimiterConfig
): TimeLimiter {
return timeLimiterRegistry.timeLimiter("resourceServerTimeLimiter", customTimeLimiterConfig)
}
}

/**********************************************************************************************************************/
/**************************************************** END OF KOTLIN ***************************************************/
/**********************************************************************************************************************/
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.frontiers.bff.routing.deduplicate

import org.springframework.cloud.gateway.filter.GlobalFilter
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpHeaders

/**********************************************************************************************************************/
/*************************************************** DEDUPE RESPONSE **************************************************/
/**********************************************************************************************************************/

@Configuration
internal class DedupeResponseHeaderConfig {

@Bean
fun dedupeResponseHeader(): GlobalFilter {
return GlobalFilter { exchange, chain ->
val headers = exchange.response.headers
dedupeHeaders(headers)
chain.filter(exchange)
}
}

private fun dedupeHeaders(headers: HttpHeaders) {
// Deduplicate specific response headers
headers.dedupeResponseHeader(
"Access-Control-Allow-Credentials",
"RETAIN_UNIQUE"
)
headers.dedupeResponseHeader(
"Access-Control-Allow-Origin",
"RETAIN_UNIQUE"
)
}

private fun HttpHeaders.dedupeResponseHeader(headerName: String, strategy: String) {
val headerValues = this[headerName] ?: return

val uniqueValues = when (strategy) {
"RETAIN_UNIQUE" -> headerValues.toSet().toList()
else -> headerValues
}

this[headerName] = uniqueValues
}

}

/**********************************************************************************************************************/
/**************************************************** END OF KOTLIN ***************************************************/
/**********************************************************************************************************************/
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.frontiers.bff.routing.headers

import org.springframework.cloud.gateway.filter.GlobalFilter
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.server.ServerWebExchange
import reactor.core.publisher.Mono

/**********************************************************************************************************************/
/****************************************************** HEADERS *******************************************************/
/**********************************************************************************************************************/

@Configuration
internal class HeaderFilterConfig {

@Bean
fun headerFilter(): GlobalFilter {
return GlobalFilter { exchange, chain ->
addCustomHeaders(exchange)
chain.filter(exchange)
}
}

private fun addCustomHeaders(exchange: ServerWebExchange) {
val headers = exchange.response.headers
headers.add(
"X-Powered-By",
"DreamStar Enterprises"
)
}

}

/**********************************************************************************************************************/
/**************************************************** END OF KOTLIN ***************************************************/
/**********************************************************************************************************************/
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.frontiers.bff.routing.ignore

import org.slf4j.LoggerFactory
import org.springframework.context.annotation.Configuration

/**********************************************************************************************************************/
/************************************************ IGNORE FILTER CONFIG ************************************************/
/**********************************************************************************************************************/

/**
* Configures endpoints considered that should be skipped
*/
@Configuration
internal class IgnoreFilterConfig {

private val logger = LoggerFactory.getLogger(IgnoreFilterConfig::class.java)

// define base paths and file extensions for security context loading
private val skipRequestPath: Map<String, List<String>> = mapOf(
"/login-options" to listOf(),
)

// main function that checks if the request path should skip static resources
fun shouldSkipRequestPath(requestPath: String): Boolean {
val isSkipped = getRequestPathMatchers().any { it.matches(requestPath) }
if (isSkipped) {
logger.info("Request path '$requestPath' is skipped as a skippable path.")
}
return isSkipped
}

// function that generates matchers for request path
private fun getRequestPathMatchers(): List<RequestPathMatcher> {
return skipRequestPath.flatMap { (basePath, extensions) ->
if (extensions.isEmpty()) {
listOf(RequestPathMatcher(basePath))
} else {
extensions.map { extension -> RequestPathMatcher(basePath, extension) }
}
}
}

// data class to encapsulate the logic for matching request paths
private data class RequestPathMatcher(val basePath: String, val extension: String? = null) {
fun matches(requestPath: String): Boolean {
return requestPath.startsWith(basePath) &&
(extension == null || requestPath.endsWith(".$extension", ignoreCase = true))
}
}

}

/**********************************************************************************************************************/
/**************************************************** END OF KOTLIN ***************************************************/
/**********************************************************************************************************************/
Loading

0 comments on commit 0a4bb85

Please sign in to comment.