Files
Flash5/flash-extensions/flash-ext-limiter/docs/annotation.md
T

4.1 KiB

@Limit annotation

Applies a rate limit to a class-based RequestHandler. The annotation is read once per handler class at boot by the LimiterExtension annotation processor — zero overhead at request time.

Declaration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Limit {
    String      key()        default "ip";
    int         requests();
    long        window();
    TimeUnit    windowUnit() default TimeUnit.SECONDS;
    LimitStrategy strategy() default LimitStrategy.FIXED_WINDOW;
}

Fields

Field Type Default Description
key String "ip" Name of the key resolver registered in LimiterConfig
requests int Maximum requests allowed per window (required)
window long Window duration in windowUnit units (required)
windowUnit TimeUnit SECONDS Time unit for window
strategy LimitStrategy FIXED_WINDOW Rate-limit algorithm

Basic usage

100 requests per second per IP (default)

@Route(method = HttpMethod.GET, path = "/api/search")
@Limit(requests = 100, window = 1)
public class SearchHandler extends RequestHandler {
    public Object handle(Request req, Response res) {
        return searchService.query(req.query("q"));
    }
}

20 requests per minute per authenticated user

@Route(method = HttpMethod.POST, path = "/api/report")
@Limit(key = "auth_user", requests = 20, window = 1, windowUnit = TimeUnit.MINUTES)
@Authenticated
public class ReportHandler extends RequestHandler {
    public Object handle(Request req, Response res) { ... }
}

Order of annotation processors: register LimiterExtension before OidcExtension so the rate-limit middleware wraps the outer layer of the chain and fires before auth.

Token bucket — absorb bursts

@Route(method = HttpMethod.POST, path = "/api/upload")
@Limit(
    key        = "api_key",
    requests   = 50,
    window     = 1,
    windowUnit = TimeUnit.MINUTES,
    strategy   = LimitStrategy.TOKEN_BUCKET
)
public class UploadHandler extends RequestHandler { ... }

Large window — 1000 requests per hour

@Route(method = HttpMethod.GET, path = "/api/export")
@Limit(requests = 1000, window = 1, windowUnit = TimeUnit.HOURS)
public class ExportHandler extends RequestHandler { ... }

Strict per-second limit on a public endpoint

@Route(method = HttpMethod.GET, path = "/api/prices")
@Limit(requests = 10, window = 1, windowUnit = TimeUnit.SECONDS)
public class PriceHandler extends RequestHandler { ... }

Combining @Limit with other annotations

@Limit composes naturally with @Authenticated, @RolesAllowed, and @ApiOperation. Each annotation is processed by its own processor; Flash collects all middleware and composes them in processor registration order.

When flash-ext-openapi is installed, @Limit also contributes OpenAPI response headers (X-RateLimit-*) and Retry-After on 429 automatically.

@Route(method = HttpMethod.DELETE, path = "/admin/users/{id}")
@Limit(key = "auth_user", requests = 5, window = 1, windowUnit = TimeUnit.MINUTES)
@RolesAllowed("admin")
@ApiOperation(summary = "Delete a user", tags = "admin")
public class DeleteUserHandler extends RequestHandler { ... }

Execution chain (outermost → handler): LimiterMiddleware → OidcRolesMiddleware → DeleteUserHandler

Fail-fast at boot

If key names a resolver not registered in LimiterConfig, the server refuses to start:

dev.relism.exceptions.InitializationException:
  Rate-limit resolver "auth_user" is not registered.
  Call LimiterConfig.registerResolver("auth_user", req -> ...) before install.

There is no silent fallback — a misconfigured rate limit is treated as a hard error.

What happens on violation

HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit:     100
X-RateLimit-Remaining: 0
X-RateLimit-Reset:     1711750860
Retry-After:           1
Content-Type:          text/plain

Too Many Requests

The handler body is never invoked. See http-headers.md for the full header reference.