Так, звичайно, це можливо в NGINX!
Що ви можете зробити, це реалізувати таку DFA :
Обмеження швидкості впровадження, засноване на $http_referer, можливо, використанні деякого регулярного вираження через a mapдля нормалізації значень. При перевищенні ліміту піднімається внутрішня сторінка помилок, яку ви можете зафіксувати через error_pageобробник відповідно до відповідного запитання , переходячи до нового внутрішнього місця розташування у вигляді внутрішнього переадресації (не видно клієнту).
У вищенаведеному місці для перевищення обмежень ви виконуєте запит оповіщення, дозволяючи зовнішній логіці виконувати сповіщення; згодом цей запит кешується, гарантуючи, що ви отримаєте лише 1 унікальний запит за задане часове вікно.
Ловіть код статусу HTTP попереднього запиту (повертаючи код статусу ≥ 300 та використовуючи proxy_intercept_errors on, або, альтернативно, використовуйте не вбудований за замовчуванням auth_requestабо add_after_bodyздійснити "безкоштовний" підзапит) та заповніть початковий запит так, ніби попередній крок не включався. Зауважте, що error_pageдля цього потрібно активувати рекурсивну обробку.
Ось мій PoC та MVP, також на 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;
}
Зауважте, що це працює, як очікувалося:
% 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"
%
Ви можете бачити, що перший запит призводить до того, що очікувалося, один передній і один запуск (як я і очікував). Мені довелося додати фіктивний мікстун до місця, яке має місце limit_req, тому return 200що перевага матиме обмеження, справжній бекенд не потрібен для решти обробки.
Другий запит знаходиться вище межі, тому ми надсилаємо сповіщення (отримання 200) та кешуємо його, повертаючи 429(це необхідно через вищезазначене обмеження, що запити нижче 300 не можуть бути спіймані), яке згодом перехоплюється передньою частиною , який зараз вільний робити все, що хоче.
Третій запит все ще перевищує ліміт, але ми вже надіслали попередження, тож нове попередження не надсилається.
Готово! Не забудьте розщедритися на GitHub!