Ya, tentu saja mungkin di NGINX!
Yang bisa Anda lakukan adalah menerapkan DFA berikut :
Terapkan pembatasan tarif, berdasarkan $http_referer
, mungkin menggunakan beberapa regex melalui a map
untuk menormalkan nilai-nilai. Ketika batas terlampaui, halaman kesalahan internal dinaikkan, yang dapat Anda tangkap melalui error_page
penangan sesuai pertanyaan terkait , pergi ke lokasi internal baru sebagai pengalihan internal (tidak terlihat oleh klien).
Di lokasi di atas untuk batas terlampaui, Anda melakukan permintaan peringatan, membiarkan logika eksternal melakukan pemberitahuan; permintaan ini selanjutnya di-cache, memastikan Anda hanya akan mendapatkan 1 permintaan unik per jendela waktu tertentu.
Tangkap kode Status HTTP dari permintaan sebelumnya (dengan mengembalikan kode status ≥ 300 dan menggunakan proxy_intercept_errors on
, atau, alternatifnya, gunakan not-built-by-default auth_request
atau add_after_body
untuk membuat subrequest "bebas"), dan lengkapi permintaan asli seolah-olah langkah sebelumnya tidak terlibat. Perhatikan bahwa kita perlu mengaktifkan error_page
penanganan rekursif agar ini berfungsi.
Ini PoC dan MVP saya, juga di https://github.com/cnst/StackOverflow.cnst.nginx.conf/blob/master/sf.432636. Mendeteksi-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;
}
Perhatikan bahwa ini berfungsi seperti yang diharapkan:
% 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"
%
Anda dapat melihat bahwa permintaan pertama menghasilkan satu front-end dan satu pukulan backend, seperti yang diharapkan (saya harus menambahkan backend dummy ke lokasi yang telah limit_req
, karena return 200
akan didahulukan dari batas, backend nyata tidak diperlukan selama sisa penanganan).
Permintaan kedua adalah di atas batas, jadi, kami mengirim peringatan (mendapatkan 200
), dan menyimpannya, mengembalikan 429
(ini diperlukan karena batasan yang disebutkan sebelumnya bahwa permintaan di bawah 300 tidak dapat ditangkap), yang kemudian ditangkap oleh front-end , yang bebas sekarang bebas untuk melakukan apa pun yang diinginkannya.
Permintaan ketiga masih melebihi batas, tetapi kami sudah mengirimkan lansiran, jadi, tidak ada lansiran baru yang dikirim.
Selesai! Jangan lupa untuk memotongnya di GitHub!