Class RateLimitGuard
RestGuard that throttles requests on a configurable per-request key.
On each request the guard atomically attempts to acquire one token from a per-key bucket. When tokens are
available the request proceeds and three advisory response headers are populated
(X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset). When the bucket is
empty a TooManyRequests exception is thrown with a Retry-After header set to the integer
number of seconds until the next token will be available.
Example usage
Keying
The default key is the request's remote address. Set
xForwardedForAware(true) to honor the first hop of the
X-Forwarded-For header — required when running behind a trusted reverse proxy.
Trusted-proxy assumption. When xForwardedForAware is enabled without a trusted proxy in front
of the application, attackers can spoof the X-Forwarded-For header and defeat the IP-based key. Only
opt in when the request edge enforces the header.
Storage
Bucket state is held by a pluggable RateLimitGuard.Storage SPI. The default RateLimitGuard.Storage.inMemory() implementation
uses a ConcurrentHashMap with an LRU-style eviction policy (capped at 100 000 keys by default), suitable
for single-pod deployments. Distributed deployments should provide a substrate-backed implementation
(e.g. Redis) so per-pod buckets do not split the shared rate envelope.
Probe paths
Built-in probe paths (/healthz, /readyz, /livez) are exempted from throttling by
default so a misconfigured limit cannot mask a healthy pod from the orchestrator. Override via
RateLimitGuard.Builder.exemptPaths(String...).
Time source
Refill math uses System.nanoTime(). This is monotonic and safe across wall-clock jumps but does not
map to a calendar instant, which slightly complicates debugging when the bucket is in an unexpected state. The
nanosecond source is pluggable on RateLimitGuard.InMemoryStorage (package-private constructor overload) so tests can
pin the refill window with a deterministic clock instead of racing real wall-clock time; production code always
goes through the single-arg constructor, which defaults to System::nanoTime. The wall-clock
RateLimitGuard.BucketState.lastRequest() instant reported by RateLimitGuard.Storage.snapshot() is read from a separate
Clock (default Clock.systemUTC()), likewise overridable through a package-private constructor.
See Also:
- Since:
- 10.0.0
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic final recordPoint-in-time view of a single token bucket as exposed byRateLimitGuard.Storage.snapshot().static classBuilder forRateLimitGuard.static final recordSnapshot of rate-limit state passed to theonLimitExceededcallback and used to populate advisory response headers.static interfaceSPI for storing per-key token-bucket state. -
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final StringResponse header name for the advisory ceiling (tokens per refill window).static final StringResponse header name for the advisory tokens remaining after this call.static final StringResponse header name for the advisory seconds until the bucket is fully refilled.static final StringStandard HTTPRetry-Afterheader name. -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionstatic RateLimitGuard.Buildercreate()Creates a new builder.intReturns the bucket capacity (the maximum number of tokens a bucket can hold).Returns the set of request paths that bypass throttling.doubleReturns the steady-state refill rate in permits per second.Returns the bucket-state storage backend in use by this guard.booleanguard(RestRequest req, RestResponse res) Checks the current HTTP request and throws aBasicHttpExceptionif the guard does not permit the request.booleanReturnstrue if the specified request can pass through this guard.booleanReturns whetherX-Forwarded-For-aware key resolution is enabled.snapshot()Convenience accessor forRateLimitGuard.Storage.snapshot()on the underlying storage backend.
-
Field Details
-
HEADER_LIMIT
Response header name for the advisory ceiling (tokens per refill window).- See Also:
-
HEADER_REMAINING
Response header name for the advisory tokens remaining after this call.- See Also:
-
HEADER_RESET
Response header name for the advisory seconds until the bucket is fully refilled.- See Also:
-
HEADER_RETRY_AFTER
Standard HTTPRetry-Afterheader name.- See Also:
-
-
Constructor Details
-
RateLimitGuard
Constructor.- Parameters:
b- The builder configuring this guard. Must not benull .
-
-
Method Details
-
create
Creates a new builder.- Returns:
- A new builder.
-
guard
Description copied from class:RestGuardChecks the current HTTP request and throws aBasicHttpExceptionif the guard does not permit the request.By default, throws an
SC_FORBIDDEN exception ifRestGuard.isRequestAllowed(RestRequest)returnsfalse .Subclasses are free to override this method to tailor the behavior of how to handle unauthorized requests.
-
isRequestAllowed
Description copied from class:RestGuardReturnstrue if the specified request can pass through this guard.- Specified by:
isRequestAllowedin classRestGuard- Parameters:
req- The servlet request.- Returns:
true if the specified request can pass through this guard.
-
getCapacity
Returns the bucket capacity (the maximum number of tokens a bucket can hold).- Returns:
- The bucket capacity.
- Since:
- 10.0.0
-
getPermitsPerSecond
Returns the steady-state refill rate in permits per second.- Returns:
- The refill rate.
- Since:
- 10.0.0
-
isXForwardedForAware
Returns whetherX-Forwarded-For-aware key resolution is enabled.- Returns:
true ifX-Forwarded-Forresolution is enabled.- Since:
- 10.0.0
-
getExemptPaths
Returns the set of request paths that bypass throttling.- Returns:
- The exempt paths. Never
null . - Since:
- 10.0.0
-
getStorage
Returns the bucket-state storage backend in use by this guard.- Returns:
- The storage backend. Never
null . - Since:
- 10.0.0
-
snapshot
Convenience accessor forRateLimitGuard.Storage.snapshot()on the underlying storage backend.Returns an empty map when the configured
RateLimitGuard.Storagedoes not overrideRateLimitGuard.Storage.snapshot()(the default behavior for storages — e.g. Redis-backed — that can't cheaply enumerate every bucket).- Returns:
- A point-in-time map of per-key bucket state. Never
null . - Since:
- 10.0.0
-