Add service rate limiter
This commit is contained in:
parent
4a1dd8849e
commit
ee92a33ede
6 changed files with 95 additions and 0 deletions
|
@ -1,6 +1,8 @@
|
|||
name: example
|
||||
publish_to: none
|
||||
dependencies:
|
||||
angel_production: ^1.0.0
|
||||
angel_redis: ^1.0.0
|
||||
angel_security:
|
||||
path: ../
|
||||
pretty_logging: ^1.0.0
|
36
example/rate_limit_redis.dart
Normal file
36
example/rate_limit_redis.dart
Normal file
|
@ -0,0 +1,36 @@
|
|||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_framework/http.dart';
|
||||
import 'package:angel_security/angel_security.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:pretty_logging/pretty_logging.dart';
|
||||
|
||||
main() async {
|
||||
// Logging boilerplate.
|
||||
Logger.root.onRecord.listen(prettyLog);
|
||||
|
||||
// Create an app, and HTTP driver.
|
||||
var app = Angel(logger: Logger('rate_limit_redis')), http = AngelHttp(app);
|
||||
|
||||
// Create a simple rate limiter that limits users to 5
|
||||
// queries per 30 seconds.
|
||||
//
|
||||
// In this case, we rate limit users by IP address.
|
||||
//
|
||||
// Our Redis store will be used to manage windows.
|
||||
var rateLimiter =
|
||||
InMemoryRateLimiter(5, Duration(seconds: 30), (req, res) => req.ip);
|
||||
|
||||
// `RateLimiter.handleRequest` is a middleware, and can be used anywhere
|
||||
// a middleware can be used. In this case, we apply the rate limiter to
|
||||
// *all* incoming requests.
|
||||
app.fallback(rateLimiter.handleRequest);
|
||||
|
||||
// Basic routes.
|
||||
app
|
||||
..get('/', (req, res) => 'Hello!')
|
||||
..fallback((req, res) => throw AngelHttpException.notFound());
|
||||
|
||||
// Start the server.
|
||||
await http.startServer('127.0.0.1', 3000);
|
||||
print('Rate limiting (redis) example listening at ${http.uri}');
|
||||
}
|
|
@ -2,3 +2,4 @@ export 'src/cookie_signer.dart';
|
|||
export 'src/in_memory_rate_limiter.dart';
|
||||
export 'src/rate_limiter.dart';
|
||||
export 'src/rate_limiting_window.dart';
|
||||
export 'src/service_rate_limiter.dart';
|
|
@ -32,4 +32,19 @@ class RateLimitingWindow<User> {
|
|||
|
||||
RateLimitingWindow(this.user, this.startTime, this.pointsConsumed,
|
||||
{this.pointLimit, this.remainingPoints, this.resetTime});
|
||||
|
||||
factory RateLimitingWindow.fromJson(Map<String, dynamic> map) {
|
||||
return RateLimitingWindow(
|
||||
map['user'] as User,
|
||||
DateTime.parse(map['start_time'] as String),
|
||||
int.parse(map['points_consumed'] as String));
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'user': user,
|
||||
'start_time': startTime.toIso8601String(),
|
||||
'points_consumed': pointsConsumed.toString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
39
lib/src/service_rate_limiter.dart
Normal file
39
lib/src/service_rate_limiter.dart
Normal file
|
@ -0,0 +1,39 @@
|
|||
import 'dart:async';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'rate_limiter.dart';
|
||||
import 'rate_limiting_window.dart';
|
||||
|
||||
/// A RateLimiter] implementation that uses a [Service]
|
||||
/// to store rate limiting information.
|
||||
class ServiceRateLimiter<Id> extends RateLimiter<Id> {
|
||||
/// The underlying [Service] used to store data.
|
||||
final Service<Id, Map<String, dynamic>> service;
|
||||
|
||||
/// A callback used to compute the current user ID.
|
||||
final FutureOr<Id> Function(RequestContext, ResponseContext) getId;
|
||||
|
||||
ServiceRateLimiter(
|
||||
int maxPointsPerWindow, Duration windowDuration, this.service, this.getId,
|
||||
{String errorMessage})
|
||||
: super(maxPointsPerWindow, windowDuration, errorMessage: errorMessage);
|
||||
|
||||
@override
|
||||
FutureOr<RateLimitingWindow<Id>> getCurrentWindow(
|
||||
RequestContext req, ResponseContext res, DateTime currentTime) async {
|
||||
var id = await getId(req, res);
|
||||
var existing = await service.read(id);
|
||||
if (existing != null) {
|
||||
return RateLimitingWindow.fromJson(existing);
|
||||
}
|
||||
|
||||
var window = RateLimitingWindow(id, currentTime, 0);
|
||||
await updateCurrentWindow(req, res, window, currentTime);
|
||||
return window;
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<void> updateCurrentWindow(RequestContext req, ResponseContext res,
|
||||
RateLimitingWindow<Id> window, DateTime currentTime) async {
|
||||
await service.update(window.user, window.toJson());
|
||||
}
|
||||
}
|
|
@ -9,6 +9,8 @@ dependencies:
|
|||
angel_framework: ^2.0.0
|
||||
dev_dependencies:
|
||||
angel_auth: ^2.0.0
|
||||
angel_production: ^1.0.0
|
||||
angel_redis: ^1.0.0
|
||||
angel_test: ^2.0.0
|
||||
angel_validate: ^2.0.0
|
||||
crypto: ^2.0.0
|
||||
|
|
Loading…
Reference in a new issue