Skip to content
PПромтбук
RUEN
04Архитектура

Архитектура загрузки файлов: presigned URLs, multipart, безопасность

Direct browser → S3 через presigned URLs, валидация и virus scanning, MIME enforcement, thumbnails, retention, scale больших файлов (multipart, resumable).

Действуй как senior backend архитектор. Спроектируй загрузку файлов: типы {{file_types}}, max size {{max_size}}, storage {{storage}}.

1. Upload flow: direct-to-storage

Never проксировать файлы через свой backend для больших файлов — это (а) кладёт твои сервера на CPU/RAM, (б) удваивает egress costs, (в) тротлит upload speed.

Правильный flow:

1. Browser → POST /api/uploads/sign  { filename, content_type, size }
2. Backend валидирует size/type, создаёт запись upload(id, status=pending),
   возвращает { upload_id, presigned_url, fields, expires_at }
3. Browser → PUT/POST presigned_url с файлом (multipart если > 100MB)
4. На success → POST /api/uploads/{id}/complete
5. Backend: HEAD объект (verify exists + size), переводит в status=processing,
   ставит job (scan + thumbnails)
6. Job завершён → status=ready, отдаём URL клиенту

Presigned URL TTL — 15 минут. Achievable upload, нечем DDoS'нуть.

2. Multipart / resumable для больших файлов

Файлы > 100MB:

  • S3 Multipart Upload: CreateMultipartUpload → presigned URL для каждой part'и (5-100MB) → CompleteMultipartUpload. Failed parts можно re-upload индивидуально.
  • На клиенте — библиотека типа evaporate.js / uppy с retry и progress.
  • Abort policy на bucket: incomplete multiparts старше 7 дней автоматически удаляются (lifecycle rule), иначе они едят storage, но не видны через UI.

Resumable (опционально, для > 1GB):

  • Браузер сохраняет multipart state в localStorage / IndexedDB.
  • При reload — продолжает с последней успешной части.

3. Безопасность: что должно быть обязательно

Validation server-side (нельзя верить клиенту)

  • Content-Length в presign request → backend проверяет size ≤ {{max_size}} до signing.
  • Content-Type проверяется по magic bytes (sniff первых 256 байт после upload), не по extension и не по client-provided MIME. image/jpeg с magic bytes MZ (Windows executable) = атака.
  • Filename sanitization — никогда не использовать user-provided filename как путь. Хранить как <uuid>.<ext>, оригинальное имя — отдельным полем в БД.

Storage hardening

  • Bucket private (no public read). Раздача через signed URLs / CloudFront with OAC.
  • Object key: uploads/<user_id>/<uuid>.<ext>user_id в path для IAM-style ACL.
  • Server-side encryption (SSE-S3 / SSE-KMS).
  • Versioning ON + lifecycle: delete old versions после N дней.

Virus / malware scanning

  • На любые user-uploaded файлы — ClamAV (через sidecar / Lambda) или managed (S3 + GuardDuty Malware Protection, Cloudflare).
  • Flow: upload completes → status=scanning → scan job → status=clean/infected. Infected — delete + alert + ban consideration.
  • Не отдавать клиенту URL до завершения scan. Иначе malware ходит между юзерами через твой бакет.

MIME enforcement at serve time

  • Когда отдаёшь файл — Content-Disposition: attachment для всего что не image/audio/video, чтобы браузер не рендерил HTML/SVG inline (XSS).
  • X-Content-Type-Options: nosniff.
  • Для images — Content-Security-Policy: default-src 'none'.

4. Image processing / thumbnails

  • Async после scan: job создаёт 3 размера (thumbnail 256px, preview 1024px, original).
  • Используй CDN с on-the-fly resize (Cloudinary, imgproxy, Cloudflare Images) — generates сразу или по first request, кешируется.
  • Strip EXIF — privacy (GPS coords из камеры юзера).

5. Retention

  • TTL по умолчанию. Файлы без owner / неиспользуемые > N дней → soft-delete → hard-delete через 30 дней.
  • Orphan cleanup job — files в storage без записи в БД → delete. Каждую неделю.
  • User-initiated delete — soft delete immediately, hard delete в background после 7 дней (window для "восстановить").
  • Compliance (GDPR right to erasure): hard delete по запросу < 30 дней, log того что удалено (без content).

6. Limits & abuse prevention

  • Per-user quota: max N MB total, max M files / day. 429 Too Many Requests на пресайн.
  • Rate limit на endpoint /sign (например 60/min) — не дать заспамить bucket мусором.
  • Cost monitor — alert если cumulative storage растёт на > X% / day без явной фичи.

Формат вывода

## Architecture diagram
<ASCII / mermaid: browser ↔ api ↔ storage ↔ scan ↔ cdn>

## API surface
POST /api/uploads/sign        — issue presigned
POST /api/uploads/{id}/complete — verify + enqueue processing
GET  /api/uploads/{id}        — status + URL when ready
DELETE /api/uploads/{id}

## Storage layout
uploads/<user_id>/<uuid>.<ext>
thumbnails/<user_id>/<uuid>_{256,1024}.jpg

## Bucket policy / lifecycle rules
<JSON / terraform snippet>

## Security checklist
✓ ...

Anti-patterns

  • ❌ Принимать file через свой backend (multipart/form-data → app) — load на сервере, медленно, дорого.
  • ❌ Доверять client-provided MIME / extension — XSS / RCE через "image.jpg.php".
  • ❌ Public bucket — кто-то найдёт ваш CDN URL pattern, скрейпит всё.
  • ❌ User-provided filename как S3 key — path traversal (../../etc/passwd), коллизии.
  • ❌ Не сканировать на virus — становишься хостингом для malware между юзерами.
  • ❌ Без MultipartUpload abort policy — incomplete uploads накапливаются, вы платите за невидимый мусор.
  • ❌ Без retention — storage растёт безгранично, через год bill в 10x.
  • ❌ EXIF не stripped → утечка GPS / device info пользователей.
  • ❌ Без размера в presign request → клиент upload'ит 100GB через URL который backend думал даст под 5MB.
К подразделу «Архитектура»
Похожие промты