Files
upload-platform/LAYER2-RESUME.md
T

146 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# РЕЗЮМЕ И ТЕХНИЧЕСКОЕ ЗАДАНИЕ: СЛОЙ 2 (ЗАГРУЗКА ЧЕРЕЗ ВМ-БУФЕР)
Документ составлен 2026-09-06 для старта нового рабочего контекста в репозитории [upload-platform](upload-platform).
---
## 1. ЦЕЛЬ И АРХИТЕКТУРНЫЕ ГРАНИЦЫ
В репозитории [upload-platform](upload-platform) разрабатывается и изолированно тестируется переиспользуемый встраиваемый модуль, состоящий из двух независимых слоёв:
1. **Слой 1 (Выбор файлов / File Picker)****ГОТОВ И ПРОТЕСТИРОВАН (v0.1.13)**:
- Фронтенд-модуль на Vanilla JS + `fflate`.
- Поддерживает выбор отдельных файлов, выбор папок (`webkitdirectory`), распаковку ZIP и вложенных архивов.
- Дедупликация, фильтрация по расширениям, древовидный UI, лимиты (`maxEntries`, `maxEntryBytes`, `maxTotalBytes`, `maxDepth`).
- Возвращает чистый плоский массив выбранных объектов `File` через метод `picker.getFiles()`. Не зависит от способа передачи данных.
2. **Слой 2 (Транзитная загрузка файлов через ВМ-буфер)****ТЕКУЩАЯ ЗАДАЧА**:
- Задача слоя: доставить файлы из браузера в память сессии бэкенд-приложения (Flask).
- **Почему нужен буфер**: шлюз ingress managed-кластера Kubernetes (`pythonk8s`) обрывает входящие HTTP-запросы с телом более 64 КБ. Прямой `POST /upload` больших файлов в кластер невозможен. При этом исходящий трафик (egress) из кластера не ограничен.
- **Решение**: Браузер загружает файлы во внешний буфер на ВМ, а бэкенд вытягивает их исходящими `GET`-запросами (pull) в оперативную память своей сессии.
- **Потребители модуля**: сервис анонимизации `drhider` и сервис сверки договоров (`contractor`). Оба сервиса встраивают Слой 1 + Слой 2 как подпапку `upload/` без изменения исходного кода модулей (только собственный конфиг) и поверх накладывают свою бизнес-логику (Слой 3: LLM, обфускация, парсинг, diff).
---
## 2. ФАКТИЧЕСКОЕ СОСТОЯНИЕ ИНФРАСТРУКТУРЫ (ПРОВЕРЕНО НА ВМ И В K8S)
Проверено 2026-09-06 прямыми командами на ВМ и в кластере:
1. **ВМ `5.172.178.213` (contracts.kube5s.ru)**:
- Доступ: `ssh -i ~/.ssh/naeel_vm_id_ed25519 naeel@5.172.178.213`.
- В `nginx` настроены WebDAV-буферы:
- `/drhider-upload/` $\rightarrow$ `alias /var/www/drhider-upload/;`, CORS: `https://drhider.pythonk8s.dev.nubes.ru`.
- `/contracts-upload/` $\rightarrow$ `alias /var/www/contracts-upload/;`, CORS: `https://contractor.pythonk8s.dev.nubes.ru`.
- **Проблема текущей реализации на ВМ**:
- Nginx WebDAV пишет файлы на диск (корневой раздел).
- Cron каждые 5 минут удаляет файлы старше 30 минут: `find /var/www/drhider-upload -type f -mmin +30 -delete`.
- По требованию безопасности файлы не должны оседать на диске — обработка и буферизация должны быть строго в оперативной памяти (RAM).
2. **Kubernetes-кластер (`iot-naeel`)**:
- Под `drhider`: namespace `20a75175-a58c-49cb-b8fa-e86367b1a8dc`, хост `drhider.pythonk8s.dev.nubes.ru`.
- Под `contractor`: namespace `b4523aba-b5e6-40f1-be56-bb4d2509357c`, хост `contractor.pythonk8s.dev.nubes.ru`.
- Под `uploader-dev` (полигон): namespace `0e108526-5bb2-4757-9499-3f0bac4f0e83`, хост `uploader-dev.pythonk8s.dev.nubes.ru` (на нём крутится `upload-platform` 0.1.13).
---
## 3. ПРИНЦИПИАЛЬНЫЕ АРХИТЕКТУРНЫЕ ИЗМЕНЕНИЯ СЛОЯ 2
### Изменение 1: Пофайловый транзит (Streaming / Per-File Transit) вместо батча
- **Как было раньше**: Браузер в цикле заливал все файлы пачки в буфер на ВМ, и только в самом конце отправлял во Flask один общий запрос `/api/upload_refs` со списком всех ссылок. Буфер был вынужден одновременно хранить все файлы пачки (до сотен мегабайт).
- **Как должно быть**:
1. Браузер берет **один файл** $\rightarrow$ делает `PUT` в буфер на ВМ.
2. Браузер сразу делает `POST /api/upload_refs` для **этого одного файла**.
3. Flask вытягивает его исходящим потоковым `GET` прямо в память сессии (`site/session.py`) и сразу отправляет `DELETE` на ВМ.
4. Файл на ВМ удален. Браузер переходит к следующему файлу.
- **Результат**: В буфере на ВМ в любой момент времени находится максимум один файл. Общий объем памяти буфера минимален.
### Изменение 2: Хранение строго в памяти (RAM)
- Никаких временных файлов на диске.
- На бэкенде Flask файлы хранятся в RAM-структуре сессии (`upload/backend/session/state.py`).
- На ВМ для буфера не должен использоваться сброс на диск (на стороне Nginx требуется проксирование в память или легковесный in-memory микросервис).
### Изменение 3: Полная параметризация (Zero Hardcode)
- Прямой IP из браузера поверх HTTPS невозможен (Mixed Content, отсутствие доверенного TLS-сертификата).
- В коде не должно быть зашитых хостов (`contracts.kube5s.ru` и т.д.).
- Все адреса передаются через конфигурацию:
- Фронтенд: `vmUploadUrl` (например, `https://contracts.kube5s.ru/drhider-upload/` или локальный URL).
- Бэкенд: `vmUploadPrefix` (для обязательной SSRF-валидации входящих URL перед pull).
### Изменение 4: Автономный mock-буфер для тестов в `upload-platform`
- Тесты в [upload-platform](upload-platform) не должны зависеть от внешней сети или чужих боевых ВМ.
- В тестовый стенд и dev-сервер [site/app.py](upload-platform/site/app.py) встраивается локальный легковесный in-memory mock WebDAV (обработка `PUT`, `GET`, `DELETE`), что позволяет гонять Playwright- и Python-тесты полностью оффлайн.
---
## 4. СТРУКТУРА МОДУЛЯ В `upload-platform`
```
upload-platform/
├── upload/
│ ├── frontend/
│ │ ├── index.js # Точка входа Слоя 1 (initFilePicker)
│ │ ├── table/ # Логика и UI Слоя 1
│ │ ├── zip/ # Распаковка архивов (fflate)
│ │ └── upload/ # СЛОЙ 2 (ФРОНТЕНД):
│ │ ├── put_to_vm.js # XHR PUT одного файла на буфер с прогрессом и abort
│ │ └── upload_via_vm.js # Пофайловый транзит (PUT -> upload_refs -> repeat)
│ └── backend/ # СЛОЙ 2 (БЭКЕНД):
│ ├── __init__.py
│ ├── upload_refs/
│ │ ├── __init__.py
│ │ ├── blueprint.py # Flask Blueprint (POST /api/upload_refs)
│ │ ├── pull_file.py # Исходящий потоковый GET с ретраями (httpx)
│ │ ├── safe_name.py # Санитизация имен (защита от path traversal)
│ │ └── config.py # Дефолтные параметры (retries, delay)
│ └── session/
│ ├── __init__.py
│ ├── state.py # In-memory хранилище сессий (_sessions, lock, TTL)
│ ├── create_session.py # Создание сессии (UUID)
│ ├── add_file.py # Добавление файла с проверкой лимита памяти
│ ├── get_files.py # Получение списка файлов
│ ├── store_result.py # Сохранение ZIP-результата
│ ├── store_csv.py # Сохранение CSV-результата
│ ├── ttl.py # touch, pause_ttl, resume_ttl
│ ├── cancel.py # Мягкая отмена сессии (Event)
│ └── cleanup.py # Очистка сессии
├── site/
│ ├── app.py # Flask demo (Слой 1 + Слой 2 + local mock buffer)
│ └── templates/index.html # Demo UI со сквозным сценарием выбора и загрузки
├── tests/ # Тесты Слоя 2 (pytest + browser smoke)
├── build.mjs # Сборка бандлов (ESM / IIFE)
├── package.json # Версионирование платформы
└── requirements.txt # Зависимости Python (Flask, httpx, pytest)
```
---
## 5. ПОШАГОВЫЙ ПЛАН РЕАЛИЗАЦИИ В НОВОМ ЧАТЕ
1. **Шаг 1. Перенос и адаптация бэкенда Слоя 2**:
- Перенести `backend/session/` и `backend/upload_refs/` из [upload/backend](upload/backend) в [upload-platform/upload/backend](upload-platform/upload/backend).
- В `upload-platform/requirements.txt` зафиксировать `httpx>=0.27.0`.
- Проверить чистоту импортов и типизацию.
2. **Шаг 2. Перенос и адаптация фронтенда Слоя 2 на пофайловый транзит**:
- Создать [upload-platform/upload/frontend/upload/put_to_vm.js](upload-platform/upload/frontend/upload/put_to_vm.js) (XHR PUT с прогрессом и возможностью `abort`).
- Создать [upload-platform/upload/frontend/upload/upload_via_vm.js](upload-platform/upload/frontend/upload/upload_via_vm.js) с реализацией **пофайловой передачи**:
- цикл по файлам;
- загрузка файла $k$ на буфер;
- немедленный вызов `/api/upload_refs` для файла $k$;
- обновление прогресса в UI;
- переход к файлу $k+1$.
3. **Шаг 3. Локальный mock-буфер и интеграция в `site/app.py`**:
- Встроить в [upload-platform/site/app.py](upload-platform/site/app.py) тестовый Blueprint mock-буфера (`PUT`, `GET`, `DELETE` в оперативной памяти).
- Подключить `create_upload_refs_blueprint` к приложению.
- Обновить [upload-platform/site/templates/index.html](upload-platform/site/templates/index.html), добавив кнопку «Загрузить» и отображение прогресса пофайловой передачи.
4. **Шаг 4. Написание автоматических тестов**:
- Python unit-тесты (`pytest`): `safe_name`, `session` (TTL, лимиты, add/get), `upload_refs` (SSRF-блокировка, pull с mock-буфера, удаление после pull).
- Playwright browser-тесты: сквозной сценарий от выбора файлов в `initFilePicker` до их успешного появления в сессии Flask.
5. **Шаг 5. Сборка, bump версии и документация**:
- Обновить `build.mjs` и `package.json` (bump версии).
- Написать подробный [upload-platform/README.md](upload-platform/README.md) с инструкцией для агентов, как встроить оба слоя в сторонний сервис за 2 минуты.
- Фиксация коммитом и пуш.