Merge branch 'spring-cloud:main' into #2975-Suggestion-Add-Permissions-Policy-as-configurable-option-to-SecureHeaders-GatewayFilter
This commit is contained in:
@@ -12,6 +12,9 @@ The `Retry` `GatewayFilter` factory supports the following parameters:
|
||||
Retries are performed after a backoff interval of `firstBackoff * (factor ^ n)`, where `n` is the iteration.
|
||||
If `maxBackoff` is configured, the maximum backoff applied is limited to `maxBackoff`.
|
||||
If `basedOnPreviousValue` is true, the backoff is calculated by using `prevBackoff * factor`.
|
||||
* `jitter`: The configured random jitter for the retries.
|
||||
Generating a backoff between `[backoff - backoff*randomFactor, backoff + backoff*randomFactor]`
|
||||
* `timeout`: The configured timeout for the retries.
|
||||
|
||||
The following defaults are configured for `Retry` filter, if enabled:
|
||||
|
||||
@@ -20,6 +23,8 @@ The following defaults are configured for `Retry` filter, if enabled:
|
||||
* `methods`: GET method
|
||||
* `exceptions`: `IOException` and `TimeoutException`
|
||||
* `backoff`: disabled
|
||||
* `jitter`: disabled
|
||||
* `timeout`: unlimited
|
||||
|
||||
The following listing configures a Retry `GatewayFilter`:
|
||||
|
||||
@@ -45,6 +50,9 @@ spring:
|
||||
maxBackoff: 50ms
|
||||
factor: 2
|
||||
basedOnPreviousValue: false
|
||||
jitter:
|
||||
randomFactor: 0.5
|
||||
timeout: 100ms
|
||||
----
|
||||
|
||||
NOTE: When using the retry filter with a `forward:` prefixed URL, the target endpoint should be written carefully so that, in case of an error, it does not do anything that could result in a response being sent to the client and committed.
|
||||
@@ -79,10 +87,13 @@ spring:
|
||||
maxBackoff: 50ms
|
||||
factor: 2
|
||||
basedOnPreviousValue: false
|
||||
jitter:
|
||||
randomFactor: 0.5
|
||||
timeout: 100ms
|
||||
|
||||
- id: retryshortcut_route
|
||||
uri: https://example.org
|
||||
filters:
|
||||
- Retry=3,INTERNAL_SERVER_ERROR,GET,10ms,50ms,2,false
|
||||
- Retry=3,INTERNAL_SERVER_ERROR,GET,10ms,50ms,2,false,0.5,100ms
|
||||
----
|
||||
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
== Forwarded Headers Filter
|
||||
The `Forwarded` Headers Filter creates a `Forwarded` header to send to the downstream service. It adds the `Host` header, scheme and port of the current request to any existing `Forwarded` header.
|
||||
|
||||
The `Forwarded by` header part can be enabled by setting the following property to true (defaults to false):
|
||||
|
||||
- `spring.cloud.gateway.forwarded.by.enabled=true`
|
||||
|
||||
[[removehopbyhop-headers-filter]]
|
||||
== RemoveHopByHop Headers Filter
|
||||
The `RemoveHopByHop` Headers Filter removes headers from forwarded requests. The default list of headers that is removed comes from the https://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-14#section-7.1.3[IETF].
|
||||
|
||||
@@ -14,6 +14,8 @@ spring:
|
||||
routes:
|
||||
- id: add_request_header_route
|
||||
uri: https://example.org
|
||||
predicates:
|
||||
- Path=/red
|
||||
filters:
|
||||
- AddRequestHeader=X-Request-red, blue
|
||||
----
|
||||
@@ -21,6 +23,7 @@ spring:
|
||||
.GatewaySampleApplication.java
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.addRequestHeader;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
@@ -30,10 +33,10 @@ class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsAddReqHeader() {
|
||||
return route("addRequestHeader")
|
||||
.route(GET("/red"), http("https://example.org"))
|
||||
.before(addRequestHeader("X-Request-red", "blue"))
|
||||
.build();
|
||||
return route("add_request_header_route")
|
||||
.GET("/red", http())
|
||||
.before(uri("https://example.org"))
|
||||
.before(addRequestHeader("X-Request-red", "blue"));
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -47,15 +50,20 @@ The following example configures an `AddRequestHeader` filter that uses a variab
|
||||
.GatewaySampleApplication.java
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.addRequestHeader;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
|
||||
@Configuration
|
||||
class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsAddReqHeader() {
|
||||
return route("addRequestHeader")
|
||||
.route(GET("/red/{segment}"), http("https://example.org"))
|
||||
.before(addRequestHeader("X-Request-red", "blue-{segment}"))
|
||||
.build();
|
||||
return route("add_request_header_route")
|
||||
.GET("/red/{segment}", http())
|
||||
.before(uri("https://example.org"))
|
||||
.before(addRequestHeader("X-Request-red", "blue-{segment}"));
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
@@ -12,8 +12,10 @@ spring:
|
||||
gateway:
|
||||
mvc:
|
||||
routes:
|
||||
- id: add_request_headers_route
|
||||
- id: add_request_headers_route_inp
|
||||
uri: https://example.org
|
||||
predicates:
|
||||
- Path=/red
|
||||
filters:
|
||||
- AddRequestHeadersIfNotPresent=X-Request-Color-1:blue,X-Request-Color-2:green
|
||||
----
|
||||
@@ -21,6 +23,7 @@ spring:
|
||||
.GatewaySampleApplication.java
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.addRequestHeadersIfNotPresent;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
@@ -29,9 +32,11 @@ import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFuncti
|
||||
class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsAddReqHeader() {
|
||||
return route(GET("/red"), http("https://example.org"))
|
||||
.before(addRequestHeadersIfNotPresent("X-Request-Color-1:blue","X-Request-Color-2:green"));
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsAddReqHeaderInp() {
|
||||
return route("add_request_headers_route_inp")
|
||||
.GET("/red", http())
|
||||
.before(uri("https://example.org"))
|
||||
.before(addRequestHeadersIfNotPresent("X-Request-Color-1:blue","X-Request-Color-2:green"));
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -49,13 +54,20 @@ The following example configures an `AddRequestHeadersIfNotPresent` filter that
|
||||
.GatewaySampleApplication.java
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.addRequestHeadersIfNotPresent;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
|
||||
@Configuration
|
||||
class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsAddReqHeader() {
|
||||
return route(GET("/red/{segment}"), http("https://example.org"))
|
||||
.before(addRequestHeadersIfNotPresent("X-Request-red", "blue-{segment}"));
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsAddReqHeaderInp() {
|
||||
return route("add_request_header_route_inp")
|
||||
.GET("/red/{segment}", http())
|
||||
.before(uri("https://example.org"))
|
||||
.before(addRequestHeadersIfNotPresent("X-Request-red", "blue-{segment}"));
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -66,7 +78,7 @@ spring:
|
||||
cloud:
|
||||
gateway:
|
||||
routes:
|
||||
- id: add_request_header_route
|
||||
- id: add_request_header_route_inp
|
||||
uri: https://example.org
|
||||
predicates:
|
||||
- Path=/red/{segment}
|
||||
|
||||
@@ -14,6 +14,8 @@ spring:
|
||||
routes:
|
||||
- id: add_request_parameter_route
|
||||
uri: https://example.org
|
||||
predicates:
|
||||
- Path=/anything/addrequestparam
|
||||
filters:
|
||||
- AddRequestParameter=red, blue
|
||||
----
|
||||
@@ -21,6 +23,7 @@ spring:
|
||||
.GatewaySampleApplication.java
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.addRequestParameter;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
@@ -29,11 +32,12 @@ import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFuncti
|
||||
class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsAddReqHeader() {
|
||||
return route("add_request_parameter_route")
|
||||
.GET("/anything/addrequestparam", http("https://example.org"))
|
||||
.before(addRequestParameter("red", "blue"))
|
||||
.build();
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsAddReqParameter() {
|
||||
return route("add_request_parameter_route")
|
||||
.GET("/anything/addrequestparam", http())
|
||||
.before(uri("https://example.org"))
|
||||
.before(addRequestParameter("red", "blue"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -47,6 +51,7 @@ The following example configures an `AddRequestParameter` filter that uses a var
|
||||
.GatewaySampleApplication.java
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.addRequestParameter;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
@@ -56,11 +61,12 @@ import static org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequ
|
||||
class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsAddReqHeader() {
|
||||
return route("add_request_parameter_route")
|
||||
.route(host("{segment}.myhost.org"), http("https://example.org"))
|
||||
.before(addRequestParameter("foo", "bar-{segment}"))
|
||||
.build();
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsAddReqParameter() {
|
||||
return route("add_request_parameter_route")
|
||||
.route(host("{segment}.myhost.org"), http())
|
||||
.before(uri("https://example.org"))
|
||||
.before(addRequestParameter("foo", "bar-{segment}"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -14,6 +14,8 @@ spring:
|
||||
routes:
|
||||
- id: add_response_header_route
|
||||
uri: https://example.org
|
||||
predicates:
|
||||
- Path=/anything/addresheader
|
||||
filters:
|
||||
- AddResponseHeader=X-Response-Red, Blue
|
||||
----
|
||||
@@ -21,6 +23,7 @@ spring:
|
||||
.GatewaySampleApplication.java
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.AfterFilterFunctions.addResponseHeader;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
@@ -30,9 +33,10 @@ class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsAddRespHeader() {
|
||||
return route("addresponseheader")
|
||||
.GET("/anything/addresheader", http("https://example.org"))
|
||||
.after(addResponseHeader("X-Response-Red", "Blue"))
|
||||
return route("add_response_header_route")
|
||||
.GET("/anything/addresheader", http())
|
||||
.before(uri("https://example.org"))
|
||||
.after(addResponseHeader("X-Response-Red", "Blue"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -47,6 +51,7 @@ The following example configures an `AddResponseHeader` filter that uses a varia
|
||||
.GatewaySampleApplication.java
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.AfterFilterFunctions.addResponseHeader;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
@@ -57,10 +62,11 @@ class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsAddRespHeader() {
|
||||
return route("add_response_header_route")
|
||||
.route(host("{segment}.myhost.org"), http("https://example.org"))
|
||||
.after(addResponseHeader("foo", "bar-{segment}"))
|
||||
.build();
|
||||
return route("add_response_header_route")
|
||||
.route(host("{segment}.myhost.org"), http())
|
||||
.before(uri("https://example.org"))
|
||||
.after(addResponseHeader("foo", "bar-{segment}"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
@@ -15,14 +15,17 @@ spring:
|
||||
gateway:
|
||||
mvc:
|
||||
routes:
|
||||
- id: circuitbreaker_route
|
||||
- id: circuitbreakernofallback
|
||||
uri: https://example.org
|
||||
predicates:
|
||||
- Path=/anything/circuitbreakernofallback
|
||||
filters:
|
||||
- CircuitBreaker=myCircuitBreaker
|
||||
----
|
||||
.GatewaySampleApplication.java
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.CircuitBreakerFilterFunctions.circuitBreaker;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
@@ -33,9 +36,10 @@ class RouteConfiguration {
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsCircuitBreakerNoFallback() {
|
||||
return route("circuitbreakernofallback")
|
||||
.route(path("/anything/circuitbreakernofallback"), http("https://example.org"))
|
||||
.filter(circuitBreaker("mycb3"))
|
||||
.build();
|
||||
.route(path("/anything/circuitbreakernofallback"), http())
|
||||
.before(uri("https://example.org"))
|
||||
.filter(circuitBreaker("myCircuitBreaker"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -74,6 +78,7 @@ The following listing does the same thing in Java:
|
||||
[source,java]
|
||||
----
|
||||
import java.net.URI;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.CircuitBreakerFilterFunctions.circuitBreaker;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
@@ -84,9 +89,10 @@ class RouteConfiguration {
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsCircuitBreakerFallback() {
|
||||
return route("circuitbreaker_route")
|
||||
.route(path("/consumingServiceEndpoint"), http("https://example.org"))
|
||||
.filter(circuitBreaker("myCircuitBreaker", URI.create("forward:/inCaseOfFailureUseThis")))
|
||||
.build();
|
||||
.route(path("/consumingServiceEndpoint"), http())
|
||||
.before(uri("https://example.org"))
|
||||
.filter(circuitBreaker("myCircuitBreaker", URI.create("forward:/inCaseOfFailureUseThis")))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -102,6 +108,7 @@ In the example below the call `consumingServiceEndpoint/users/1` will be redirec
|
||||
[source,java]
|
||||
----
|
||||
import java.net.URI;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.CircuitBreakerFilterFunctions.circuitBreaker;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
@@ -112,9 +119,10 @@ class RouteConfiguration {
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsCircuitBreakerFallback() {
|
||||
return route("circuitbreaker_route")
|
||||
.route(path("/consumingServiceEndpoint/{*segments}"), http("https://example.org"))
|
||||
.filter(circuitBreaker("myCircuitBreaker", URI.create("forward:/inCaseOfFailureUseThis/{segments}")))
|
||||
.build();
|
||||
.route(path("/consumingServiceEndpoint/{*segments}"), http())
|
||||
.before(uri("https://example.org"))
|
||||
.filter(circuitBreaker("myCircuitBreaker", URI.create("forward:/inCaseOfFailureUseThis/{segments}")))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -126,6 +134,7 @@ However, you can also reroute the request to a controller or handler in an exter
|
||||
.GatewaySampleApplication.java
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.CircuitBreakerFilterFunctions.circuitBreaker;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.LoadBalancerFilterFunctions.lb;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
@@ -142,7 +151,8 @@ class RouteConfiguration {
|
||||
.filter(circuitBreaker("fetchIngredients", URI.create("forward:/fallback")))
|
||||
.build()
|
||||
.and(route("ingredients-fallback")
|
||||
.route(path("/fallback"), http("http://localhost:9994"))
|
||||
.route(path("/fallback"), http())
|
||||
.before(uri("https://localhost:9994"))
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ spring:
|
||||
routes:
|
||||
- id: dedupe_response_header_route
|
||||
uri: https://example.org
|
||||
predicates:
|
||||
- Path=/hello
|
||||
filters:
|
||||
- DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin
|
||||
----
|
||||
@@ -21,18 +23,19 @@ spring:
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.AfterFilterFunctions.dedupeResponseHeader;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
import static org.springframework.web.servlet.function.RequestPredicates.path;
|
||||
|
||||
|
||||
@Configuration
|
||||
class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsDedupeResponseHeader() {
|
||||
return route("dedupe_response_header_route")
|
||||
.route(path("/hello"), http("https://example.org"))
|
||||
.route(path("/hello"), http())
|
||||
.before(uri("https://example.org"))
|
||||
.after(dedupeResponseHeader("Access-Control-Allow-Credentials Access-Control-Allow-Origin"))
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ spring:
|
||||
- id: ingredients
|
||||
uri: lb://ingredients
|
||||
predicates:
|
||||
- Path=//ingredients/**
|
||||
- Path=/ingredients/**
|
||||
filters:
|
||||
- name: CircuitBreaker
|
||||
args:
|
||||
@@ -33,8 +33,9 @@ spring:
|
||||
.GatewaySampleApplication.java
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.CircuitBreakerFilterFunctions.circuitBreaker;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.fallbackHeaders;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.CircuitBreakerFilterFunctions.circuitBreaker;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.LoadBalancerFilterFunctions.lb;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
@@ -50,7 +51,8 @@ class RouteConfiguration {
|
||||
.filter(circuitBreaker("fetchIngredients", URI.create("forward:/fallback")))
|
||||
.build()
|
||||
.and(route("ingredients-fallback")
|
||||
.route(path("/fallback"), http("http://localhost:9994"))
|
||||
.route(path("/fallback"), http())
|
||||
.before(uri("http://localhost:9994"))
|
||||
.before(fallbackHeaders())
|
||||
.build());
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@ spring:
|
||||
- Path=/api/**
|
||||
----
|
||||
|
||||
WARNING: If using the `lb()` filter, it needs to be after any filter that manipulates the path such as `setPath()` or `stripPrefix()`, otherwise the resulting url could be incorrect. The `lb:` scheme handler in configuration, automatically puts the filter in the highest precedence order.
|
||||
|
||||
NOTE: By default, when a service instance cannot be found by the `ReactorLoadBalancer`, a `503` is returned.
|
||||
// TODO: implement use404
|
||||
// You can configure the gateway to return a `404` by setting `spring.cloud.gateway.loadbalancer.use404=true`.
|
||||
|
||||
@@ -17,6 +17,8 @@ spring:
|
||||
routes:
|
||||
- id: map_request_header_route
|
||||
uri: https://example.org
|
||||
predicates:
|
||||
- Path=/mypath
|
||||
filters:
|
||||
- MapRequestHeader=Blue, X-Request-Red
|
||||
----
|
||||
@@ -24,7 +26,8 @@ spring:
|
||||
.GatewaySampleApplication.java
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.addRequestParameter;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.mapRequestHeader;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
|
||||
@@ -34,7 +37,8 @@ class RouteConfiguration {
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsMapRequestHeader() {
|
||||
return route("map_request_header_route")
|
||||
.GET("/mypath", http("https://example.org"))
|
||||
.GET("/mypath", http())
|
||||
.before(uri("https://example.org"))
|
||||
.before(mapRequestHeader("Blue", "X-Request-Red"))
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ The following listing shows how to modify a request body filter:
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.modifyRequestBody;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
import static org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.host;
|
||||
@@ -20,12 +21,13 @@ import org.springframework.http.MediaType;
|
||||
class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsAddReqHeader() {
|
||||
return route("rewrite_request_obj")
|
||||
.route(host("*.rewriterequestobj.org"), http("https://example.org"))
|
||||
.before(modifyRequestBody(String.class, Hello.class, MediaType.APPLICATION_JSON_VALUE,
|
||||
(request, s) -> new Hello(s.toUpperCase())))
|
||||
.build();
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsModifyRequestBody() {
|
||||
return route("modify_request_body")
|
||||
.route(host("*.modifyrequestbody.org"), http())
|
||||
.before(uri("https://example.org"))
|
||||
.before(modifyRequestBody(String.class, Hello.class, MediaType.APPLICATION_JSON_VALUE,
|
||||
(request, s) -> new Hello(s.toUpperCase())))
|
||||
.build();
|
||||
}
|
||||
|
||||
record Hello(String message) { }
|
||||
|
||||
@@ -5,24 +5,34 @@ You can use the `ModifyResponseBody` filter to modify the response body before i
|
||||
|
||||
NOTE: This filter can be configured only by using the Java DSL.
|
||||
|
||||
The following listing shows how to modify a response body filter:
|
||||
The following listing shows how to modify a response body filter:
|
||||
|
||||
.GatewaySampleApplication.java
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.AfterFilterFunctions.modifyResponseBody;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsModifyResponseBodySimple() {
|
||||
return route("modify_response_body")
|
||||
.GET("/anything/modifyresponsebody", http())
|
||||
.before(new HttpbinUriResolver())
|
||||
.before(uri("https://example.org"))
|
||||
.after(modifyResponseBody(String.class, String.class, null,
|
||||
(request, response, s) -> s.replace("fooval", "FOOVAL")))
|
||||
.build();
|
||||
}
|
||||
----
|
||||
|
||||
The sample above does not change the content type or do anything dynamic. Below, the sample changes the content type and dynamically modifies the content.
|
||||
|
||||
.GatewaySampleApplication.java
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.AfterFilterFunctions.modifyResponseBody;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
import static org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.host;
|
||||
@@ -32,11 +42,13 @@ import org.springframework.http.MediaType;
|
||||
class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsAddReqHeader() {
|
||||
return route("rewrite_request_obj")
|
||||
.route(host("*.rewriterequestobj.org"), http("https://example.org"))
|
||||
.before(modifyResponseBody(String.class, String.class, MediaType.APPLICATION_JSON_VALUE, (request, response, s) -> s.toUpperCase()))
|
||||
.build();
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsModifyResponseBody() {
|
||||
return route("modify_response_bodu")
|
||||
.route(host("*.modifyresponsebodu.org"), http())
|
||||
.before(uri("https://example.org"))
|
||||
.after(modifyResponseBody(String.class, String.class, MediaType.APPLICATION_JSON_VALUE,
|
||||
(request, s) -> s.toUpperCase()))
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ spring:
|
||||
routes:
|
||||
- id: prefixpath_route
|
||||
uri: https://example.org
|
||||
predicates:
|
||||
- Path=/**
|
||||
filters:
|
||||
- PrefixPath=/mypath
|
||||
----
|
||||
@@ -22,6 +24,7 @@ spring:
|
||||
.GatewaySampleApplication.java
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.prefixPath;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
@@ -31,10 +34,11 @@ class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsPrefixPath() {
|
||||
return route("prefixpath_route")
|
||||
.GET("/**", http("https://example.org"))
|
||||
.before("/mypath")
|
||||
.build();
|
||||
return route("prefixpath_route")
|
||||
.GET("/**", http())
|
||||
.before(uri("https://example.org"))
|
||||
.before(prefixPath("/mypath"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -42,3 +46,4 @@ class RouteConfiguration {
|
||||
This prefixes `/mypath` to the path of all matching requests.
|
||||
So a request to `/hello` is sent to `/mypath/hello`.
|
||||
|
||||
WARNING: If using the `lb()` filter, it needs to be after the `prefixPath()` filter, otherwise the resulting url could be incorrect. The `lb:` scheme handler in configuration, automatically puts the filter in the highest precedence order.
|
||||
|
||||
@@ -16,6 +16,8 @@ spring:
|
||||
routes:
|
||||
- id: preserve_host_route
|
||||
uri: https://example.org
|
||||
predicates:
|
||||
- Path=/**
|
||||
filters:
|
||||
- PreserveHostHeader
|
||||
----
|
||||
@@ -23,6 +25,7 @@ spring:
|
||||
.GatewaySampleApplication.java
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.preserveHostHeader;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
@@ -32,10 +35,11 @@ class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsPreserveHostHeader() {
|
||||
return route("preserve_host_route")
|
||||
.GET("/**", http("https://example.org"))
|
||||
.before(preserveHostHeader())
|
||||
.build();
|
||||
return route("preserve_host_route")
|
||||
.GET("/**", http())
|
||||
.before(uri("https://example.org"))
|
||||
.before(preserveHostHeader())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
@@ -47,6 +47,7 @@ The following is an example of configuring a route with rate limiting:
|
||||
.RouteConfiguration.java
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.Bucket4jFilterFunctions.rateLimit;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
@@ -56,12 +57,13 @@ class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsRateLimited() {
|
||||
return route("rate_limited_route")
|
||||
.GET("/api/**", http("https://example.org"))
|
||||
.filter(rateLimit(c -> c.setCapacity(100)
|
||||
.setPeriod(Duration.ofMinutes(1))
|
||||
.setKeyResolver(request -> request.servletRequest().getUserPrincipal().getName())))
|
||||
.build();
|
||||
return route("rate_limited_route")
|
||||
.GET("/api/**", http())
|
||||
.before(uri("https://example.org"))
|
||||
.filter(rateLimit(c -> c.setCapacity(100)
|
||||
.setPeriod(Duration.ofMinutes(1))
|
||||
.setKeyResolver(request -> request.servletRequest().getUserPrincipal().getName())))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
@@ -18,6 +18,8 @@ spring:
|
||||
routes:
|
||||
- id: redirectto_route
|
||||
uri: https://example.org
|
||||
predicates:
|
||||
- Path=/**
|
||||
filters:
|
||||
- RedirectTo=302, https://acme.org
|
||||
----
|
||||
@@ -25,6 +27,7 @@ spring:
|
||||
.GatewaySampleApplication.java
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.FilterFunctions.redirectTo;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
@@ -34,10 +37,11 @@ class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsRedirectTo() {
|
||||
return route("redirectto_route")
|
||||
.GET("/**", http("https://example.org"))
|
||||
.filter(redirectTo(302, URI.create("acme.org")))
|
||||
.build();
|
||||
return route("redirectto_route")
|
||||
.GET("/**", http())
|
||||
.before(uri("https://example.org"))
|
||||
.filter(redirectTo(302, URI.create("acme.org")))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
@@ -16,6 +16,8 @@ spring:
|
||||
routes:
|
||||
- id: removerequestheader_route
|
||||
uri: https://example.org
|
||||
predicates:
|
||||
- Path=/**
|
||||
filters:
|
||||
- RemoveRequestHeader=X-Request-Foo
|
||||
----
|
||||
@@ -23,6 +25,7 @@ spring:
|
||||
.GatewaySampleApplication.java
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.removeRequestHeader;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
@@ -31,11 +34,12 @@ import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFuncti
|
||||
class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsremoveRequestHeader() {
|
||||
return route("removerequestheader_route")
|
||||
.GET("/**", http("https://example.org"))
|
||||
.before(removeRequestHeader("X-Request-Foo"))
|
||||
.build();
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsRemoveRequestHeader() {
|
||||
return route("removerequestheader_route")
|
||||
.GET("/**", http())
|
||||
.before(uri("https://example.org"))
|
||||
.before(removeRequestHeader("X-Request-Foo"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
@@ -16,6 +16,8 @@ spring:
|
||||
routes:
|
||||
- id: removerequestparameter_route
|
||||
uri: https://example.org
|
||||
predicates:
|
||||
- Path=/**
|
||||
filters:
|
||||
- RemoveRequestParameter=red
|
||||
----
|
||||
@@ -23,7 +25,8 @@ spring:
|
||||
.GatewaySampleApplication.java
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.addRequestParameter;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.removeRequestParameter;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
|
||||
@@ -31,11 +34,12 @@ import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFuncti
|
||||
class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsAddReqHeader() {
|
||||
return route("removerequestparameter_route")
|
||||
.GET("/**", http("https://example.org"))
|
||||
.before(removeRequestParameter("red"))
|
||||
.build();
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsRemoveRequestParameter() {
|
||||
return route("removerequestparameter_route")
|
||||
.GET("/**", http())
|
||||
.before(uri("https://example.org"))
|
||||
.before(removeRequestParameter("red"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
@@ -15,6 +15,8 @@ spring:
|
||||
routes:
|
||||
- id: removeresponseheader_route
|
||||
uri: https://example.org
|
||||
predicates:
|
||||
- Path=/anything/removeresponseheader
|
||||
filters:
|
||||
- RemoveResponseHeader=X-Response-Foo
|
||||
----
|
||||
@@ -22,6 +24,7 @@ spring:
|
||||
.GatewaySampleApplication.java
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.AfterFilterFunctions.removeResponseHeader;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
@@ -31,9 +34,10 @@ class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsRemoveResponseHeader() {
|
||||
return route("addresponseheader")
|
||||
.GET("/anything/addresheader", http("https://example.org"))
|
||||
.after(removeResponseHeader("X-Response-Foo"))
|
||||
return route("removeresponseheader_route")
|
||||
.GET("/anything/removeresponseheader", http())
|
||||
.before(uri("https://example.org"))
|
||||
.after(removeResponseHeader("X-Response-Foo"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ spring:
|
||||
routes:
|
||||
- id: requestheadersize_route
|
||||
uri: https://example.org
|
||||
predicates:
|
||||
- Path=/**
|
||||
filters:
|
||||
- RequestHeaderSize=1000B
|
||||
----
|
||||
@@ -23,6 +25,7 @@ spring:
|
||||
.GatewaySampleApplication.java
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.requestHeaderSize;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
@@ -32,10 +35,11 @@ class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsRequestHeaderSize() {
|
||||
return route("requestheadersize_route")
|
||||
.GET("/**", http("https://example.org"))
|
||||
.before(requestHeaderSize("1000B"))
|
||||
.build();
|
||||
return route("requestheadersize_route")
|
||||
.GET("/**", http())
|
||||
.before(uri("https://example.org"))
|
||||
.before(requestHeaderSize("1000B"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
@@ -28,6 +28,7 @@ spring:
|
||||
.GatewaySampleApplication.java
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.requestSize;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
@@ -37,10 +38,11 @@ class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsRequestSize() {
|
||||
return route("request_size_route")
|
||||
.GET("/upload", http("http://localhost:8080"))
|
||||
.before(requestSize("5000000"))
|
||||
.build();
|
||||
return route("request_size_route")
|
||||
.GET("/upload", http())
|
||||
.before(uri("http://localhost:8080"))
|
||||
.before(requestSize("5000000"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
@@ -36,7 +36,7 @@ spring:
|
||||
gateway:
|
||||
mvc:
|
||||
routes:
|
||||
- id: retry_test
|
||||
- id: retry_route
|
||||
uri: http://localhost:8080/flakey
|
||||
predicates:
|
||||
- Host=*.retry.com
|
||||
@@ -53,6 +53,7 @@ spring:
|
||||
.GatewaySampleApplication.java
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.FilterFunctions.adaptCachedBody;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.RetryFilterFunctions.retry;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
@@ -63,12 +64,16 @@ import static org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequ
|
||||
class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsAddReqHeader() {
|
||||
return route("add_request_parameter_route")
|
||||
.route(host("*.retry.com"), http("https://example.org"))
|
||||
.filter(retry(config -> config.setRetries(3).setSeries(Set.of(HttpStatus.Series.SERVER_ERROR)).setMethods(Set.of(HttpMethod.GET, HttpMethod.POST)).setCacheBody(true)))
|
||||
.filter(adaptCachedBody())
|
||||
.build();
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsRetry() {
|
||||
return route("retry_route")
|
||||
.route(host("*.retry.com"), http())
|
||||
.before(uri("http://localhost:8080/flakey"))
|
||||
.filter(retry(config -> config.setRetries(3)
|
||||
.setSeries(Set.of(HttpStatus.Series.SERVER_ERROR))
|
||||
.setMethods(Set.of(HttpMethod.GET, HttpMethod.POST))
|
||||
.setCacheBody(true)))
|
||||
.filter(adaptCachedBody())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -79,11 +84,11 @@ Instead, it should throw an `Exception` or signal an error (for example, through
|
||||
|
||||
NOTE: When using the retry filter, it will retry all filters that come after it. Make sure the results of the filters following the retry filter are as expected when they are executed multiple times.
|
||||
|
||||
// WARNING: When using the retry filter with any HTTP method with a body, the body will be cached and the gateway will become memory constrained. The body is cached in a request attribute defined by `ServerWebExchangeUtils.CACHED_REQUEST_BODY_ATTR`. The type of the object is `org.springframework.core.io.buffer.DataBuffer`.
|
||||
WARNING: When using the retry filter with any HTTP method with a body and `cacheBody=true`, the body will be cached and the gateway will become memory constrained. The body is cached in a request attribute defined by `MvcUtils.CACHED_REQUEST_BODY_ATT`. The type of the object is `ByteArrayInputStream`.
|
||||
|
||||
A simplified "shortcut" notation can be added with a single `status` and `method`.
|
||||
|
||||
The following two examples are equivalent:
|
||||
The following two example routes are equivalent:
|
||||
|
||||
.application.yml
|
||||
[source,yaml]
|
||||
|
||||
@@ -15,6 +15,8 @@ spring:
|
||||
routes:
|
||||
- id: rewritelocationresponseheader_route
|
||||
uri: http://example.org
|
||||
predicates:
|
||||
- Path=/**
|
||||
filters:
|
||||
- RewriteLocationResponseHeader=AS_IN_REQUEST, Location, ,
|
||||
----
|
||||
@@ -22,7 +24,8 @@ spring:
|
||||
.GatewaySampleApplication.java
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.AfterFilterFunctions.addResponseHeader;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.RewriteLocationResponseHeaderFilterFunctions.rewriteLocationResponseHeader;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.RewriteLocationResponseHeaderFilterFunctions.StripVersion;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
@@ -33,9 +36,11 @@ class RouteConfiguration {
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsRewriteLocationResponseHeader() {
|
||||
return route("rewritelocationresponseheader_route")
|
||||
.GET("/**", http("https://example.org"))
|
||||
.after(rewriteLocationResponseHeader(config -> config.setLocationHeaderName("Location").setStripVersion(StripVersion.AS_IN_REQUEST)))
|
||||
.build();
|
||||
.GET("/**", http())
|
||||
.before(uri("https://example.org"))
|
||||
.after(rewriteLocationResponseHeader(config -> config.setLocationHeaderName("Location")
|
||||
.setStripVersion(StripVersion.AS_IN_REQUEST)))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
@@ -25,6 +25,7 @@ spring:
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.rewritePath;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
|
||||
@@ -33,13 +34,15 @@ class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsRewritePath() {
|
||||
return route("rewritepath_route")
|
||||
.GET("/red/**", http("https://example.org"))
|
||||
.before(rewritePath("/red/(?<segment>.*)", "/${segment}"))
|
||||
.build();
|
||||
return route("rewritepath_route")
|
||||
.GET("/red/**", http())
|
||||
.before(uri("https://example.org"))
|
||||
.before(rewritePath("/red/(?<segment>.*)", "/${segment}"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
For a request path of `/red/blue`, this sets the path to `/blue` before making the downstream request. Note that in `application.yml` the `$` should be replaced with `$\` because of the YAML specification.
|
||||
|
||||
WARNING: If using the `lb()` filter, it needs to be after the `rewritePath()` filter, otherwise the resulting url could be incorrect. The `lb:` scheme handler in configuration, automatically puts the filter in the highest precedence order.
|
||||
@@ -26,6 +26,7 @@ spring:
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.addRequestParameter;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
|
||||
@@ -33,11 +34,12 @@ import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFuncti
|
||||
class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsAddReqHeader() {
|
||||
return route("add_request_parameter_route")
|
||||
.GET("/anything/addrequestparam", http("https://example.org"))
|
||||
.before(addRequestParameter("red", "blue"))
|
||||
.build();
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsRewriteRequestParameter() {
|
||||
return route("rewriterequestparameter_route")
|
||||
.GET("products", http())
|
||||
.before(uri("https://example.org"))
|
||||
.before(addRequestParameter("red", "blue"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
@@ -16,14 +16,17 @@ spring:
|
||||
routes:
|
||||
- id: rewriteresponseheader_route
|
||||
uri: https://example.org
|
||||
predicates:
|
||||
- Path=/**
|
||||
filters:
|
||||
- RewriteResponseHeader=X-Response-Red, , password=[^&]+, password=***
|
||||
- RewriteResponseHeader=X-Response-Red, password=[^&]+, password=***
|
||||
----
|
||||
|
||||
.GatewaySampleApplication.java
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.AfterFilterFunctions.addResponseHeader;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.AfterFilterFunctions.rewriteResponseHeader;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
|
||||
@@ -33,8 +36,9 @@ class RouteConfiguration {
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsRewriteResponseHeader() {
|
||||
return route("rewriteresponseheader_route")
|
||||
.GET("/**", http("https://example.org"))
|
||||
.after(rewriteResponseHeader("X-Request-Red", "password=[^&]+", "password=***"))
|
||||
.GET("/**", http())
|
||||
.before(uri("https://example.org"))
|
||||
.after(rewriteResponseHeader("X-Request-Red", "password=[^&]+", "password=***"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ spring:
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.setPath;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
|
||||
@@ -35,13 +36,15 @@ class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsSetPath() {
|
||||
return route("add_request_parameter_route")
|
||||
.GET("/red/{segment}", http("https://example.org"))
|
||||
.before(setPath("/{segment"))
|
||||
.build();
|
||||
return route("setpath_route")
|
||||
.GET("/red/{segment}", http())
|
||||
.before(uri("https://example.org"))
|
||||
.before(setPath("/{segment"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
For a request path of `/red/blue`, this sets the path to `/blue` before making the downstream request.
|
||||
|
||||
WARNING: If using the `lb()` filter, it needs to be after the `setPath()` filter, otherwise the resulting url could be incorrect. The `lb:` scheme handler in configuration, automatically puts the filter in the highest precedence order.
|
||||
@@ -14,6 +14,8 @@ spring:
|
||||
routes:
|
||||
- id: setrequestheader_route
|
||||
uri: https://example.org
|
||||
predicates:
|
||||
- Path=/**
|
||||
filters:
|
||||
- SetRequestHeader=X-Request-Red, Blue
|
||||
----
|
||||
@@ -22,6 +24,7 @@ spring:
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.setRequestHostHeader;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
|
||||
@@ -30,10 +33,11 @@ class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsSetRequestHeader() {
|
||||
return route("add_request_parameter_route")
|
||||
.GET("/**", http("https://example.org"))
|
||||
.before(setRequestHostHeader("X-Request-Red", "Blue"))
|
||||
.build();
|
||||
return route("setrequestheader_route")
|
||||
.GET("/**", http())
|
||||
.before(uri("https://example.org"))
|
||||
.before(setRequestHostHeader("X-Request-Red", "Blue"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -64,19 +68,21 @@ spring:
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.setRequestHostHeader;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
import static import static org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.host;
|
||||
import static org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.host;
|
||||
|
||||
@Configuration
|
||||
class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsSetRequestHeader() {
|
||||
return route("add_request_parameter_route")
|
||||
.route(host("{segment}.myhost.org"), http("https://example.org"))
|
||||
.before(setRequestHostHeader("X-Request-Red", "Blue-{segment}"))
|
||||
.build();
|
||||
return route("setrequestheader_route")
|
||||
.route(host("{segment}.myhost.org"), http())
|
||||
.before(uri("https://example.org"))
|
||||
.before(setRequestHostHeader("X-Request-Red", "Blue-{segment}"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -26,6 +26,7 @@ spring:
|
||||
.GatewaySampleApplication.java
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.setRequestHostHeader;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
@@ -35,10 +36,11 @@ class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsSetRequestHostHeader() {
|
||||
return route("set_request_host_header_route")
|
||||
.GET("/headers", http("http://localhost:8080"))
|
||||
.before(setRequestHostHeader("example.org"))
|
||||
.build();
|
||||
return route("set_request_host_header_route")
|
||||
.GET("/headers", http())
|
||||
.before(uri("http://localhost:8080"))
|
||||
.before(setRequestHostHeader("example.org"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
@@ -14,6 +14,8 @@ spring:
|
||||
routes:
|
||||
- id: setresponseheader_route
|
||||
uri: https://example.org
|
||||
predicates:
|
||||
- Path=/anything/setresponseheader
|
||||
filters:
|
||||
- SetResponseHeader=X-Response-Red, Blue
|
||||
----
|
||||
@@ -22,6 +24,7 @@ spring:
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.AfterFilterFunctions.setResponseHeader;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
|
||||
@@ -30,9 +33,10 @@ class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsSetResponseHeader() {
|
||||
return route("addresponseheader")
|
||||
.GET("/anything/addresheader", http("https://example.org"))
|
||||
.after(setResponseHeader("X-Response-Red", "Blue"))
|
||||
return route("setresponseheader_route")
|
||||
.GET("/anything/setresponseheader", http())
|
||||
.before(uri("https://example.org"))
|
||||
.after(setResponseHeader("X-Response-Red", "Blue"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -64,6 +68,7 @@ spring:
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.AfterFilterFunctions.setResponseHeader;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
import static org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.host;
|
||||
@@ -73,10 +78,11 @@ class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsSetResponseHeader() {
|
||||
return route("add_response_header_route")
|
||||
.route(host("{segment}.myhost.org"), http("https://example.org"))
|
||||
.after(setResponseHeader("foo", "bar-{segment}"))
|
||||
.build();
|
||||
return route("setresponseheader_route")
|
||||
.route(host("{segment}.myhost.org"), http())
|
||||
.before(uri("https://example.org"))
|
||||
.after(setResponseHeader("foo", "bar-{segment}"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
@@ -16,10 +16,14 @@ spring:
|
||||
routes:
|
||||
- id: setstatusstring_route
|
||||
uri: https://example.org
|
||||
predicates:
|
||||
- Path=/path1
|
||||
filters:
|
||||
- SetStatus=UNAUTHORIZED
|
||||
- id: setstatusint_route
|
||||
uri: https://example.org
|
||||
predicates:
|
||||
- Path=/path2
|
||||
filters:
|
||||
- SetStatus=401
|
||||
----
|
||||
@@ -28,6 +32,7 @@ spring:
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.AfterFilterFunctions.setStatus;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
|
||||
@@ -37,13 +42,15 @@ class RouteConfiguration {
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsSetStatus() {
|
||||
return route("setstatus_route")
|
||||
.GET("/path1", http("https://example.org"))
|
||||
.GET("/path1", http())
|
||||
.before(uri("https://example.org"))
|
||||
// setStatus("UNAUTHORIZED") works as well
|
||||
.after(setStatus(HttpStatus.UNAUTHORIZED))
|
||||
.build().and(route("setstatusint_route")
|
||||
.GET("/path2", http("https://example.org"))
|
||||
.before(uri("https://example.org"))
|
||||
.GET("/path2", http())
|
||||
.after(setStatus("401"))
|
||||
.build());
|
||||
.build());
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
@@ -13,8 +13,8 @@ spring:
|
||||
gateway:
|
||||
mvc:
|
||||
routes:
|
||||
- id: nameRoot
|
||||
uri: https://nameservice
|
||||
- id: strip_prefix_route
|
||||
uri: https://example.org
|
||||
predicates:
|
||||
- Path=/name/**
|
||||
filters:
|
||||
@@ -25,6 +25,7 @@ spring:
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.stripPrefix;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
|
||||
@@ -33,13 +34,15 @@ class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsStripPrefix() {
|
||||
return route("nameRoot")
|
||||
.GET("/name/**", http("https://example.org"))
|
||||
.before(stripPrefix(2))
|
||||
.build();
|
||||
return route("strip_prefix_route")
|
||||
.GET("/name/**", http())
|
||||
.before(uri("https://example.org"))
|
||||
.before(stripPrefix(2))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
When a request is made through the gateway to `/name/blue/red`, the request made to `nameservice` looks like `https://nameservice/red`.
|
||||
When a request is made through the gateway to `/name/blue/red`, the full request url looks like `https://example.org/red`.
|
||||
|
||||
WARNING: If using the `lb()` filter, it needs to be after the `stripPrefix()` filter, otherwise the resulting url could be incorrect. The `lb:` scheme handler in configuration, automatically puts the filter in the highest precedence order.
|
||||
@@ -54,6 +54,7 @@ Spring Cloud Gateway Server MVC can forward the OAuth2 access token of the curre
|
||||
.RouteConfiguration.java
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.TokenRelayFilterFunctions.tokenRelay;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
@@ -62,11 +63,12 @@ import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFuncti
|
||||
class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsAddReqHeader() {
|
||||
return route("resource")
|
||||
.GET("/resource", http("http://localhost:9000"))
|
||||
.filter(tokenRelay())
|
||||
.build();
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsTokenRelay() {
|
||||
return route("resource")
|
||||
.GET("/resource", http())
|
||||
.before(uri("https://localhost:9000"))
|
||||
.filter(tokenRelay())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
@@ -31,6 +31,7 @@ spring:
|
||||
[source,java]
|
||||
----
|
||||
import java.time.ZonedDateTime;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
import static org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.after;
|
||||
@@ -40,9 +41,10 @@ class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsAfter() {
|
||||
return route("after_route")
|
||||
.route(after(ZonedDateTime.parse("2017-01-20T17:42:47.789-07:00[America/Denver]")), http("https://example.org"))
|
||||
.build();
|
||||
return route("after_route")
|
||||
.route(after(ZonedDateTime.parse("2017-01-20T17:42:47.789-07:00[America/Denver]")), http())
|
||||
.before(uri("https://example.org"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -74,6 +76,7 @@ spring:
|
||||
[source,java]
|
||||
----
|
||||
import java.time.ZonedDateTime;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
import static org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.before;
|
||||
@@ -83,9 +86,10 @@ class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsBefore() {
|
||||
return route("before_route")
|
||||
.route(before(ZonedDateTime.parse("2017-01-20T17:42:47.789-07:00[America/Denver]")), http("https://example.org"))
|
||||
.build();
|
||||
return route("before_route")
|
||||
.route(before(ZonedDateTime.parse("2017-01-20T17:42:47.789-07:00[America/Denver]")), http()
|
||||
.before(uri("https://example.org"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -119,6 +123,7 @@ spring:
|
||||
[source,java]
|
||||
----
|
||||
import java.time.ZonedDateTime;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
import static org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.between;
|
||||
@@ -128,9 +133,11 @@ class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsBetween() {
|
||||
return route("between_route")
|
||||
.route(between(ZonedDateTime.parse("2017-01-20T17:42:47.789-07:00[America/Denver]"), ZonedDateTime.parse("2017-01-21T17:42:47.789-07:00[America/Denver]")), http("https://example.org"))
|
||||
.build();
|
||||
return route("between_route")
|
||||
.route(between(ZonedDateTime.parse("2017-01-20T17:42:47.789-07:00[America/Denver]"),
|
||||
ZonedDateTime.parse("2017-01-21T17:42:47.789-07:00[America/Denver]")), http())
|
||||
.before(uri("https://example.org"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -162,18 +169,20 @@ spring:
|
||||
.GatewaySampleApplication.java
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
import static org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.between;
|
||||
import static org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.cookie;
|
||||
|
||||
@Configuration
|
||||
class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsCookie() {
|
||||
return route("cookie_route")
|
||||
.route(cookie("chocolate", "ch.p"), http("https://example.org"))
|
||||
.build();
|
||||
return route("cookie_route")
|
||||
.route(cookie("chocolate", "ch.p"), http())
|
||||
.before(uri("https://example.org"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -204,6 +213,7 @@ spring:
|
||||
.GatewaySampleApplication.java
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
import static org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.header;
|
||||
@@ -212,10 +222,11 @@ import static org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequ
|
||||
class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsCookie() {
|
||||
return route("cookie_route")
|
||||
.route(header("X-Request-Id", "\\d+"), http("https://example.org"))
|
||||
.build();
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsHeader() {
|
||||
return route("header_route")
|
||||
.route(header("X-Request-Id", "\\d+"), http())
|
||||
.before(uri("https://example.org"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -247,6 +258,7 @@ spring:
|
||||
.GatewaySampleApplication.java
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
import static org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.host;
|
||||
@@ -256,9 +268,10 @@ class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsHost() {
|
||||
return route("host_route")
|
||||
.route(host("**.somehost.org", "**.anotherhost.org"), http("https://example.org"))
|
||||
.build();
|
||||
return route("host_route")
|
||||
.route(host("**.somehost.org", "**.anotherhost.org"), http())
|
||||
.before(uri("https://example.org"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -296,6 +309,7 @@ spring:
|
||||
[source,java]
|
||||
----
|
||||
import org.springframework.http.HttpMethod;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
import static org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.method;
|
||||
@@ -305,9 +319,10 @@ class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsMethod() {
|
||||
return route("method_route")
|
||||
.route(method(HttpMethod.GET, HttpMethod.POST), http("https://example.org"))
|
||||
.build();
|
||||
return route("method_route")
|
||||
.route(method(HttpMethod.GET, HttpMethod.POST), http())
|
||||
.before(uri("https://example.org"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -319,19 +334,19 @@ This route matches if the request method was a `GET` or a `POST`.
|
||||
.GatewaySampleApplication.java
|
||||
[source,java]
|
||||
----
|
||||
import org.springframework.http.HttpMethod;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
import static org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.methods;
|
||||
|
||||
@Configuration
|
||||
class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsMethod() {
|
||||
return route("method_route")
|
||||
.GET("/mypath", http("https://example.org"))
|
||||
.build();
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsMethodAndPath() {
|
||||
return route("method_and_path_route")
|
||||
.GET("/mypath", http())
|
||||
.before(uri("https://example.org"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -363,19 +378,20 @@ spring:
|
||||
.GatewaySampleApplication.java
|
||||
[source,java]
|
||||
----
|
||||
import org.springframework.http.HttpMethod;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
import static org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.method;
|
||||
import static org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.path;
|
||||
|
||||
@Configuration
|
||||
class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsPath() {
|
||||
return route("path_route")
|
||||
.route(path("/red/{segment}", "/blue/{segment}"), http("https://example.org"))
|
||||
.build();
|
||||
return route("path_route")
|
||||
.route(path("/red/{segment}", "/blue/{segment}"), http())
|
||||
.before(uri("https://example.org"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -421,7 +437,7 @@ spring:
|
||||
.GatewaySampleApplication.java
|
||||
[source,java]
|
||||
----
|
||||
import org.springframework.http.HttpMethod;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
import static org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.query;
|
||||
@@ -431,9 +447,10 @@ class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsQuery() {
|
||||
return route("query_route")
|
||||
.route(query("green"), http("https://example.org"))
|
||||
.build();
|
||||
return route("query_route")
|
||||
.route(query("green"), http())
|
||||
.before(uri("https://example.org"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -457,7 +474,7 @@ spring:
|
||||
.GatewaySampleApplication.java
|
||||
[source,java]
|
||||
----
|
||||
import org.springframework.http.HttpMethod;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
import static org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.query;
|
||||
@@ -467,9 +484,10 @@ class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsQuery() {
|
||||
return route("query_route")
|
||||
.route(query("red", "gree."), http("https://example.org"))
|
||||
.build();
|
||||
return route("query_route")
|
||||
.route(query("red", "gree."), http())
|
||||
.before(uri("https://example.org"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -585,22 +603,25 @@ spring:
|
||||
.GatewaySampleApplication.java
|
||||
[source,java]
|
||||
----
|
||||
import org.springframework.http.HttpMethod;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
import static org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.method;
|
||||
import static org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.path;
|
||||
import static org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.weight;
|
||||
|
||||
@Configuration
|
||||
class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsWeights() {
|
||||
return route("weight_high")
|
||||
.route(weight("group1", 8).and(path("/**")), http("https://weighthigh.org"))
|
||||
.build().and(
|
||||
route("weight_low")
|
||||
.route(weight("group1", 2).and(path("/**")), http("https://weightlow.org"))
|
||||
.build());
|
||||
return route("weight_high")
|
||||
.route(weight("group1", 8).and(path("/**")), http())
|
||||
.before(uri("https://weighthigh.org"))
|
||||
.build().and(
|
||||
route("weight_low")
|
||||
.route(weight("group1", 2).and(path("/**")), http())
|
||||
.before(uri("https://weightlow.org"))
|
||||
.build());
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
@@ -9,12 +9,15 @@ A https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springfra
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.web.servlet.function.RouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
|
||||
class SimpleGateway {
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> getRoute() {
|
||||
return route().GET("/get", http("https://httpbin.org")).build();
|
||||
return route().GET("/get", http())
|
||||
.before(uri("https://example.org"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -29,13 +32,16 @@ Some advanced filters require some metadata to be added to request attributes. T
|
||||
.GatewaySampleApplication.java
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
|
||||
class SimpleGateway {
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> getRoute() {
|
||||
return route("simple_route").GET("/get", http("https://httpbin.org")).build();
|
||||
return route("simple_route").GET("/get", http())
|
||||
.before(uri("https://example.org"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -43,4 +49,6 @@ class SimpleGateway {
|
||||
[[gateway-handlerfunctions]]
|
||||
== Gateway MVC Handler Functions
|
||||
|
||||
Various `RouterFunctions.Builder` methods require a `HandlerFunction<ServerResponse>`. To create a route that is proxied by the MVC Gateway, `HandlerFunction` implementations are supplied in `org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions`. The most basic is the `http()` `HandlerFunction`. If a `URI` is supplied as a parameter, that is the `URI` used as the downstream target for sending the HTTP requests (as seen in the example above). If no parameter is passed, the function looks for a `URI` in the `org.springframework.cloud.gateway.server.mvc.common.MvcUtils.GATEWAY_REQUEST_URL_ATTR` request attribute. This allows for dynamic targets such as load balancing to set the `URI`.
|
||||
Various `RouterFunctions.Builder` methods require a `HandlerFunction<ServerResponse>`. To create a route that is proxied by the MVC Gateway, `HandlerFunction` implementations are supplied in `org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions`. The most basic is the `http()` `HandlerFunction`. The function looks for a `URI` in the `org.springframework.cloud.gateway.server.mvc.common.MvcUtils.GATEWAY_REQUEST_URL_ATTR` request attribute. This allows for dynamic targets such as load balancing to set the `URI`.
|
||||
|
||||
WARNING: As of version 4.1.7, `HandlerFunctions.http(String)` and `HandlerFunctions.http(URI)` are now deprecated. Please use `HandlerFunctions.http()` in combination with the `BeforeFilterFunctions.uri()` filter instead. This fixes inconsistencies in dealing with the route url request attribute.
|
||||
@@ -48,6 +48,7 @@ To use our new `headerExists` `RequestPredicate`, we need to plug it in to an ap
|
||||
[source,java]
|
||||
----
|
||||
import static SampleRequestPredicates.headerExists;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
|
||||
@@ -56,9 +57,10 @@ class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> headerExistsRoute() {
|
||||
return route("header_exists_route")
|
||||
.route(headerExists("X-Green"), http("https://example.org"))
|
||||
.build();
|
||||
return route("header_exists_route")
|
||||
.route(headerExists("X-Green"), http())
|
||||
.before(uri("https://example.org"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -111,6 +113,7 @@ First, a new `ServerRequest` is created from the existing request. This allows u
|
||||
[source,java]
|
||||
----
|
||||
import static SampleHandlerFilterFunctions.instrument;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
|
||||
@@ -120,9 +123,10 @@ class RouteConfiguration {
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> instrumentRoute() {
|
||||
return route("instrument_route")
|
||||
.GET("/**", http("https://example.org"))
|
||||
.filter(instrument("X-Request-Id", "X-Response-Id"))
|
||||
.build();
|
||||
.GET("/**", http())
|
||||
.filter(instrument("X-Request-Id", "X-Response-Id"))
|
||||
.before(uri("https://example.org"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -160,6 +164,7 @@ A new `ServerRequest` is created from the existing request. This allows us to ad
|
||||
[source,java]
|
||||
----
|
||||
import static SampleBeforeFilterFunctions.instrument;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
|
||||
@@ -168,10 +173,10 @@ class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> instrumentRoute() {
|
||||
return route("instrument_route")
|
||||
.GET("/**", http("https://example.org"))
|
||||
.before(instrument("X-Request-Id"))
|
||||
.build();
|
||||
return route("instrument_route").GET("/**", http())
|
||||
.before(uri("https://example.org"))
|
||||
.before(instrument("X-Request-Id"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -213,6 +218,7 @@ In this case we simply add the header to the response and return it.
|
||||
[source,java]
|
||||
----
|
||||
import static SampleAfterFilterFunctions.instrument;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
|
||||
@@ -221,10 +227,11 @@ class RouteConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> instrumentRoute() {
|
||||
return route("instrument_route")
|
||||
.GET("/**", http("https://example.org"))
|
||||
.after(instrument("X-Response-Id"))
|
||||
.build();
|
||||
return route("instrument_route")
|
||||
.GET("/**", http())
|
||||
.before(uri("https://example.org"))
|
||||
.after(instrument("X-Response-Id"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
|spring.cloud.gateway.filter.set-response-header.enabled | `+++true+++` | Enables the set-response-header filter.
|
||||
|spring.cloud.gateway.filter.set-status.enabled | `+++true+++` | Enables the set-status filter.
|
||||
|spring.cloud.gateway.filter.strip-prefix.enabled | `+++true+++` | Enables the strip-prefix filter.
|
||||
|spring.cloud.gateway.forwarded.by.enabled | `+++false+++` | Enables the Forwarded: by header part.
|
||||
|spring.cloud.gateway.forwarded.enabled | `+++true+++` | Enables the ForwardedHeadersFilter.
|
||||
|spring.cloud.gateway.global-filter.adapt-cached-body.enabled | `+++true+++` | Enables the adapt-cached-body global filter.
|
||||
|spring.cloud.gateway.global-filter.forward-path.enabled | `+++true+++` | Enables the forward-path global filter.
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<version>4.3.0-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
|
||||
<modules>
|
||||
<module>grpc</module>
|
||||
<module>http2</module>
|
||||
|
||||
@@ -136,4 +136,4 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
</project>
|
||||
|
||||
@@ -25,6 +25,7 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@@ -72,6 +73,11 @@ public abstract class MvcUtils {
|
||||
*/
|
||||
public static final String GATEWAY_ATTRIBUTES_ATTR = qualify("gatewayAttributes");
|
||||
|
||||
/**
|
||||
* Gateway original request URL attribute name.
|
||||
*/
|
||||
public static final String GATEWAY_ORIGINAL_REQUEST_URL_ATTR = qualify("gatewayOriginalRequestUrl");
|
||||
|
||||
/**
|
||||
* Gateway request URL attribute name.
|
||||
*/
|
||||
@@ -250,6 +256,13 @@ public abstract class MvcUtils {
|
||||
request.servletRequest().setAttribute(GATEWAY_REQUEST_URL_ATTR, url);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static void addOriginalRequestUrl(ServerRequest request, URI url) {
|
||||
LinkedHashSet<URI> urls = (LinkedHashSet<URI>) request.attributes()
|
||||
.computeIfAbsent(GATEWAY_ORIGINAL_REQUEST_URL_ATTR, s -> new LinkedHashSet<>());
|
||||
urls.add(url);
|
||||
}
|
||||
|
||||
private record ByteArrayInputMessage(ServerRequest request, ByteArrayInputStream body) implements HttpInputMessage {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2013-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.gateway.server.mvc.config;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.function.RouterFunction;
|
||||
import org.springframework.web.servlet.function.ServerResponse;
|
||||
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.fn;
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnClass(name = "org.springframework.cloud.function.context.FunctionCatalog")
|
||||
@ConditionalOnProperty(name = "spring.cloud.gateway.function.enabled", havingValue = "true", matchIfMissing = true)
|
||||
public class DefaultFunctionConfiguration {
|
||||
|
||||
@Bean
|
||||
RouterFunction<ServerResponse> gatewayToFunctionRouter() {
|
||||
// @formatter:off
|
||||
return route("functionroute")
|
||||
.POST("/{path}/{name}", fn("{path}/{name}"))
|
||||
.POST("/{path}", fn("{path}"))
|
||||
.GET("/{path}/{name}", fn("{path}/{name}"))
|
||||
.GET("/{path}", fn("{path}"))
|
||||
.build();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.cloud.gateway.server.mvc.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
@@ -38,7 +39,6 @@ import org.springframework.boot.context.properties.source.ConfigurationPropertyS
|
||||
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
|
||||
import org.springframework.cloud.gateway.server.mvc.common.Configurable;
|
||||
import org.springframework.cloud.gateway.server.mvc.common.MvcUtils;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.FilterDiscoverer;
|
||||
import org.springframework.cloud.gateway.server.mvc.handler.HandlerDiscoverer;
|
||||
import org.springframework.cloud.gateway.server.mvc.invoke.InvocationContext;
|
||||
@@ -149,15 +149,6 @@ public class RouterFunctionHolderFactory {
|
||||
|
||||
RouterFunctions.Builder builder = route(routeId);
|
||||
|
||||
// MVC.fn users won't need this anonymous filter as url will be set directly.
|
||||
// Put this function first, so if a filter from a handler changes the url
|
||||
// it is after this one.
|
||||
builder.filter((request, next) -> {
|
||||
MvcUtils.setRequestUrl(request, routeProperties.getUri());
|
||||
return next.handle(request);
|
||||
});
|
||||
builder.before(BeforeFilterFunctions.routeId(routeId));
|
||||
|
||||
MultiValueMap<String, OperationMethod> handlerOperations = handlerDiscoverer.getOperations();
|
||||
// TODO: cache?
|
||||
// translate handlerFunction
|
||||
@@ -178,12 +169,17 @@ public class RouterFunctionHolderFactory {
|
||||
NormalizedOperationMethod normalizedOpMethod = handlerOperationMethod.get();
|
||||
Object response = invokeOperation(normalizedOpMethod, normalizedOpMethod.getNormalizedArgs());
|
||||
HandlerFunction<ServerResponse> handlerFunction = null;
|
||||
|
||||
// filters added by HandlerDiscoverer need to go last, so save them
|
||||
List<HandlerFilterFunction<ServerResponse, ServerResponse>> lowerPrecedenceFilters = new ArrayList<>();
|
||||
List<HandlerFilterFunction<ServerResponse, ServerResponse>> higherPrecedenceFilters = new ArrayList<>();
|
||||
if (response instanceof HandlerFunction<?>) {
|
||||
handlerFunction = (HandlerFunction<ServerResponse>) response;
|
||||
}
|
||||
else if (response instanceof HandlerDiscoverer.Result result) {
|
||||
handlerFunction = result.getHandlerFunction();
|
||||
result.getFilters().forEach(builder::filter);
|
||||
lowerPrecedenceFilters.addAll(result.getLowerPrecedenceFilters());
|
||||
higherPrecedenceFilters.addAll(result.getHigherPrecedenceFilters());
|
||||
}
|
||||
if (handlerFunction == null) {
|
||||
throw new IllegalStateException(
|
||||
@@ -214,6 +210,9 @@ public class RouterFunctionHolderFactory {
|
||||
builder.route(predicate.get(), handlerFunction);
|
||||
predicate.set(null);
|
||||
|
||||
// HandlerDiscoverer filters needing lower priority, so put them first
|
||||
lowerPrecedenceFilters.forEach(builder::filter);
|
||||
|
||||
// translate filters
|
||||
MultiValueMap<String, OperationMethod> filterOperations = filterDiscoverer.getOperations();
|
||||
routeProperties.getFilters().forEach(filterProperties -> {
|
||||
@@ -221,6 +220,9 @@ public class RouterFunctionHolderFactory {
|
||||
translate(filterOperations, filterProperties.getName(), args, HandlerFilterFunction.class, builder::filter);
|
||||
});
|
||||
|
||||
// HandlerDiscoverer filters need higher priority, so put them last
|
||||
higherPrecedenceFilters.forEach(builder::filter);
|
||||
|
||||
builder.withAttribute(MvcUtils.GATEWAY_ROUTE_ID_ATTR, routeId);
|
||||
|
||||
return builder.build();
|
||||
|
||||
@@ -189,6 +189,7 @@ public abstract class BeforeFilterFunctions {
|
||||
final UriTemplate uriTemplate = new UriTemplate(prefix);
|
||||
|
||||
return request -> {
|
||||
MvcUtils.addOriginalRequestUrl(request, request.uri());
|
||||
Map<String, Object> uriVariables = MvcUtils.getUriTemplateVariables(request);
|
||||
URI uri = uriTemplate.expand(uriVariables);
|
||||
|
||||
@@ -326,16 +327,18 @@ public abstract class BeforeFilterFunctions {
|
||||
String normalizedReplacement = replacement.replace("$\\", "$");
|
||||
Pattern pattern = Pattern.compile(regexp);
|
||||
return request -> {
|
||||
// TODO: original request url
|
||||
String path = request.uri().getRawPath();
|
||||
MvcUtils.addOriginalRequestUrl(request, request.uri());
|
||||
String path = request.uri().getPath();
|
||||
String newPath = pattern.matcher(path).replaceAll(normalizedReplacement);
|
||||
|
||||
URI rewrittenUri = UriComponentsBuilder.fromUri(request.uri()).replacePath(newPath).build().toUri();
|
||||
URI rewrittenUri = UriComponentsBuilder.fromUri(request.uri())
|
||||
.replacePath(newPath)
|
||||
.encode()
|
||||
.build()
|
||||
.toUri();
|
||||
|
||||
ServerRequest modified = ServerRequest.from(request).uri(rewrittenUri).build();
|
||||
|
||||
// TODO: can this be restored at some point?
|
||||
// MvcUtils.setRequestUrl(modified, modified.uri());
|
||||
return modified;
|
||||
};
|
||||
}
|
||||
@@ -372,14 +375,12 @@ public abstract class BeforeFilterFunctions {
|
||||
UriTemplate uriTemplate = new UriTemplate(path);
|
||||
|
||||
return request -> {
|
||||
MvcUtils.addOriginalRequestUrl(request, request.uri());
|
||||
Map<String, Object> uriVariables = MvcUtils.getUriTemplateVariables(request);
|
||||
URI uri = uriTemplate.expand(uriVariables);
|
||||
|
||||
URI prefixedUri = UriComponentsBuilder.fromUri(request.uri())
|
||||
.replacePath(uri.getRawPath())
|
||||
.build(true)
|
||||
.toUri();
|
||||
return ServerRequest.from(request).uri(prefixedUri).build();
|
||||
URI newUri = UriComponentsBuilder.fromUri(request.uri()).replacePath(uri.getRawPath()).build(true).toUri();
|
||||
return ServerRequest.from(request).uri(newUri).build();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -410,6 +411,7 @@ public abstract class BeforeFilterFunctions {
|
||||
|
||||
public static Function<ServerRequest, ServerRequest> stripPrefix(int parts) {
|
||||
return request -> {
|
||||
MvcUtils.addOriginalRequestUrl(request, request.uri());
|
||||
// TODO: gateway url attributes
|
||||
String path = request.uri().getRawPath();
|
||||
// TODO: begin duplicate code from StripPrefixGatewayFilterFactory
|
||||
@@ -435,10 +437,22 @@ public abstract class BeforeFilterFunctions {
|
||||
.replacePath(newPath.toString())
|
||||
.build(true)
|
||||
.toUri();
|
||||
|
||||
return ServerRequest.from(request).uri(prefixedUri).build();
|
||||
};
|
||||
}
|
||||
|
||||
public static Function<ServerRequest, ServerRequest> uri(String uri) {
|
||||
return uri(URI.create(uri));
|
||||
}
|
||||
|
||||
public static Function<ServerRequest, ServerRequest> uri(URI uri) {
|
||||
return request -> {
|
||||
MvcUtils.setRequestUrl(request, uri);
|
||||
return request;
|
||||
};
|
||||
}
|
||||
|
||||
public static class FallbackHeadersConfig {
|
||||
|
||||
private String executionExceptionTypeHeaderName = CB_EXECUTION_EXCEPTION_TYPE;
|
||||
|
||||
@@ -228,6 +228,15 @@ public interface FilterFunctions {
|
||||
return ofResponseProcessor(AfterFilterFunctions.setStatus(statusCode));
|
||||
}
|
||||
|
||||
static HandlerFilterFunction<ServerResponse, ServerResponse> uri(String uri) {
|
||||
return ofRequestProcessor(BeforeFilterFunctions.uri(uri));
|
||||
}
|
||||
|
||||
@Shortcut
|
||||
static HandlerFilterFunction<ServerResponse, ServerResponse> uri(URI uri) {
|
||||
return ofRequestProcessor(BeforeFilterFunctions.uri(uri));
|
||||
}
|
||||
|
||||
class FilterSupplier extends SimpleFilterSupplier {
|
||||
|
||||
public FilterSupplier() {
|
||||
|
||||
@@ -63,6 +63,8 @@ public abstract class LoadBalancerFilterFunctions {
|
||||
public static HandlerFilterFunction<ServerResponse, ServerResponse> lb(String serviceId,
|
||||
BiFunction<ServiceInstance, URI, URI> reconstructUriFunction) {
|
||||
return (request, next) -> {
|
||||
MvcUtils.addOriginalRequestUrl(request, request.uri());
|
||||
|
||||
LoadBalancerClientFactory clientFactory = getApplicationContext(request)
|
||||
.getBean(LoadBalancerClientFactory.class);
|
||||
Set<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator
|
||||
|
||||
@@ -40,7 +40,7 @@ public class LoadBalancerHandlerSupplier implements HandlerSupplier {
|
||||
|
||||
public static HandlerDiscoverer.Result lb(URI uri) {
|
||||
// TODO: how to do something other than http
|
||||
return new HandlerDiscoverer.Result(HandlerFunctions.http(),
|
||||
return new HandlerDiscoverer.Result(HandlerFunctions.http(), Collections.emptyList(),
|
||||
Collections.singletonList(LoadBalancerFilterFunctions.lb(uri.getHost())));
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import java.util.Map;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
|
||||
import org.springframework.boot.context.properties.PropertyMapper;
|
||||
import org.springframework.cloud.gateway.server.mvc.common.MvcUtils;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
@@ -397,18 +398,15 @@ public class XForwardedRequestHeadersFilter implements HttpHeadersFilter.Request
|
||||
// - see XForwardedHeadersFilterTests, so first get uris, then extract paths
|
||||
// and remove one from another if it's the ending part.
|
||||
|
||||
LinkedHashSet<URI> originalUris = null; // TODO:
|
||||
// exchange.getAttribute(GATEWAY_ORIGINAL_REQUEST_URL_ATTR);
|
||||
URI requestUri = null; // TODO:
|
||||
// exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
|
||||
LinkedHashSet<URI> originalUris = MvcUtils.getAttribute(request,
|
||||
MvcUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR);
|
||||
URI requestUri = request.uri();
|
||||
|
||||
if (originalUris != null && requestUri != null) {
|
||||
|
||||
originalUris.forEach(originalUri -> {
|
||||
|
||||
if (originalUri != null && originalUri.getPath() != null) {
|
||||
String prefix = originalUri.getPath();
|
||||
|
||||
// strip trailing slashes before checking if request path is end
|
||||
// of original path
|
||||
String originalUriPath = stripTrailingSlash(originalUri);
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright 2013-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.gateway.server.mvc.handler;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.cloud.gateway.server.mvc.common.MvcUtils;
|
||||
import org.springframework.cloud.gateway.server.mvc.config.RouteProperties;
|
||||
import org.springframework.web.servlet.function.HandlerFilterFunction;
|
||||
import org.springframework.web.servlet.function.HandlerFunction;
|
||||
import org.springframework.web.servlet.function.ServerResponse;
|
||||
|
||||
class DefaultHandlerSupplier implements HandlerSupplier {
|
||||
|
||||
@Override
|
||||
public Collection<Method> get() {
|
||||
return Arrays.asList(getClass().getMethods());
|
||||
}
|
||||
|
||||
public static HandlerDiscoverer.Result fn(RouteProperties routeProperties) {
|
||||
// fn:fnName
|
||||
return fn(routeProperties.getUri().getSchemeSpecificPart());
|
||||
}
|
||||
|
||||
public static HandlerDiscoverer.Result fn(String functionName) {
|
||||
return new HandlerDiscoverer.Result(HandlerFunctions.fn(functionName), Collections.emptyList(),
|
||||
Collections.emptyList());
|
||||
}
|
||||
|
||||
public static HandlerDiscoverer.Result forward(RouteProperties routeProperties) {
|
||||
return forward(routeProperties.getId(), routeProperties.getUri());
|
||||
}
|
||||
|
||||
public static HandlerDiscoverer.Result forward(String id, URI uri) {
|
||||
return new HandlerDiscoverer.Result(HandlerFunctions.forward(uri.getPath()), Collections.emptyList());
|
||||
}
|
||||
|
||||
public static HandlerDiscoverer.Result http(RouteProperties routeProperties) {
|
||||
return http(routeProperties.getId(), routeProperties.getUri());
|
||||
}
|
||||
|
||||
public static HandlerDiscoverer.Result http(String id, URI uri) {
|
||||
HandlerFunction<ServerResponse> http = HandlerFunctions.http();
|
||||
return getResult(id, uri, http);
|
||||
}
|
||||
|
||||
public static HandlerDiscoverer.Result https(RouteProperties routeProperties) {
|
||||
return https(routeProperties.getId(), routeProperties.getUri());
|
||||
}
|
||||
|
||||
public static HandlerDiscoverer.Result https(String id, URI uri) {
|
||||
return getResult(id, uri, HandlerFunctions.https());
|
||||
}
|
||||
|
||||
public static HandlerDiscoverer.Result no(RouteProperties routeProperties) {
|
||||
return no(routeProperties.getId(), routeProperties.getUri());
|
||||
}
|
||||
|
||||
public static HandlerDiscoverer.Result no(String id, URI uri) {
|
||||
return getResult(id, uri, HandlerFunctions.no());
|
||||
}
|
||||
|
||||
// for properties
|
||||
public static HandlerDiscoverer.Result stream(RouteProperties routeProperties) {
|
||||
// stream:bindingName
|
||||
return stream(routeProperties.getUri().getSchemeSpecificPart());
|
||||
}
|
||||
|
||||
public static HandlerDiscoverer.Result stream(String bindingName) {
|
||||
return new HandlerDiscoverer.Result(HandlerFunctions.stream(bindingName), Collections.emptyList(),
|
||||
Collections.emptyList());
|
||||
}
|
||||
|
||||
private static HandlerDiscoverer.Result getResult(String id, URI uri,
|
||||
HandlerFunction<ServerResponse> handlerFunction) {
|
||||
HandlerFilterFunction<ServerResponse, ServerResponse> setId = setIdFilter(id);
|
||||
HandlerFilterFunction<ServerResponse, ServerResponse> setRequest = setRequestUrlFilter(uri);
|
||||
return new HandlerDiscoverer.Result(handlerFunction, Arrays.asList(setId, setRequest), Collections.emptyList());
|
||||
}
|
||||
|
||||
private static HandlerFilterFunction<ServerResponse, ServerResponse> setIdFilter(String id) {
|
||||
return (request, next) -> {
|
||||
MvcUtils.setRouteId(request, id);
|
||||
return next.handle(request);
|
||||
};
|
||||
}
|
||||
|
||||
private static HandlerFilterFunction<ServerResponse, ServerResponse> setRequestUrlFilter(URI uri) {
|
||||
return (request, next) -> {
|
||||
MvcUtils.setRequestUrl(request, uri);
|
||||
return next.handle(request);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.cloud.gateway.server.mvc.handler;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
@@ -50,9 +51,16 @@ final class FunctionHandlerRequestProcessingHelper {
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
static ServerResponse processRequest(ServerRequest request, FunctionInvocationWrapper function, Object argument,
|
||||
boolean eventStream, List<String> ignoredHeaders, List<String> requestOnlyHeaders) {
|
||||
return processRequest(request, function, argument, eventStream, ignoredHeaders, requestOnlyHeaders, null);
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
static ServerResponse processRequest(ServerRequest request, FunctionInvocationWrapper function, Object argument,
|
||||
boolean eventStream, List<String> ignoredHeaders, List<String> requestOnlyHeaders,
|
||||
Map<String, String> additionalHeaders) {
|
||||
|
||||
if (argument == null) {
|
||||
argument = "";
|
||||
}
|
||||
@@ -70,42 +78,29 @@ final class FunctionHandlerRequestProcessingHelper {
|
||||
builder = builder.setHeader(FunctionHandlerHeaderUtils.HTTP_REQUEST_PARAM,
|
||||
request.params().toSingleValueMap());
|
||||
}
|
||||
|
||||
if (!CollectionUtils.isEmpty(additionalHeaders)) {
|
||||
builder.copyHeaders(additionalHeaders);
|
||||
}
|
||||
inputMessage = builder.copyHeaders(headers.toSingleValueMap()).build();
|
||||
|
||||
if (function.isRoutingFunction()) {
|
||||
function.setSkipOutputConversion(true);
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Sending request to " + function + " with argument: " + inputMessage);
|
||||
}
|
||||
Object result = function.apply(inputMessage);
|
||||
if (function.isConsumer()) {
|
||||
/*
|
||||
* if (result instanceof Publisher) { Mono.from((Publisher)
|
||||
* result).subscribe(); }
|
||||
*/
|
||||
return HttpMethod.DELETE.equals(request.method()) ? ServerResponse.ok().build()
|
||||
: ServerResponse.accepted()
|
||||
.headers(h -> h.addAll(sanitize(headers, ignoredHeaders, requestOnlyHeaders)))
|
||||
.build();
|
||||
// Mono.empty() :
|
||||
// Mono.just(ResponseEntity.accepted().headers(FunctionHandlerHeaderUtils.sanitize(headers,
|
||||
// ignoredHeaders, requestOnlyHeaders)).build());
|
||||
}
|
||||
|
||||
BodyBuilder responseOkBuilder = ServerResponse.ok()
|
||||
.headers(h -> h.addAll(sanitize(headers, ignoredHeaders, requestOnlyHeaders)));
|
||||
|
||||
// FIXME: Mono/Flux
|
||||
/*
|
||||
* Publisher pResult; if (result instanceof Publisher) { pResult = (Publisher)
|
||||
* result; if (eventStream) { return Flux.from(pResult); }
|
||||
*
|
||||
* if (pResult instanceof Flux) { pResult = ((Flux) pResult).onErrorContinue((e,
|
||||
* v) -> { logger.error("Failed to process value: " + v, (Throwable) e);
|
||||
* }).collectList(); } pResult = Mono.from(pResult); } else { pResult =
|
||||
* Mono.just(result); }
|
||||
*/
|
||||
|
||||
// return Mono.from(pResult).map(v -> {
|
||||
if (result instanceof Iterable i) {
|
||||
List aggregatedResult = (List) StreamSupport.stream(i.spliterator(), false).map(m -> {
|
||||
return m instanceof Message ? processMessage(responseOkBuilder, (Message<?>) m, ignoredHeaders) : m;
|
||||
@@ -118,7 +113,6 @@ final class FunctionHandlerRequestProcessingHelper {
|
||||
else {
|
||||
return responseOkBuilder.body(result);
|
||||
}
|
||||
// });
|
||||
}
|
||||
|
||||
private static Object processMessage(BodyBuilder responseOkBuilder, Message<?> message,
|
||||
|
||||
@@ -37,20 +37,39 @@ public class HandlerDiscoverer extends AbstractGatewayDiscoverer {
|
||||
|
||||
private final HandlerFunction<ServerResponse> handlerFunction;
|
||||
|
||||
private final List<HandlerFilterFunction<ServerResponse, ServerResponse>> filters;
|
||||
private final List<HandlerFilterFunction<ServerResponse, ServerResponse>> lowerPrecedenceFilters;
|
||||
|
||||
private final List<HandlerFilterFunction<ServerResponse, ServerResponse>> higherPrecedenceFilters;
|
||||
|
||||
@Deprecated
|
||||
public Result(HandlerFunction<ServerResponse> handlerFunction,
|
||||
List<HandlerFilterFunction<ServerResponse, ServerResponse>> filters) {
|
||||
this(handlerFunction, Collections.emptyList(), filters);
|
||||
}
|
||||
|
||||
public Result(HandlerFunction<ServerResponse> handlerFunction,
|
||||
List<HandlerFilterFunction<ServerResponse, ServerResponse>> lowerPrecedenceFilters,
|
||||
List<HandlerFilterFunction<ServerResponse, ServerResponse>> higherPrecedenceFilters) {
|
||||
this.handlerFunction = handlerFunction;
|
||||
this.filters = Objects.requireNonNullElse(filters, Collections.emptyList());
|
||||
this.lowerPrecedenceFilters = Objects.requireNonNullElse(lowerPrecedenceFilters, Collections.emptyList());
|
||||
this.higherPrecedenceFilters = Objects.requireNonNullElse(higherPrecedenceFilters, Collections.emptyList());
|
||||
}
|
||||
|
||||
public HandlerFunction<ServerResponse> getHandlerFunction() {
|
||||
return handlerFunction;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public List<HandlerFilterFunction<ServerResponse, ServerResponse>> getFilters() {
|
||||
return filters;
|
||||
return getHigherPrecedenceFilters();
|
||||
}
|
||||
|
||||
public List<HandlerFilterFunction<ServerResponse, ServerResponse>> getLowerPrecedenceFilters() {
|
||||
return lowerPrecedenceFilters;
|
||||
}
|
||||
|
||||
public List<HandlerFilterFunction<ServerResponse, ServerResponse>> getHigherPrecedenceFilters() {
|
||||
return higherPrecedenceFilters;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,15 +19,21 @@ package org.springframework.cloud.gateway.server.mvc.handler;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.cloud.function.context.FunctionCatalog;
|
||||
import org.springframework.cloud.function.context.FunctionProperties;
|
||||
import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper;
|
||||
import org.springframework.cloud.function.context.config.RoutingFunction;
|
||||
import org.springframework.cloud.gateway.server.mvc.GatewayMvcClassPathWarningAutoConfiguration;
|
||||
import org.springframework.cloud.gateway.server.mvc.common.MvcUtils;
|
||||
import org.springframework.cloud.gateway.server.mvc.config.RouteProperties;
|
||||
import org.springframework.cloud.stream.function.StreamOperations;
|
||||
@@ -43,37 +49,58 @@ import static org.springframework.cloud.gateway.server.mvc.handler.FunctionHandl
|
||||
|
||||
public abstract class HandlerFunctions {
|
||||
|
||||
private static final Log log = LogFactory.getLog(GatewayMvcClassPathWarningAutoConfiguration.class);
|
||||
|
||||
private HandlerFunctions() {
|
||||
|
||||
}
|
||||
|
||||
// for properties
|
||||
public static HandlerFunction<ServerResponse> fn(RouteProperties routeProperties) {
|
||||
// fn:fnName
|
||||
return fn(routeProperties.getUri().getSchemeSpecificPart());
|
||||
}
|
||||
|
||||
public static HandlerFunction<ServerResponse> fn(String functionName) {
|
||||
Assert.hasText(functionName, "'functionName' must not be empty");
|
||||
return request -> {
|
||||
String expandedFunctionName = MvcUtils.expand(request, functionName);
|
||||
FunctionCatalog functionCatalog = MvcUtils.getApplicationContext(request).getBean(FunctionCatalog.class);
|
||||
FunctionInvocationWrapper function = functionCatalog.lookup(expandedFunctionName,
|
||||
request.headers().accept().stream().map(MimeType::toString).toArray(String[]::new));
|
||||
String expandedFunctionName = MvcUtils.expand(request, functionName);
|
||||
FunctionInvocationWrapper function;
|
||||
Object body = null;
|
||||
if (expandedFunctionName.contains("/")) {
|
||||
String[] functionBodySplit = expandedFunctionName.split("/");
|
||||
function = functionCatalog.lookup(functionBodySplit[0],
|
||||
request.headers().accept().stream().map(MimeType::toString).toArray(String[]::new));
|
||||
if (function != null && function.isSupplier()) {
|
||||
log.warn("Supplier must not have any arguments. Supplier: '" + function.getFunctionDefinition()
|
||||
+ "' has '" + functionBodySplit[1] + "' as an argument which is ignored.");
|
||||
}
|
||||
body = functionBodySplit[1];
|
||||
}
|
||||
else {
|
||||
function = functionCatalog.lookup(expandedFunctionName,
|
||||
request.headers().accept().stream().map(MimeType::toString).toArray(String[]::new));
|
||||
}
|
||||
|
||||
/*
|
||||
* If function can not be found in the current runtime, we will default to
|
||||
* RoutingFunction which has additional logic to determine the function to
|
||||
* invoke.
|
||||
*/
|
||||
Map<String, String> additionalRequestHeaders = new HashMap<>();
|
||||
if (function == null) {
|
||||
additionalRequestHeaders.put(FunctionProperties.FUNCTION_DEFINITION, expandedFunctionName);
|
||||
|
||||
function = functionCatalog.lookup(RoutingFunction.FUNCTION_NAME,
|
||||
request.headers().accept().stream().map(MimeType::toString).toArray(String[]::new));
|
||||
}
|
||||
|
||||
if (function != null) {
|
||||
Object body = function.isSupplier() ? null : request.body(function.getRawInputType());
|
||||
return processRequest(request, function, body, false, Collections.emptyList(), Collections.emptyList());
|
||||
if (body == null) {
|
||||
body = function.isSupplier() ? null : request.body(function.getRawInputType());
|
||||
}
|
||||
return processRequest(request, function, body, false, Collections.emptyList(), Collections.emptyList(),
|
||||
additionalRequestHeaders);
|
||||
}
|
||||
return ServerResponse.notFound().build();
|
||||
};
|
||||
}
|
||||
|
||||
// for properties
|
||||
public static HandlerFunction<ServerResponse> stream(RouteProperties routeProperties) {
|
||||
// stream:bindingName
|
||||
return stream(routeProperties.getUri().getSchemeSpecificPart());
|
||||
}
|
||||
|
||||
public static HandlerFunction<ServerResponse> stream(String bindingName) {
|
||||
Assert.hasText(bindingName, "'bindingName' must not be empty");
|
||||
// TODO: validate bindingName
|
||||
@@ -115,14 +142,17 @@ public abstract class HandlerFunctions {
|
||||
|
||||
// TODO: current discovery only goes by method name
|
||||
// so last one wins, so put parameterless last
|
||||
@Deprecated
|
||||
public static HandlerFunction<ServerResponse> http(String uri) {
|
||||
return http(URI.create(uri));
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static HandlerFunction<ServerResponse> http(URI uri) {
|
||||
return new LookupProxyExchangeHandlerFunction(uri);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static HandlerFunction<ServerResponse> https(URI uri) {
|
||||
return new LookupProxyExchangeHandlerFunction(uri);
|
||||
}
|
||||
@@ -141,6 +171,7 @@ public abstract class HandlerFunctions {
|
||||
|
||||
static class LookupProxyExchangeHandlerFunction implements HandlerFunction<ServerResponse> {
|
||||
|
||||
@Deprecated
|
||||
private final URI uri;
|
||||
|
||||
private AtomicReference<ProxyExchangeHandlerFunction> proxyExchangeHandlerFunction = new AtomicReference<>();
|
||||
@@ -149,6 +180,7 @@ public abstract class HandlerFunctions {
|
||||
this.uri = null;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
LookupProxyExchangeHandlerFunction(URI uri) {
|
||||
this.uri = uri;
|
||||
}
|
||||
@@ -156,12 +188,15 @@ public abstract class HandlerFunctions {
|
||||
@Override
|
||||
public ServerResponse handle(ServerRequest serverRequest) {
|
||||
if (uri != null) {
|
||||
// TODO: in 2 places now, here and
|
||||
// GatewayMvcPropertiesBeanDefinitionRegistrar
|
||||
MvcUtils.putAttribute(serverRequest, MvcUtils.GATEWAY_REQUEST_URL_ATTR, uri);
|
||||
// TODO: log warning of deprecated usage
|
||||
MvcUtils.setRequestUrl(serverRequest, uri);
|
||||
}
|
||||
this.proxyExchangeHandlerFunction.compareAndSet(null, lookup(serverRequest));
|
||||
return proxyExchangeHandlerFunction.get().handle(serverRequest);
|
||||
return proxyExchangeHandlerFunction.updateAndGet(function -> {
|
||||
if (function == null) {
|
||||
return lookup(serverRequest);
|
||||
}
|
||||
return function;
|
||||
}).handle(serverRequest);
|
||||
}
|
||||
|
||||
private static ProxyExchangeHandlerFunction lookup(ServerRequest request) {
|
||||
@@ -179,12 +214,13 @@ public abstract class HandlerFunctions {
|
||||
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static class HandlerSupplier
|
||||
implements org.springframework.cloud.gateway.server.mvc.handler.HandlerSupplier {
|
||||
|
||||
@Override
|
||||
public Collection<Method> get() {
|
||||
return Arrays.asList(HandlerFunctions.class.getMethods());
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ org.springframework.cloud.gateway.server.mvc.filter.FilterSupplier=\
|
||||
org.springframework.cloud.gateway.server.mvc.filter.FilterFunctions.FilterSupplier
|
||||
|
||||
org.springframework.cloud.gateway.server.mvc.handler.HandlerSupplier=\
|
||||
org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.HandlerSupplier,\
|
||||
org.springframework.cloud.gateway.server.mvc.handler.DefaultHandlerSupplier,\
|
||||
org.springframework.cloud.gateway.server.mvc.filter.LoadBalancerHandlerSupplier
|
||||
|
||||
org.springframework.cloud.gateway.server.mvc.predicate.PredicateSupplier=\
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
org.springframework.cloud.gateway.server.mvc.GatewayServerMvcAutoConfiguration
|
||||
org.springframework.cloud.gateway.server.mvc.GatewayMvcClassPathWarningAutoConfiguration
|
||||
org.springframework.cloud.gateway.server.mvc.handler.GatewayMultipartAutoConfiguration
|
||||
org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration
|
||||
org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration
|
||||
org.springframework.cloud.gateway.server.mvc.config.DefaultFunctionConfiguration
|
||||
@@ -79,6 +79,7 @@ import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StreamUtils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
@@ -110,6 +111,7 @@ import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFu
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.requestHeaderToRequestUri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.requestSize;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.routeId;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.Bucket4jFilterFunctions.rateLimit;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.CircuitBreakerFilterFunctions.circuitBreaker;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.FilterFunctions.addRequestHeader;
|
||||
@@ -140,7 +142,8 @@ import static org.springframework.web.servlet.function.RequestPredicates.POST;
|
||||
import static org.springframework.web.servlet.function.RequestPredicates.path;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@SpringBootTest(properties = { "spring.cloud.gateway.mvc.http-client.type=jdk" },
|
||||
@SpringBootTest(
|
||||
properties = { "spring.cloud.gateway.mvc.http-client.type=jdk", "spring.cloud.gateway.function.enabled=false" },
|
||||
webEnvironment = WebEnvironment.RANDOM_PORT)
|
||||
@ContextConfiguration(initializers = HttpbinTestcontainers.class)
|
||||
@ExtendWith(OutputCaptureExtension.class)
|
||||
@@ -239,6 +242,7 @@ public class ServerMvcIntegrationTests {
|
||||
public void stripPrefixWorks() {
|
||||
restClient.get()
|
||||
.uri("/long/path/to/get")
|
||||
.header("Host", "www.stripprefix.org")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
@@ -246,6 +250,13 @@ public class ServerMvcIntegrationTests {
|
||||
.consumeWith(res -> {
|
||||
Map<String, Object> map = res.getResponseBody();
|
||||
Map<String, Object> headers = getMap(map, "headers");
|
||||
assertThat(headers).containsKeys(XForwardedRequestHeadersFilter.X_FORWARDED_PREFIX_HEADER,
|
||||
XForwardedRequestHeadersFilter.X_FORWARDED_HOST_HEADER,
|
||||
XForwardedRequestHeadersFilter.X_FORWARDED_PORT_HEADER,
|
||||
XForwardedRequestHeadersFilter.X_FORWARDED_PROTO_HEADER,
|
||||
XForwardedRequestHeadersFilter.X_FORWARDED_FOR_HEADER);
|
||||
assertThat(headers).containsEntry(XForwardedRequestHeadersFilter.X_FORWARDED_PREFIX_HEADER,
|
||||
"/long/path/to");
|
||||
assertThat(headers).containsEntry("X-Test", "stripPrefix");
|
||||
});
|
||||
}
|
||||
@@ -264,10 +275,40 @@ public class ServerMvcIntegrationTests {
|
||||
Map<String, Object> map = res.getResponseBody();
|
||||
assertThat(map).containsEntry("data", "hello");
|
||||
Map<String, Object> headers = getMap(map, "headers");
|
||||
assertThat(headers).containsKeys(XForwardedRequestHeadersFilter.X_FORWARDED_PREFIX_HEADER,
|
||||
XForwardedRequestHeadersFilter.X_FORWARDED_HOST_HEADER,
|
||||
XForwardedRequestHeadersFilter.X_FORWARDED_PORT_HEADER,
|
||||
XForwardedRequestHeadersFilter.X_FORWARDED_PROTO_HEADER,
|
||||
XForwardedRequestHeadersFilter.X_FORWARDED_FOR_HEADER);
|
||||
assertThat(headers).containsEntry(XForwardedRequestHeadersFilter.X_FORWARDED_PREFIX_HEADER,
|
||||
"/long/path/to");
|
||||
assertThat(headers).containsEntry("X-Test", "stripPrefixPost");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stripPrefixLbWorks() {
|
||||
restClient.get()
|
||||
.uri("/long/path/to/get")
|
||||
.header("Host", "www.stripprefixlb.org")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectBody(Map.class)
|
||||
.consumeWith(res -> {
|
||||
Map<String, Object> map = res.getResponseBody();
|
||||
Map<String, Object> headers = getMap(map, "headers");
|
||||
assertThat(headers).containsKeys(XForwardedRequestHeadersFilter.X_FORWARDED_PREFIX_HEADER,
|
||||
XForwardedRequestHeadersFilter.X_FORWARDED_HOST_HEADER,
|
||||
XForwardedRequestHeadersFilter.X_FORWARDED_PORT_HEADER,
|
||||
XForwardedRequestHeadersFilter.X_FORWARDED_PROTO_HEADER,
|
||||
XForwardedRequestHeadersFilter.X_FORWARDED_FOR_HEADER);
|
||||
assertThat(headers).containsEntry(XForwardedRequestHeadersFilter.X_FORWARDED_PREFIX_HEADER,
|
||||
"/long/path/to");
|
||||
assertThat(headers).containsEntry("X-Test", "stripPrefixLb");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setStatusGatewayRouterFunctionWorks() {
|
||||
restClient.get()
|
||||
@@ -384,7 +425,8 @@ public class ServerMvcIntegrationTests {
|
||||
public void circuitBreakerInvalidFallbackThrowsException() {
|
||||
// @formatter:off
|
||||
Assertions.assertThatThrownBy(() -> route("testcircuitbreakergatewayfallback")
|
||||
.route(path("/anything/circuitbreakergatewayfallback"), http(URI.create("https://nonexistantdomain.com1234")))
|
||||
.route(path("/anything/circuitbreakergatewayfallback"), http())
|
||||
.before(uri("https://nonexistantdomain.com1234"))
|
||||
.filter(circuitBreaker("mycb2", URI.create("http://example.com")))
|
||||
.build()).isInstanceOf(IllegalArgumentException.class);
|
||||
// @formatter:on
|
||||
@@ -987,6 +1029,11 @@ public class ServerMvcIntegrationTests {
|
||||
@LoadBalancerClient(name = "httpbin", configuration = TestLoadBalancerConfig.Httpbin.class)
|
||||
protected static class TestConfiguration {
|
||||
|
||||
@Bean
|
||||
StaticPortController staticPortController() {
|
||||
return new StaticPortController();
|
||||
}
|
||||
|
||||
@Bean
|
||||
TestHandler testHandler() {
|
||||
return new TestHandler();
|
||||
@@ -1067,8 +1114,8 @@ public class ServerMvcIntegrationTests {
|
||||
// @formatter:off
|
||||
return route("testsetpath")
|
||||
.route(POST("/mycustompath{extra}").and(host("**.setpathpost.org")), http())
|
||||
.filter(new HttpbinUriResolver())
|
||||
.filter(setPath("/{extra}"))
|
||||
.filter(new HttpbinUriResolver())
|
||||
.build();
|
||||
// @formatter:on
|
||||
}
|
||||
@@ -1076,11 +1123,12 @@ public class ServerMvcIntegrationTests {
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsStripPrefix() {
|
||||
// @formatter:off
|
||||
return route(GET("/long/path/to/get"), http())
|
||||
.filter(new HttpbinUriResolver())
|
||||
return route("teststripprefix")
|
||||
.route(GET("/long/path/to/get").and(host("**.stripprefix.org")), http())
|
||||
.filter(stripPrefix(3))
|
||||
.filter(addRequestHeader("X-Test", "stripPrefix"))
|
||||
.withAttribute(MvcUtils.GATEWAY_ROUTE_ID_ATTR, "teststripprefix");
|
||||
.filter(new HttpbinUriResolver())
|
||||
.build();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@@ -1089,9 +1137,21 @@ public class ServerMvcIntegrationTests {
|
||||
// @formatter:off
|
||||
return route("teststripprefixpost")
|
||||
.route(POST("/long/path/to/post").and(host("**.stripprefixpost.org")), http())
|
||||
.filter(new HttpbinUriResolver())
|
||||
.filter(stripPrefix(3))
|
||||
.filter(addRequestHeader("X-Test", "stripPrefixPost"))
|
||||
.filter(new HttpbinUriResolver())
|
||||
.build();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsStripPrefixLb() {
|
||||
// @formatter:off
|
||||
return route("teststripprefix")
|
||||
.route(GET("/long/path/to/get").and(host("**.stripprefixlb.org")), http())
|
||||
.filter(stripPrefix(3))
|
||||
.filter(addRequestHeader("X-Test", "stripPrefixLb"))
|
||||
.filter(lb("httpbin"))
|
||||
.build();
|
||||
// @formatter:on
|
||||
}
|
||||
@@ -1143,7 +1203,8 @@ public class ServerMvcIntegrationTests {
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsCircuitBreakerFallback() {
|
||||
// @formatter:off
|
||||
return route("testcircuitbreakerfallback")
|
||||
.route(path("/anything/circuitbreakerfallback"), http(URI.create("https://nonexistantdomain.com1234")))
|
||||
.route(path("/anything/circuitbreakerfallback"), http())
|
||||
.before(uri("https://nonexistantdomain.com1234"))
|
||||
.filter(circuitBreaker("mycb1", "/hello"))
|
||||
.build();
|
||||
// @formatter:on
|
||||
@@ -1153,7 +1214,8 @@ public class ServerMvcIntegrationTests {
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsCircuitBreakerFallbackToGatewayRoute() {
|
||||
// @formatter:off
|
||||
return route("testcircuitbreakergatewayfallback")
|
||||
.route(path("/anything/circuitbreakergatewayfallback"), http(URI.create("https://nonexistantdomain.com1234")))
|
||||
.route(path("/anything/circuitbreakergatewayfallback"), http())
|
||||
.before(uri("https://nonexistantdomain.com1234"))
|
||||
.filter(circuitBreaker("mycb2", URI.create("forward:/anything/gatewayfallback")))
|
||||
.build()
|
||||
.and(route("testgatewayfallback")
|
||||
@@ -1426,8 +1488,8 @@ public class ServerMvcIntegrationTests {
|
||||
return route("requestheadertorequesturi")
|
||||
.route(cloudFoundryRouteService().and(host("**.requestheadertorequesturi.org")), http())
|
||||
//.before(new HttpbinUriResolver()) NO URI RESOLVER!
|
||||
.before(requestHeaderToRequestUri("X-CF-Forwarded-Url"))
|
||||
.filter(setPath("/hello"))
|
||||
.before(requestHeaderToRequestUri("X-CF-Forwarded-Url"))
|
||||
.build();
|
||||
// @formatter:on
|
||||
}
|
||||
@@ -1644,6 +1706,16 @@ public class ServerMvcIntegrationTests {
|
||||
|
||||
}
|
||||
|
||||
@RestController
|
||||
protected static class StaticPortController {
|
||||
|
||||
@GetMapping(path = "/anything/staticport", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ResponseEntity<?> messageEvents() {
|
||||
return ResponseEntity.status(HttpStatus.NOT_ACCEPTABLE).build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@RestController
|
||||
protected static class EventController {
|
||||
|
||||
|
||||
@@ -53,7 +53,8 @@ import org.springframework.web.servlet.function.ServerRequest;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.cloud.gateway.server.mvc.test.TestUtils.getMap;
|
||||
|
||||
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
|
||||
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT,
|
||||
properties = { "spring.cloud.gateway.function.enabled=false" })
|
||||
@ActiveProfiles("propertiesbeandefinitionregistrartests")
|
||||
@ContextConfiguration(initializers = HttpbinTestcontainers.class)
|
||||
public class GatewayMvcPropertiesBeanDefinitionRegistrarTests {
|
||||
@@ -122,7 +123,7 @@ public class GatewayMvcPropertiesBeanDefinitionRegistrarTests {
|
||||
predicate.accept(new AbstractRequestPredicatesVisitor() {
|
||||
@Override
|
||||
public void path(String pattern) {
|
||||
assertThat(pattern).isEqualTo("/anything/listRoute3");
|
||||
assertThat(pattern).isEqualTo("/extra/anything/listRoute3");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -181,7 +182,7 @@ public class GatewayMvcPropertiesBeanDefinitionRegistrarTests {
|
||||
@SuppressWarnings("unchecked")
|
||||
public void lbRouteWorks() {
|
||||
restClient.get()
|
||||
.uri("/anything/listRoute3")
|
||||
.uri("/extra/anything/listRoute3")
|
||||
.header("MyHeaderName", "MyHeaderVal")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
|
||||
@@ -28,6 +28,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author raccoonback
|
||||
* @author Jens Mallien
|
||||
*/
|
||||
class BeforeFilterFunctionsTests {
|
||||
|
||||
@@ -157,6 +158,18 @@ class BeforeFilterFunctionsTests {
|
||||
assertThat(result.uri().toString()).hasToString("http://localhost/path/%C3%A9/last?foo=replacement");
|
||||
}
|
||||
|
||||
@Test
|
||||
void stripPrefixWithPort() {
|
||||
MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost:77/depth1/depth2/depth3")
|
||||
.buildRequest(null);
|
||||
|
||||
ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList());
|
||||
|
||||
ServerRequest result = BeforeFilterFunctions.stripPrefix(2).apply(request);
|
||||
|
||||
assertThat(result.uri().toString()).hasToString("http://localhost:77/depth3");
|
||||
}
|
||||
|
||||
@Test
|
||||
void stripPrefixWithEncodedPath() {
|
||||
MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/depth1/depth2/depth3/é")
|
||||
@@ -183,4 +196,53 @@ class BeforeFilterFunctionsTests {
|
||||
assertThat(result.uri().toString()).hasToString("http://localhost/depth3?baz%5B%5D=qux%5B%5D");
|
||||
}
|
||||
|
||||
@Test
|
||||
void rewritePath() {
|
||||
MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/get").buildRequest(null);
|
||||
|
||||
ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList());
|
||||
|
||||
ServerRequest modified = BeforeFilterFunctions.rewritePath("get", "modified").apply(request);
|
||||
|
||||
assertThat(modified.uri().getRawPath()).isEqualTo("/modified");
|
||||
}
|
||||
|
||||
@Test
|
||||
void rewritePathWithSpace() {
|
||||
MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/get/path/with spaces")
|
||||
.buildRequest(null);
|
||||
|
||||
ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList());
|
||||
|
||||
ServerRequest modified = BeforeFilterFunctions.rewritePath("get", "modified").apply(request);
|
||||
|
||||
assertThat(modified.uri().getRawPath()).isEqualTo("/modified/path/with%20spaces");
|
||||
}
|
||||
|
||||
@Test
|
||||
void rewritePathWithEnDash() {
|
||||
MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/get/path/with–en–dashes")
|
||||
.buildRequest(null);
|
||||
|
||||
ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList());
|
||||
|
||||
ServerRequest modified = BeforeFilterFunctions.rewritePath("get", "modified").apply(request);
|
||||
|
||||
assertThat(modified.uri().getRawPath()).isEqualTo("/modified/path/with%E2%80%93en%E2%80%93dashes");
|
||||
}
|
||||
|
||||
@Test
|
||||
void rewritePathWithEnDashAndSpace() {
|
||||
MockHttpServletRequest servletRequest = MockMvcRequestBuilders
|
||||
.get("http://localhost/get/path/with–en–dashes and spaces")
|
||||
.buildRequest(null);
|
||||
|
||||
ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList());
|
||||
|
||||
ServerRequest modified = BeforeFilterFunctions.rewritePath("get", "modified").apply(request);
|
||||
|
||||
assertThat(modified.uri().getRawPath())
|
||||
.isEqualTo("/modified/path/with%E2%80%93en%E2%80%93dashes%20and%20spaces");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -56,8 +56,8 @@ import static org.springframework.cloud.gateway.server.mvc.filter.RetryFilterFun
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@SpringBootTest(properties = {}, webEnvironment = WebEnvironment.RANDOM_PORT)
|
||||
@SpringBootTest(properties = { "spring.cloud.gateway.function.enabled=false" },
|
||||
webEnvironment = WebEnvironment.RANDOM_PORT)
|
||||
@ContextConfiguration(initializers = HttpbinTestcontainers.class)
|
||||
public class RetryFilterFunctionTests {
|
||||
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright 2013-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.gateway.server.mvc.filter;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||
import org.springframework.cloud.gateway.server.mvc.test.HttpbinTestcontainers;
|
||||
import org.springframework.cloud.gateway.server.mvc.test.TestLoadBalancerConfig;
|
||||
import org.springframework.cloud.gateway.server.mvc.test.client.TestRestClient;
|
||||
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.function.RouterFunction;
|
||||
import org.springframework.web.servlet.function.ServerResponse;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.FilterFunctions.stripPrefix;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.FilterFunctions.uri;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
import static org.springframework.cloud.gateway.server.mvc.test.TestUtils.getMap;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@SpringBootTest(properties = {}, webEnvironment = WebEnvironment.RANDOM_PORT)
|
||||
@ActiveProfiles("stripprefixstaticport")
|
||||
@ContextConfiguration(initializers = HttpbinTestcontainers.class)
|
||||
public class StripPrefixStaticPortTests {
|
||||
|
||||
@Autowired
|
||||
TestRestClient restClient;
|
||||
|
||||
@BeforeAll
|
||||
static void beforeAll() {
|
||||
HttpbinTestcontainers.initializeSystemProperties();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stripPrefixStaticPort() {
|
||||
restClient.get()
|
||||
.uri("/long/path/to/anything/staticport")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectBody(Map.class)
|
||||
.consumeWith(res -> {
|
||||
Map<String, Object> map = res.getResponseBody();
|
||||
Map<String, Object> headers = getMap(map, "headers");
|
||||
assertThat(headers)
|
||||
.containsKeys(XForwardedRequestHeadersFilter.X_FORWARDED_PREFIX_HEADER,
|
||||
XForwardedRequestHeadersFilter.X_FORWARDED_HOST_HEADER,
|
||||
XForwardedRequestHeadersFilter.X_FORWARDED_PORT_HEADER,
|
||||
XForwardedRequestHeadersFilter.X_FORWARDED_PROTO_HEADER,
|
||||
XForwardedRequestHeadersFilter.X_FORWARDED_FOR_HEADER)
|
||||
.containsEntry(XForwardedRequestHeadersFilter.X_FORWARDED_PREFIX_HEADER, "/long/path/to");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stripPrefixStaticPortDsl() {
|
||||
restClient.get()
|
||||
.uri("/long/path/to/anything/staticportdsl")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectBody(Map.class)
|
||||
.consumeWith(res -> {
|
||||
Map<String, Object> map = res.getResponseBody();
|
||||
Map<String, Object> headers = getMap(map, "headers");
|
||||
assertThat(headers)
|
||||
.containsKeys(XForwardedRequestHeadersFilter.X_FORWARDED_PREFIX_HEADER,
|
||||
XForwardedRequestHeadersFilter.X_FORWARDED_HOST_HEADER,
|
||||
XForwardedRequestHeadersFilter.X_FORWARDED_PORT_HEADER,
|
||||
XForwardedRequestHeadersFilter.X_FORWARDED_PROTO_HEADER,
|
||||
XForwardedRequestHeadersFilter.X_FORWARDED_FOR_HEADER)
|
||||
.containsEntry(XForwardedRequestHeadersFilter.X_FORWARDED_PREFIX_HEADER, "/long/path/to");
|
||||
});
|
||||
}
|
||||
|
||||
@SpringBootConfiguration
|
||||
@EnableAutoConfiguration
|
||||
@LoadBalancerClient(name = "httpbin", configuration = TestLoadBalancerConfig.Httpbin.class)
|
||||
protected static class TestConfiguration {
|
||||
|
||||
@Bean
|
||||
StaticPortController staticPortController() {
|
||||
return new StaticPortController();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsStripPrefixStaticPortDsl(Environment env) {
|
||||
// @formatter:off
|
||||
return route("teststripprefixstaticportdsl")
|
||||
.GET("/long/path/to/anything/staticportdsl", http())
|
||||
.filter(uri(env.getProperty("strip.prefix.static.uri")))
|
||||
.filter(stripPrefix(3))
|
||||
.build();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@RestController
|
||||
protected static class StaticPortController {
|
||||
|
||||
@GetMapping(path = "/anything/staticport", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ResponseEntity<?> messageEvents() {
|
||||
return ResponseEntity.status(HttpStatus.NOT_ACCEPTABLE).build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright 2013-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.gateway.server.mvc.handler;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.gateway.server.mvc.test.client.TestRestClient;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@SpringBootTest(properties = {}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
public class DefaultRouteFunctionHandlerTests {
|
||||
|
||||
@Autowired
|
||||
private TestRestClient restClient;
|
||||
|
||||
@Test
|
||||
public void testSupplierWorks() {
|
||||
restClient.get()
|
||||
.uri("/hello")
|
||||
.accept(MediaType.TEXT_PLAIN)
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectBody(String.class)
|
||||
.isEqualTo("hello");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFunctionWorksGET() {
|
||||
restClient.get()
|
||||
.uri("/upper/bob")
|
||||
.accept(MediaType.TEXT_PLAIN)
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectBody(String.class)
|
||||
.isEqualTo("BOB");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFunctionWorksPOST() {
|
||||
restClient.post()
|
||||
.uri("/upper")
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.bodyValue("bob")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectBody(String.class)
|
||||
.isEqualTo("BOB");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConsumerWorksGET() {
|
||||
restClient.get().uri("/consume/hello").accept(MediaType.TEXT_PLAIN).exchange().expectStatus().isAccepted();
|
||||
assertThat(TestConfiguration.consumerInvoked).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConsumerWorksPOST() {
|
||||
restClient.post()
|
||||
.uri("/consume")
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.bodyValue("hello")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isAccepted();
|
||||
assertThat(TestConfiguration.consumerInvoked).isTrue();
|
||||
}
|
||||
|
||||
@SpringBootConfiguration
|
||||
@EnableAutoConfiguration
|
||||
protected static class TestConfiguration {
|
||||
|
||||
static boolean consumerInvoked;
|
||||
|
||||
@Bean
|
||||
Function<String, String> upper() {
|
||||
return s -> s.toUpperCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
@Bean
|
||||
Consumer<String> consume() {
|
||||
return s -> {
|
||||
consumerInvoked = false;
|
||||
assertThat(s).isEqualTo("hello");
|
||||
consumerInvoked = true;
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
Supplier<String> hello() {
|
||||
return () -> "hello";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,8 +18,6 @@ package org.springframework.cloud.gateway.server.mvc.test;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.wait.strategy.HttpWaitStrategy;
|
||||
import org.testcontainers.utility.DockerImageName;
|
||||
@@ -35,8 +33,6 @@ public class HttpbinTestcontainers implements ApplicationContextInitializer<Conf
|
||||
// https://hub.docker.com/r/mccutchen/go-httpbin
|
||||
private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("mccutchen/go-httpbin");
|
||||
|
||||
static final Logger logger = LoggerFactory.getLogger(HttpbinTestcontainers.class);
|
||||
|
||||
/**
|
||||
* Default httpbin port.
|
||||
*/
|
||||
@@ -59,7 +55,6 @@ public class HttpbinTestcontainers implements ApplicationContextInitializer<Conf
|
||||
MutablePropertySources sources = context.getEnvironment().getPropertySources();
|
||||
|
||||
if (!sources.contains("httpbinTestcontainer")) {
|
||||
boolean running = container.isRunning();
|
||||
Integer mappedPort = container.getMappedPort(DEFAULT_PORT);
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
map.put("httpbin.port", String.valueOf(mappedPort));
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
|
||||
package org.springframework.cloud.gateway.server.mvc.test;
|
||||
|
||||
import java.lang.reflect.UndeclaredThrowableException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.cloud.gateway.server.mvc.common.MvcUtils;
|
||||
@@ -36,7 +38,15 @@ public class HttpbinUriResolver
|
||||
String host = context.getEnvironment().getProperty("httpbin.host");
|
||||
Assert.hasText(host, "httpbin.host is not set, did you initialize HttpbinTestcontainers?");
|
||||
Assert.notNull(port, "httpbin.port is not set, did you initialize HttpbinTestcontainers?");
|
||||
return URI.create(String.format("http://%s:%d", host, port));
|
||||
URI original = request.uri();
|
||||
try {
|
||||
return new URI("http", original.getUserInfo(), host, port, original.getPath(), original.getQuery(),
|
||||
original.getFragment());
|
||||
}
|
||||
catch (URISyntaxException e) {
|
||||
throw new UndeclaredThrowableException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -38,9 +38,12 @@ spring.cloud.gateway.mvc:
|
||||
- id: listRoute3
|
||||
uri: lb://httpbin
|
||||
predicates:
|
||||
- Path=/anything/listRoute3
|
||||
- Path=/extra/anything/listRoute3
|
||||
- Header=MyHeaderName,MyHeader.*
|
||||
filters:
|
||||
- name: StripPrefix
|
||||
args:
|
||||
parts: 1
|
||||
- name: AddRequestHeader
|
||||
args:
|
||||
name: X-Test
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
strip.prefix.static.uri: http://${httpbin.host}:${httpbin.port}
|
||||
spring.cloud.gateway.mvc:
|
||||
routes:
|
||||
- id: strip_prefix_static_port_config
|
||||
uri: ${strip.prefix.static.uri}
|
||||
predicates:
|
||||
- Path=/long/path/to/anything/staticport
|
||||
filters:
|
||||
- StripPrefix=3
|
||||
logging:
|
||||
level:
|
||||
org.springframework.cloud.gateway.server.mvc: TRACE
|
||||
@@ -317,8 +317,12 @@ public class GatewayAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = "spring.cloud.gateway.forwarded.enabled", matchIfMissing = true)
|
||||
public ForwardedHeadersFilter forwardedHeadersFilter() {
|
||||
return new ForwardedHeadersFilter();
|
||||
public ForwardedHeadersFilter forwardedHeadersFilter(Environment env, ServerProperties serverProperties) {
|
||||
boolean forwardedByEnabled = env.getProperty("spring.cloud.gateway.forwarded.by.enabled", Boolean.class, false);
|
||||
ForwardedHeadersFilter forwardedHeadersFilter = new ForwardedHeadersFilter();
|
||||
forwardedHeadersFilter.setForwardedByEnabled(forwardedByEnabled);
|
||||
forwardedHeadersFilter.setServerPort(serverProperties.getPort());
|
||||
return forwardedHeadersFilter;
|
||||
}
|
||||
|
||||
// HttpHeaderFilter beans
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.cloud.gateway.filter.factory;
|
||||
|
||||
import org.springframework.cloud.gateway.event.EnableBodyCachingEvent;
|
||||
import org.springframework.cloud.gateway.support.AbstractConfigurable;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.ApplicationEventPublisherAware;
|
||||
@@ -43,6 +44,13 @@ public abstract class AbstractGatewayFilterFactory<C> extends AbstractConfigurab
|
||||
return this.publisher;
|
||||
}
|
||||
|
||||
protected void enableBodyCaching(String routeId) {
|
||||
if (routeId != null && getPublisher() != null) {
|
||||
// send an event to enable caching
|
||||
getPublisher().publishEvent(new EnableBodyCachingEvent(this, routeId));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
|
||||
this.publisher = publisher;
|
||||
|
||||
@@ -30,17 +30,18 @@ import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.netty.Connection;
|
||||
import reactor.retry.Backoff;
|
||||
import reactor.retry.Jitter;
|
||||
import reactor.retry.Repeat;
|
||||
import reactor.retry.RepeatContext;
|
||||
import reactor.retry.Retry;
|
||||
import reactor.retry.RetryContext;
|
||||
|
||||
import org.springframework.cloud.gateway.event.EnableBodyCachingEvent;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilter;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.support.HasRouteId;
|
||||
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
|
||||
import org.springframework.cloud.gateway.support.TimeoutException;
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.HttpStatus.Series;
|
||||
@@ -70,7 +71,7 @@ public class RetryGatewayFilterFactory extends AbstractGatewayFilterFactory<Retr
|
||||
@Override
|
||||
public List<String> shortcutFieldOrder() {
|
||||
return Arrays.asList("retries", "statuses", "methods", "backoff.firstBackoff", "backoff.maxBackoff",
|
||||
"backoff.factor", "backoff.basedOnPreviousValue");
|
||||
"backoff.factor", "backoff.basedOnPreviousValue", "jitter.randomFactor", "timeout");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -124,10 +125,16 @@ public class RetryGatewayFilterFactory extends AbstractGatewayFilterFactory<Retr
|
||||
if (backoff != null) {
|
||||
statusCodeRepeat = statusCodeRepeat.backoff(getBackoff(backoff));
|
||||
}
|
||||
JitterConfig jitter = retryConfig.getJitter();
|
||||
if (jitter != null) {
|
||||
statusCodeRepeat = statusCodeRepeat.jitter(getJitter(jitter));
|
||||
}
|
||||
Duration timeout = retryConfig.getTimeout();
|
||||
if (timeout != null) {
|
||||
statusCodeRepeat = statusCodeRepeat.timeout(timeout);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: support timeout, backoff, jitter, etc... in Builder
|
||||
|
||||
Retry<ServerWebExchange> exceptionRetry = null;
|
||||
if (!retryConfig.getExceptions().isEmpty()) {
|
||||
Predicate<RetryContext<ServerWebExchange>> retryContextPredicate = context -> {
|
||||
@@ -163,6 +170,14 @@ public class RetryGatewayFilterFactory extends AbstractGatewayFilterFactory<Retr
|
||||
if (backoff != null) {
|
||||
exceptionRetry = exceptionRetry.backoff(getBackoff(backoff));
|
||||
}
|
||||
JitterConfig jitter = retryConfig.getJitter();
|
||||
if (jitter != null) {
|
||||
exceptionRetry = exceptionRetry.jitter(getJitter(jitter));
|
||||
}
|
||||
Duration timeout = retryConfig.getTimeout();
|
||||
if (timeout != null) {
|
||||
exceptionRetry = exceptionRetry.timeout(timeout);
|
||||
}
|
||||
}
|
||||
|
||||
GatewayFilter gatewayFilter = apply(retryConfig.getRouteId(), statusCodeRepeat, exceptionRetry);
|
||||
@@ -180,6 +195,9 @@ public class RetryGatewayFilterFactory extends AbstractGatewayFilterFactory<Retr
|
||||
.append("statuses", retryConfig.getStatuses())
|
||||
.append("methods", retryConfig.getMethods())
|
||||
.append("exceptions", retryConfig.getExceptions())
|
||||
.append("backoff", retryConfig.getBackoff())
|
||||
.append("jitter", retryConfig.getJitter())
|
||||
.append("timeout", retryConfig.getTimeout())
|
||||
.toString();
|
||||
}
|
||||
};
|
||||
@@ -204,6 +222,10 @@ public class RetryGatewayFilterFactory extends AbstractGatewayFilterFactory<Retr
|
||||
backoff.basedOnPreviousValue);
|
||||
}
|
||||
|
||||
private Jitter getJitter(JitterConfig jitter) {
|
||||
return Jitter.random(jitter.randomFactor);
|
||||
}
|
||||
|
||||
public boolean exceedsMaxIterations(ServerWebExchange exchange, RetryConfig retryConfig) {
|
||||
Integer iteration = exchange.getAttribute(RETRY_ITERATION_KEY);
|
||||
|
||||
@@ -229,10 +251,7 @@ public class RetryGatewayFilterFactory extends AbstractGatewayFilterFactory<Retr
|
||||
}
|
||||
|
||||
public GatewayFilter apply(String routeId, Repeat<ServerWebExchange> repeat, Retry<ServerWebExchange> retry) {
|
||||
if (routeId != null && getPublisher() != null) {
|
||||
// send an event to enable caching
|
||||
getPublisher().publishEvent(new EnableBodyCachingEvent(this, routeId));
|
||||
}
|
||||
enableBodyCaching(routeId);
|
||||
return (exchange, chain) -> {
|
||||
trace("Entering retry-filter");
|
||||
|
||||
@@ -295,6 +314,10 @@ public class RetryGatewayFilterFactory extends AbstractGatewayFilterFactory<Retr
|
||||
|
||||
private BackoffConfig backoff;
|
||||
|
||||
private JitterConfig jitter;
|
||||
|
||||
private Duration timeout;
|
||||
|
||||
public RetryConfig allMethods() {
|
||||
return setMethods(HttpMethod.values());
|
||||
}
|
||||
@@ -307,6 +330,35 @@ public class RetryGatewayFilterFactory extends AbstractGatewayFilterFactory<Retr
|
||||
if (this.backoff != null) {
|
||||
this.backoff.validate();
|
||||
}
|
||||
if (this.jitter != null) {
|
||||
this.jitter.validate();
|
||||
}
|
||||
if (this.timeout != null) {
|
||||
Assert.isTrue(!timeout.isNegative(), "timeout should be >= 0");
|
||||
}
|
||||
}
|
||||
|
||||
public Duration getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
public RetryConfig setTimeout(Duration timeout) {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
public JitterConfig getJitter() {
|
||||
return jitter;
|
||||
}
|
||||
|
||||
public RetryConfig setJitter(JitterConfig jitter) {
|
||||
this.jitter = jitter;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RetryConfig setJitter(double randomFactor) {
|
||||
this.jitter = new JitterConfig(randomFactor);
|
||||
return this;
|
||||
}
|
||||
|
||||
public BackoffConfig getBackoff() {
|
||||
@@ -437,6 +489,47 @@ public class RetryGatewayFilterFactory extends AbstractGatewayFilterFactory<Retr
|
||||
this.basedOnPreviousValue = basedOnPreviousValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringCreator(this).append("firstBackoff", firstBackoff)
|
||||
.append("maxBackoff", maxBackoff)
|
||||
.append("factor", factor)
|
||||
.append("basedOnPreviousValue", basedOnPreviousValue)
|
||||
.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class JitterConfig {
|
||||
|
||||
private double randomFactor = 0.5;
|
||||
|
||||
public void validate() {
|
||||
Assert.isTrue(randomFactor >= 0 && randomFactor <= 1,
|
||||
"random factor must be between 0 and 1 (default 0.5)");
|
||||
}
|
||||
|
||||
public JitterConfig() {
|
||||
}
|
||||
|
||||
public JitterConfig(double randomFactor) {
|
||||
this.randomFactor = randomFactor;
|
||||
}
|
||||
|
||||
public double getRandomFactor() {
|
||||
return randomFactor;
|
||||
}
|
||||
|
||||
public void setRandomFactor(double randomFactor) {
|
||||
this.randomFactor = randomFactor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringCreator(this).append("randomFactor", randomFactor).toString();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -89,6 +89,9 @@ public abstract class SpringCloudCircuitBreakerFilterFactory
|
||||
|
||||
@Override
|
||||
public GatewayFilter apply(Config config) {
|
||||
if (config.getFallbackUri() != null) {
|
||||
enableBodyCaching(config.getRouteId());
|
||||
}
|
||||
ReactiveCircuitBreaker cb = reactiveCircuitBreakerFactory.create(config.getId());
|
||||
Set<HttpStatus> statuses = config.getStatusCodes()
|
||||
.stream()
|
||||
|
||||
@@ -20,11 +20,15 @@ import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
@@ -34,8 +38,18 @@ import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
/**
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @author Tillmann Heigel
|
||||
*/
|
||||
public class ForwardedHeadersFilter implements HttpHeadersFilter, Ordered {
|
||||
|
||||
private Integer serverPort;
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private boolean forwardedByEnabled = false;
|
||||
|
||||
/**
|
||||
* Forwarded header.
|
||||
*/
|
||||
@@ -84,6 +98,14 @@ public class ForwardedHeadersFilter implements HttpHeadersFilter, Ordered {
|
||||
return result;
|
||||
}
|
||||
|
||||
public void setForwardedByEnabled(boolean forwardedByEnabled) {
|
||||
this.forwardedByEnabled = forwardedByEnabled;
|
||||
}
|
||||
|
||||
public void setServerPort(Integer serverPort) {
|
||||
this.serverPort = serverPort;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 0;
|
||||
@@ -134,13 +156,38 @@ public class ForwardedHeadersFilter implements HttpHeadersFilter, Ordered {
|
||||
}
|
||||
forwarded.put("for", forValue);
|
||||
}
|
||||
// TODO: support by?
|
||||
|
||||
if (forwardedByEnabled) {
|
||||
addForwardedByHeader(forwarded);
|
||||
}
|
||||
|
||||
updated.add(FORWARDED_HEADER, forwarded.toHeaderValue());
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
private void addForwardedByHeader(Forwarded forwarded) {
|
||||
try {
|
||||
addForwardedBy(forwarded, InetAddress.getLocalHost());
|
||||
}
|
||||
catch (UnknownHostException e) {
|
||||
this.logger.warn("Can not resolve host address, skipping Forwarded 'by' header", e);
|
||||
}
|
||||
}
|
||||
|
||||
/* visible for testing */ void addForwardedBy(Forwarded forwarded, InetAddress localAddress) {
|
||||
if (localAddress != null) {
|
||||
String byValue = localAddress.getHostAddress();
|
||||
if (localAddress instanceof Inet6Address) {
|
||||
byValue = "[" + byValue + "]";
|
||||
}
|
||||
if (serverPort != null && serverPort > 0) {
|
||||
byValue = byValue + ":" + serverPort;
|
||||
}
|
||||
forwarded.put("by", byValue);
|
||||
}
|
||||
}
|
||||
|
||||
/* for testing */ static class Forwarded {
|
||||
|
||||
private static final char EQUALS = '=';
|
||||
|
||||
@@ -389,6 +389,12 @@
|
||||
"description": "Enables the ForwardedHeadersFilter.",
|
||||
"defaultValue": "true"
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.forwarded.by.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Enables the Forwarded: by header part.",
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.httpserver.wiretap",
|
||||
"type": "java.lang.Boolean",
|
||||
|
||||
@@ -109,6 +109,30 @@ public class RetryGatewayFilterFactoryIntegrationTests extends BaseWebClientTest
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void retryWithBackoffJitterTimeout() {
|
||||
// @formatter:off
|
||||
testClient.get()
|
||||
.uri("/retry?key=retry-with-backoff-jitter-timeout&count=3")
|
||||
.header(HttpHeaders.HOST, "www.retrywithbackoffjittertimeout.org")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectHeader().value("X-Retry-Count", CoreMatchers.equalTo("3"));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void retryWithBackoffTimeout() {
|
||||
// backoff > timeout
|
||||
testClient.get()
|
||||
.uri("/retry?key=retry-with-backoff-timeout&count=3")
|
||||
.header(HttpHeaders.HOST, "www.retrywithbackofftimeout.org")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
assertThat(TestConfig.map.get("retry-with-backoff-timeout")).isNotNull().hasValue(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void retryFilterGetJavaDsl() {
|
||||
testClient.get()
|
||||
@@ -363,6 +387,21 @@ public class RetryGatewayFilterFactoryIntegrationTests extends BaseWebClientTest
|
||||
r -> r.host("**.retrywithbackoff.org").filters(f -> f.prefixPath("/httpbin").retry(config -> {
|
||||
config.setRetries(2).setBackoff(Duration.ofMillis(100), null, 2, true);
|
||||
})).uri(uri))
|
||||
.route("retry_with_backoff_jitter_timeout_test", r -> r.host("**.retrywithbackoffjittertimeout.org")
|
||||
.filters(f -> f.prefixPath("/httpbin").retry(config -> {
|
||||
config.setRetries(3)
|
||||
.setBackoff(Duration.ofMillis(50), Duration.ofMillis(100), 2, true)
|
||||
.setJitter(0.1)
|
||||
.setTimeout(Duration.ofMillis(1000));
|
||||
}))
|
||||
.uri(uri))
|
||||
.route("retry_with_backoff_timeout_test", r -> r.host("**.retrywithbackofftimeout.org")
|
||||
.filters(f -> f.prefixPath("/httpbin").retry(config -> {
|
||||
config.setRetries(3)
|
||||
.setBackoff(Duration.ofMillis(100), null, 2, true)
|
||||
.setTimeout(Duration.ofMillis(200));
|
||||
}))
|
||||
.uri(uri))
|
||||
|
||||
.route("retry_with_loadbalancer",
|
||||
r -> r.host("**.retrywithloadbalancer.org")
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable;
|
||||
|
||||
import org.springframework.cloud.gateway.test.BaseWebClientTests;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.reactive.function.BodyInserters;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON;
|
||||
@@ -243,4 +244,17 @@ public abstract class SpringCloudCircuitBreakerFilterFactoryTests extends BaseWe
|
||||
.valueEquals(ROUTE_ID_HEADER, "circuitbreaker_resume_without_error");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void filterPostFallback() {
|
||||
testClient.post()
|
||||
.uri("/post")
|
||||
.body(BodyInserters.fromValue("hello"))
|
||||
.header("Host", "www.circuitbreakerfallbackpost.org")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectBody()
|
||||
.json("{\"body\":\"hello\"}");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -38,6 +38,8 @@ import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
@@ -68,6 +70,11 @@ public class SpringCloudCircuitBreakerTestConfig {
|
||||
return Collections.singletonMap("from", "circuitbreakerfallbackcontroller");
|
||||
}
|
||||
|
||||
@PostMapping("/circuitbreakerPostFallbackController")
|
||||
public Map<String, String> postFallbackController(@RequestBody String body) {
|
||||
return Collections.singletonMap("body", body);
|
||||
}
|
||||
|
||||
@GetMapping("/circuitbreakerUriFallbackController/**")
|
||||
public Map<String, String> uriFallbackcontroller(ServerWebExchange exchange, @RequestParam("a") String a) {
|
||||
return Collections.singletonMap("uri", exchange.getRequest().getURI().toString());
|
||||
|
||||
@@ -25,6 +25,7 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.cloud.gateway.filter.headers.ForwardedHeadersFilter.Forwarded;
|
||||
@@ -201,4 +202,43 @@ public class ForwardedHeadersFilterTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forwardedByForIpv4AddressIsAdded() throws UnknownHostException {
|
||||
Forwarded forwarded = new Forwarded();
|
||||
InetAddress ipv4Address = InetAddress.getByName("216.103.69.111");
|
||||
ForwardedHeadersFilter forwardedHeadersFilter = new ForwardedHeadersFilter();
|
||||
forwardedHeadersFilter.setForwardedByEnabled(true);
|
||||
|
||||
forwardedHeadersFilter.addForwardedBy(forwarded, ipv4Address);
|
||||
|
||||
Assertions.assertThat(forwarded.getValues()).containsEntry("by", "216.103.69.111");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forwardedByForIpv6AddressIsAdded() throws UnknownHostException {
|
||||
Forwarded forwarded = new Forwarded();
|
||||
InetAddress ipv6Address = InetAddress.getByName("abc4:babf:955f:1724:11bc:0153:275c:d36e");
|
||||
ForwardedHeadersFilter forwardedHeadersFilter = new ForwardedHeadersFilter();
|
||||
forwardedHeadersFilter.setForwardedByEnabled(true);
|
||||
|
||||
forwardedHeadersFilter.addForwardedBy(forwarded, ipv6Address);
|
||||
|
||||
Assertions.assertThat(forwarded.getValues())
|
||||
.containsEntry("by", "\"[abc4:babf:955f:1724:11bc:153:275c:d36e]\"");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forwardedByIsNotAddedIfFeatureIsDisabled() throws UnknownHostException {
|
||||
Forwarded forwarded = new Forwarded();
|
||||
InetAddress ipv4Address = InetAddress.getByName("216.103.69.111");
|
||||
ForwardedHeadersFilter forwardedHeadersFilter = new ForwardedHeadersFilter();
|
||||
forwardedHeadersFilter.setForwardedByEnabled(false);
|
||||
|
||||
forwardedHeadersFilter.addForwardedBy(forwarded, ipv4Address);
|
||||
|
||||
Assertions.assertThat(forwarded.getValues()).containsEntry("by", "216.103.69.111");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.G
|
||||
public class XForwardedHeadersFilterTests {
|
||||
|
||||
@Test
|
||||
public void remoteAddressIsNull() throws Exception {
|
||||
public void remoteAddressIsNull() {
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost:8080/get")
|
||||
.header(HttpHeaders.HOST, "myhost")
|
||||
.build();
|
||||
|
||||
@@ -54,7 +54,7 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
|
||||
* @author Denis Cutic
|
||||
* @author Andrey Muchnik
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = RANDOM_PORT)
|
||||
@SpringBootTest(webEnvironment = RANDOM_PORT, properties = { "spring.cloud.gateway.function.enabled=false" })
|
||||
@DirtiesContext
|
||||
@Testcontainers
|
||||
@Tag("DockerRequired")
|
||||
|
||||
@@ -62,7 +62,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
|
||||
import static org.springframework.cloud.gateway.test.TestUtils.getMap;
|
||||
|
||||
@SpringBootTest(webEnvironment = RANDOM_PORT)
|
||||
@SpringBootTest(webEnvironment = RANDOM_PORT, properties = "spring.cloud.gateway.forwarded.by.enabled=true")
|
||||
@DirtiesContext
|
||||
@SuppressWarnings("unchecked")
|
||||
@ExtendWith(OutputCaptureExtension.class)
|
||||
@@ -125,7 +125,8 @@ class GatewayIntegrationTests extends BaseWebClientTests {
|
||||
assertThat(headers.get(ForwardedHeadersFilter.FORWARDED_HEADER)).asString()
|
||||
.contains("proto=http")
|
||||
.contains("host=\"localhost:")
|
||||
.contains("for=\"127.0.0.1:");
|
||||
.contains("for=\"127.0.0.1:")
|
||||
.contains("by=");
|
||||
assertThat(headers.get(XForwardedHeadersFilter.X_FORWARDED_HOST_HEADER)).asString()
|
||||
.isEqualTo("localhost:" + this.port);
|
||||
assertThat(headers.get(XForwardedHeadersFilter.X_FORWARDED_PORT_HEADER)).asString()
|
||||
|
||||
@@ -32,91 +32,91 @@ import java.net.URI
|
||||
@SpringBootTest(classes = [Config::class])
|
||||
class RouteDslTests {
|
||||
|
||||
@Autowired
|
||||
lateinit var builder: RouteLocatorBuilder
|
||||
@Autowired
|
||||
lateinit var builder: RouteLocatorBuilder
|
||||
|
||||
@Test
|
||||
fun sampleRouteDsl() {
|
||||
val routeLocator = builder.routes {
|
||||
route(id = "test") {
|
||||
host("**.abc.org") and path("/image/png")
|
||||
filters {
|
||||
addResponseHeader("X-TestHeader", "foobar")
|
||||
}
|
||||
uri("http://httpbin.org:80")
|
||||
}
|
||||
@Test
|
||||
fun sampleRouteDsl() {
|
||||
val routeLocator = builder.routes {
|
||||
route(id = "test") {
|
||||
host("**.abc.org") and path("/image/png")
|
||||
filters {
|
||||
addResponseHeader("X-TestHeader", "foobar")
|
||||
}
|
||||
uri("http://httpbin.org:80")
|
||||
}
|
||||
|
||||
route(id = "test2") {
|
||||
path("/image/webp") or path("/image/anotherone")
|
||||
filters {
|
||||
addResponseHeader("X-AnotherHeader", "baz")
|
||||
addResponseHeader("X-AnotherHeader-2", "baz-2")
|
||||
}
|
||||
uri("https://httpbin.org:443")
|
||||
}
|
||||
}
|
||||
route(id = "test2") {
|
||||
path("/image/webp") or path("/image/anotherone")
|
||||
filters {
|
||||
addResponseHeader("X-AnotherHeader", "baz")
|
||||
addResponseHeader("X-AnotherHeader-2", "baz-2")
|
||||
}
|
||||
uri("https://httpbin.org:443")
|
||||
}
|
||||
}
|
||||
|
||||
StepVerifier
|
||||
.create(routeLocator.routes)
|
||||
.expectNextMatches({
|
||||
it.id == "test" && it.filters.size == 1 && it.uri == URI.create("http://httpbin.org:80")
|
||||
})
|
||||
.expectNextMatches({
|
||||
it.id == "test2" && it.filters.size == 2 && it.uri == URI.create("https://httpbin.org:443")
|
||||
})
|
||||
.expectComplete()
|
||||
.verify()
|
||||
StepVerifier
|
||||
.create(routeLocator.routes)
|
||||
.expectNextMatches({
|
||||
it.id == "test" && it.filters.size == 1 && it.uri == URI.create("http://httpbin.org:80")
|
||||
})
|
||||
.expectNextMatches({
|
||||
it.id == "test2" && it.filters.size == 2 && it.uri == URI.create("https://httpbin.org:443")
|
||||
})
|
||||
.expectComplete()
|
||||
.verify()
|
||||
|
||||
val sampleExchange: ServerWebExchange = MockServerWebExchange.from(MockServerHttpRequest.get("/image/webp")
|
||||
.header("Host", "test.abc.org").build())
|
||||
val sampleExchange: ServerWebExchange = MockServerWebExchange.from(MockServerHttpRequest.get("/image/webp")
|
||||
.header("Host", "test.abc.org").build())
|
||||
|
||||
val filteredRoutes = routeLocator.routes.filter({
|
||||
sampleExchange.attributes.put(ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR, it.id)
|
||||
it.predicate.apply(sampleExchange).toMono().block()
|
||||
})
|
||||
val filteredRoutes = routeLocator.routes.filter({
|
||||
sampleExchange.attributes.put(ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR, it.id)
|
||||
it.predicate.apply(sampleExchange).toMono().block()
|
||||
})
|
||||
|
||||
StepVerifier.create(filteredRoutes)
|
||||
.expectNextMatches({
|
||||
it.id == "test2" && it.filters.size == 2 && it.uri == URI.create("https://httpbin.org:443")
|
||||
})
|
||||
.expectComplete()
|
||||
.verify()
|
||||
}
|
||||
StepVerifier.create(filteredRoutes)
|
||||
.expectNextMatches({
|
||||
it.id == "test2" && it.filters.size == 2 && it.uri == URI.create("https://httpbin.org:443")
|
||||
})
|
||||
.expectComplete()
|
||||
.verify()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dslWithFunctionParameters() {
|
||||
val routerLocator = builder.routes {
|
||||
route(id = "test1", order = 10, uri = "http://httpbin.org") {
|
||||
host("**.abc.org")
|
||||
}
|
||||
route(id = "test2", order = 10, uri = "http://someurl") {
|
||||
host("**.abc.org")
|
||||
uri("http://override-url")
|
||||
}
|
||||
}
|
||||
@Test
|
||||
fun dslWithFunctionParameters() {
|
||||
val routerLocator = builder.routes {
|
||||
route(id = "test1", order = 10, uri = "http://httpbin.org") {
|
||||
host("**.abc.org")
|
||||
}
|
||||
route(id = "test2", order = 10, uri = "http://someurl") {
|
||||
host("**.abc.org")
|
||||
uri("http://override-url")
|
||||
}
|
||||
}
|
||||
|
||||
StepVerifier.create(routerLocator.routes)
|
||||
.expectNextMatches({
|
||||
it.id == "test1" &&
|
||||
it.uri == URI.create("http://httpbin.org:80") &&
|
||||
it.order == 10 &&
|
||||
it.predicate.apply(MockServerWebExchange
|
||||
.from(MockServerHttpRequest
|
||||
.get("/someuri").header("Host", "test.abc.org")))
|
||||
.toMono().block()
|
||||
})
|
||||
.expectNextMatches({
|
||||
it.id == "test2" &&
|
||||
it.uri == URI.create("http://override-url:80") &&
|
||||
it.order == 10 &&
|
||||
it.predicate.apply(MockServerWebExchange
|
||||
.from(MockServerHttpRequest
|
||||
.get("/someuri").header("Host", "test.abc.org")))
|
||||
.toMono().block()
|
||||
})
|
||||
.expectComplete()
|
||||
.verify()
|
||||
}
|
||||
StepVerifier.create(routerLocator.routes)
|
||||
.expectNextMatches({
|
||||
it.id == "test1" &&
|
||||
it.uri == URI.create("http://httpbin.org:80") &&
|
||||
it.order == 10 &&
|
||||
it.predicate.apply(MockServerWebExchange
|
||||
.from(MockServerHttpRequest
|
||||
.get("/someuri").header("Host", "test.abc.org")))
|
||||
.toMono().block()
|
||||
})
|
||||
.expectNextMatches({
|
||||
it.id == "test2" &&
|
||||
it.uri == URI.create("http://override-url:80") &&
|
||||
it.order == 10 &&
|
||||
it.predicate.apply(MockServerWebExchange
|
||||
.from(MockServerHttpRequest
|
||||
.get("/someuri").header("Host", "test.abc.org")))
|
||||
.toMono().block()
|
||||
})
|
||||
.expectComplete()
|
||||
.verify()
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
|
||||
@@ -104,6 +104,19 @@ spring:
|
||||
name: fallbackcmd
|
||||
fallbackUri: forward:/circuitbreakerFallbackController
|
||||
|
||||
# =====================================
|
||||
- id: circuitbreaker_fallback_test_post
|
||||
uri: ${test.uri}
|
||||
predicates:
|
||||
- Host=**.circuitbreakerfallbackpost.org
|
||||
filters:
|
||||
- name: CircuitBreaker
|
||||
args:
|
||||
name: fallbackcmd
|
||||
statusCodes:
|
||||
- 200
|
||||
fallbackUri: forward:/circuitbreakerPostFallbackController
|
||||
|
||||
# =====================================
|
||||
- id: circuitbreaker_fallback_test_variables
|
||||
uri: ${test.uri}
|
||||
|
||||
Reference in New Issue
Block a user