Ja, natürlich ist das in NGINX möglich!
Sie können den folgenden DFA implementieren :
Implementieren Sie eine Ratenbegrenzung, basierend auf der $http_referer
Verwendung von Regex durch a map
, um die Werte zu normalisieren. Wenn das Limit überschritten wird, wird eine interne Fehlerseite ausgelöst, die Sie gemäß einer verwandten Frage über einen error_page
Handler abrufen können und die als interne Umleitung an einen neuen internen Speicherort wechselt (für den Client nicht sichtbar).
An der oben genannten Stelle für überschrittene Grenzwerte führen Sie eine Warnanforderung aus, sodass die externe Logik die Benachrichtigung ausführt. Diese Anfrage wird anschließend zwischengespeichert, um sicherzustellen, dass Sie nur 1 eindeutige Anfrage pro Zeitfenster erhalten.
Fangen Sie den HTTP-Statuscode der vorherigen Anforderung ab (indem Sie einen Statuscode ≥ 300 zurückgeben und proxy_intercept_errors on
den nicht standardmäßig nicht erstellten Standardcode verwenden auth_request
oder alternativ add_after_body
eine "kostenlose" Unteranforderung erstellen) und schließen Sie die ursprüngliche Anforderung so ab, als ob Der vorherige Schritt war nicht beteiligt. Beachten Sie, dass wir die rekursive error_page
Behandlung aktivieren müssen, damit dies funktioniert.
Hier ist mein PoC und ein MVP, ebenfalls unter 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;
}
Beachten Sie, dass dies wie erwartet funktioniert:
% 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"
%
Sie können sehen, dass die erste Anforderung erwartungsgemäß zu einem Front-End- und einem Back-End-Treffer führt (ich musste dem Speicherort ein Dummy-Back-End hinzufügen , bei dem ein echtes Back -End nicht erforderlich ist limit_req
, da a return 200
Vorrang vor den Grenzwerten hat für den Rest der Handhabung).
Die zweite Anforderung liegt über dem Grenzwert, daher senden wir die Warnung (Abrufen 200
) und zwischenspeichern sie und geben sie zurück 429
(dies ist aufgrund der oben genannten Einschränkung erforderlich, dass Anforderungen unter 300 nicht abgefangen werden können), die anschließend vom Front-End abgefangen wird , die jetzt frei ist, frei zu tun, was sie will.
Die dritte Anforderung überschreitet immer noch das Limit, aber wir haben die Warnung bereits gesendet, sodass keine neue Warnung gesendet wird.
Erledigt! Vergiss nicht, es auf GitHub zu teilen!