Vâng, tất nhiên là có thể trong NGINX!
Những gì bạn có thể làm là thực hiện DFA sau :
Thực hiện giới hạn tỷ lệ, dựa trên $http_referer
, có thể sử dụng một số biểu thức chính quy thông qua a map
để chuẩn hóa các giá trị. Khi vượt quá giới hạn, một trang lỗi nội bộ được đưa ra, bạn có thể bắt gặp một người error_page
xử lý theo một câu hỏi liên quan , đi đến một vị trí nội bộ mới dưới dạng chuyển hướng nội bộ (không hiển thị cho khách hàng).
Ở vị trí trên vượt quá giới hạn, bạn thực hiện yêu cầu cảnh báo, để logic bên ngoài thực hiện thông báo; yêu cầu này sau đó được lưu trữ, đảm bảo bạn sẽ chỉ nhận được 1 yêu cầu duy nhất cho mỗi cửa sổ thời gian nhất định.
Bắt mã Trạng thái HTTP của yêu cầu trước đó (bằng cách trả lại mã trạng thái ≥ 300 và sử dụng proxy_intercept_errors on
, hoặc, thay vào đó, sử dụng mã không được xây dựng theo mặc định auth_request
hoặc add_after_body
để thực hiện một yêu cầu con "miễn phí" và hoàn thành yêu cầu ban đầu như thể bước trước không tham gia. Lưu ý rằng chúng ta cần kích hoạt error_page
xử lý đệ quy để điều này hoạt động.
Đây là PoC của tôi và MVP, cũng tại 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;
}
Lưu ý rằng điều này hoạt động như mong đợi:
% 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"
%
Bạn có thể thấy rằng yêu cầu đầu tiên dẫn đến một lần truy cập trước và một lần truy cập phụ trợ, như mong đợi (tôi phải thêm một phụ trợ giả vào vị trí có limit_req
, bởi vì return 200
sẽ được ưu tiên hơn các giới hạn, một phụ trợ thực sự là không cần thiết cho phần còn lại của việc xử lý).
Yêu cầu thứ hai vượt quá giới hạn, vì vậy, chúng tôi gửi cảnh báo (nhận 200
) và lưu lại bộ đệm, trả lại 429
(điều này là cần thiết do giới hạn nói trên mà các yêu cầu dưới 300 không thể bắt được), sau đó bị bắt trước , đó là miễn phí bây giờ miễn phí để làm bất cứ điều gì nó muốn.
Yêu cầu thứ ba vẫn vượt quá giới hạn, nhưng chúng tôi đã gửi cảnh báo, vì vậy, không có thông báo mới nào được gửi.
Làm xong! Đừng quên fork nó trên GitHub!