Sim, claro que é possível no NGINX!
O que você pode fazer é implementar os seguintes DFA :
-
Limite de taxa de implementação, com base em $http_referer
, possivelmente usando algum regex por meio de um map
para normalizar o valores. Quando o limite é excedido, é gerada uma página de erro interna, que você pode capturar por meio de um manipulador error_page
como por uma questão relacionada , indo para um novo local interno como um redirecionado interno (não visível para o cliente).
-
No local acima para limites excedidos, você realiza uma solicitação de alerta, permitindo que a lógica externa execute a notificação; essa solicitação é armazenada em cache subsequentemente, garantindo que você receba apenas uma solicitação exclusiva por uma determinada janela de tempo.
-
Pegue o código de status HTTP da solicitação anterior (retornando um código de status ≥ 300 e usando proxy_intercept_errors on
ou, em alternativa, utilize o auth_request
ou add_after_body
para fazer uma sub-requisição" livre ") e completar a solicitação original como se a etapa anterior não estivesse envolvida. Observe que precisamos ativar o tratamento error_page
recursivo para que isso funcione.
Aqui está meu PoC e um MVP, também em link :
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;
}
Observe que isso funciona como esperado:
% 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"
%
Você pode ver que a primeira solicitação resulta em um front-end e um backend, como esperado (eu tive que adicionar um back-end fictício ao local que tem limit_req
, porque um return 200
teria precedência sobre o limites, um back-end real não é necessário para o resto do manuseio).
O segundo pedido está acima do limite, então, enviamos o alerta (obtendo 200
), e o armazenamos em cache, retornando 429
(isso é necessário devido à limitação mencionada que pedidos abaixo de 300 não podem ser capturados), que é posteriormente capturado pelo front-end, que agora é livre para fazer o que quiser.
O terceiro pedido ainda excede o limite, mas já enviamos o alerta, por isso, nenhum novo alerta é enviado.
Feito! Não se esqueça de usar o GitHub!