Единый лимит 30r/m на весь /api/ был рассчитан как защита от абуза, но бил по обычной навигации: страница статистики делает два запроса на загрузку плюс автообновление, поэтому уже 4-5 перезагрузок подряд упирались в лимит и nginx отдавал 503. Лимиты разделены по цене операции: разбор ссылки и запуск загрузки (дёргают сеть и yt-dlp) остаются на 30r/m, а чтение статистики, ленты и статусов задач получает 240r/m. Проверено: 44 запроса чтения подряд проходят, /api/info по-прежнему отсекается на 429. Ответ при превышении сменён с 503 на 429: это точный код для «слишком часто», и он не приходит HTML-страницей, на которой ломался разбор JSON. Лимит одновременных соединений поднят с 8 до 24 — браузер сам открывает до шести, а SSE держит своё на всю загрузку. Фронтенд больше не выдаёт сбой за пустоту. Раньше неудачный запрос ронял разбор JSON, и страница показывала «Пока пусто» — то есть утверждала, что данных нет, хотя их просто не отдали. Теперь показывается причина. Также: срок жизни файла поднят с часа до двух (за это время файл в 2 ГБ успевает скачаться даже на 2,3 Мбит/с), и устранена утечка в rate-limiter — ключи словаря _hits не удалялись никогда, поэтому перебор адресов медленно выедал память процесса. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AwY2Cg54RK7WZdvMaLV5Di
136 lines
5.6 KiB
Text
136 lines
5.6 KiB
Text
# yt-grab — yt.zapret.moe
|
||
# Устанавливается в /etc/nginx/sites-available/ и линкуется в sites-enabled/.
|
||
|
||
# Ограничение частоты на уровне nginx (вдобавок к лимитам в приложении).
|
||
#
|
||
# Два разных лимита, потому что операции несопоставимы по цене:
|
||
# * heavy — разбор ссылки и запуск загрузки: дёргают сеть и yt-dlp;
|
||
# * read — чтение статистики, ленты и статусов задач: почти бесплатно.
|
||
# Единый строгий лимит ломал обычную навигацию: страница статистики делает
|
||
# два запроса на загрузку плюс автообновление, и несколько перезагрузок
|
||
# подряд выбивали 503 на ровном месте.
|
||
limit_req_zone $binary_remote_addr zone=ytgrab_heavy:10m rate=30r/m;
|
||
limit_req_zone $binary_remote_addr zone=ytgrab_read:10m rate=240r/m;
|
||
limit_conn_zone $binary_remote_addr zone=ytgrab_conn:10m;
|
||
|
||
# Отдаём 429 вместо 503: это честный код для «слишком часто», и клиент
|
||
# может его распознать (503 приходил HTML-страницей и ломал разбор JSON).
|
||
limit_req_status 429;
|
||
limit_conn_status 429;
|
||
|
||
server {
|
||
listen 80;
|
||
listen [::]:80;
|
||
server_name yt.zapret.moe;
|
||
|
||
location ^~ /.well-known/acme-challenge/ {
|
||
root /var/www/letsencrypt;
|
||
default_type text/plain;
|
||
}
|
||
|
||
location / {
|
||
return 301 https://yt.zapret.moe$request_uri;
|
||
}
|
||
}
|
||
|
||
server {
|
||
listen 443 ssl;
|
||
listen [::]:443 ssl;
|
||
http2 on;
|
||
server_name yt.zapret.moe;
|
||
|
||
ssl_certificate /etc/letsencrypt/live/yt.zapret.moe/fullchain.pem;
|
||
ssl_certificate_key /etc/letsencrypt/live/yt.zapret.moe/privkey.pem;
|
||
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||
|
||
add_header Strict-Transport-Security "max-age=31536000" always;
|
||
add_header X-Content-Type-Options "nosniff" always;
|
||
add_header Referrer-Policy "no-referrer" always;
|
||
add_header X-Frame-Options "DENY" always;
|
||
|
||
charset utf-8;
|
||
|
||
# Загрузок от клиента нет — тело запроса всегда маленький JSON.
|
||
client_max_body_size 64k;
|
||
|
||
# Одновременные соединения с одного IP. Браузер сам открывает до шести,
|
||
# плюс SSE держит своё открытым на всю загрузку, поэтому 8 было тесно.
|
||
limit_conn ytgrab_conn 24;
|
||
|
||
# --- Поток прогресса (SSE): буферизация должна быть выключена ---
|
||
location ~ ^/api/tasks/[^/]+/progress$ {
|
||
proxy_pass http://127.0.0.1:8090;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Connection "";
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
|
||
proxy_buffering off;
|
||
proxy_cache off;
|
||
gzip off;
|
||
chunked_transfer_encoding on;
|
||
proxy_read_timeout 1h;
|
||
proxy_send_timeout 1h;
|
||
}
|
||
|
||
# --- Отдача готового файла: большие тела, докачка по Range ---
|
||
location ~ ^/api/tasks/[^/]+/file$ {
|
||
proxy_pass http://127.0.0.1:8090;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
|
||
# не копить многогигабайтные ответы во временные файлы
|
||
proxy_buffering off;
|
||
proxy_max_temp_file_size 0;
|
||
proxy_read_timeout 1h;
|
||
}
|
||
|
||
# --- Дорогие операции: разбор ссылки и запуск загрузки ---
|
||
location = /api/info {
|
||
limit_req zone=ytgrab_heavy burst=10 nodelay;
|
||
proxy_pass http://127.0.0.1:8090;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
proxy_read_timeout 180s;
|
||
}
|
||
|
||
location = /api/downloads {
|
||
limit_req zone=ytgrab_heavy burst=10 nodelay;
|
||
proxy_pass http://127.0.0.1:8090;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
proxy_read_timeout 180s;
|
||
}
|
||
|
||
# --- Дешёвое чтение: статистика, лента, статусы задач ---
|
||
location /api/ {
|
||
limit_req zone=ytgrab_read burst=60 nodelay;
|
||
proxy_pass http://127.0.0.1:8090;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
proxy_read_timeout 180s;
|
||
}
|
||
|
||
location / {
|
||
proxy_pass http://127.0.0.1:8090;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
}
|
||
}
|