Fazer o upload de um vídeo de dois gigabytes via WiFi de uma cafeteria é um exercício de otimismo. Você observa a barra de progresso avançar lentamente até os setenta por cento e, de repente, a conexão oscila. O upload falha. Quando você tenta novamente, o servidor espera um arquivo totalmente novo. Você começa do zero. Para quem está construindo um aplicativo que lida com vídeos gerados por usuários, isso não é um caso isolado. É a norma. O protocolo tus foi construído especificamente para acabar com esse problema. É um padrão aberto que roda sobre o HTTP comum e dá memória aos uploads. Quando a rede se recupera, a transferência de arquivo retoma exatamente de onde parou.

Por que os uploads padrão falham

Uploads de arquivos multipart padrão tratam todo o payload como uma única transação. O navegador ou aplicativo abre uma conexão TCP, transmite os bytes e espera um 200 OK ao final. Se a conexão for reiniciada porque o usuário mudou de LTE para WiFi, ou porque o laptop entrou em modo de suspensão, o servidor não tem como saber quais bytes chegaram com segurança. A maioria dos frameworks simplesmente descarta os dados parciais. O usuário fica com um formulário que falhou e de mau humor. Arquivos de vídeo amplificam essa dor porque são grandes, frequentemente enviados de dispositivos móveis em redes instáveis e muitas vezes codificados em formatos que não podem ser processados até que o arquivo esteja completo.

Como o tus muda o jogo

O tus repensa o upload como uma conversa com estado, em vez de uma entrega única. Em vez de enviar um arquivo em um fluxo contínuo de bytes, o cliente divide o payload em chunks. Mais importante ainda, o servidor lembra o offset, a contagem exata de bytes que recebeu até agora. Essa persistência de estado é o que torna a retomada possível. O protocolo é intencionalmente simples. Não requer WebSockets, gRPC ou SDKs proprietários. Ele utiliza os métodos HTTP que você já conhece.

As três requisições que possibilitam a retomada

Todo upload tus segue uma dança previsível.

Primeiro, o cliente envia uma requisição POST para um endpoint tus conhecido. Os cabeçalhos descrevem o arquivo. O cabeçalho Upload-Length indica o tamanho final em bytes, e metadados opcionais, como o nome do arquivo ou o tipo de conteúdo, viajam no cabeçalho Upload-Metadata como uma lista separada por vírgulas de pares chave-valor codificados em base64. O servidor cria um recurso de upload vazio e responde com um status 201 Created, além de um cabeçalho Location apontando para uma URL única. Esta URL é o endereço do upload pelo resto de sua existência.

Em seguida, vem a requisição PATCH. O cliente envia chunks de dados binários brutos para essa URL única. Dois cabeçalhos críticos acompanham o payload. O Content-Type deve ser application/offset+octet-stream, e o Upload-Offset deve corresponder à posição do byte onde este chunk começa. O servidor verifica se o offset corresponde aos seus próprios registros. Se não corresponder, o servidor retorna um 409 Conflict, protegendo contra estados corrompidos. Se tudo estiver alinhado, o servidor anexa os bytes, atualiza seu offset interno e retorna uma resposta 204 No Content junto com o novo Upload-Offset. O tamanho do chunk é configurável, mas a prática comum fica entre algumas centenas de kilobytes e alguns megabytes. Chunks menores criam mais overhead HTTP, mas se recuperam mais rápido de erros. Chunks maiores reduzem as viagens de ida e volta (round trips), mas desperdiçam mais largura de banda se falharem durante o processo.

Finalmente, a requisição HEAD. Esta é a rede de segurança. Se um PATCH falhar no meio de um chunk, digamos após três megabytes de um pedaço de cinco megabytes, a conexão cai. Quando o cliente recupera o acesso à rede, ele envia uma requisição HEAD para a URL de upload. O servidor responde com o Upload-Offset atual. O cliente compara