Resumable Uploads (TUS 1.0.0)
Anvil implements the TUS 1.0.0 resumable upload protocol so large files survive transient failures without restarting. The threshold for switching from single-shot upload to TUS is whatever the client decides - both Hammer and the SDKs default to 5 MiB.
Protocol overview
| Step | Endpoint | Headers |
|---|---|---|
| Initiate | POST /storage/v1/upload/resumable | Tus-Resumable, Upload-Length, Upload-Metadata |
| Append chunk | PATCH /storage/v1/upload/resumable/{id} | Upload-Offset, Content-Type=application/offset+octet-stream |
| Query offset | HEAD /storage/v1/upload/resumable/{id} | Tus-Resumable |
| Abort | DELETE /storage/v1/upload/resumable/{id} | Tus-Resumable |
| Discovery | OPTIONS /storage/v1/upload/resumable | Returns Tus-Version, Tus-Extension, Tus-Max-Size |
Extensions advertised
| Extension | Behaviour |
|---|---|
| creation | POST initiates a session |
| expiration | Upload-Expires header; expired sessions return 410 |
| checksum | Upload-Checksum: sha256 <base64> per chunk |
| termination | DELETE removes the session + temp file |
Walk-through
# 1. Create session - Upload-Metadata carries bucket/path/mime as base64. curl -i -X POST http://localhost:7474/storage/v1/upload/resumable \ -H "Authorization: Bearer $TOKEN" \ -H "Tus-Resumable: 1.0.0" \ -H "Upload-Length: 10485760" \ -H "Upload-Metadata: bucket dmlkZW9z,path Y2xpcC5tcDQ=,mime dmlkZW8vbXA0" # 201 Created # Location: /storage/v1/upload/resumable/<session-id> # 2. Append a chunk (any size up to MAX_CHUNK_BYTES = 1 GiB). curl -i -X PATCH http://localhost:7474/storage/v1/upload/resumable/<session-id> \ -H "Authorization: Bearer $TOKEN" \ -H "Tus-Resumable: 1.0.0" \ -H "Content-Type: application/offset+octet-stream" \ -H "Upload-Offset: 0" \ --data-binary @part0.bin # 204 No Content # Upload-Offset: <new offset> # When offset == Upload-Length, also: # Location: /storage/v1/object/<bucket>/<path> # X-Anvil-Content-Hash: <sha256>
Resume after failure
If a chunk fails, issue HEAD on the session URL to discover the server's current Upload-Offset, then resume PATCHing from there. The Anvil client SDKs and the Hammer upload zone do this transparently.
Garbage collection
Expired upload sessions are cleaned up by the same background loop that handles snapshot checkpoints. The expiry window comes from file_storage.upload_expiry.