125 lines
7.4 KiB
Markdown
125 lines
7.4 KiB
Markdown
# ПЛАН: перенос паттерна «ВМ-буфер + pull» в drhider
|
||
|
||
_Дата: 2026-08-21. Основа: `contracts/loadtest/History/2026-08-21-pattern-vm-buffer-pull.md`._
|
||
_Статус: РЕАЛИЗОВАНО 2026-08-21 (v0.0.58), см. `2026-08-21-vm-upload-implemented.md`._
|
||
|
||
---
|
||
|
||
## Цель
|
||
|
||
drhider (Managed Flask, Штурвал, `drhider.pythonk8s.dev.nubes.ru`) получает файлы через
|
||
`POST /api/upload` (multipart). Входной шлюз managed-кластера обрывает тела >~64КБ
|
||
(HTTP 000 ~10с). Решение — загружать файл на ВМ напрямую, Flask тянет сам (egress).
|
||
|
||
## Директива (обязательно)
|
||
|
||
**ВСЕ файловые операции загрузки в Flask (из браузера или вообще снаружи) — ТОЛЬКО через ВМ.**
|
||
Никаких прямых `POST /api/upload` с телом файла во Flask.
|
||
|
||
## Среда (уточнено 2026-08-21)
|
||
|
||
- `iot-naeel` — СОБСТВЕННЫЙ кластер, параметры/аннотации правились через kubectl. Пока
|
||
тестируем/смотрим поведение на нём (лимит 64КБ там может не воспроизводиться).
|
||
- В дальнейшем деплой — на ОБЫЧНЫЙ managed-кластер (лимит 64КБ будет реальным).
|
||
- ВМ — ОДНА (`5.172.178.213`, `contracts.kube5s.ru`), та же, что для loadtest. Location
|
||
`/drhider-upload/` добавляется в существующий nginx рядом с `/lt-serve/`.
|
||
- Вывод: VM-путь строим СЕЙЧАС, независимо от поведения iot-naeel — прод всё равно на обычном.
|
||
|
||
## Текущий поток
|
||
|
||
`uploadFiles()` (index.html ~413): по одному файлу в цикле → `FormData` → `POST /api/upload`.
|
||
Бэк: `MAX_CONTENT_LENGTH=200МБ`, сессия 500МБ. HTTP-клиент — **httpx** (не requests).
|
||
|
||
## Целевой поток
|
||
|
||
```
|
||
Браузер ──(PUT, файл целиком)──▶ ВМ nginx (без лимита 64КБ)
|
||
│ └─ получил URL + токен
|
||
│
|
||
└──▶ Flask POST /api/upload_refs (JSON: [{name,size,url}], <64КБ)
|
||
│
|
||
▼
|
||
Flask egress GET с ВМ ──▶ читает файл ──▶ add_file() ──▶ дальше как сейчас
|
||
```
|
||
|
||
## Изменения (4 места)
|
||
|
||
### 1. ВМ `5.172.178.213`, `nginx-contracts.conf` — приём загрузки
|
||
Новый location (рядом с `/lt-serve/`), приём через PUT + статическая отдача:
|
||
```nginx
|
||
location /drhider-upload/ {
|
||
alias /var/www/drhider-upload/;
|
||
dav_methods PUT;
|
||
create_full_put_path on;
|
||
client_max_body_size 1024m;
|
||
# CORS: браузер с drhider.pythonk8s.dev.nubes.ru шлёт PUT (не simple → preflight)
|
||
add_header Access-Control-Allow-Origin https://drhider.pythonk8s.dev.nubes.ru always;
|
||
add_header Access-Control-Allow-Methods 'PUT, GET, OPTIONS, DELETE' always;
|
||
add_header Access-Control-Allow-Headers 'Content-Type' always;
|
||
if ($request_method = OPTIONS) { return 204; }
|
||
}
|
||
```
|
||
- Каталог `/var/www/drhider-upload/` (владелец `www-data`), НЕ `/home/naeel` (403).
|
||
- Порядок: backup → правка → `nginx -t` → `systemctl reload nginx`.
|
||
|
||
### 2. Flask — новый endpoint pull (`site/routes/api_bp.py`)
|
||
```python
|
||
@api_bp.route("/upload_refs", methods=["POST"])
|
||
def upload_refs():
|
||
data = request.get_json(silent=True) or {}
|
||
sid = data.get("session") or create_session()
|
||
refs = data.get("files") or []
|
||
if not refs:
|
||
return jsonify({"ok": False, "error": "No files"}), 400
|
||
added = 0
|
||
with httpx.Client(timeout=120, follow_redirects=True) as client:
|
||
for ref in refs:
|
||
name, url = ref.get("name"), ref.get("url")
|
||
if not name or not url:
|
||
continue
|
||
content = b"".join(client.stream("GET", url).iter_bytes()) # egress, по частям
|
||
if not add_file(sid, name, content):
|
||
return jsonify({"ok": False, "error": "Session not found"}), 404
|
||
added += 1
|
||
return jsonify({"ok": True, "session": sid, "count": file_count(sid)})
|
||
```
|
||
- Импорт `httpx` (уже в requirements.txt) и `file_count` из session.
|
||
- `stream(...).iter_bytes()` — НЕ `content` целиком (OOM при 100МБ+).
|
||
- Обработка ошибок egress (таймаут/403/404) → вернуть 502 с именем файла.
|
||
|
||
### 3. Фронт (`site/templates/index.html`, `uploadFiles()`)
|
||
Разбить «Фазу 1» на два шага:
|
||
- **Шаг 1a:** каждый файл → `fetch(<ВМ>/drhider-upload/<token>/<name>, {method:'PUT', body:file})`,
|
||
собирать `[{name, size, url}]`. Прогресс — `xhr.upload.onprogress` заменить на `fetch` +
|
||
`ReadableStream` (или оставить XHR, но на PUT к ВМ).
|
||
- **Шаг 1b:** один `POST /api/upload_refs` с JSON `{session, files:[...]}` (<64КБ).
|
||
- Токен: `crypto.randomUUID()`. URL ВМ — константа/`data-атрибут`.
|
||
- Показывать прогресс «загрузка на ВМ» отдельно от «загрузка в Flask».
|
||
|
||
### 4. `requirements.txt` — без изменений (httpx уже есть).
|
||
|
||
## Безопасность и жизненный цикл
|
||
- Токен в пути URL — только `crypto.randomUUID()`, не переиспользуется.
|
||
- Flask после `add_file` делает `DELETE` на URL ВМ (или ВМ чистит по TTL — cron/systemd-timer
|
||
`find /var/www/drhider-upload -mmin +30 -delete`).
|
||
- ВМ отдаёт файл только по валидному URL с токеном (нет открытого листинга: `autoindex off`).
|
||
|
||
## Грабли (из pattern-дока + drhider)
|
||
- CORS: PUT — не simple-метод → браузер шлёт OPTIONS preflight; nginx обязан отвечать 204.
|
||
- `site/` конфликтует со stdlib `site.py` (для loadtest; у drhider — Штурвал, без gunicorn).
|
||
- egress через `httpx` с `stream`, timeout 120; большие ОТВЕТЫ проходят (проверено 50МБ).
|
||
- OOM: не читать файл в память целиком; сессия 500МБ уже ограничивает.
|
||
- Локальные curl на Krupski идут через прокси `172.17.192.1:10808` → для теста ВМ `--noproxy '*'`.
|
||
|
||
## Порядок работ + проверка
|
||
1. ВМ nginx (location + каталог + CORS) → проверить `curl --noproxy '*' -X PUT`.
|
||
2. Flask `/api/upload_refs` → проверить `python3 -c "import py_compile; py_compile.compile(...)"`.
|
||
3. Фронт `uploadFiles()` → `node -c` нет (это EJS/HTML+JS внутри шаблона) — ручная проверка.
|
||
4. Сквозной тест: файл 10МБ → ВМ → Flask → обфускация → ZIP.
|
||
5. Commit + push (правило: после правки + bump версии `VERSION` в `site/app.py`).
|
||
|
||
## Решения (закрыты)
|
||
|
||
- Кластер: сейчас `iot-naeel` (тест), прод — обычный managed. VM-путь обязателен.
|
||
- ВМ: одна, `contracts.kube5s.ru` (`5.172.178.213`), общая с loadtest — добавляем location.
|