upload — переиспользуемые слои загрузки через ВМ
Два самодостаточных слоя для выноса в любой другой проект БЕЗ изменения кода (меняется только конфиг). Поведение 1:1 с drhider v0.0.75.
upload/
frontend/
zip/ # распаковка ZIP (чистые функции)
table/ # слой 1: выбор файлов/папки/архива, дедуп, статусы, таблица
upload/ # слой 2 (фронт): PUT на ВМ + POST /api/upload_refs
backend/
upload_refs/ # слой 2 (бэк): Blueprint upload_refs (SSRF, _safe_name, ретраи)
session/ # in-memory сессия с TTL и лимитами
config.example.json
Что это
| Слой | Где | Ответственность |
|---|---|---|
| 1. Выбор файлов | фронт | таблица, дедуп, раскрытие ZIP, путь, статусы, кнопки |
| 2. Закачка через ВМ | фронт + бэк | PUT на ВМ-буфер (фронт) → upload_refs pull (бэк) → сессия |
| 3. Логика приложения | — | у каждого приложения своя (обфускация, SSE и т.п.). В модуле её НЕТ |
Паттерн (зачем ВМ): шлюз managed-кластера рвёт тела >64КБ, egress не ограничен.
Поэтому: браузер → PUT на ВМ-буфер → Flask POST /api/upload_refs → egress GET → сессия.
Подключение фронта
Подключить ES-модули (<script type="module">) и собрать слой 1:
import { initUploadTable } from './upload/frontend/table/init_upload_table.js';
import { uploadViaVM } from './upload/frontend/upload/upload_via_vm.js';
const cfg = {
allowedExt: ['.pdf', '.doc', '.docx', '.txt', '.md'],
maxFileBytes: 50 * 1024 * 1024,
maxSessionBytes: 500 * 1024 * 1024,
estMbSec: 12,
};
const table = initUploadTable(cfg, {
fileInput: document.getElementById('fileInput'), // <input type="file" multiple>
folderInput: document.getElementById('folderInput'), // <input webkitdirectory>
tableBody: document.getElementById('fileList'), // <tbody>
countEl: document.getElementById('fileCount'),
uploadBtnEl: document.getElementById('uploadBtn'),
onStatus(cls, text) { /* сообщения (cls: ''|'progress'|'done'|'error') */ },
});
// Слой 2 — закачка учитываемых файлов на ВМ:
const res = await uploadViaVM(table.getFiles(), cfg.vmUploadUrl, {
session: currentSid,
onStatus(k, html) { table.setStatus(idxInSf[k], html); }, // маппинг idx → строка таблицы
onUploadStatus(text) { /* статусная строка */ },
});
// res = {ok:true, session, count} | {ok:false, error}
// После этого у вас в сессии res.session лежат файлы — запускайте СВОЮ обработку.
API слоя 1 (initUploadTable(cfg, els) → table):
addFiles(File[])— дедуп + раскрытие ZIP + фильтр + лимиты;getFiles()→[{name, size, file}]— только учитываемые (без сверхлимитных);getOverNames()→Set— имена сверх лимита;setStatus(idx, html)— статус в ячейке таблицы;render()— перерисовать таблицу;clear()— очистить список;setBusy(bool)— заблокировать список на время загрузки/обработки;state— доступ к состоянию (для слоя 3: установитьstate.procдля 3-секционной таблицы).
Подключение бэка
from flask import Flask
from upload.backend.upload_refs import create_upload_refs_blueprint
from upload.backend.session import create_session, add_file, get_files
app = Flask(__name__)
app.register_blueprint(create_upload_refs_blueprint({
"apiPrefix": "/api", # префикс эндпоинтов
"vmUploadPrefix": "https://.../drhider-upload/", # доверенный префикс (SSRF)
"maxFileBytes": 50 * 1024 * 1024,
"pullRetries": 3,
"pullRetryDelay": 2,
}))
Эндпоинт: POST {apiPrefix}/upload_refs — принимает JSON
{"session": "...", "files": [{"name", "size", "url"}]}, тянет каждый файл с ВМ
(SSRF-валидация по vmUploadPrefix, _safe_name, ретраи), кладёт в сессию.
Возвращает {"ok": true, "session", "count"}.
Сессия: create_session() → sid; add_file(sid, name, content) (лимит 500МБ);
get_files(sid) → [(name, bytes), ...] или None. TTL 30 мин (таймер в фоне).
Конфиг (слой 0)
Всё drhider-специфичное задаётся конфигом, а не кодом слоёв:
| Поле | Назначение |
|---|---|
vmUploadUrl |
базовый URL ВМ-буфера для PUT (фронт) |
vmUploadPrefix |
тот же префикс для SSRF-валидации (бэк) |
allowedExt |
расширения документов из папки/архивов |
maxFileBytes / maxSessionBytes |
лимиты 50 МБ / 500 МБ |
apiPrefix |
префикс Blueprint /api |
pullRetries / pullRetryDelay |
ретраи pull (3 × 2с) |
estMbSec |
оценка времени обработки, сек/МБ (только UI) |
Что НЕ трогать
- Слой 3 — логика приложения (обработка файлов из сессии, SSE-прогресс) у каждого своя.
- CORS на ВМ-буфере — если домен приложения другой, правится nginx на ВМ, а не код модуля.