Перейти к основному содержимому

Файлы и загрузки

Забрать фотографию, документ или видео с устройства в хранилище — и отдать обратно тому, кому их можно показать. Всё просто ровно до момента, когда файл оказывается большим, связь — мобильным интернетом из электрички, а сам файл — не тем, чем он представляется.

Почему это важно. Загрузка файлов — самое частое место, где мобильное приложение расходует впустую трафик и терпение пользователя, а обработчики загрузки — излюбленная точка входа для всех, кто прощупывает приложение. Обе проблемы снимаются проектированием, а не починкой задним числом.

Что нужно понимать

  • Где на самом деле проходят байты и обязан ли ваш сервер быть на этом пути
  • Чем файл является на самом деле, а не чем его объявляют имя и content type
  • Кому можно его прочитать после сохранения и чем это ограничение обеспечено
  • Что происходит, когда загрузка обрывается на восьмидесяти процентах
  • Во что обходится хранить файлы, отдавать их и не удалять

Основные темы

Отправка

  • Напрямую в хранилище по подписанной ссылке вместо загрузки через свой сервер
  • Multipart и загрузка по частям, докачка после обрыва
  • Прогресс, отмена и фоновая загрузка на мобильных
  • Ограничения размера, которые срабатывают до приёма байтов, а не после

Проверка

  • Тип содержимого определяется по байтам, а не по тому, что сказано в запросе
  • Проверка расширения и magic number — и то, чего она не ловит
  • Ограничения на размеры изображения и объём файла
  • Отношение к любому загруженному файлу как к враждебному — включая его имя

Хранение

  • Объектное хранилище против файловой системы против базы данных
  • Ключи, по которым нельзя ни узнать пользователя, ни угадать чужой файл
  • Метаданные в базе, байты в хранилище — и согласованность между ними
  • Жизненный цикл: сроки хранения, «сироты» и уборка после удалённой записи

Отдача

  • Подписанные ссылки с ограниченным сроком жизни для приватных файлов
  • CDN и заголовки кеширования
  • Производные: превью и перекодированные версии, которые генерируются один раз
  • Range-запросы, чтобы видео можно было перематывать

Уровни

УровеньКак это выглядит
JuniorЗагружает файл через сервер и затем показывает его.
MiddleЗагружает напрямую в хранилище, проверяет файл по содержимому, показывает прогресс, обрабатывает ошибки и отмену.
SeniorПроектирует весь путь целиком — подписи, сроки хранения, производные, доступ, стоимость — и добивается того, чтобы большие загрузки переживали плохую связь.

Практика

Для начала

  • Проверяйте по-настоящему Переименуйте текстовый файл в .jpg, загрузите его и добейтесь, чтобы сервер его отклонил.

  • Покажите прогресс Добавьте к загрузке настоящий индикатор прогресса и работающую отмену.

  • Ограничьте размер Отклоняйте файлы сверх лимита, не дожидаясь приёма всего тела запроса.

Глубже

  • Обойдите свой сервер Переведите загрузку на подписанную ссылку напрямую в хранилище и сравните нагрузку.

  • Докачайте прерванную загрузку Добейтесь, чтобы большая загрузка пережила обрыв связи на середине.

  • Уберите «сирот» Найдите в хранилище файлы, записей о которых в базе уже нет, и перестаньте создавать новые такие.

Проверьте себя

  • Проходит ли каждый загружаемый файл через ваш сервер — и нужно ли это?
  • Как вы определяете, что за файл к вам пришёл на самом деле?
  • Кто сможет прочитать сохранённый файл, если у него есть ссылка?
  • Что происходит с байтами, когда пользователь удаляет запись, которая на них ссылалась?
  • Что делает загрузка, когда связь пропадает на восьмидесяти процентах?
  • Во сколько обходится ваше хранилище и что в нём лежит такого, на что уже ничто не ссылается?

Ресурсы

  • OWASP File Upload Cheat Sheet — атаки и проверки, которые их останавливают. Читать до того, как писать обработчик загрузки, а не после инцидента.
  • Serverpod: file uploads — подписанные прямые загрузки и разграничение доступа в backend на Dart.
  • Amazon S3: presigned URLs — каноническое объяснение подхода, который повторяют все остальные объектные хранилища.
  • MDN: Range requests — за счёт чего работает перемотка видео и что для этого должен поддерживать слой отдачи файлов.
  • image_picker и file_picker — клиентская часть; поведение платформенных разрешений описано в их README.