コーヒーショップのWi-Fiで2GBの動画をアップロードするのは、楽観的な試みと言えるでしょう。プログレスバーが70%に向かってゆっくり進むのを眺めていると、接続が途切れます。アップロードは失敗します。再試行しても、サーバーは新しいファイルを期待するため、またゼロからやり直しになります。ユーザー生成動画を扱うアプリケーションを構築している人にとって、これは例外的なケースではなく、日常茶飯事です。tusプロトコルは、まさにこの問題を打破するために構築されました。これは通常のHTTP上で動作するオープンスタンダードであり、アップロードに「記憶」を持たせることができます。ネットワークが復旧すると、ファイル転送は中断された箇所から正確に再開されます。

標準的なアップロードが失敗する理由

標準的なマルチパート・ファイルアップロードは、ペイロード全体を単一のトランザクションとして扱います。ブラウザやアプリはTCP接続を開き、バイト列をストリーミングし、最後に200 OKが返ってくることを期待します。ユーザーがLTEからWi-Fiに切り替えたり、ノートPCがスリープ状態になったりして接続がリセットされると、サーバーはどのバイトが安全に届いたかを知る術がありません。ほとんどのフレームワークは、単に部分的なデータを破棄してしまいます。ユーザーに残されるのは、失敗したフォームと不快な気分だけです。動画ファイルはこの苦痛を増幅させます。ファイルサイズが大きく、不安定なネットワーク環境にあるモバイルデバイスからアップロードされることが多く、またファイル全体が揃うまで処理できない形式でエンコードされていることが多いためです。

tusがどのように状況を変えるのか

tusは、アップロードを「一回限りの配信」ではなく、「状態を持つ対話」として再定義します。クライアントは、ファイルを連続したバイトの塊として送る代わりに、ペイロードをチャンク(塊)に分割します。さらに重要なのは、サーバーがこれまでに受信した正確なバイト数であるオフセットを記憶していることです。この状態の永続性こそが、レジューム(再開)を可能にする鍵です。プロトコルは意図的にシンプルに設計されています。WebSocketsやgRPC、独自のSDKを必要とせず、既知のHTTPメソッドを使用します。

レジュームを実現する3つのリクエスト

すべてのtusアップロードは、予測可能な手順に従います。

まず、クライアントは既知のtusエンドポイントにPOSTリクエストを送信します。ヘッダーがファイルの詳細を記述します。Upload-Lengthヘッダーには最終的なサイズ(バイト単位)が指定され、ファイル名やコンテンツタイプなどのオプションのメタデータは、Upload-Metadataヘッダーにbase64エンコードされたキーと値のペアのカンマ区切りリストとして送られます。サーバーは空のアップロードリソースを作成し、201 Createdステータスと、一意のURLを指すLocationヘッダーを返します。このURLは、そのアップロードの全工程を通じてのアドレスとなります。

次に、PATCHリクエストが行われます。クライアントはその一意のURLに対して、生のバイナリデータのチャンクを送信します。ペイロードには2つの重要なヘッダーが伴います。Content-Typeはapplication/offset+octet-streamである必要があり、Upload-Offsetはこのチャンクが始まるバイト位置と一致していなければなりません。サーバーは、オフセットが自身の記録と一致するかどうかを確認します。一致しない場合、サーバーは409 Conflictを返し、状態の破損を防ぎます。すべてが一致すれば、サーバーはバイトを追加して内部のオフセットを更新し、新しいUpload-Offsetとともに204 No Contentレスポンスを返します。チャンクサイズは設定可能ですが、一般的には数百キロバイトから数メガバイトの間で設定されます。小さなチャンクはHTTPのオーバーヘッドが増えますが、エラーからの復旧は早くなります。大きなチャンクはラウンドトリップを減らせますが、転送中に失敗した場合の帯域幅の無駄が多くなります。

最後に、HEADリクエストです。これがセーフティネットとなります。例えば、5MBのチャンクのうち3MBまで送ったところで接続が切断され、PATCHが失敗したとします。クライアントがネットワーク接続を回復すると、アップロードURLに対してHEADリクエストを送信します。サーバーは現在のUpload-Offsetを返します。クライアントは比較します