diff --git a/docs/modules/ROOT/pages/spring-cloud-gateway-server-mvc/gateway-request-predicates.adoc b/docs/modules/ROOT/pages/spring-cloud-gateway-server-mvc/gateway-request-predicates.adoc index f44860749f..88ae56b630 100644 --- a/docs/modules/ROOT/pages/spring-cloud-gateway-server-mvc/gateway-request-predicates.adoc +++ b/docs/modules/ROOT/pages/spring-cloud-gateway-server-mvc/gateway-request-predicates.adoc @@ -1,4 +1,589 @@ [[gateway-request-predicates]] = Gateway Request Predicates -TODO \ No newline at end of file +Spring Cloud Gateway MVC matches routes as part of the Spring WebMvc.fn `HandlerMapping` infrastructure. +Spring Cloud Gateway reuses many https://docs.spring.io/spring-framework/reference/web/webmvc-functional.html#webmvc-fn-predicates[`RequestPredicate`] implementations from WebMvc.fn and includes other custom `RequestPredicate` implementations +All of these predicates match on different attributes of the HTTP request. +You can combine multiple route predicate factories with the `RequestPredicate.and()` and `RequestPredicate.or()` methods. + +[[after-request-predicate]] +== The After Request Predicate + +The `After` route predicate factory takes one parameter, a `datetime` (which is a java `ZonedDateTime`). +This predicate matches requests that happen after the specified datetime. +The following example configures an after route predicate: + +.application.yml +[source,yaml] +---- +spring: + cloud: + gateway: + routes: + - id: after_route + uri: https://example.org + predicates: + - After=2017-01-20T17:42:47.789-07:00[America/Denver] +---- + +.GatewaySampleApplication.java +[source,java] +---- +import java.time.ZonedDateTime; +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; + +@Configuration +class RouteConfiguration { + + @Bean + public RouterFunction gatewayRouterFunctionsAfter() { + return route("after_route") + .route(after(ZonedDateTime.parse("2017-01-20T17:42:47.789-07:00[America/Denver]")), http("https://example.org")) + .build(); + } +} +---- + +This route matches any request made after Jan 20, 2017 17:42 Mountain Time (Denver). + +[[before-request-predicate]] +== The Before Request Predicate + +The `Before` route predicate factory takes one parameter, a `datetime` (which is a java `ZonedDateTime`). +This predicate matches requests that happen before the specified `datetime`. +The following example configures a before route predicate: + +.application.yml +[source,yaml] +---- +spring: + cloud: + gateway: + routes: + - id: before_route + uri: https://example.org + predicates: + - Before=2017-01-20T17:42:47.789-07:00[America/Denver] +---- + +.GatewaySampleApplication.java +[source,java] +---- +import java.time.ZonedDateTime; +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; + +@Configuration +class RouteConfiguration { + + @Bean + public RouterFunction gatewayRouterFunctionsBefore() { + return route("before_route") + .route(before(ZonedDateTime.parse("2017-01-20T17:42:47.789-07:00[America/Denver]")), http("https://example.org")) + .build(); + } +} +---- + +This route matches any request made before Jan 20, 2017 17:42 Mountain Time (Denver). + +[[between-request-predicate]] +== The Between Request Predicate + +The `Between` route predicate factory takes two parameters, `datetime1` and `datetime2` +which are java `ZonedDateTime` objects. +This predicate matches requests that happen after `datetime1` and before `datetime2`. +The `datetime2` parameter must be after `datetime1`. +The following example configures a between route predicate: + +.application.yml +[source,yaml] +---- +spring: + cloud: + gateway: + routes: + - id: between_route + uri: https://example.org + predicates: + - Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver] +---- + +.GatewaySampleApplication.java +[source,java] +---- +import java.time.ZonedDateTime; +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; + +@Configuration +class RouteConfiguration { + + @Bean + public RouterFunction 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(); + } +} +---- + +This route matches any request made after Jan 20, 2017 17:42 Mountain Time (Denver) and before Jan 21, 2017 17:42 Mountain Time (Denver). +This could be useful for maintenance windows. + +[[cookie-request-predicate]] +== The Cookie Request Predicate + +The `Cookie` route predicate factory takes two parameters, the cookie `name` and a `regexp` (which is a Java regular expression). +This predicate matches cookies that have the given name and whose values match the regular expression. +The following example configures a cookie route predicate factory: + +.application.yml +[source,yaml] +---- +spring: + cloud: + gateway: + routes: + - id: cookie_route + uri: https://example.org + predicates: + - Cookie=chocolate, ch.p +---- + +.GatewaySampleApplication.java +[source,java] +---- +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; + +@Configuration +class RouteConfiguration { + + @Bean + public RouterFunction gatewayRouterFunctionsCookie() { + return route("cookie_route") + .route(cookie("chocolate", "ch.p"), http("https://example.org")) + .build(); + } +} +---- + +This route matches requests that have a cookie named `chocolate` whose value matches the `ch.p` regular expression. + +[[header-request-predicate]] +== The Header Request Predicate + +The `Header` route predicate factory takes two parameters, the `header` and a `regexp` (which is a Java regular expression). +This predicate matches with a header that has the given name whose value matches the regular expression. +The following example configures a header route predicate: + +.application.yml +[source,yaml] +---- +spring: + cloud: + gateway: + routes: + - id: header_route + uri: https://example.org + predicates: + - Header=X-Request-Id, \d+ +---- + +.GatewaySampleApplication.java +[source,java] +---- +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; + +@Configuration +class RouteConfiguration { + + @Bean + public RouterFunction gatewayRouterFunctionsCookie() { + return route("cookie_route") + .route(header("X-Request-Id", "\\d+"), http("https://example.org")) + .build(); + } +} +---- + +This route matches if the request has a header named `X-Request-Id` whose value matches the `\d+` regular expression (that is, it has a value of one or more digits). + +[[host-request-predicate]] +== The Host Request Predicate + +The `Host` route predicate factory takes one parameter: a list of host name `patterns`. +The pattern is an Ant-style pattern with `.` as the separator. +This predicates matches the `Host` header that matches the pattern. +The following example configures a host route predicate: + +.application.yml +[source,yaml] +---- +spring: + cloud: + gateway: + routes: + - id: host_route + uri: https://example.org + predicates: + - Host=**.somehost.org,**.anotherhost.org +---- + +.GatewaySampleApplication.java +[source,java] +---- +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; + +@Configuration +class RouteConfiguration { + + @Bean + public RouterFunction gatewayRouterFunctionsHost() { + return route("host_route") + .route(host("**.somehost.org", "**.anotherhost.org"), http("https://example.org")) + .build(); + } +} +---- + +URI template variables (such as `\{sub}.myhost.org`) are supported as well. + +This route matches if the request has a `Host` header with a value of `www.somehost.org` or `beta.somehost.org` or `www.anotherhost.org`. + +This predicate extracts the URI template variables (such as `sub`, defined in the preceding example) as a map of names and values and places it in the `ServerRequest.attributes()` with a key defined in `MvcUtils.URI_TEMPLATE_VARIABLES_ATTRIBUTE`. +// TODO: figure out link to gateway-handler-filter-functions +Those values are then available for use by Gateway Handler Filter Functions. + + +[[method-request-predicate]] +== The Method Request Predicate + +The `Method` Request Predicate takes a `methods` argument which is one or more parameters: the HTTP methods to match. +The following example configures a method route predicate: + +.application.yml +[source,yaml] +---- +spring: + cloud: + gateway: + routes: + - id: method_route + uri: https://example.org + predicates: + - Method=GET,POST +---- + +.GatewaySampleApplication.java +[source,java] +---- +import org.springframework.http.HttpMethod; +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; + +@Configuration +class RouteConfiguration { + + @Bean + public RouterFunction gatewayRouterFunctionsMethod() { + return route("method_route") + .route(method(HttpMethod.GET, HttpMethod.POST), http("https://example.org")) + .build(); + } +} +---- + +This route matches if the request method was a `GET` or a `POST`. + +`GatewayRequestPredicates.method` is a simple alias for https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/function/RequestPredicates.html#methods(org.springframework.http.HttpMethod...)[`RequestPredicates.methods`]. Also, the `RouterFunctions.Builder` API includes convenience methods that combine the `method` and `path` `RequestPredicates`. + +.GatewaySampleApplication.java +[source,java] +---- +import org.springframework.http.HttpMethod; +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 gatewayRouterFunctionsMethod() { + return route("method_route") + .GET("/mypath", http("https://example.org")) + .build(); + } +} +---- + +This route matches if the request method was a `GET` and the path was `/mypath`. + +[[path-request-predicate]] +== The Path Request Predicate + +The `Path` Request Predicate takes two parameters: a list of Spring `PathPattern` `patterns`. +// and an optional flag called `matchTrailingSlash` (defaults to `true`). +This Request Predicate uses https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/function/RequestPredicates.html#path(java.lang.String)[`RequestPredicates.path()`] as the underlying implementation. +The following example configures a path route predicate: + +.application.yml +[source,yaml] +---- +spring: + cloud: + gateway: + routes: + - id: path_route + uri: https://example.org + predicates: + - Path=/red/{segment},/blue/{segment} +---- + +.GatewaySampleApplication.java +[source,java] +---- +import org.springframework.http.HttpMethod; +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; + +@Configuration +class RouteConfiguration { + + @Bean + public RouterFunction gatewayRouterFunctionsPath() { + return route("path_route") + .route(path("/red/{segment}", "/blue/{segment}"), http("https://example.org")) + .build(); + } +} +---- + +This route matches if the request path was, for example: `/red/1` or `/red/1/` or `/red/blue` or `/blue/green`. + +//If `matchTrailingSlash` is set to `false`, then request path `/red/1/` will not be matched. + +This predicate extracts the URI template variables (such as `segment`, defined in the preceding example) as a map of names and values and places it in the `ServerRequest.attributes()` with a key defined in `RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE`. +// TODO: figure out link +Those values are then available for use by Gateway Handler Filter Functions. + +A utility method (called `get`) is available to make access to these variables easier. +The following example shows how to use the `get` method: + +[source,java] +---- +Map uriVariables = MvcUtils.getUriTemplateVariables(request); + +String segment = uriVariables.get("segment"); +---- + +//// +TODO: query predicate +[[query-request-predicate]] +== The Query Request Predicate + +The `Query` route predicate factory takes two parameters: a required `param` and an optional `regexp` (which is a Java regular expression). +The following example configures a query route predicate: + +.application.yml +[source,yaml] +---- +spring: + cloud: + gateway: + routes: + - id: query_route + uri: https://example.org + predicates: + - Query=green +---- + +The preceding route matches if the request contained a `green` query parameter. + +.application.yml +[source,yaml] +---- +spring: + cloud: + gateway: + routes: + - id: query_route + uri: https://example.org + predicates: + - Query=red, gree. +---- + +The preceding route matches if the request contained a `red` query parameter whose value matched the `gree.` regexp, so `green` and `greet` would match. + +TODO: remoteAddr predicate +[[remoteaddr-request-predicate]] +== The RemoteAddr Request Predicate + +The `RemoteAddr` route predicate factory takes a list (min size 1) of `sources`, which are CIDR-notation (IPv4 or IPv6) strings, such as `192.168.0.1/16` (where `192.168.0.1` is an IP address and `16` is a subnet mask). +The following example configures a RemoteAddr route predicate: + +.application.yml +[source,yaml] +---- +spring: + cloud: + gateway: + routes: + - id: remoteaddr_route + uri: https://example.org + predicates: + - RemoteAddr=192.168.1.1/24 +---- + +This route matches if the remote address of the request was, for example, `192.168.1.10`. + +[[modifying-the-way-remote-addresses-are-resolved]] +=== Modifying the Way Remote Addresses Are Resolved + +By default, the RemoteAddr route predicate factory uses the remote address from the incoming request. +This may not match the actual client IP address if Spring Cloud Gateway sits behind a proxy layer. + +You can customize the way that the remote address is resolved by setting a custom `RemoteAddressResolver`. +Spring Cloud Gateway comes with one non-default remote address resolver that is based off of the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For[X-Forwarded-For header], `XForwardedRemoteAddressResolver`. + +`XForwardedRemoteAddressResolver` has two static constructor methods, which take different approaches to security: + +* `XForwardedRemoteAddressResolver::trustAll` returns a `RemoteAddressResolver` that always takes the first IP address found in the `X-Forwarded-For` header. +This approach is vulnerable to spoofing, as a malicious client could set an initial value for the `X-Forwarded-For`, which would be accepted by the resolver. + +* `XForwardedRemoteAddressResolver::maxTrustedIndex` takes an index that correlates to the number of trusted infrastructure running in front of Spring Cloud Gateway. +If Spring Cloud Gateway is, for example only accessible through HAProxy, then a value of 1 should be used. +If two hops of trusted infrastructure are required before Spring Cloud Gateway is accessible, then a value of 2 should be used. + +Consider the following header value: + +[source] +---- +X-Forwarded-For: 0.0.0.1, 0.0.0.2, 0.0.0.3 +---- + +The following `maxTrustedIndex` values yield the following remote addresses: + +[options="header"] +|=== +|`maxTrustedIndex` | result +|[`Integer.MIN_VALUE`,0] | (invalid, `IllegalArgumentException` during initialization) +|1 | 0.0.0.3 +|2 | 0.0.0.2 +|3 | 0.0.0.1 +|[4, `Integer.MAX_VALUE`] | 0.0.0.1 +|=== + +[[gateway-route-filters]] +The following example shows how to achieve the same configuration with Java: + +.GatewayConfig.java +[source,java] +---- +RemoteAddressResolver resolver = XForwardedRemoteAddressResolver + .maxTrustedIndex(1); + +... + +.route("direct-route", + r -> r.remoteAddr("10.1.1.1", "10.10.1.1/24") + .uri("https://downstream1") +.route("proxied-route", + r -> r.remoteAddr(resolver, "10.10.1.1", "10.10.1.1/24") + .uri("https://downstream2") +) +---- +//// + +[[weight-request-predicate]] +== The Weight Request Predicate + +The `Weight` route predicate factory takes two arguments: `group` and `weight` (an int). The weights are calculated per group. +The following example configures a weight route predicate: + +.application.yml +[source,yaml] +---- +spring: + cloud: + gateway: + routes: + - id: weight_high + uri: https://weighthigh.org + predicates: + - Weight=group1, 8 + - id: weight_low + uri: https://weightlow.org + predicates: + - Weight=group1, 2 +---- + +.GatewaySampleApplication.java +[source,java] +---- +import org.springframework.http.HttpMethod; +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; + +@Configuration +class RouteConfiguration { + + @Bean + public RouterFunction 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()); + } +} +---- + +This route would forward ~80% of traffic to https://weighthigh.org and ~20% of traffic to https://weightlow.org + +//// +TODO: XForwardedRemoteAddr predicate +[[xforwarded-remote-addr-request-predicate]] +== The XForwarded Remote Addr Request Predicate + +The `XForwarded Remote Addr` route predicate factory takes a list (min size 1) of `sources`, which are CIDR-notation (IPv4 or IPv6) strings, such as `192.168.0.1/16` (where `192.168.0.1` is an IP address and `16` is a subnet mask). + +This route predicate allows requests to be filtered based on the `X-Forwarded-For` HTTP header. + +This can be used with reverse proxies such as load balancers or web application firewalls where +the request should only be allowed if it comes from a trusted list of IP addresses used by those +reverse proxies. + + +The following example configures a XForwardedRemoteAddr route predicate: + +.application.yml +[source,yaml] +---- +spring: + cloud: + gateway: + routes: + - id: xforwarded_remoteaddr_route + uri: https://example.org + predicates: + - XForwardedRemoteAddr=192.168.1.1/24 +---- + +This route matches if the `X-Forwarded-For` header contains, for example, `192.168.1.10`. +////