RpcRateLimitInterceptor class

Rate-limiting interceptor for RpcResponderEndpoint.

RpcRateLimitInterceptor(
  global: RpcRateLimit.slidingWindow(max: 5000, window: Duration(seconds: 1)),
  perService: {'Heavy': RpcRateLimit.slidingWindow(max: 10, window: second)},
  perMethod: {'Sync.push': RpcRateLimit.tokenBucket(max: 10, window: second)},
  perKeyFallback: RpcRateLimit.slidingWindow(max: 50, window: second),
  keyExtractor: (call) => call.context.getValue(userIdKey),
)

Which counters a call charges

global is a CEILING, not a fallback. It applies to every call, and one more specific limit applies alongside it: perMethod[key] > perService[key] > perKeyFallback[key:method], the first that matches and only that one. BOTH must admit, so the tighter of the two binds and a call refused by either costs nothing in the other.

So global: 5/s with perMethod: {'Feed.hot': 1000/s} admits 5 calls to Feed.hot per second, not 1000. Set global above every specific limit, or leave it unset, if that is not what you want.

Without keyExtractor every slot is ONE shared counter. With it, each key gets independent perService and perMethod counters, so one caller cannot exhaust another's budget; global stays shared either way. A key of null skips the dynamic limits and charges global alone.

perKeyFallback is a catch-all per (key, method) for calls no other spec matches — the same thing as listing every method, without enumerating them.

What a streaming call costs

Every shape is admitted at establishment, before the handler runs, so no call is free. Metering then follows the direction of client-driven load:

  • Unary — one token per call.
  • Client-stream / bidirectional — one token per INBOUND request message, MINIMUM ONE: the establishment token covers the first message. So a ten-message upload costs ten as it always did, and a call that sends no request messages costs one instead of nothing. That last case is not hypothetical — a bidi subscription is exactly it, zero requests with the responses pushed by the server, and per-message metering alone let an unlimited number of them through. This cannot be disabled.
  • Server-stream — one token at establishment, like a unary call. Responses are server-paced output rather than client load, and charging them lets a burst of pushes tear down a long-lived subscription. Set meterServerStreamMessages to charge per emitted response instead, e.g. to cap a client-attributable firehose; the stream then errors RESOURCE_EXHAUSTED and stops when the limit is reached.

Enforced within a single Dart isolate, so no locking. Idle counters are swept as calls arrive; dispose drops them all at shutdown.

Inheritance

Constructors

RpcRateLimitInterceptor({RpcRateLimit? global, Map<String, RpcRateLimit> perService = const {}, Map<String, RpcRateLimit> perMethod = const {}, RpcRateLimit? perKeyFallback, String? keyExtractor(RpcCallInfo)?, Duration cleanupInterval = const Duration(minutes: 5), int maxTrackedKeys = 100000, bool meterServerStreamMessages = false, DateTime clock()?})
Creates a rate limiter.

Properties

hashCode → int
The hash code for this object.
no setterinherited
runtimeType → Type
A representation of the runtime type of the object.
no setterinherited

Methods

check(RpcCallInfo call) → FutureOr<void>
Runs before this interceptor's method for unary, server-stream, client-stream and bidirectional calls alike.
inherited
dispose() → void
Releases dynamic counter state and stops enforcing.
interceptBidirectionalStream<TRequest, TResponse>(RpcCallInfo call, Stream<TRequest> requests, RpcBidirectionalStreamNext<TRequest, TResponse> next) → FutureOr<Stream<TResponse>>
Bidirectional-stream call interceptor.
override
interceptClientStream<TRequest, TResponse>(RpcCallInfo call, Stream<TRequest> requests, RpcClientStreamNext<TRequest, TResponse> next) → Future<TResponse>
Client-stream call interceptor.
override
interceptServerStream<TRequest, TResponse>(RpcCallInfo call, TRequest request, RpcServerStreamNext<TRequest, TResponse> next) → FutureOr<Stream<TResponse>>
Server-stream call interceptor.
override
interceptUnary<TRequest, TResponse>(RpcCallInfo call, TRequest request, RpcUnaryNext<TRequest, TResponse> next) → Future<TResponse>
Unary call interceptor.
override
noSuchMethod(Invocation invocation) → dynamic
Invoked when a nonexistent method or property is accessed.
inherited
toString() → String
A string representation of this object.
inherited

Operators

operator ==(Object other) → bool
The equality operator.
inherited