Class RateLimitGuard

java.lang.Object
org.apache.juneau.rest.server.guard.RestGuard
org.apache.juneau.rest.server.guard.RateLimitGuard

public class RateLimitGuard extends RestGuard
Token-bucket 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

@Rest(path="/api") public class ApiResource extends BasicRestServlet { // Per-IP rate limit, 100 req/min, 200-burst. @Bean(name="guards") public RestGuardList rateLimits(BeanStore bs) { return RestGuardList.create(bs) .append(RateLimitGuard.create() .permitsPerMinute(100) .burst(200) .build()) .build(); } }

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
  • Field Details

  • Constructor Details

  • Method Details

    • create

      public static RateLimitGuard.Builder create()
      Creates a new builder.
      Returns:
      A new builder.
    • guard

      public boolean guard(RestRequest req, RestResponse res)
      Description copied from class: RestGuard
      Checks the current HTTP request and throws a BasicHttpException if the guard does not permit the request.

      By default, throws an SC_FORBIDDEN exception if RestGuard.isRequestAllowed(RestRequest) returns false.

      Subclasses are free to override this method to tailor the behavior of how to handle unauthorized requests.

      Overrides:
      guard in class RestGuard
      Parameters:
      req - The servlet request.
      res - The servlet response.
      Returns:
      true if request can proceed. Specify false if you're doing something like a redirection to a login page.
    • isRequestAllowed

      public boolean isRequestAllowed(RestRequest req)
      Description copied from class: RestGuard
      Returns true if the specified request can pass through this guard.
      Specified by:
      isRequestAllowed in class RestGuard
      Parameters:
      req - The servlet request.
      Returns:
      true if the specified request can pass through this guard.
    • getCapacity

      public int getCapacity()
      Returns the bucket capacity (the maximum number of tokens a bucket can hold).
      Returns:
      The bucket capacity.
      Since:
      10.0.0
    • getPermitsPerSecond

      public double getPermitsPerSecond()
      Returns the steady-state refill rate in permits per second.
      Returns:
      The refill rate.
      Since:
      10.0.0
    • isXForwardedForAware

      public boolean isXForwardedForAware()
      Returns whether X-Forwarded-For-aware key resolution is enabled.
      Returns:
      true if X-Forwarded-For resolution 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 for RateLimitGuard.Storage.snapshot() on the underlying storage backend.

      Returns an empty map when the configured RateLimitGuard.Storage does not override RateLimitGuard.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