diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index 8dca60c2..a95ff91c 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -92,7 +92,7 @@ *** xref:spring-cloud-gateway-server-mvc/filters/removerequestparameter.adoc[] *** xref:spring-cloud-gateway-server-mvc/filters/removeresponseheader.adoc[] *** xref:spring-cloud-gateway-server-mvc/filters/requestheadersize.adoc[] -*** xref:spring-cloud-gateway-server-mvc/filters/requestratelimiter.adoc[] +*** xref:spring-cloud-gateway-server-mvc/filters/ratelimiter.adoc[] *** xref:spring-cloud-gateway-server-mvc/filters/rewritelocationresponseheader.adoc[] *** xref:spring-cloud-gateway-server-mvc/filters/rewritepath.adoc[] //*** xref:spring-cloud-gateway-server-mvc/filters/rewriterequestparameter.adoc[] diff --git a/docs/modules/ROOT/pages/spring-cloud-gateway-server-mvc/filters/ratelimiter.adoc b/docs/modules/ROOT/pages/spring-cloud-gateway-server-mvc/filters/ratelimiter.adoc new file mode 100644 index 00000000..cfde996c --- /dev/null +++ b/docs/modules/ROOT/pages/spring-cloud-gateway-server-mvc/filters/ratelimiter.adoc @@ -0,0 +1,69 @@ +[[ratelimiter-filter]] += RateLimiter Filter + +The RateLimiter Filter use https://bucket4j.com/[Bucket4j] 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. + +Please review https://bucket4j.com/8.7.0/toc.html#concepts[Bucket4j Concepts] prior to reading this documentation. + +The algorithm used by Bucket4j is the https://en.wikipedia.org/wiki/Token_bucket[Token Bucket Algorithm]. + +The filter takes a `keyResolver` parameter and other Bucket4j configuration parameters. The key resolver is a `java.util.Function`. This allows the user to extract any information out of the request to use as a key in the configured https://github.com/bucket4j/bucket4j#bucket4j-distributed-features[Bucket4j distribution] mechanism. A common key would be the `Principal` retrieved from the `ServerRequest`. + +By default, if the key resolver does not find a key, requests are denied with the `FORBIDDEN` status. + +NOTE: Currently, the only way to configure key resolvers is through the Java DSL and not through external properties. + +== Bucket4j Distributed Configuration + +A bean of type `io.github.bucket4j.distributed.proxy.AsyncProxyManager`. To do this, use the `ProxyManager.asAsync()` method. + +.RateLimiterConfiguration.java +[source,java] +---- +import com.github.benmanes.caffeine.cache.Caffeine; +import io.github.bucket4j.caffeine.CaffeineProxyManager; + +@Configuration +class RateLimiterConfiguration { + + @Bean + public AsyncProxyManager caffeineProxyManager() { + Caffeine builder = (Caffeine) Caffeine.newBuilder().maximumSize(100); + return new CaffeineProxyManager<>(builder, Duration.ofMinutes(1)).asAsync(); + } +} +---- + +The above configures an `AsyncProxyManager` using `Caffeine`, a local in-memory cache, useful for testing. + +== Configuring Buckets + +By default, the Bucket is configured using a configured `capacity` and `period`. Capacity is how many tokens the bucket has. The period is a `java.util.Duration` that defines how long for the tokens available in the bucket to be regenerated. + +Other configuration items are the `statusCode` returned when the request is denied. By default it is 429, TO_MANY_REQUESTS. The `tokens` item defines how many tokens are used for each request and defaults to 1. The `headerName` item is the name of the header that contains the number of remaining tokens, this defaults to `X-RateLimit-Remaining`. The `timeout` option defines a `Duration` for the distributed bucket to return an answer and is not set by default. + +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.Bucket4jFilterFunctions.rateLimit; +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 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(); + } +} +---- + +This configures the rate limiting with a bucket capacity of 100 tokens per minute. The key resolver gets the principle name from the Servlet request. diff --git a/docs/modules/ROOT/pages/spring-cloud-gateway-server-mvc/filters/requestratelimiter.adoc b/docs/modules/ROOT/pages/spring-cloud-gateway-server-mvc/filters/requestratelimiter.adoc deleted file mode 100644 index 6c05a083..00000000 --- a/docs/modules/ROOT/pages/spring-cloud-gateway-server-mvc/filters/requestratelimiter.adoc +++ /dev/null @@ -1,120 +0,0 @@ -[[requestratelimiter-filter]] -= `RequestRateLimiter` Filter - -The `RequestRateLimiter` filter 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 xref:spring-cloud-gateway/gatewayfilter-factories/requestratelimiter-factory.adoc#key-resolver-section[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); -} ----- - -[[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} ----- -===== - -[[redis-ratelimiter]] -== 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}" - ----- -