Add service rate limiter

This commit is contained in:
Tobe O 2019-08-16 11:25:51 -04:00
parent 4a1dd8849e
commit ee92a33ede
6 changed files with 95 additions and 0 deletions

View file

@ -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

View 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}');
}

View file

@ -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';

View file

@ -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(),
};
}
}

View 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());
}
}

View file

@ -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