Update ratelimiter.adoc for Bucket4j rate limiter.

This commit is contained in:
sgibb
2023-12-05 16:16:41 -05:00
parent 26d8fcbb55
commit b13a82850d
3 changed files with 70 additions and 121 deletions

View File

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

View File

@@ -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<ServerRequest, String>`. 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<String> caffeineProxyManager() {
Caffeine<String, RemoteBucketState> 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<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();
}
}
----
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.

View File

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