Files
upload-platform/LAYER2-RESUME.md
T

13 KiB
Raw Blame History

РЕЗЮМЕ И ТЕХНИЧЕСКОЕ ЗАДАНИЕ: СЛОЙ 2 (ЗАГРУЗКА ЧЕРЕЗ ВМ-БУФЕР)

Документ составлен 2026-09-06 для старта нового рабочего контекста в репозитории upload-platform.


1. ЦЕЛЬ И АРХИТЕКТУРНЫЕ ГРАНИЦЫ

В репозитории 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 не должны зависеть от внешней сети или чужих боевых ВМ.
  • В тестовый стенд и dev-сервер 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-platform/upload/backend.
    • В upload-platform/requirements.txt зафиксировать httpx>=0.27.0.
    • Проверить чистоту импортов и типизацию.
  2. Шаг 2. Перенос и адаптация фронтенда Слоя 2 на пофайловый транзит:

  3. Шаг 3. Локальный mock-буфер и интеграция в site/app.py:

    • Встроить в upload-platform/site/app.py тестовый Blueprint mock-буфера (PUT, GET, DELETE в оперативной памяти).
    • Подключить create_upload_refs_blueprint к приложению.
    • Обновить 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 с инструкцией для агентов, как встроить оба слоя в сторонний сервис за 2 минуты.
    • Фиксация коммитом и пуш.