Fixes routing issue with path filters and static uri (#3747)

* Fixes routing issue with path filters and static uri

The problem arises from when MvcUtils.setRequestUrl is called depending on if the route was created via config, using `http(uri)` or having it set in a before filter.

To fix this, the uri is now always set in a before filter and the `http(uri)` methods are deprecated in favor of a BeforeFilterFunctions.uri() method.

A new DefaultHandlerSupplier was created to use an updated HandlerDiscoverer.Result that has fields for lowerPrecedenceFilters and higherPrecedenceFilters as the `http()` family of Handler methods need associated filters.

This results in a more consistent handling of the request url attribute.

Existing methods and constructors are deprecated.

Fixes gh-3736
This commit is contained in:
Spencer Gibb
2025-03-28 14:59:19 -04:00
committed by GitHub
parent b382bb6cf0
commit 55b3cd0c51
48 changed files with 754 additions and 282 deletions

View File

@@ -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,8 +33,10 @@ class RouteConfiguration {
@Bean
public RouterFunction<ServerResponse> gatewayRouterFunctionsAddReqHeader() {
return route(GET("/red"), http("https://example.org"))
.before(addRequestHeader("X-Request-red", "blue"));
return route("add_request_header_route")
.GET("/red", http())
.before(uri("https://example.org"))
.before(addRequestHeader("X-Request-red", "blue"));
}
}
----
@@ -45,13 +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(GET("/red/{segment}"), http("https://example.org"))
.before(addRequestHeader("X-Request-red", "blue-{segment}"));
return route("add_request_header_route")
.GET("/red/{segment}", http())
.before(uri("https://example.org"))
.before(addRequestHeader("X-Request-red", "blue-{segment}"));
}
}
----

View File

@@ -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}

View File

@@ -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();
}
}
----

View File

@@ -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();
}
}
----

View File

@@ -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());
}
}

View File

@@ -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();
}

View File

@@ -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());
}

View File

@@ -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();
}

View File

@@ -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) { }

View File

@@ -7,22 +7,11 @@ NOTE: This filter can be configured only by using the Java DSL.
The following listing shows how to modify a response body filter:
[source,java]
----
@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
return builder.routes()
.route("rewrite_response_upper", r -> r.host("*.rewriteresponseupper.org")
.filters(f -> f.prefixPath("/httpbin")
.modifyResponseBody(String.class, String.class,
(exchange, s) -> Mono.just(s.toUpperCase()))).uri(uri))
.build();
}
----
.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 +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(modifyResponseBody(String.class, String.class, MediaType.APPLICATION_JSON_VALUE, (request, 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();
}
}

View File

@@ -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(prefixPath("/mypath"))
.build();
return route("prefixpath_route")
.GET("/**", http())
.before(uri("https://example.org"))
.before(prefixPath("/mypath"))
.build();
}
}
----

View File

@@ -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();
}
}
----

View File

@@ -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();
}
}
----

View File

@@ -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();
}
}
----

View File

@@ -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();
}
}
----

View File

@@ -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();
}
}
----

View File

@@ -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();
}
}

View File

@@ -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();
}
}
----

View File

@@ -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();
}
}
----

View File

@@ -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();
}
}
----
@@ -77,11 +82,11 @@ NOTE: When using the retry filter with a `forward:` prefixed URL, the target end
For example, if the target endpoint is an annotated controller, the target controller method should not return `ResponseEntity` with an error status code.
Instead, it should throw an `Exception` or signal an error (for example, through a `Mono.error(ex)` return value), which the retry filter can be configured to handle by retrying.
// 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]

View File

@@ -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();
}
}
----

View File

@@ -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,10 +34,11 @@ 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();
}
}
----

View File

@@ -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();
}
}
----

View File

@@ -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();
}
}

View File

@@ -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,10 +36,11 @@ 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();
}
}
----

View File

@@ -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();
}
}
----

View File

@@ -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();
}
}
----

View File

@@ -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();
}
}
----

View File

@@ -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());
}
}
----

View File

@@ -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,14 +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.

View File

@@ -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();
}
}
----

View File

@@ -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());
}
}
----

View File

@@ -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.

View File

@@ -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();
}
}
----

View File

@@ -39,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;
@@ -150,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
@@ -181,13 +171,15 @@ public class RouterFunctionHolderFactory {
HandlerFunction<ServerResponse> handlerFunction = null;
// filters added by HandlerDiscoverer need to go last, so save them
List<HandlerFilterFunction<ServerResponse, ServerResponse>> handlerFilterFunctionFilters = new ArrayList<>();
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();
handlerFilterFunctionFilters.addAll(result.getFilters());
lowerPrecedenceFilters.addAll(result.getLowerPrecedenceFilters());
higherPrecedenceFilters.addAll(result.getHigherPrecedenceFilters());
}
if (handlerFunction == null) {
throw new IllegalStateException(
@@ -218,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 -> {
@@ -226,7 +221,7 @@ public class RouterFunctionHolderFactory {
});
// HandlerDiscoverer filters need higher priority, so put them last
handlerFilterFunctionFilters.forEach(builder::filter);
higherPrecedenceFilters.forEach(builder::filter);
builder.withAttribute(MvcUtils.GATEWAY_ROUTE_ID_ATTR, routeId);

View File

@@ -196,7 +196,6 @@ public abstract class BeforeFilterFunctions {
String newPath = uri.getRawPath() + request.uri().getRawPath();
URI prefixedUri = UriComponentsBuilder.fromUri(request.uri()).replacePath(newPath).build().toUri();
MvcUtils.setRequestUrl(request, prefixedUri);
return ServerRequest.from(request).uri(prefixedUri).build();
};
}
@@ -340,7 +339,6 @@ public abstract class BeforeFilterFunctions {
ServerRequest modified = ServerRequest.from(request).uri(rewrittenUri).build();
MvcUtils.setRequestUrl(request, rewrittenUri);
return modified;
};
}
@@ -361,7 +359,6 @@ public abstract class BeforeFilterFunctions {
URI uri = uriTemplate.expand(uriVariables);
URI newUri = UriComponentsBuilder.fromUri(request.uri()).replacePath(uri.getRawPath()).build(true).toUri();
MvcUtils.setRequestUrl(request, newUri);
return ServerRequest.from(request).uri(newUri).build();
};
}
@@ -419,12 +416,22 @@ public abstract class BeforeFilterFunctions {
.replacePath(newPath.toString())
.build(true)
.toUri();
MvcUtils.setRequestUrl(request, prefixedUri);
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;

View File

@@ -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() {

View File

@@ -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())));
}

View File

@@ -400,7 +400,7 @@ public class XForwardedRequestHeadersFilter implements HttpHeadersFilter.Request
LinkedHashSet<URI> originalUris = MvcUtils.getAttribute(request,
MvcUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR);
URI requestUri = MvcUtils.getAttribute(request, MvcUtils.GATEWAY_REQUEST_URL_ATTR);
URI requestUri = request.uri();
if (originalUris != null && requestUri != null) {

View File

@@ -0,0 +1,92 @@
/*
* 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 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());
}
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);
};
}
}

View File

@@ -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;
}
}

View File

@@ -19,8 +19,8 @@ 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.concurrent.atomic.AtomicReference;
import jakarta.servlet.ServletException;
@@ -60,14 +60,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);
}
@@ -86,6 +89,7 @@ public abstract class HandlerFunctions {
static class LookupProxyExchangeHandlerFunction implements HandlerFunction<ServerResponse> {
@Deprecated
private final URI uri;
private AtomicReference<ProxyExchangeHandlerFunction> proxyExchangeHandlerFunction = new AtomicReference<>();
@@ -94,6 +98,7 @@ public abstract class HandlerFunctions {
this.uri = null;
}
@Deprecated
LookupProxyExchangeHandlerFunction(URI uri) {
this.uri = uri;
}
@@ -101,12 +106,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) {
@@ -124,12 +132,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();
}
}

View File

@@ -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=\

View File

@@ -76,6 +76,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;
@@ -107,6 +108,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;
@@ -298,7 +300,7 @@ public class ServerMvcIntegrationTests {
XForwardedRequestHeadersFilter.X_FORWARDED_FOR_HEADER);
assertThat(headers).containsEntry(XForwardedRequestHeadersFilter.X_FORWARDED_PREFIX_HEADER,
"/long/path/to");
assertThat(headers).containsEntry("X-Test", "stripPrefix");
assertThat(headers).containsEntry("X-Test", "stripPrefixLb");
});
}
@@ -418,7 +420,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
@@ -1016,6 +1019,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();
@@ -1132,7 +1140,7 @@ public class ServerMvcIntegrationTests {
return route("teststripprefix")
.route(GET("/long/path/to/get").and(host("**.stripprefixlb.org")), http())
.filter(stripPrefix(3))
.filter(addRequestHeader("X-Test", "stripPrefix"))
.filter(addRequestHeader("X-Test", "stripPrefixLb"))
.filter(lb("httpbin"))
.build();
// @formatter:on
@@ -1185,7 +1193,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
@@ -1195,7 +1204,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")
@@ -1686,6 +1696,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 {

View File

@@ -156,6 +156,18 @@ class BeforeFilterFunctionsTests {
assertThat(result.uri().toString()).hasToString("http://localhost/depth3");
}
@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/é")

View File

@@ -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();
}
}
}

View File

@@ -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));

View File

@@ -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