docs: сортировка History по темам (sonnet, opus, llm, sse, ux-frontend, upload, infra, plans, tests, base, code-fixes)
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
# ПЛАН: перенос паттерна «ВМ-буфер + 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.
|
||||
Reference in New Issue
Block a user