112c84f11aaed5f18044d33cdd110fcf2c99fef3
Upload Platform
Модульная платформа загрузки и транзита документов (версия 0.2.0). Состоит из двух независимых автономных слоёв с чистыми API:
- Слой 1 (File Picker): Клиентский выбор файлов, папок и вложенных ZIP-архивов с дедупликацией, фильтрацией по расширениям, проверкой лимитов и визуализацией в виде дерева.
- Слой 2 (Transit Upload via VM Buffer): Пофайловый потоковый транзит файлов из браузера в оперативную память (RAM) сессии бэкенда через внешний WebDAV-буфер на ВМ. Решает проблему ограничения Ingress Kubernetes на входящий payload (64 КБ) за счёт исходящего pull из кластера.
Архитектура слоёв и API
[ Браузер ]
│
├─► Слой 1: initFilePicker() ──► picker.getFiles() -> File[]
│
└─► Слой 2: uploadViaVM(files, { vmUploadUrl, backendUploadUrl })
│
├─ 1. PUT файл k ──► [ ВМ-буфер (RAM) ]
│ ▲
└─ 2. POST /api/upload_refs│
│ │
▼ │
[ Flask Backend ] ────┘ 3. Потоковый GET (pull) в RAM
│
├─► 4. DELETE файл k с ВМ
└─► 5. session.add_file(sid, name, bytes)
│
▼
[ Слой 3: drhider / contractor ]
Слой 1: File Picker API
- Вход: DOM-контейнер (
mount), разрешённые расширения (allowedExt), лимиты (limits). - Выход:
picker.getFiles()возвращает плоский массив объектов{ path, name, size, file }. - Автономность: Не зависит от транспорта и бэкенда.
Слой 2: Transit Upload API
- Фронтенд: функция
uploadViaVM(files, options):vmUploadUrl: базовый URL буфера на ВМ (Zero Hardcode).backendUploadUrl: эндпоинт приёма ссылок (/api/upload_refs).signal:AbortSignalдля отмены загрузки.onProgress: колбэк прогресса загрузки текущего файла.onFileStatus: колбэк статуса строки файла.onFileComplete: колбэк завершения переноса файла в сессию бэкенда.
- Бэкенд: Flask Blueprint
create_upload_refs_blueprint(cfg):- Принимает ссылку на один файл (пофайловый транзит).
- Проверяет URL на доверенный префикс (
vmUploadPrefix, защита от SSRF). - Санитизирует имя файла (
safe_name, защита от path traversal). - Вытягивает байты потоком исходящим
GET(ретраи, таймауты) прямо в RAM. - Немедленно удаляет файл из буфера на ВМ (
DELETE). - Сохраняет файл в RAM сессии (
upload/backend/session). - При необходимости вызывает
onFileReceived(sid, name, content)для передачи в Слой 3.
Структура репозитория
upload-platform/
├── upload/ # Переиспользуемый встраиваемый модуль
│ ├── frontend/
│ │ ├── index.js # Точка входа фронтенда (initFilePicker, uploadViaVM, putToVm)
│ │ ├── table/ # UI и дерево файлов (Слой 1)
│ │ ├── zip/ # Распаковка архивов fflate (Слой 1)
│ │ └── upload/ # Транспорт Слоя 2:
│ │ ├── put_to_vm.js # XHR PUT одного файла на буфер с прогрессом и abort
│ │ └── upload_via_vm.js # Пофайловый транзит (PUT -> upload_refs -> repeat)
│ └── backend/ # Серверная часть Слоя 2:
│ ├── upload_refs/
│ │ ├── blueprint.py # Flask Blueprint (POST /api/upload_refs)
│ │ ├── pull_file.py # Исходящий потоковый GET с ретраями (httpx)
│ │ ├── safe_name.py # Защита от path traversal
│ │ └── config.py # Дефолтные параметры
│ └── session/
│ ├── state.py # In-memory хранилище сессий (_sessions, TTL, lock)
│ ├── create_session.py # Создание сессии (UUID)
│ ├── add_file.py # Добавление файла в RAM с проверкой лимита
│ ├── get_files.py # Чтение файлов сессии
│ ├── store_result.py # Хранение результирующего ZIP
│ ├── store_csv.py # Хранение CSV
│ ├── ttl.py # touch, pause_ttl, resume_ttl
│ ├── cancel.py # Мягкая отмена сессии (threading.Event)
│ └── cleanup.py # Удаление сессии
├── site/ # Тестовый стенд и demo-сервер
│ ├── app.py # Flask demo + встроенный RAM mock WebDAV буфер
│ ├── templates/index.html # Demo UI со сквозным сценарием выбора и загрузки
│ └── static/style.css
├── dist/ # Готовые бандлы (собираются через build.mjs)
│ ├── file-picker.esm.js
│ └── file-picker.iife.js
├── tests/ # Автоматические тесты (100% offline)
│ ├── test_safe_name.py # Тесты санитизации путей
│ ├── test_session.py # Тесты RAM-хранилища сессий и лимитов
│ ├── test_upload_refs.py # Тесты Blueprint, SSRF-защиты и pull
│ ├── test_app_integration.py # Сквозной интеграционный тест site/app.py
│ └── test_upload_layer2.test.mjs # Юнит-тесты фронтенда Слоя 2 (node:test)
├── build.mjs # esbuild сборщик
├── package.json # Версия 0.2.0, npm-скрипты
└── requirements.txt # Python зависимости (Flask, httpx, pytest)
Быстрый старт и запуск
1. Установка зависимостей
pip install -r requirements.txt
npm install
2. Сборка фронтенд-бандлов
npm run build
3. Запуск локального demo-сервера
python site/app.py
# Сервер доступен по адресу http://127.0.0.1:5000/
В стенде встроен локальный in-memory mock-буфер (/mock-buffer/), поэтому demo полностью функционально локально без подключения к боевой ВМ.
4. Запуск всех тестов
# Бэкенд тесты (pytest)
pytest tests/ -v
# Фронтенд тесты (Node.js test runner)
npm test
Инструкция: интеграция в сторонний сервис за 2 минуты
Чтобы встроить оба слоя в целевой сервис (drhider, contractor и др.):
1. Скопировать модуль
Скопировать директорию upload/ в корень целевого проекта:
cp -r upload-platform/upload/ /path/to/service/upload/
2. Подключить бэкенд во Flask
В файле создания Flask-приложения (например, app.py):
from upload.backend.upload_refs import create_upload_refs_blueprint
from upload.backend.session import get_files
upload_bp = create_upload_refs_blueprint({
"apiPrefix": "/api",
"vmUploadPrefix": "https://contracts.kube5s.ru/drhider-upload/", # Префикс вашего буфера
"maxFileBytes": 50 * 1024 * 1024,
"maxSessionBytes": 500 * 1024 * 1024,
})
app.register_blueprint(upload_bp)
3. Подключить фронтенд
В шаблоне страницы:
<div id="file-picker"></div>
<button id="upload-btn">Загрузить</button>
<script src="/dist/file-picker.iife.js"></script>
<script>
const picker = FilePicker.initFilePicker({
mount: '#file-picker',
allowedExt: ['.pdf', '.docx', '.txt'],
});
document.getElementById('upload-btn').addEventListener('click', async () => {
const files = picker.getFiles();
const result = await FilePicker.uploadViaVM(files, {
vmUploadUrl: 'https://contracts.kube5s.ru/drhider-upload/',
backendUploadUrl: '/api/upload_refs',
onFileStatus: (idx, status) => console.log(`File ${idx}: ${status}`),
});
if (result.ok) {
console.log('Успешно загружено в сессию:', result.session);
// Запуск Слоя 3 (обработка файлов сессии)
}
});
</script>
4. Получение файлов в Слое 3
Бизнес-логика получает файлы из оперативной памяти:
files = get_files(session_id)
# files: [("document.pdf", b"...bytes..."), ...]
Никаких временных файлов на диске. Полная безопасность и изоляция.
Languages
JavaScript
50.1%
Python
36.7%
HTML
9.4%
CSS
3.8%