[--execute]`: dry run по умолчанию, local ↔ S3 и S3 ↔ S3 без удаления source. - Последовательное копирование с проверкой размера и полным SHA-256 readback destination; конфликтующий объект не перезаписывается. Временный диск — максимум один переносимый файл дополнительно к постоянному destination. - Durable migration journal позволяет продолжить прерванную операцию с теми же конфигурациями. Незавершённый или повреждённый journal блокирует startup; backend identity меняется только после проверки всех файлов. - Native `uploads`/`upload_stats`, slots и JWT сохраняются; поддерживаются native локальные файлы без Rust completion sidecar. Миграция требует остановки RustXMPP и Prosody, использующих общий `data_path`. - Multipart intent сохраняется до создания удалённого upload. Recovery находит потерянный UploadId через paginated listing и очищает только точный key принадлежащего slot. При сохранных metadata cleanup не зависит от lifecycle; lifecycle остаётся дополнительной защитой, а не гарантией. - Перед completion сохраняется durable marker. Неопределённый результат completion при отсутствии подтверждённого объекта помещает только этот slot в quarantine: PUT 409, retention не удаляет его, maintenance повторяет сверку. Startup продолжается; сохраняется single-use семантика. - Добавлены network regressions миграции и расширены проверки multipart recovery, потерянных ответов, отмены запросов и конфликтов destination. Настройка, команды миграции, recovery и ограничения отката: [docs/HTTP-UPLOAD.md](docs/HTTP-UPLOAD.md). Команды, результаты и границы подтверждения, включая qualification реального object store и 10 GiB: [docs/VALIDATION-0.7.9.md](docs/VALIDATION-0.7.9.md). Итог: 265 тестов, нагрузка 500 TLS клиентов и native reference — успешно. Реальные 10 GiB через MinIO (описанный test adapter), 1280parts и полный SHA-256 пройдены; подробности — [VALIDATION-0.7.9](docs/VALIDATION-0.7.9.md).
252 lines
19 KiB
Markdown
252 lines
19 KiB
Markdown
# HTTP File Upload (0.7.9)
|
||
|
||
XEP-0363 component `http_file_share` выдаёт slots через XMPP и обслуживает
|
||
HTTP PUT/GET/HEAD. Доступны локальные файлы и S3-compatible object storage.
|
||
В S3-режиме RustXMPP работает как gateway: клиент получает URL component,
|
||
а сервер подписывает запросы к bucket по AWS Signature Version 4. Это не
|
||
выдача клиенту S3 credentials или presigned bucket URLs.
|
||
|
||
## Несколько components
|
||
|
||
Каждый component сохраняет собственные XMPP JID, discovery, размер файла,
|
||
квоты, slots, JWT secret, backend и HTTP mount. HTTP-маршрут выбирается по
|
||
`Host` и `http_paths.file_share`. Можно использовать разные HTTP hosts с
|
||
одинаковым mount либо один host с непересекающимися mounts:
|
||
|
||
```lua
|
||
-- Добавьте к существующей конфигурации; сохраните VirtualHost, TLS и модули.
|
||
Component "upload.example.org" "http_file_share"
|
||
http_host = "files.example.org";
|
||
http_paths = { file_share = "/personal"; }
|
||
http_file_share_backend = "local";
|
||
http_file_share_size_limit = 10737418240; -- 10 GiB
|
||
http_file_share_daily_quota = 21474836480;
|
||
http_file_share_global_quota = 107374182400;
|
||
http_file_share_expires_after = "7d";
|
||
|
||
Component "media.example.org" "http_file_share"
|
||
http_host = "files.example.org";
|
||
http_paths = { file_share = "/media"; }
|
||
http_file_share_backend = "local";
|
||
http_file_share_size_limit = 104857600;
|
||
```
|
||
|
||
Для одного effective HTTP host одинаковые или вложенные mounts отвергаются
|
||
при startup с сообщением `ambiguous HTTP upload components`, содержащим
|
||
имена обоих components и пути. Например, `/files` конфликтует с `/files`
|
||
и `/files/private`; `/files` и `/files-other` не конфликтуют. Mount `/`
|
||
пересекается со всеми mounts этого host. Hosts сравниваются без учёта
|
||
регистра; порт в HTTP Host не выбирает отдельный component.
|
||
|
||
WebSocket, BOSH и `http_altconnect` используют общие HTTP listeners вместе
|
||
с uploads. Их маршруты обрабатываются соответствующим middleware. Выделяйте
|
||
uploads собственные mounts, чтобы действующие transport/discovery endpoints
|
||
не перекрывали публичные адреса загрузки.
|
||
|
||
`http_external_url` задаёт публичную базу, сохраняя внутренний mount.
|
||
`http_file_share_base_url` явно переопределяет полный публичный upload prefix.
|
||
При reverse proxy настройте переписывание пути и передавайте правильный Host:
|
||
одного изменения рекламируемого URL недостаточно для настройки proxy.
|
||
Пример конфигурации proxy — [HTTP-PROXY.md](HTTP-PROXY.md).
|
||
|
||
## S3-compatible backend
|
||
|
||
```lua
|
||
Component "upload-s3.example.org" "http_file_share"
|
||
http_host = "files.example.org";
|
||
http_paths = { file_share = "/objects"; }
|
||
http_external_url = "https://files.example.org";
|
||
http_file_share_backend = "s3";
|
||
http_file_share_size_limit = 10737418240;
|
||
http_file_share_daily_quota = 21474836480;
|
||
http_file_share_global_quota = 107374182400;
|
||
http_file_share_s3 = {
|
||
endpoint = "https://objects.example.org";
|
||
bucket = "xmpp-uploads";
|
||
region = "us-east-1";
|
||
access_key_env = "RUSTXMPP_S3_ACCESS_KEY";
|
||
secret_key_env = "RUSTXMPP_S3_SECRET_KEY";
|
||
-- Только для временных credentials:
|
||
-- session_token_env = "RUSTXMPP_S3_SESSION_TOKEN";
|
||
key_prefix = "upload-s3";
|
||
}
|
||
```
|
||
|
||
Переменные должны присутствовать в окружении процесса сервера при startup,
|
||
например через защищённый EnvironmentFile службы. Их отсутствие вызывает
|
||
ошибку startup. Доступны и строковые поля `access_key`, `secret_key`,
|
||
`session_token`; соответствующее поле `*_env` имеет приоритет. Credentials
|
||
не включаются в сохранённую backend identity.
|
||
|
||
Endpoint использует path-style bucket addressing. HTTPS требуется для
|
||
нелокальных S3 endpoints. HTTP допускается только для `localhost` или
|
||
loopback IP, например `http://127.0.0.1:9000` в изолированном тесте.
|
||
Production endpoint должен иметь сертификат, доверенный системным root CAs.
|
||
Не путайте HTTPS object-store endpoint с публичным HTTPS upload URL:
|
||
оба соединения настраиваются независимо. Для публичных клиентов используйте
|
||
HTTPS listener либо существующий TLS reverse proxy с HTTPS advertised URL.
|
||
|
||
Bucket должен разрешать создание multipart upload, загрузку частей, завершение,
|
||
abort, `ListMultipartUploads` (`s3:ListBucketMultipartUploads`), HEAD/GET
|
||
и удаление объектов для retention.
|
||
Перед CreateMultipartUpload сервер сохраняет intent для принадлежащего ему
|
||
slot. Если ответ с UploadId потерян, recovery перечисляет multipart uploads
|
||
с pagination и прерывает только uploads с полным совпадением object key;
|
||
соседние keys с тем же prefix не затрагиваются. Сохранных native metadata,
|
||
подтверждающих владение slot, достаточно и для legacy records без intent или
|
||
UploadId. Cleanup такого slot не зависит от bucket lifecycle.
|
||
|
||
Lifecycle `AbortIncompleteMultipartUpload` рекомендуется как дополнительная
|
||
защита, например при утрате metadata или внешних orphan uploads. Он не даёт
|
||
абсолютной гарантии очистки; scope, сроки и выполнение правил зависят от
|
||
object store. Не запускайте внешних writers с теми же slot keys: cleanup
|
||
предполагает исключительное владение ими.
|
||
|
||
Gateway передаёт файл частями по 8 MiB и ограничивает число одновременно
|
||
активных S3 uploads восемью на component. Файл целиком не буферизуется в памяти и не
|
||
сохраняется во временный локальный файл. Служебная память, TLS/HTTP buffers
|
||
и буфер текущего входящего frame добавляются к памяти частей; ограничение
|
||
не является гарантией точного общего RSS. Ограничение размера применяется
|
||
и к chunked PUT, а квоты учитывают зарезервированные slots component.
|
||
|
||
## Metadata, recovery и смена credentials
|
||
|
||
XMPP upload metadata остаются в native хранилище `uploads` и `upload_stats`
|
||
в `data_path`. S3 заменяет хранение содержимого файлов, а не XMPP metadata.
|
||
Сохраняйте `data_path` вместе с bucket: один bucket не восстанавливает
|
||
владельцев, quotas и slots. В `rust_uploads.json` сохраняются служебное
|
||
состояние slots, completion, multipart intent и незавершённые UploadId;
|
||
файл не заменяет native архив. `rust_upload_backend.json` фиксирует backend identity.
|
||
|
||
Перед открытием listeners сервер восстанавливает незавершённые S3 records:
|
||
прерывает принадлежащие slot multipart uploads, затем проверяет удалённый
|
||
объект и размер и учитывает уже завершённый commit. Ошибка обязательного
|
||
recovery, кроме quarantine completion, останавливает startup. Перед
|
||
CompleteMultipartUpload сохраняется `completion_attempted`. Если результат
|
||
completion неизвестен и после cleanup
|
||
HEAD ещё возвращает 404, только этот slot остаётся в quarantine: PUT возвращает
|
||
409, retention его не удаляет, остальные slots и startup продолжают работу.
|
||
Maintenance каждые 60 секунд повторяет сверку; подтверждённый объект
|
||
ожидаемого размера снимает quarantine и завершает recovery. Неопределённый
|
||
результат требует сверки оператором с object store, если объект так и не
|
||
появился. Корректный явный S3 completion error допускает повтор после
|
||
reconciliation; network/timeout или некорректный ответ не разрешают
|
||
перезапись возможного позднего commit. Ошибки обязательных listing/HEAD
|
||
по-прежнему останавливают startup.
|
||
PUT остаётся single-use; повторная запись завершённого slot возвращает conflict.
|
||
|
||
При наличии slots нельзя незаметно переключить local/S3 backend, endpoint,
|
||
bucket или prefix: startup отвергает изменение backend identity. Добавление
|
||
S3 к существующим local slots также требует явной миграции через offline CLI,
|
||
описанный ниже; не удаляйте identity-файл для обхода проверки.
|
||
|
||
Для rotation S3 credentials обновите переменные окружения/секреты службы
|
||
и перезапустите сервер, сохранив endpoint, bucket и prefix. Новая identity
|
||
credentials не содержит, поэтому их rotation не считается миграцией.
|
||
Сначала обеспечьте новым credentials доступ к существующим объектам и
|
||
multipart cleanup. Rotation `http_file_share_secret` — отдельная операция:
|
||
смена JWT signing secret делает ранее выданные PUT tokens недействительными.
|
||
|
||
## Offline миграция local ↔ S3 и S3 ↔ S3
|
||
|
||
Остановите RustXMPP и Prosody, использующие этот `data_path`, и сохраните
|
||
резервную копию metadata и содержимого backend. Подготовьте две полные
|
||
конфигурации: `current.cfg.lua` описывает действующий source, `new.cfg.lua` —
|
||
destination. Обе должны содержать тот же VirtualHost, upload component,
|
||
`data_path`, HTTP/HTTPS ports, публичные маршруты и `http_file_share_secret`;
|
||
меняйте только параметры backend. Credentials обоих S3 хранилищ должны быть
|
||
доступны CLI; используйте отдельные переменные окружения для source и
|
||
destination. Для каждого component выполняется отдельная команда:
|
||
|
||
```sh
|
||
# План: без копирования объектов, изменения journal или backend identity.
|
||
rust-xmpp upload-migrate current.cfg.lua new.cfg.lua --component upload.example.org
|
||
|
||
# Копирование и проверка; при остановке повторите эту же команду.
|
||
rust-xmpp upload-migrate current.cfg.lua new.cfg.lua --component upload.example.org --execute
|
||
|
||
# После сообщения Migration committed запускайте сервер с новой конфигурацией.
|
||
rust-xmpp serve new.cfg.lua --experimental
|
||
```
|
||
|
||
Команда захватывает exclusive lock `data_path`, исключающий параллельный
|
||
RustXMPP или другую миграцию; Prosody этот lock не использует. Source и
|
||
destination должны иметь разные backend identities. Поддерживаются local →
|
||
S3, S3 → local и S3 → другой endpoint, bucket или prefix. Перед миграцией
|
||
восстановите незавершённые uploads исходным сервером. Dry run проверяет
|
||
конфигурации и metadata и определяет объекты для копирования; полный SHA-256
|
||
рассчитывается при `--execute`.
|
||
|
||
Копируются завершённые файлы, включая native файлы без Rust completion
|
||
sidecar и файлы, опубликованные перед сбоем записи completion cache.
|
||
Отсутствующие незавершённые reservations остаются в metadata. Native
|
||
`uploads`, `upload_stats`, владельцы, slots и JWT не переписываются.
|
||
Source objects никогда не удаляются. Для каждого файла CLI проверяет размер,
|
||
считает SHA-256 source и повторно читает destination для сверки; ETag не
|
||
используется как checksum. Существующий destination с другим содержимым
|
||
вызывает отказ без перезаписи.
|
||
|
||
Миграция обрабатывает файлы последовательно через приватный временный файл:
|
||
нужно свободное место размером максимум в один переносимый файл дополнительно
|
||
к постоянному local destination. Память ограничена buffers частей, файл
|
||
целиком в RAM не загружается. После сбоя временный spool удаляется и текущий
|
||
файл читается заново; это возобновление по объектам, не по byte offset.
|
||
|
||
`rust_upload_migration.json` хранит identities, fingerprint metadata,
|
||
checksums и прогресс. Повторный запуск с теми же конфигурациями сверяет source
|
||
и уже скопированные файлы, восстанавливает multipart cleanup и продолжает
|
||
работу. Backend identity переключается только после проверки всех объектов.
|
||
Незавершённый или повреждённый journal блокирует startup до offline recovery,
|
||
даже если component убран из новой конфигурации. Не удаляйте journal для
|
||
обхода барьера и не редактируйте metadata во время миграции.
|
||
|
||
Сохранённый source позволяет сверить данные и подготовить откат, но простая
|
||
подмена конфигурации после commit отвергается identity guard. Пока migration
|
||
не завершена, продолжите её с теми же параметрами. После commit выполняйте
|
||
обратную offline миграцию с текущей конфигурацией как source и прежней как
|
||
destination. Новые uploads могли появиться только в destination; откат к
|
||
старой резервной копии без их переноса потеряет эти изменения. Конфликтующие
|
||
объекты обратная миграция также не перезаписывает. Удаление прежнего source
|
||
после проверки результата — отдельное решение оператора.
|
||
|
||
## Проверки и границы подтверждения
|
||
|
||
`tests/upload_multi_network.py` проверяет настоящий binary через TLS XMPP
|
||
и HTTP: отдельные/shared hosts, discovery и IQ slots, квоты, изоляцию JWT,
|
||
PUT/GET и restart, а также композицию с WS/BOSH/host-meta и отказ при
|
||
неоднозначных mounts.
|
||
|
||
`tests/upload_large_network.py` выполняет opt-in передачу полного файла по
|
||
проверяемому HTTPS с local backend и записывает измерения в JSON. Для default
|
||
10 GiB необходимо более 10 GiB свободного места. Это отдельная проверка от S3
|
||
fixture, и её результат не подтверждает передачу 10 GiB в object store.
|
||
|
||
`tests/upload_s3_network.py` использует независимый локальный HTTP fixture,
|
||
который повторно вычисляет SigV4 и реализует S3 protocol для проверки gateway,
|
||
multipart, чтения и обработки ошибок. Это не qualification на AWS S3,
|
||
MinIO или другом production object store. Их совместимость требует отдельного
|
||
запуска в целевой среде.
|
||
|
||
`tests/upload_migration_network.py` проверяет offline миграцию с независимым
|
||
SigV4 fixture: local ↔ S3, S3 ↔ S3, dry run, прерывание/возобновление,
|
||
checksums, сохранность source и native metadata, блокировку startup.
|
||
|
||
`tests/upload_minio_network.py` — opt-in запуск с настоящим MinIO executable.
|
||
По умолчанию он передаёт 10 GiB через HTTPS gateway в MinIO, проверяет полный
|
||
GET и SHA-256, multipart, Range, restart и crash cleanup. Малый `--size`
|
||
проверяет smoke path, но не подтверждает передачу большого объекта.
|
||
|
||
Итоговые команды, результаты и измерения release validation публикуются в
|
||
[release validation report](VALIDATION-0.7.9.md). Предыдущий результат 10 GiB
|
||
с local backend, SHA-256, Range за 4 GiB и выборочным RSS опубликован в
|
||
[отчёте 0.7.8](VALIDATION-0.7.8.md); сам по себе он не подтверждает S3 backend.
|
||
|
||
|
||
В 0.7.9 фактические 10 GiB HTTPS PUT/GET через MinIO успешно пройдены: 1280
|
||
multipart parts, SHA-256 совпал, Range/restart/crash cleanup проверены.
|
||
MinIO собран из upstream release с test-only fallback перечисления интерфейсов
|
||
на loopback при EPERM; S3/auth/storage code не менялся. Точный diff и hashes
|
||
в VALIDATION-0.7.9.md. Немодифицированный MinIO/AWS/distributed deployment этим
|
||
не сертифицируются. MINIO_BINARY перед verify-local.sh включает opt-in 10 GiB
|
||
прогон; для обычной среды используйте собственный MinIO executable.
|