Загрузка двухгигабайтного видео через Wi-Fi в кофейне — это упражнение в оптимизме. Вы наблюдаете, как полоса прогресса медленно ползет к семидесяти процентам, и вдруг соединение прерывается. Загрузка завершается ошибкой. При повторной попытке сервер ожидает совершенно новый файл. Вы начинаете с нуля. Для любого, кто разрабатывает приложение для работы с пользовательским видеоконтентом, это не исключительный случай. Это норма. Протокол tus был создан специально для решения этой проблемы. Это открытый стандарт, работающий поверх обычного HTTP, который наделяет загрузку «памятью». Когда сеть восстанавливается, передача файла продолжается ровно с того места, где она прервалась.

Почему стандартные загрузки обрываются

Стандартная многокомпонентная (multipart) загрузка файлов рассматривает всю полезную нагрузку как одну транзакцию. Браузер или приложение открывает TCP-соединение, передает байты потоком и ожидает ответа 200 OK в конце. Если соединение разрывается из-за того, что пользователь переключился с LTE на Wi-Fi или ноутбук ушел в спящий режим, сервер никак не может узнать, какие байты были доставлены успешно. Большинство фреймворков просто отбрасывают неполные данные. Пользователь остается с ошибкой в форме и плохим настроением. Видеофайлы усугубляют эту проблему, так как они велики по размеру, часто загружаются с мобильных устройств в нестабильных сетях и нередко закодированы в форматах, которые невозможно обработать, пока файл не будет загружен целиком.

Как tus меняет правила игры

Tus переосмысливает загрузку как диалог с сохранением состояния, а не как разовую передачу. Вместо того чтобы отправлять файл одним непрерывным потоком байтов, клиент разбивает полезную нагрузку на фрагменты. Что еще важнее, сервер запоминает смещение (offset) — точное количество байтов, полученных на данный момент. Именно эта сохранность состояния делает возобновление загрузки возможным. Протокол намеренно прост. Он не требует WebSockets, gRPC или проприетарных SDK. Он использует методы HTTP, которые вы уже знаете.

Три запроса, обеспечивающие возобновление

Каждая загрузка tus следует предсказуемому алгоритму.

Во-первых, клиент отправляет POST-запрос на известный tus-эндпоинт. Заголовки описывают файл. Заголовок Upload-Length указывает конечный размер в байтах, а необязательные метаданные, такие как имя файла или тип контента, передаются в заголовке Upload-Metadata в виде списка пар «ключ-значение», закодированных в base64 и разделенных запятыми. Сервер создает пустой ресурс загрузки и отвечает статусом 201 Created, а также заголовком Location, указывающим на уникальный URL. Этот URL будет адресом загрузки на протяжении всего её жизненного цикла.

Далее следует запрос PATCH. Клиент отправляет фрагменты необработанных бинарных данных на этот уникальный URL. Полезную нагрузку сопровождают два критически важных заголовка. Content-Type должен быть application/offset+octet-stream, а Upload-Offset должен соответствовать позиции байта, с которого начинается этот фрагмент. Сервер проверяет, совпадает ли смещение с его собственными записями. Если нет, сервер возвращает 409 Conflict, защищая систему от поврежденного состояния. Если всё совпадает, сервер добавляет байты, обновляет свое внутреннее смещение и возвращает ответ 204 No Content вместе с новым значением Upload-Offset. Размер фрагмента настраивается, но на практике он обычно составляет от нескольких сотен килобайт до пары мегабайт. Меньшие фрагменты создают больше накладных расходов HTTP, но позволяют быстрее восстанавливаться после ошибок. Более крупные фрагменты сокращают количество циклов приема-передачи, но тратят больше трафика в случае сбоя в процессе передачи.

Наконец, запрос HEAD. Это страховочная сетка. Если запрос PATCH прерывается на середине фрагмента (например, после трех мегабайт из пятимегабайтного куска), соединение разрывается. Когда клиент восстанавливает доступ к сети, он отправляет HEAD-запрос на URL загрузки. Сервер отвечает текущим значением Upload-Offset. Клиент сравнивает