RpcRateLimitInterceptor constructor
RpcRateLimitInterceptor({
- RpcRateLimit? global,
- Map<
String, RpcRateLimit> perService = const {}, - Map<
String, RpcRateLimit> perMethod = const {}, - RpcRateLimit? perKeyFallback,
- String? keyExtractor()?,
- Duration cleanupInterval = const Duration(minutes: 5),
- int maxTrackedKeys = 100000,
- bool meterServerStreamMessages = false,
- DateTime clock()?,
Creates a rate limiter.
When keyExtractor is provided, perService and perMethod counters
are created dynamically per extracted key. global is always shared.
cleanupInterval controls how often stale per-key counters are evicted
(only relevant when keyExtractor is set).
clock replaces the time source, as on RpcContext.withClock, for a
fake clock in tests. The default is a monotonic Stopwatch read as
microseconds, so wall-clock jumps cannot break the windows and a call
allocates no DateTime.
Implementation
RpcRateLimitInterceptor({
RpcRateLimit? global,
Map<String, RpcRateLimit> perService = const {},
Map<String, RpcRateLimit> perMethod = const {},
RpcRateLimit? perKeyFallback,
String? Function(RpcCallInfo)? keyExtractor,
Duration cleanupInterval = const Duration(minutes: 5),
int maxTrackedKeys = 100000,
bool meterServerStreamMessages = false,
DateTime Function()? clock,
}) : _globalSpec = global,
_perServiceSpec = Map.unmodifiable(perService),
_perMethodSpec = Map.unmodifiable(perMethod),
_perKeyFallbackSpec = perKeyFallback,
_keyExtractor = keyExtractor,
_meterServerStreamMessages = meterServerStreamMessages,
_cleanupIntervalUs = cleanupInterval.inMicroseconds,
_nowMicros = clock == null
? _defaultMonotonicMicros
: (() => clock().microsecondsSinceEpoch) {
// A throw, not an assert, which vanishes in release. At 0 the store evicts
// each key as it is inserted, so every call gets a fresh counter and no
// limit is ever enforced -- the opposite of what the operator asked for.
if (maxTrackedKeys <= 0) {
throw ArgumentError.value(
maxTrackedKeys,
'maxTrackedKeys',
'must be > 0',
);
}
_dynamicServiceCounters = _KeyedLruStore(maxTrackedKeys);
_dynamicMethodCounters = _KeyedLruStore(maxTrackedKeys);
_dynamicFallbackCounters = _KeyedLruStore(maxTrackedKeys);
// Validate EVERY spec up front, in every build mode. A spec that cannot
// enforce anything must fail loudly at construction rather than at first
// use -- or, worse, never (see RpcRateLimit._validate).
global?._validate();
_perKeyFallbackSpec?._validate();
for (final spec in perService.values) {
spec._validate();
}
for (final spec in perMethod.values) {
spec._validate();
}
_globalCounter = global?._createCounter(_nowMicros);
_lastCleanupUs = _nowMicros();
if (keyExtractor == null) {
// Static mode: create one counter per slot up front.
_staticServiceCounters = {
for (final e in perService.entries)
e.key: e.value._createCounter(_nowMicros),
};
_staticMethodCounters = {
for (final e in perMethod.entries)
e.key: e.value._createCounter(_nowMicros),
};
} else {
_staticServiceCounters = const {};
_staticMethodCounters = const {};
}
}