Oui, bien sûr, c'est possible dans NGINX!
Vous pouvez implémenter le DFA suivant :
Implémentez la limitation de débit, basée sur $http_referer
, éventuellement en utilisant une expression régulière via a map
pour normaliser les valeurs. Lorsque la limite est dépassée, une page d'erreur interne s'affiche, que vous pouvez intercepter via un error_page
gestionnaire selon une question connexe , en allant vers un nouvel emplacement interne en tant que redirection interne (non visible pour le client).
À l'emplacement ci-dessus pour les limites dépassées, vous effectuez une demande d'alerte, laissant la logique externe effectuer la notification; cette demande est ensuite mise en cache, ce qui garantit que vous n'obtiendrez qu'une seule demande par fenêtre de temps donnée.
Attrapez le code d'état HTTP de la demande précédente (en renvoyant un code d'état ≥ 300 et en utilisant proxy_intercept_errors on
, ou, alternativement, utilisez le non-construit par défaut auth_request
ou add_after_body
pour faire une sous-demande "gratuite"), et complétez la demande d'origine comme si l'étape précédente n'était pas impliquée. Notez que nous devons activer la error_page
gestion récursive pour que cela fonctionne.
Voici mon PoC et un MVP, également sur https://github.com/cnst/StackOverflow.cnst.nginx.conf/blob/master/sf.432636.detecting-slashdot-effect-in-nginx.conf :
limit_req_zone $http_referer zone=slash:10m rate=1r/m; # XXX: how many req/minute?
server {
listen 2636;
location / {
limit_req zone=slash nodelay;
#limit_req_status 429; #nginx 1.3.15
#error_page 429 = @dot;
error_page 503 = @dot;
proxy_pass http://localhost:2635;
# an outright `return 200` has a higher precedence over the limit
}
recursive_error_pages on;
location @dot {
proxy_pass http://127.0.0.1:2637/?ref=$http_referer;
# if you don't have `resolver`, no URI modification is allowed:
#proxy_pass http://localhost:2637;
proxy_intercept_errors on;
error_page 429 = @slash;
}
location @slash {
# XXX: placeholder for your content:
return 200 "$uri: we're too fast!\n";
}
}
server {
listen 2635;
# XXX: placeholder for your content:
return 200 "$uri: going steady\n";
}
proxy_cache_path /tmp/nginx/slashdotted inactive=1h
max_size=64m keys_zone=slashdotted:10m;
server {
# we need to flip the 200 status into the one >=300, so that
# we can then catch it through proxy_intercept_errors above
listen 2637;
error_page 429 @/.;
return 429;
location @/. {
proxy_cache slashdotted;
proxy_cache_valid 200 60s; # XXX: how often to get notifications?
proxy_pass http://localhost:2638;
}
}
server {
# IRL this would be an actual script, or
# a proxy_pass redirect to an HTTP to SMS or SMTP gateway
listen 2638;
return 200 authorities_alerted\n;
}
Notez que cela fonctionne comme prévu:
% sh -c 'rm /tmp/slashdotted.nginx/*; mkdir /tmp/slashdotted.nginx; nginx -s reload; for i in 1 2 3; do curl -H "Referer: test" localhost:2636; sleep 2; done; tail /var/log/nginx/access.log'
/: going steady
/: we're too fast!
/: we're too fast!
127.0.0.1 - - [26/Aug/2017:02:05:49 +0200] "GET / HTTP/1.1" 200 16 "test" "curl/7.26.0"
127.0.0.1 - - [26/Aug/2017:02:05:49 +0200] "GET / HTTP/1.0" 200 16 "test" "curl/7.26.0"
127.0.0.1 - - [26/Aug/2017:02:05:51 +0200] "GET / HTTP/1.1" 200 19 "test" "curl/7.26.0"
127.0.0.1 - - [26/Aug/2017:02:05:51 +0200] "GET /?ref=test HTTP/1.0" 200 20 "test" "curl/7.26.0"
127.0.0.1 - - [26/Aug/2017:02:05:51 +0200] "GET /?ref=test HTTP/1.0" 429 20 "test" "curl/7.26.0"
127.0.0.1 - - [26/Aug/2017:02:05:53 +0200] "GET / HTTP/1.1" 200 19 "test" "curl/7.26.0"
127.0.0.1 - - [26/Aug/2017:02:05:53 +0200] "GET /?ref=test HTTP/1.0" 429 20 "test" "curl/7.26.0"
%
Vous pouvez voir que la première demande aboutit à un hit frontal et à un backend, comme prévu (j'ai dû ajouter un backend factice à l'emplacement qui a limit_req
, car un return 200
aurait priorité sur les limites, un vrai backend n'est pas nécessaire pour le reste de la manipulation).
La deuxième demande est au-dessus de la limite, donc, nous envoyons l'alerte (obtention 200
), et la mettons en cache, en retournant 429
(cela est nécessaire en raison de la limitation susmentionnée selon laquelle les demandes inférieures à 300 ne peuvent pas être interceptées), qui est ensuite interceptée par le frontal , qui est maintenant libre de faire ce qu'il veut.
La troisième demande dépasse toujours la limite, mais nous avons déjà envoyé l'alerte, donc aucune nouvelle alerte n'est envoyée.
Terminé! N'oubliez pas de le bifurquer sur GitHub!