From 50922f7dd8d7f7f097e877b842b346641b0a4b56 Mon Sep 17 00:00:00 2001 From: Marta Medio Date: Wed, 16 Nov 2022 16:22:22 +0100 Subject: [PATCH] Filter Documentation improvements (#2761) * Fix different typos across the doc file * Standardize titles of GatewayFilter Factories section and order alphabetically (no content changed) * Order alphabetically section of Global Filters (no content changed) * Add docs for Global Local Cache Response filter * Set correct order for LocalResponseCache filter config * Add link for key-resolver-section --- .../main/asciidoc/spring-cloud-gateway.adoc | 1017 +++++++++-------- .../cache/LocalResponseCacheProperties.java | 2 +- 2 files changed, 521 insertions(+), 498 deletions(-) diff --git a/docs/src/main/asciidoc/spring-cloud-gateway.adoc b/docs/src/main/asciidoc/spring-cloud-gateway.adoc index e8255727..eee1fd10 100644 --- a/docs/src/main/asciidoc/spring-cloud-gateway.adoc +++ b/docs/src/main/asciidoc/spring-cloud-gateway.adoc @@ -47,7 +47,7 @@ NOTE: URIs defined in routes without a port get default port values of 80 and 44 There are two ways to configure predicates and filters: shortcuts and fully expanded arguments. Most examples below use the shortcut way. -The name and argument names will be listed as `code` in the first sentance or two of the each section. The arguments are typically listed in the order that would be needed for the shortcut configuration. +The name and argument names are listed as `code` in the first sentence or two of each section. The arguments are typically listed in the order that are needed for the shortcut configuration. === Shortcut Configuration @@ -667,33 +667,9 @@ spring: ---- ==== -=== The `DedupeResponseHeader` `GatewayFilter` Factory - -The DedupeResponseHeader GatewayFilter factory takes a `name` parameter and an optional `strategy` parameter. `name` can contain a space-separated list of header names. -The following example configures a `DedupeResponseHeader` `GatewayFilter`: - -.application.yml -==== -[source,yaml] ----- -spring: - cloud: - gateway: - routes: - - id: dedupe_response_header_route - uri: https://example.org - filters: - - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin ----- -==== - -This removes duplicate values of `Access-Control-Allow-Credentials` and `Access-Control-Allow-Origin` response headers in cases when both the gateway CORS logic and the downstream logic add them. - -The `DedupeResponseHeader` filter also accepts an optional `strategy` parameter. -The accepted values are `RETAIN_FIRST` (default), `RETAIN_LAST`, and `RETAIN_UNIQUE`. [[spring-cloud-circuitbreaker-filter-factory]] -=== Spring Cloud CircuitBreaker GatewayFilter Factory +=== The `CircuitBreaker` `GatewayFilter` Factory The Spring Cloud CircuitBreaker GatewayFilter factory uses the Spring Cloud CircuitBreaker APIs to wrap Gateway routes in a circuit breaker. Spring Cloud CircuitBreaker supports multiple libraries that can be used with Spring Cloud Gateway. Spring Cloud supports Resilience4J out of the box. @@ -874,6 +850,74 @@ public RouteLocator routes(RouteLocatorBuilder builder) { ---- ==== +=== The `CacheRequestBody` `GatewayFilter` Factory +Some situations necessitate reading the request body. Since the request can be read only once, we need to cache the request body. +You can use the `CacheRequestBody` filter to cache the request body before sending it downstream and getting the body from `exchange` attribute. + +The following listing shows how to cache the request body `GatewayFilter`: + +==== +[source,java] +---- +@Bean +public RouteLocator routes(RouteLocatorBuilder builder) { + return builder.routes() + .route("cache_request_body_route", r -> r.path("/downstream/**") + .filters(f -> f.prefixPath("/httpbin") + .cacheRequestBody(String.class).uri(uri)) + .build(); +} +---- +==== + + +.application.yml +==== +[source,yaml] +---- +spring: + cloud: + gateway: + routes: + - id: cache_request_body_route + uri: lb://downstream + predicates: + - Path=/downstream/** + filters: + - name: CacheRequestBody + args: + bodyClass: java.lang.String +---- +==== +`CacheRequestBody` extracts the request body and converts it to a body class (such as `java.lang.String`, defined in the preceding example). +`CacheRequestBody` then places it in the attributes available from `ServerWebExchange.getAttributes()`, with a key defined in `ServerWebExchangeUtils.CACHED_REQUEST_BODY_ATTR`. + +NOTE: This filter works only with HTTP (including HTTPS) requests. + +=== The `DedupeResponseHeader` `GatewayFilter` Factory + +The `DedupeResponseHeader` GatewayFilter factory takes a `name` parameter and an optional `strategy` parameter. `name` can contain a space-separated list of header names. +The following example configures a `DedupeResponseHeader` `GatewayFilter`: + +.application.yml +==== +[source,yaml] +---- +spring: + cloud: + gateway: + routes: + - id: dedupe_response_header_route + uri: https://example.org + filters: + - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin +---- +==== + +This removes duplicate values of `Access-Control-Allow-Credentials` and `Access-Control-Allow-Origin` response headers in cases when both the gateway CORS logic and the downstream logic add them. + +The `DedupeResponseHeader` filter also accepts an optional `strategy` parameter. +The accepted values are `RETAIN_FIRST` (default), `RETAIN_LAST`, and `RETAIN_UNIQUE`. [[fallback-headers]] @@ -921,6 +965,137 @@ You can overwrite the names of the headers in the configuration by setting the v For more information on circuit breakers and the gateway see the <>. +=== The `JsonToGrpc` `GatewayFilter` Factory + +The JSONToGRPCFilter GatewayFilter Factory converts a JSON payload to a gRPC request. + +The filter takes the following arguments: + +* `protoDescriptor`: Proto descriptor file. + +This file can be generated using `protoc` and specifying the `--descriptor_set_out` flag: + +[source,bash] +---- +protoc --proto_path=src/main/resources/proto/ \ +--descriptor_set_out=src/main/resources/proto/hello.pb \ +src/main/resources/proto/hello.proto +---- + +* `protoFile`: Proto definition file. + +* `service`: Fully qualified name of the service that handles the request. + +* `method`: Method name in the service that handles the request. + +NOTE: `streaming` is not supported. + + +*application.yml.* + +[source,java] +---- +@Bean +public RouteLocator routes(RouteLocatorBuilder builder) { + return builder.routes() + .route("json-grpc", r -> r.path("/json/hello").filters(f -> { + String protoDescriptor = "file:src/main/proto/hello.pb"; + String protoFile = "file:src/main/proto/hello.proto"; + String service = "HelloService"; + String method = "hello"; + return f.jsonToGRPC(protoDescriptor, protoFile, service, method); + }).uri(uri)) +---- + +[source,yaml] +---- +spring: + cloud: + gateway: + routes: + - id: json-grpc + uri: https://localhost:6565/testhello + predicates: + - Path=/json/** + filters: + - name: JsonToGrpc + args: + protoDescriptor: file:proto/hello.pb + protoFile: file:proto/hello.proto + service: com.example.grpcserver.hello.HelloService + method: hello + +---- + +When a request is made through the gateway to `/json/hello`, the request is transformed by using the definition provided in `hello.proto`, sent to `com.example.grpcserver.hello.HelloService/hello`, and the response back is transformed to JSON. + +By default, it creates a `NettyChannel` by using the default `TrustManagerFactory`. However, you can customize this `TrustManager` by creating a bean of type `GrpcSslConfigurer`: + +[source,java] +---- + +@Configuration +public class GRPCLocalConfiguration { + @Bean + public GRPCSSLContext sslContext() { + TrustManager trustManager = trustAllCerts(); + return new GRPCSSLContext(trustManager); + } +} +---- + + +=== The `LocalResponseCache` `GatewayFilter` Factory + +This filter allows caching the response body and headers to follow these rules: + +* It can only cache bodiless GET requests. +* It caches the response only for one of the following status codes: HTTP 200 (OK), HTTP 206 (Partial Content), or HTTP 301 (Moved Permanently). +* Response data is not cached if `Cache-Control` header does not allow it (`no-store` present in the request or `no-store` or `private` present in the response). +* If the response is already cached and a new request is performed with no-cache value in `Cache-Control` header, it returns a bodiless response with 304 (Not Modified). + +This filter (which configures the local response cache per route) is available only if the <> is enabled. + +It accepts the first parameter to override the time to expire a cache entry (expressed in `s` for seconds, `m` for minutes, and `h` for hours) and a second parameter to set the maximum size of the cache to evict entries for this route (KB, MB, or GB). + +The following listing shows how to add local response cache `GatewayFilter`: + +==== +[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") + .localResponseCache(Duration.ofMinutes(30), "500MB") + ).uri(uri)) + .build(); +} +---- + +or this + +.application.yaml +[source,yaml] +---- +spring: + cloud: + gateway: + routes: + - id: resource + uri: http://localhost:9000 + predicates: + - Path=/resource + filters: + - LocalResponseCache=30m,500MB +---- + +NOTE: This filter also automatically calculates the `max-age` value in the HTTP `Cache-Control` header. +Only if `max-age` is present on the original response is the value rewritten with the number of seconds set in the `timeToLive` configuration parameter. +In consecutive calls, this value is recalculated with the number of seconds left until the response expires. +==== + === The `MapRequestHeader` `GatewayFilter` Factory The `MapRequestHeader` `GatewayFilter` factory takes `fromHeader` and `toHeader` parameters. @@ -944,7 +1119,77 @@ spring: ---- ==== -This adds `X-Request-Red:` header to the downstream request with updated values from the incoming HTTP request's `Blue` header. +This adds the `X-Request-Red:` header to the downstream request with updated values from the incoming HTTP request's `Blue` header. + +=== The `ModifyRequestBody` `GatewayFilter` Factory + +You can use the `ModifyRequestBody` filter to modify the request body before it is sent downstream by the gateway. + +NOTE: This filter can be configured only by using the Java DSL. + +The following listing shows how to modify a request body `GatewayFilter`: + +==== +[source,java] +---- +@Bean +public RouteLocator routes(RouteLocatorBuilder builder) { + return builder.routes() + .route("rewrite_request_obj", r -> r.host("*.rewriterequestobj.org") + .filters(f -> f.prefixPath("/httpbin") + .modifyRequestBody(String.class, Hello.class, MediaType.APPLICATION_JSON_VALUE, + (exchange, s) -> return Mono.just(new Hello(s.toUpperCase())))).uri(uri)) + .build(); +} + +static class Hello { + String message; + + public Hello() { } + + public Hello(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} +---- + +NOTE: If the request has no body, the `RewriteFilter` is passed `null`. `Mono.empty()` should be returned to assign a missing body in the request. + +==== + + +=== The `ModifyResponseBody` `GatewayFilter` Factory + +You can use the `ModifyResponseBody` filter to modify the response body before it is sent back to the client. + +NOTE: This filter can be configured only by using the Java DSL. + +The following listing shows how to modify a response body `GatewayFilter`: + +==== +[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(); +} +---- + +NOTE: If the response has no body, the `RewriteFilter` is passed `null`. `Mono.empty()` should be returned to assign a missing body in the response. +==== === The `PrefixPath` `GatewayFilter` Factory @@ -966,13 +1211,13 @@ spring: ---- ==== -This will prefix `/mypath` to the path of all matching requests. -So a request to `/hello` would be sent to `/mypath/hello`. +This prefixes `/mypath` to the path of all matching requests. +So a request to `/hello` is sent to `/mypath/hello`. === The `PreserveHostHeader` `GatewayFilter` Factory The `PreserveHostHeader` `GatewayFilter` factory has no parameters. -This filter sets a request attribute that the routing filter inspects to determine if the original host header should be sent, rather than the host header determined by the HTTP client. +This filter sets a request attribute that the routing filter inspects to determine if the original host header should be sent rather than the host header determined by the HTTP client. The following example configures a `PreserveHostHeader` `GatewayFilter`: .application.yml @@ -990,133 +1235,6 @@ spring: ---- ==== -=== The `RequestRateLimiter` `GatewayFilter` Factory - -The `RequestRateLimiter` `GatewayFilter` factory uses a `RateLimiter` implementation to determine if the current request is allowed to proceed. If it is not, a status of `HTTP 429 - Too Many Requests` (by default) is returned. - -This filter takes an optional `keyResolver` parameter and parameters specific to the rate limiter (described later in this section). - -`keyResolver` is a bean that implements the `KeyResolver` interface. -In configuration, reference the bean by name using SpEL. -`#{@myKeyResolver}` is a SpEL expression that references a bean named `myKeyResolver`. -The following listing shows the `KeyResolver` interface: - -.KeyResolver.java -==== -[source,java] ----- -public interface KeyResolver { - Mono resolve(ServerWebExchange exchange); -} ----- -==== - -The `KeyResolver` interface lets pluggable strategies derive the key for limiting requests. -In future milestone releases, there will be some `KeyResolver` implementations. - -The default implementation of `KeyResolver` is the `PrincipalNameKeyResolver`, which retrieves the `Principal` from the `ServerWebExchange` and calls `Principal.getName()`. - -By default, if the `KeyResolver` does not find a key, requests are denied. -You can adjust this behavior by setting the `spring.cloud.gateway.filter.request-rate-limiter.deny-empty-key` (`true` or `false`) and `spring.cloud.gateway.filter.request-rate-limiter.empty-key-status-code` properties. - -[NOTE] -===== -The `RequestRateLimiter` is not configurable with the "shortcut" notation. The following example below is _invalid_: - -.application.properties -==== ----- -# INVALID SHORTCUT CONFIGURATION -spring.cloud.gateway.routes[0].filters[0]=RequestRateLimiter=2, 2, #{@userkeyresolver} ----- -==== -===== - -==== The Redis `RateLimiter` - -The Redis implementation is based off of work done at https://stripe.com/blog/rate-limiters[Stripe]. -It requires the use of the `spring-boot-starter-data-redis-reactive` Spring Boot starter. - -The algorithm used is the https://en.wikipedia.org/wiki/Token_bucket[Token Bucket Algorithm]. - -The `redis-rate-limiter.replenishRate` property is how many requests per second you want a user to be allowed to do, without any dropped requests. -This is the rate at which the token bucket is filled. - -The `redis-rate-limiter.burstCapacity` property is the maximum number of requests a user is allowed to do in a single second. -This is the number of tokens the token bucket can hold. -Setting this value to zero blocks all requests. - -The `redis-rate-limiter.requestedTokens` property is how many tokens a request costs. -This is the number of tokens taken from the bucket for each request and defaults to `1`. - -A steady rate is accomplished by setting the same value in `replenishRate` and `burstCapacity`. -Temporary bursts can be allowed by setting `burstCapacity` higher than `replenishRate`. -In this case, the rate limiter needs to be allowed some time between bursts (according to `replenishRate`), as two consecutive bursts will result in dropped requests (`HTTP 429 - Too Many Requests`). -The following listing configures a `redis-rate-limiter`: - -Rate limits bellow `1 request/s` are accomplished by setting `replenishRate` to the wanted number of requests, `requestedTokens` to the timespan in seconds and `burstCapacity` to the product of `replenishRate` and `requestedTokens`, e.g. setting `replenishRate=1`, `requestedTokens=60` and `burstCapacity=60` will result in a limit of `1 request/min`. - -.application.yml -==== -[source,yaml] ----- -spring: - cloud: - gateway: - routes: - - id: requestratelimiter_route - uri: https://example.org - filters: - - name: RequestRateLimiter - args: - redis-rate-limiter.replenishRate: 10 - redis-rate-limiter.burstCapacity: 20 - redis-rate-limiter.requestedTokens: 1 - ----- -==== - -The following example configures a KeyResolver in Java: - -.Config.java -==== -[source,java] ----- -@Bean -KeyResolver userKeyResolver() { - return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("user")); -} ----- -==== - -This defines a request rate limit of 10 per user. A burst of 20 is allowed, but, in the next second, only 10 requests are available. -The `KeyResolver` is a simple one that gets the `user` request parameter (note that this is not recommended for production). - -You can also define a rate limiter as a bean that implements the `RateLimiter` interface. -In configuration, you can reference the bean by name using SpEL. -`#{@myRateLimiter}` is a SpEL expression that references a bean with named `myRateLimiter`. -The following listing defines a rate limiter that uses the `KeyResolver` defined in the previous listing: - -.application.yml -==== -[source,yaml] ----- -spring: - cloud: - gateway: - routes: - - id: requestratelimiter_route - uri: https://example.org - filters: - - name: RequestRateLimiter - args: - rate-limiter: "#{@myRateLimiter}" - key-resolver: "#{@userKeyResolver}" - ----- -==== - - === The `RedirectTo` `GatewayFilter` Factory The `RedirectTo` `GatewayFilter` factory takes two parameters, `status` and `url`. @@ -1143,6 +1261,7 @@ spring: This will send a status 302 with a `Location:https://acme.org` header to perform a redirect. + === `RemoveJsonAttributesResponseBody` `GatewayFilter` Factory The `RemoveJsonAttributesResponseBody` `GatewayFilter` factory takes a collection of `attribute names` to search for, an optional last parameter from the list can be a boolean to remove the attributes just at root level (that's the default value if not present at the end of the parameter configuration, `false`) or recursively (`true`). @@ -1211,7 +1330,31 @@ spring: This removes the `X-Request-Foo` header before it is sent downstream. -=== `RemoveResponseHeader` `GatewayFilter` Factory +=== The `RemoveRequestParameter` `GatewayFilter` Factory + +The `RemoveRequestParameter` `GatewayFilter` factory takes a `name` parameter. +It is the name of the query parameter to be removed. +The following example configures a `RemoveRequestParameter` `GatewayFilter`: + +.application.yml +==== +[source,yaml] +---- +spring: + cloud: + gateway: + routes: + - id: removerequestparameter_route + uri: https://example.org + filters: + - RemoveRequestParameter=red +---- +==== + +This will remove the `red` parameter before it is sent downstream. + + +=== The `RemoveResponseHeader` `GatewayFilter` Factory The `RemoveResponseHeader` `GatewayFilter` factory takes a `name` parameter. It is the name of the header to be removed. @@ -1237,33 +1380,11 @@ This will remove the `X-Response-Foo` header from the response before it is retu To remove any kind of sensitive header, you should configure this filter for any routes for which you may want to do so. In addition, you can configure this filter once by using `spring.cloud.gateway.default-filters` and have it applied to all routes. -=== The `RemoveRequestParameter` `GatewayFilter` Factory -The `RemoveRequestParameter` `GatewayFilter` factory takes a `name` parameter. -It is the name of the query parameter to be removed. -The following example configures a `RemoveRequestParameter` `GatewayFilter`: - -.application.yml -==== -[source,yaml] ----- -spring: - cloud: - gateway: - routes: - - id: removerequestparameter_route - uri: https://example.org - filters: - - RemoveRequestParameter=red ----- -==== - -This will remove the `red` parameter before it is sent downstream. - -=== `RequestHeaderSize` `GatewayFilter` Factory +=== The `RequestHeaderSize` `GatewayFilter` Factory The `RequestHeaderSize` `GatewayFilter` factory takes `maxSize` and `errorHeaderName` parameters. -The `maxSize` parameter is the maximum data size allowed of the request header (incuding key and value). The `errorHeaderName` parameter sets the name of the response header containing an error message, by default it is "errorMessage". +The `maxSize` parameter is the maximum data size allowed by the request header (including key and value). The `errorHeaderName` parameter sets the name of the response header containing an error message, by default it is "errorMessage". The following listing configures a `RequestHeaderSize` `GatewayFilter`: .application.yml @@ -1283,6 +1404,170 @@ spring: This will send a status 431 if size of any request header is greater than 1000 Bytes. +=== The `RequestRateLimiter` `GatewayFilter` Factory + +The `RequestRateLimiter` `GatewayFilter` factory uses a `RateLimiter` implementation to determine if the current request is allowed to proceed. If it is not, a status of `HTTP 429 - Too Many Requests` (by default) is returned. + +This filter takes an optional `keyResolver` parameter and parameters specific to the rate limiter (described <>). + +`keyResolver` is a bean that implements the `KeyResolver` interface. +In configuration, reference the bean by name using SpEL. +`#{@myKeyResolver}` is a SpEL expression that references a bean named `myKeyResolver`. +The following listing shows the `KeyResolver` interface: + +.KeyResolver.java +==== +[source,java] +---- +public interface KeyResolver { + Mono resolve(ServerWebExchange exchange); +} +---- +==== + +[[key-resolver-section]] +The `KeyResolver` interface lets pluggable strategies derive the key for limiting requests. +In future milestone releases, there will be some `KeyResolver` implementations. + +The default implementation of `KeyResolver` is the `PrincipalNameKeyResolver`, which retrieves the `Principal` from the `ServerWebExchange` and calls `Principal.getName()`. + +By default, if the `KeyResolver` does not find a key, requests are denied. +You can adjust this behavior by setting the `spring.cloud.gateway.filter.request-rate-limiter.deny-empty-key` (`true` or `false`) and `spring.cloud.gateway.filter.request-rate-limiter.empty-key-status-code` properties. + +[NOTE] +===== +The `RequestRateLimiter` is not configurable with the "shortcut" notation. The following example below is _invalid_: + +.application.properties +==== +---- +# INVALID SHORTCUT CONFIGURATION +spring.cloud.gateway.routes[0].filters[0]=RequestRateLimiter=2, 2, #{@userkeyresolver} +---- +==== +===== + +==== The Redis `RateLimiter` + +The Redis implementation is based on work done at https://stripe.com/blog/rate-limiters[Stripe]. +It requires the use of the `spring-boot-starter-data-redis-reactive` Spring Boot starter. + +The algorithm used is the https://en.wikipedia.org/wiki/Token_bucket[Token Bucket Algorithm]. + +The `redis-rate-limiter.replenishRate` property defines how many requests per second to allow (without any dropped requests). +This is the rate at which the token bucket is filled. + +The `redis-rate-limiter.burstCapacity` property is the maximum number of requests a user is allowed in a single second (without any dropped requests). +This is the number of tokens the token bucket can hold. +Setting this value to zero blocks all requests. + +The `redis-rate-limiter.requestedTokens` property is how many tokens a request costs. +This is the number of tokens taken from the bucket for each request and defaults to `1`. + +A steady rate is accomplished by setting the same value in `replenishRate` and `burstCapacity`. +Temporary bursts can be allowed by setting `burstCapacity` higher than `replenishRate`. +In this case, the rate limiter needs to be allowed some time between bursts (according to `replenishRate`), as two consecutive bursts results in dropped requests (`HTTP 429 - Too Many Requests`). +The following listing configures a `redis-rate-limiter`: + +Rate limits below `1 request/s` are accomplished by setting `replenishRate` to the wanted number of requests, `requestedTokens` to the timespan in seconds, and `burstCapacity` to the product of `replenishRate` and `requestedTokens`. +For example, setting `replenishRate=1`, `requestedTokens=60`, and `burstCapacity=60` results in a limit of `1 request/min`. +.application.yml +==== +[source,yaml] +---- +spring: + cloud: + gateway: + routes: + - id: requestratelimiter_route + uri: https://example.org + filters: + - name: RequestRateLimiter + args: + redis-rate-limiter.replenishRate: 10 + redis-rate-limiter.burstCapacity: 20 + redis-rate-limiter.requestedTokens: 1 + +---- +==== + +The following example configures a `KeyResolver` in Java: + +.Config.java +==== +[source,java] +---- +@Bean +KeyResolver userKeyResolver() { + return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("user")); +} +---- +==== + +This defines a request rate limit of 10 per user. A burst of 20 is allowed, but, in the next second, only 10 requests are available. +The `KeyResolver` is a simple one that gets the `user` request parameter +NOTE: This is not recommended for production + +You can also define a rate limiter as a bean that implements the `RateLimiter` interface. +In configuration, you can reference the bean by name using SpEL. +`#{@myRateLimiter}` is a SpEL expression that references a bean with named `myRateLimiter`. +The following listing defines a rate limiter that uses the `KeyResolver` defined in the previous listing: + +.application.yml +==== +[source,yaml] +---- +spring: + cloud: + gateway: + routes: + - id: requestratelimiter_route + uri: https://example.org + filters: + - name: RequestRateLimiter + args: + rate-limiter: "#{@myRateLimiter}" + key-resolver: "#{@userKeyResolver}" + +---- +==== + +=== The `RewriteLocationResponseHeader` `GatewayFilter` Factory + +The `RewriteLocationResponseHeader` `GatewayFilter` factory modifies the value of the `Location` response header, usually to get rid of backend-specific details. +It takes the `stripVersionMode`, `locationHeaderName`, `hostValue`, and `protocolsRegex` parameters. +The following listing configures a `RewriteLocationResponseHeader` `GatewayFilter`: + +.application.yml +==== +[source,yaml] +---- +spring: + cloud: + gateway: + routes: + - id: rewritelocationresponseheader_route + uri: http://example.org + filters: + - RewriteLocationResponseHeader=AS_IN_REQUEST, Location, , +---- +==== + +For example, for a request of `POST https://api.example.com/some/object/name`, the `Location` response header value of `https://object-service.prod.example.net/v2/some/object/id` is rewritten as `https://api.example.com/some/object/id`. + +The `stripVersionMode` parameter has the following possible values: `NEVER_STRIP`, `AS_IN_REQUEST` (default), and `ALWAYS_STRIP`. + +* `NEVER_STRIP`: The version is not stripped, even if the original request path contains no version. +* `AS_IN_REQUEST`: The version is stripped only if the original request path contains no version. +* `ALWAYS_STRIP`: The version is always stripped, even if the original request path contains version. + +The `hostValue` parameter, if provided, is used to replace the `host:port` portion of the response `Location` header. +If it is not provided, the value of the `Host` request header is used. + +The `protocolsRegex` parameter must be a valid regex `String`, against which the protocol name is matched. +If it is not matched, the filter does nothing. +The default is `http|https|ftp|ftps`. + === The `RewritePath` `GatewayFilter` Factory The `RewritePath` `GatewayFilter` factory takes a path `regexp` parameter and a `replacement` parameter. @@ -1308,42 +1593,6 @@ spring: For a request path of `/red/blue`, this sets the path to `/blue` before making the downstream request. Note that the `$` should be replaced with `$\` because of the YAML specification. -=== `RewriteLocationResponseHeader` `GatewayFilter` Factory - -The `RewriteLocationResponseHeader` `GatewayFilter` factory modifies the value of the `Location` response header, usually to get rid of backend-specific details. -It takes `stripVersionMode`, `locationHeaderName`, `hostValue`, and `protocolsRegex` parameters. -The following listing configures a `RewriteLocationResponseHeader` `GatewayFilter`: - -.application.yml -==== -[source,yaml] ----- -spring: - cloud: - gateway: - routes: - - id: rewritelocationresponseheader_route - uri: http://example.org - filters: - - RewriteLocationResponseHeader=AS_IN_REQUEST, Location, , ----- -==== - -For example, for a request of `POST https://api.example.com/some/object/name`, the `Location` response header value of `https://object-service.prod.example.net/v2/some/object/id` is rewritten as `https://api.example.com/some/object/id`. - -The `stripVersionMode` parameter has the following possible values: `NEVER_STRIP`, `AS_IN_REQUEST` (default), and `ALWAYS_STRIP`. - - * `NEVER_STRIP`: The version is not stripped, even if the original request path contains no version. - * `AS_IN_REQUEST` The version is stripped only if the original request path contains no version. - * `ALWAYS_STRIP` The version is always stripped, even if the original request path contains version. - -The `hostValue` parameter, if provided, is used to replace the `host:port` portion of the response `Location` header. -If it is not provided, the value of the `Host` request header is used. - -The `protocolsRegex` parameter must be a valid regex `String`, against which the protocol name is matched. -If it is not matched, the filter does nothing. -The default is `http|https|ftp|ftps`. - === The `RewriteResponseHeader` `GatewayFilter` Factory The `RewriteResponseHeader` `GatewayFilter` factory takes `name`, `regexp`, and `replacement` parameters. @@ -1615,7 +1864,7 @@ spring: When a request is made through the gateway to `/name/blue/red`, the request made to `nameservice` looks like `https://nameservice/red`. -=== The Retry `GatewayFilter` Factory +=== The `Retry` `GatewayFilter` Factory The `Retry` `GatewayFilter` factory supports the following parameters: @@ -1769,126 +2018,8 @@ spring: The `SetRequestHostHeader` `GatewayFilter` factory replaces the value of the host header with `example.org`. -=== Modify a Request Body `GatewayFilter` Factory -You can use the `ModifyRequestBody` filter filter to modify the request body before it is sent downstream by the gateway. - -NOTE: This filter can be configured only by using the Java DSL. - -The following listing shows how to modify a request body `GatewayFilter`: - -==== -[source,java] ----- -@Bean -public RouteLocator routes(RouteLocatorBuilder builder) { - return builder.routes() - .route("rewrite_request_obj", r -> r.host("*.rewriterequestobj.org") - .filters(f -> f.prefixPath("/httpbin") - .modifyRequestBody(String.class, Hello.class, MediaType.APPLICATION_JSON_VALUE, - (exchange, s) -> return Mono.just(new Hello(s.toUpperCase())))).uri(uri)) - .build(); -} - -static class Hello { - String message; - - public Hello() { } - - public Hello(String message) { - this.message = message; - } - - public String getMessage() { - return message; - } - - public void setMessage(String message) { - this.message = message; - } -} ----- - -NOTE: if the request has no body, the `RewriteFilter` will be passed `null`. `Mono.empty()` should be returned to assign a missing body in the request. - -==== - -=== Local Response Cache `GatewayFilter` Factory - -This filter allows to cache response body and headers to follow the next rules: - -* It can only cache bodyless GET requests -* It only caches the response as long has one of the following status codes: HTTP 200 (OK), HTTP 206 (Partial Content) and HTTP 301 (Moved Permanently). -Response data will not be cached if `Cache-Control` header doesn't allow it (`no-store` present in the request, `no-store` or `private` present in the response). -* If the response is already cached and a new request is performed with no-cache value in `Cache-Control` header, it will return a bodyless response with 304 (Not Modified). - -Take into account that this filter to configure local response cache per route only will be available if the local response global cache is enabled. - -It accepts the first parameter to override the maximum size of the cache to evict entries for this route, it takes size format in KB, MB and GB; and a second parameter to override the time to expire a cache entry expressed in s for seconds, m for minutes and h for hours. - -The following listing shows how to add local response cache `GatewayFilter`: - -==== -[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") - .localResponseCache(Duration.ofMinutes(30), "500MB") - ).uri(uri)) - .build(); -} ----- - -or this - -.application.yaml -[source,yaml] ----- -spring: - cloud: - gateway: - routes: - - id: resource - uri: http://localhost:9000 - predicates: - - Path=/resource - filters: - - LocalResponseCache=30m,500MB ----- - -NOTE: This filter also implements the automatic calculation of the max-age value in the HTTP Cache-Control header. -Only if "max-age" is present on the original response the value will be rewritten with the number of seconds set in the timeToLive configuration parameter; and in consecutive calls this value will be recalculated with the number of seconds left until the response expires. -==== - -=== Modify a Response Body `GatewayFilter` Factory - -You can use the `ModifyResponseBody` filter to modify the response body before it is sent back to the client. - -NOTE: This filter can be configured only by using the Java DSL. - -The following listing shows how to modify a response body `GatewayFilter`: - -==== -[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(); -} ----- - -NOTE: if the response has no body, the `RewriteFilter` will be passed `null`. `Mono.empty()` should be returned to assign a missing body in the response. -==== - -=== Token Relay `GatewayFilter` Factory +=== The `TokenRelay` `GatewayFilter` Factory A Token Relay is where an OAuth2 consumer acts as a Client and forwards the incoming token to outgoing resource requests. The @@ -1896,8 +2027,7 @@ consumer can be a pure Client (like an SSO application) or a Resource Server. Spring Cloud Gateway can forward OAuth2 access tokens downstream to the services -it is proxying. To add this functionlity to gateway you need to add the -`TokenRelayGatewayFilterFactory` like this: +it is proxying. To add this functionality to the gateway, you need to add the `TokenRelayGatewayFilterFactory` like this: .App.java [source,java] @@ -1952,131 +2082,6 @@ uses an in-memory data store. You will need to provide your own implementation if you need a more robust solution. -=== The `CacheRequestBody` `GatewayFilter` Factory -There are certain situation need to read body.Since the request body stream can only be read once, we need to cache the request body. -You can use the `CacheRequestBody` filter to cache request body before it send to the downstream and get body from exchagne attribute. - - -The following listing shows how to cache the request body `GatewayFilter`: - -==== -[source,java] ----- -@Bean -public RouteLocator routes(RouteLocatorBuilder builder) { - return builder.routes() - .route("cache_request_body_route", r -> r.path("/downstream/**") - .filters(f -> f.prefixPath("/httpbin") - .cacheRequestBody(String.class).uri(uri)) - .build(); -} ----- -==== - - -.application.yml -==== -[source,yaml] ----- -spring: - cloud: - gateway: - routes: - - id: cache_request_body_route - uri: lb://downstream - predicates: - - Path=/downstream/** - filters: - - name: CacheRequestBody - args: - bodyClass: java.lang.String ----- -==== - -`CacheRequestBody` will extract request body and conver it to body class (such as `java.lang.String`, defined in the preceding example). then places it in the `ServerWebExchange.getAttributes()` with a key defined in `ServerWebExchangeUtils.CACHED_REQUEST_BODY_ATTR`. - -NOTE: This filter only works with http request (including https). - -=== The `JsonToGrpc` `GatewayFilter` Factory - -The JSONToGRPCFilter GatewayFilter Factory converts a JSON payload to a gRPC request. - -The filter takes the following arguments: - -* `protoDescriptor` Proto descriptor file. - -This file can be generated using `protoc` specifying the `--descriptor_set_out` flag: - -[source,bash] ----- -protoc --proto_path=src/main/resources/proto/ \ ---descriptor_set_out=src/main/resources/proto/hello.pb \ -src/main/resources/proto/hello.proto ----- - -* `protoFile` Proto definition file. - -* `service` Fully qualified name of the service that will handle the request. - -* `method` Method name in the service that will handle the request. - -NOTE: `streaming` is not supported. - - -*application.yml.* - -[source,java] ----- -@Bean -public RouteLocator routes(RouteLocatorBuilder builder) { - return builder.routes() - .route("json-grpc", r -> r.path("/json/hello").filters(f -> { - String protoDescriptor = "file:src/main/proto/hello.pb"; - String protoFile = "file:src/main/proto/hello.proto"; - String service = "HelloService"; - String method = "hello"; - return f.jsonToGRPC(protoDescriptor, protoFile, service, method); - }).uri(uri)) ----- - -[source,yaml] ----- -spring: - cloud: - gateway: - routes: - - id: json-grpc - uri: https://localhost:6565/testhello - predicates: - - Path=/json/** - filters: - - name: JsonToGrpc - args: - protoDescriptor: file:proto/hello.pb - protoFile: file:proto/hello.proto - service: com.example.grpcserver.hello.HelloService - method: hello - ----- - -When a request is made through the gateway to `/json/hello` the request will be transformed using the definition provided in `hello.proto`, sent to `com.example.grpcserver.hello.HelloService/hello`, and transform the response back to JSON. - -By default, it will create a `NettyChannel` using the default `TrustManagerFactory`. However, this `TrustManager` can be customized by creating a bean of type `GrpcSslConfigurer`: - -[source,java] ----- - -@Configuration -public class GRPCLocalConfiguration { - @Bean - public GRPCSSLContext sslContext() { - TrustManager trustManager = trustAllCerts(); - return new GRPCSSLContext(trustManager); - } -} ----- - - === Default Filters To add a filter and apply it to all routes, you can use `spring.cloud.gateway.default-filters`. @@ -2138,6 +2143,43 @@ public class CustomGlobalFilter implements GlobalFilter, Ordered { ---- ==== +=== The Gateway Metrics Filter + +To enable gateway metrics, add `spring-boot-starter-actuator` as a project dependency. Then, by default, the gateway metrics filter runs as long as the `spring.cloud.gateway.metrics.enabled` property is not set to `false`. +This filter adds a timer metric named `spring.cloud.gateway.requests` with the following tags: + +* `routeId`: The route ID. +* `routeUri`: The URI to which the API is routed. +* `outcome`: The outcome, as classified by link:https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/HttpStatus.Series.html[HttpStatus.Series]. +* `status`: The HTTP status of the request returned to the client. +* `httpStatusCode`: The HTTP Status of the request returned to the client. +* `httpMethod`: The HTTP method used for the request. + +In addition, through the `spring.cloud.gateway.metrics.tags.path.enabled` property (by default, `false`), you can activate an extra metric with the path tag: + +* `path`: The path of the request. + +These metrics are then available to be scraped from `/actuator/metrics/spring.cloud.gateway.requests` and can be easily integrated with Prometheus to create a link:images/gateway-grafana-dashboard.jpeg[Grafana] link:gateway-grafana-dashboard.json[dashboard]. + +NOTE: To enable the prometheus endpoint, add `micrometer-registry-prometheus` as a project dependency. + +[[local-cache-response-global-filter]] +=== The Local Response Cache Filter + +The `LocalResponseCache` runs if its associated property is enabled (`spring.cloud.gateway.filter.local-response-cache.enabled`) and activates a local cache for all responses that meet the following criteria: +- The request is a bodiless GET. +- The response has one of the following status codes: HTTP 200 (OK), HTTP 206 (Partial Content), or HTTP 301 (Moved Permanently). +- The HTTP `Cache-Control` header allows caching (that means it does not have any of the following values: `no-store` present in the request and `no-store` or `private` present in the response). + +It accepts two configuration parameters: +- `spring.cloud.gateway.filter.local-response-cache.size`: Sets the maximum size of the cache to evict entries for this route (in KB, MB and GB). +- `spring.cloud.gateway.filter.local-response-cache.timeToLive` Sets the time to expire a cache entry (expressed in s for seconds, m for minutes, and h for hours). +If none of these parameters are configured but the global filter is enabled, by default, it configures 5 minutes of time to live for the cached response. + +This filter also implements the automatic calculation of the `max-age value in the HTTP `Cache-Control` header. +If `max-age` is present on the original response, the value is rewritten with the number of seconds set in the `timeToLive` configuration parameter. +In subsequent calls, this value is recalculated with the number of seconds left until the response expires. + === Forward Routing Filter The `ForwardRoutingFilter` looks for a URI in the exchange attribute `ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR`. @@ -2145,6 +2187,19 @@ If the URL has a `forward` scheme (such as `forward:///localendpoint`), it uses The path part of the request URL is overridden with the path in the forward URL. The unmodified original URL is appended to the list in the `ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR` attribute. +=== The Netty Routing Filter + +The Netty routing filter runs if the URL located in the `ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR` exchange attribute has a `http` or `https` scheme. +It uses the Netty `HttpClient` to make the downstream proxy request. +The response is put in the `ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR` exchange attribute for use in a later filter. +(There is also an experimental `WebClientHttpRoutingFilter` that performs the same function but does not require Netty.) + +=== The Netty Write Response Filter + +The `NettyWriteResponseFilter` runs if there is a Netty `HttpClientResponse` in the `ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR` exchange attribute. +It runs after all other filters have completed and writes the proxy response back to the gateway client response. +(There is also an experimental `WebClientWriteResponseFilter` that performs the same function but does not require Netty.) + [[reactive-loadbalancer-client-filter]] === The `ReactiveLoadBalancerClientFilter` @@ -2181,19 +2236,6 @@ However, if `GATEWAY_SCHEME_PREFIX_ATTR` is specified for the route in the Gatew TIP: Gateway supports all the LoadBalancer features. You can read more about them in the https://docs.spring.io/spring-cloud-commons/docs/current/reference/html/#spring-cloud-loadbalancer[Spring Cloud Commons documentation]. -=== The Netty Routing Filter - -The Netty routing filter runs if the URL located in the `ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR` exchange attribute has a `http` or `https` scheme. -It uses the Netty `HttpClient` to make the downstream proxy request. -The response is put in the `ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR` exchange attribute for use in a later filter. -(There is also an experimental `WebClientHttpRoutingFilter` that performs the same function but does not require Netty.) - -=== The Netty Write Response Filter - -The `NettyWriteResponseFilter` runs if there is a Netty `HttpClientResponse` in the `ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR` exchange attribute. -It runs after all other filters have completed and writes the proxy response back to the gateway client response. -(There is also an experimental `WebClientWriteResponseFilter` that performs the same function but does not require Netty.) - === The `RouteToRequestUrl` Filter If there is a `Route` object in the `ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR` exchange attribute, the `RouteToRequestUrlFilter` runs. @@ -2233,25 +2275,6 @@ spring: ---- ==== -=== The Gateway Metrics Filter - -To enable gateway metrics, add spring-boot-starter-actuator as a project dependency. Then, by default, the gateway metrics filter runs as long as the property `spring.cloud.gateway.metrics.enabled` is not set to `false`. This filter adds a timer metric named `spring.cloud.gateway.requests` with the following tags: - -* `routeId`: The route ID. -* `routeUri`: The URI to which the API is routed. -* `outcome`: The outcome, as classified by link:https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/HttpStatus.Series.html[HttpStatus.Series]. -* `status`: The HTTP status of the request returned to the client. -* `httpStatusCode`: The HTTP Status of the request returned to the client. -* `httpMethod`: The HTTP method used for the request. - -In addition, through the property `spring.cloud.gateway.metrics.tags.path.enabled` (by default, set to false), you can activate an extra metric with the tag: - -* `path`: Path of the request. - -These metrics are then available to be scraped from `/actuator/metrics/spring.cloud.gateway.requests` and can be easily integrated with Prometheus to create a link:images/gateway-grafana-dashboard.jpeg[Grafana] link:gateway-grafana-dashboard.json[dashboard]. - -NOTE: To enable the prometheus endpoint, add `micrometer-registry-prometheus` as a project dependency. - === Marking An Exchange As Routed After the gateway has routed a `ServerWebExchange`, it marks that exchange as "`routed`" by adding `gatewayAlreadyRouted` diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheProperties.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheProperties.java index ea3efb8c..96e76da1 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheProperties.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheProperties.java @@ -66,7 +66,7 @@ public class LocalResponseCacheProperties { @Override public String toString() { - return "LocalResponseCacheProperties{" + "size='" + getSize() + '\'' + ", timeToLive=" + getTimeToLive() + '}'; + return "LocalResponseCacheProperties{" + "timeToLive=" + getTimeToLive() + '\'' + ", size='" + getSize() + '}'; } }