In memory rate limiter
This commit is contained in:
parent
3ee00e9e0f
commit
17c625ce6b
3 changed files with 38 additions and 7 deletions
|
@ -1,2 +1,3 @@
|
||||||
|
export 'src/in_memory_rate_limiter.dart';
|
||||||
export 'src/rate_limiter.dart';
|
export 'src/rate_limiter.dart';
|
||||||
export 'src/rate_limiting_window.dart';
|
export 'src/rate_limiting_window.dart';
|
30
lib/src/in_memory_rate_limiter.dart
Normal file
30
lib/src/in_memory_rate_limiter.dart
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
|
import 'rate_limiter.dart';
|
||||||
|
import 'rate_limiting_window.dart';
|
||||||
|
|
||||||
|
/// A simple [RateLimiter] implementation that uses a simple in-memory map
|
||||||
|
/// to store rate limiting information.
|
||||||
|
class InMemoryRateLimiter<User> extends RateLimiter<User> {
|
||||||
|
/// A callback used to compute the current user.
|
||||||
|
final FutureOr<User> Function(RequestContext, ResponseContext) getUser;
|
||||||
|
final _cache = <User, RateLimitingWindow<User>>{};
|
||||||
|
|
||||||
|
InMemoryRateLimiter(
|
||||||
|
int maxPointsPerWindow, Duration windowDuration, this.getUser,
|
||||||
|
{String errorMessage})
|
||||||
|
: super(maxPointsPerWindow, windowDuration, errorMessage: errorMessage);
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<RateLimitingWindow<User>> getCurrentWindow(
|
||||||
|
RequestContext req, ResponseContext res, DateTime currentTime) async {
|
||||||
|
var user = await getUser(req, res);
|
||||||
|
return _cache[user] ??= RateLimitingWindow(user, currentTime, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<void> updateCurrentWindow(RequestContext req, ResponseContext res,
|
||||||
|
RateLimitingWindow<User> window, DateTime currentTime) {
|
||||||
|
_cache[window.user] = window;
|
||||||
|
}
|
||||||
|
}
|
|
@ -34,12 +34,12 @@ abstract class RateLimiter<User> {
|
||||||
/// then you would return a window containing the current hour,
|
/// then you would return a window containing the current hour,
|
||||||
/// and the number of requests the user has sent in the past hour.
|
/// and the number of requests the user has sent in the past hour.
|
||||||
FutureOr<RateLimitingWindow<User>> getCurrentWindow(
|
FutureOr<RateLimitingWindow<User>> getCurrentWindow(
|
||||||
RequestContext req, ResponseContext res);
|
RequestContext req, ResponseContext res, DateTime currentTime);
|
||||||
|
|
||||||
/// Updates the underlying store with information about the new
|
/// Updates the underlying store with information about the new
|
||||||
/// [window] that the user is operating in.
|
/// [window] that the user is operating in.
|
||||||
FutureOr<void> updateCurrentWindow(
|
FutureOr<void> updateCurrentWindow(RequestContext req, ResponseContext res,
|
||||||
RequestContext req, ResponseContext res, RateLimitingWindow<User> window);
|
RateLimitingWindow<User> window, DateTime currentTime);
|
||||||
|
|
||||||
/// Computes the amount of points that a given request will cost. This amount
|
/// Computes the amount of points that a given request will cost. This amount
|
||||||
/// is then added to the amount of points that the user has already consumed
|
/// is then added to the amount of points that the user has already consumed
|
||||||
|
@ -96,11 +96,11 @@ abstract class RateLimiter<User> {
|
||||||
/// [maxPointsPerWindow].
|
/// [maxPointsPerWindow].
|
||||||
Future handleRequest(RequestContext req, ResponseContext res) async {
|
Future handleRequest(RequestContext req, ResponseContext res) async {
|
||||||
// Obtain information about the current window.
|
// Obtain information about the current window.
|
||||||
var currentWindow = await getCurrentWindow(req, res);
|
var now = DateTime.now().toUtc();
|
||||||
|
var currentWindow = await getCurrentWindow(req, res, now);
|
||||||
// Check if the rate limit has been exceeded. If so, reject the request.
|
// Check if the rate limit has been exceeded. If so, reject the request.
|
||||||
// To perform this check, we must first determine whether a new window
|
// To perform this check, we must first determine whether a new window
|
||||||
// has begun since the previous request.
|
// has begun since the previous request.
|
||||||
var now = DateTime.now().toUtc();
|
|
||||||
var currentWindowEnd = currentWindow.startTime.toUtc().add(windowDuration);
|
var currentWindowEnd = currentWindow.startTime.toUtc().add(windowDuration);
|
||||||
// We must also compute the missing information about the current window,
|
// We must also compute the missing information about the current window,
|
||||||
// so that we can relay that information to the client.
|
// so that we can relay that information to the client.
|
||||||
|
@ -119,7 +119,7 @@ abstract class RateLimiter<User> {
|
||||||
..pointLimit = maxPointsPerWindow
|
..pointLimit = maxPointsPerWindow
|
||||||
..remainingPoints = remainingPoints < 0 ? 0 : remainingPoints
|
..remainingPoints = remainingPoints < 0 ? 0 : remainingPoints
|
||||||
..resetTime = now.add(windowDuration);
|
..resetTime = now.add(windowDuration);
|
||||||
await updateCurrentWindow(req, res, newWindow);
|
await updateCurrentWindow(req, res, newWindow, now);
|
||||||
await sendWindowInformation(req, res, newWindow);
|
await sendWindowInformation(req, res, newWindow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,7 +143,7 @@ abstract class RateLimiter<User> {
|
||||||
if (currentWindow.remainingPoints < 0) {
|
if (currentWindow.remainingPoints < 0) {
|
||||||
currentWindow.remainingPoints = 0;
|
currentWindow.remainingPoints = 0;
|
||||||
}
|
}
|
||||||
await updateCurrentWindow(req, res, currentWindow);
|
await updateCurrentWindow(req, res, currentWindow, now);
|
||||||
await sendWindowInformation(req, res, currentWindow);
|
await sendWindowInformation(req, res, currentWindow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue