recipe: harden auth/metrics, add rate limiting and tests
This commit is contained in:
@@ -641,3 +641,111 @@ nginx-маршруты без redirect и health `/receipt/health`. Старые
|
||||
IP `127.0.0.1`, MIME `image/png`, размер 531101 байт, длину prompt 88 и
|
||||
непустой usage JSON. Тестовый image-файл удалён после запроса; приложение
|
||||
читает изображение в память и не пишет его на диск.
|
||||
|
||||
## 2026-08-31: Реализация backend-фиксов по плану v2
|
||||
|
||||
По команде пользователя выполнены изменения backend-компонентов и тестов.
|
||||
|
||||
### Изменения в recipe_service
|
||||
|
||||
- `recipe_service/metrics.py`:
|
||||
- из `record()` удалён вызов `initialize()`;
|
||||
- добавлена `count_since(client_ip, started_at_from)` для rate limiting.
|
||||
- `recipe_service/app.py`:
|
||||
- добавлен `ProxyFix(..., x_for=1, x_proto=1, x_host=1)`;
|
||||
- сравнение токена переведено на `hmac.compare_digest`;
|
||||
- введён единый финализатор `finalize(...)` вместо дублирования `record(...)`;
|
||||
- `duration_ms` считается во всех ветках через `time.monotonic()`;
|
||||
- добавлен rate limit `20` запросов/минута на IP (`429 too many requests`);
|
||||
- зафиксирован контракт `502`:
|
||||
`{"error":"upstream recognition failed","code":"upstream_error"}`;
|
||||
- детали апстрима пишутся только в лог сервера с `request_id`.
|
||||
- добавлен `recipe_service/test_app.py` (покрытие: `health`, `401`, `400`,
|
||||
`415`, `413`, `200`, `502`-контракт, `429`).
|
||||
|
||||
### Изменения в gemini_proxy
|
||||
|
||||
- `gemini_proxy/app.py`: удалён `api_key_override`; ключ только из
|
||||
`GEMINI_API_KEY`.
|
||||
- `gemini_proxy/test_app.py`: добавлен тест, что `api_key_override` в форме
|
||||
не даёт доступ без `GEMINI_API_KEY`.
|
||||
|
||||
### Изменения зависимостей
|
||||
|
||||
- выровнен root `requirements.txt` по version bounds с
|
||||
`recipe_service/requirements.txt`:
|
||||
- `Flask>=3.0,<4`
|
||||
- `gunicorn>=21.2,<24`
|
||||
- `requests>=2.31,<3`
|
||||
|
||||
### Проверки
|
||||
|
||||
- `py_compile` изменённых Python-файлов: успешно.
|
||||
- `recipe_service`: `pytest -q` -> `8 passed`.
|
||||
- `gemini_proxy`: `pytest -q` -> `4 passed`.
|
||||
|
||||
### Отдельно зафиксировано
|
||||
|
||||
Первый запуск тестов `recipe_service` дал `PermissionError` на `/var/lib/recipe`
|
||||
при import-time `initialize()`. Исправлено в тесте ранней установкой
|
||||
`RECIPE_METRICS_DB` в временный путь до импорта `app`.
|
||||
|
||||
## 2026-08-31: Безопасная оптимизация без смены поведения
|
||||
|
||||
По дополнительной команде пользователя выполнен пакет low-risk улучшений,
|
||||
направленный на производительность и устойчивость, без изменения основного
|
||||
контракта API.
|
||||
|
||||
### Изменения
|
||||
|
||||
- `recipe_service/metrics.py`:
|
||||
- добавлен индекс
|
||||
`idx_requests_client_ip_started_at ON requests(client_ip, started_at)`
|
||||
для ускорения выборки rate limiting.
|
||||
- `recipe_service/app.py`:
|
||||
- ответ `429` унифицирован и дополнен стабильным полем
|
||||
`code="rate_limited"` при сохранении `error="too many requests"`.
|
||||
- `recipe_service/test_app.py`:
|
||||
- обновлена проверка `429` с новым полем `code`;
|
||||
- добавлен тест граничного случая лимитера (`19` запросов -> `200`);
|
||||
- добавлен тест чтения последней записи в SQLite-метриках с проверкой
|
||||
`status_code`, `duration_ms` и `error` после ветки `502`.
|
||||
|
||||
### Проверки
|
||||
|
||||
- `py_compile` изменённых Python-файлов: успешно.
|
||||
- `recipe_service`: `pytest -q` -> `10 passed`.
|
||||
- `gemini_proxy`: `pytest -q` -> `4 passed`.
|
||||
|
||||
### Вывод
|
||||
|
||||
Оптимизации применены без регрессий. Поведение успешного запроса, а также
|
||||
статусы `400/401/413/415/502` сохранены; `429` дополнен машинным кодом
|
||||
ошибки для стабильной клиентской обработки.
|
||||
|
||||
## 2026-08-31: Nginx-level rate limiting (основной лимитер)
|
||||
|
||||
По команде пользователя добавлен основной лимит запросов на уровне nginx,
|
||||
при сохранении app-level fallback в `recipe_service/app.py`.
|
||||
|
||||
### Изменения конфигурации
|
||||
|
||||
- Добавлен новый файл `recipe_service/nginx-rate-limit-http.conf`:
|
||||
- `limit_req_zone $binary_remote_addr zone=recipe_api_per_ip:10m rate=20r/m;`
|
||||
- `limit_req_status 429;`
|
||||
- файл предназначен для single-include внутри `http { ... }`.
|
||||
- Обновлён `recipe_service/nginx-recipe.conf`:
|
||||
- для `location = /recipe`, `location /recipe/`, `location = /receipt`,
|
||||
`location /receipt/` добавлен
|
||||
`limit_req zone=recipe_api_per_ip burst=5 nodelay;`.
|
||||
|
||||
### Результат
|
||||
|
||||
- Лимит теперь применяется единообразно для всех воркеров gunicorn на входе
|
||||
nginx, а не только внутри отдельного процесса приложения.
|
||||
- Python fallback-лимитер сохранён как защитный второй контур.
|
||||
|
||||
### Проверки
|
||||
|
||||
- `py_compile` изменённых Python-файлов: успешно.
|
||||
- `recipe_service`: `pytest -q` -> `10 passed`.
|
||||
|
||||
Reference in New Issue
Block a user