Tussle

TestsHex.pmHexDocsGitHub starsSponsor

An implementation of a tusserver in Elixir

Documentation: https://hexdocs.pm/tussle/

tus is a protocol based on HTTP for resumable file uploads. Resumable means that an upload can be interrupted at any moment and can be resumed without re-uploading the previous data again.

An interruption may happen willingly, if the user wants to pause, or by accident in case of an network issue or server outage.

Tussle is capable of accepting uploads with arbitrary sizes and storing them locally on disk, or in Amazon S3 by installing the tus_storage_s3 hex package. Due to its modularization and extensibility, support for any other cloud provider can be easily added.

Features

This library implements the core TUS API v1.0.0 protocol and the following extensions:

Deferring the upload's length is not supported; Upload-Length must be known at creation time.

Installation

Add this repo to your list of dependencies in mix.exs:

def deps do
[
{:tussle, "~> 0.4.0"},
]
end

Usage

1. Add new controller(s)

defmodule DemoWeb.UploadController do
use DemoWeb, :controller
use Tussle.Controller
# Optional callback before upload starts
def on_begin_upload(file) do
...
:ok # or {:error, reason} to reject the upload
end
# Optional callback when upload completes
def on_complete_upload(file) do
...
end
end

2. Add routes for each of your upload controllers

The simplest way is to use the Tussle.Routes macro:

defmodule DemoWeb.Router do
use DemoWeb, :router
import Tussle.Routes
scope "/files", DemoWeb do
pipe_through :api
add_tus_routes UploadController
end
end

Or define routes manually:

scope "/files", DemoWeb do
options "/", UploadController, :options
post "/", UploadController, :post
match :head, "/:uid", UploadController, :head
get "/:uid", UploadController, :get # CloudFlare compatibility
patch "/:uid", UploadController, :patch
delete "/:uid", UploadController, :delete
end

⚠️ CloudFlare Compatibility Note

CloudFlare's caching layer converts HEAD requests to GET requests. The TUS protocol specifies HEAD for metadata retrieval, so this conversion can cause requests to not match HEAD routes, resulting in 404 errors.

The add_tus_routes/1 macro includes a GET route that mirrors HEAD behavior automatically. If you define routes manually, you must add the GET route yourself to ensure resumable uploads work correctly behind CloudFlare or similar CDNs.

3. Add config for each controller (see next section)

Configuration

# List all of your upload controllers
config :tussle, controllers: [DemoWeb.UploadController]
# Configuration for the DemoWeb.UploadController
config :tussle, DemoWeb.UploadController,
storage: Tussle.Storage.Local,
base_path: "priv/static/files/",
# Optional: expire unfinished uploads after N seconds
expiration_period: 300,
cache: Tussle.Cache.Memory,
# max supported file size, in bytes (default 20 MB)
max_size: 1024 * 1024 * 20

Options for Tussle.Storage.Local

Read Body Options

These options control how the server reads upload request bodies:

config :tussle,
read_body_length: 100_000_000, # max body size in bytes (default: 100MB)
read_body_read_length: 262_144, # chunk size for reading (default: 256KB)
read_body_timeout: 30_000 # timeout per read in ms (default: 30s)

CORS

Tussle sets no CORS headers of its own. If your uploads come from a browser on a different origin than the API, the host application must add them, and an incomplete Access-Control-Expose-Headers list is the most common cause of uploads that appear to succeed but never resume: a browser hides Upload-Offset and Location from JavaScript unless they are explicitly exposed, so the client cannot learn where to resume from.

Using cors_plug as an example (it is not a Tussle dependency, any CORS plug will do):

plug CORSPlug,
origin: ["https://app.example.com"],
methods: ["OPTIONS", "POST", "HEAD", "GET", "PATCH", "DELETE"],
# Request headers tus clients send.
headers: [
"Authorization",
"Content-Type",
"Tus-Resumable",
"Upload-Length",
"Upload-Offset",
"Upload-Metadata",
"X-HTTP-Method-Override",
"X-Requested-With"
],
# Response headers the client must be able to read.
expose: [
"Location",
"Tus-Resumable",
"Tus-Version",
"Tus-Extension",
"Tus-Max-Size",
"Upload-Offset",
"Upload-Length",
"Upload-Metadata",
"Upload-Expires"
]

Two further notes:

Protocol Compatibility

Tussle implements the tus 1.0.0 core protocol and works with current tus-js-client releases out of the box. See docs/UPSTREAM_COMPAT.md for which tus extensions are still missing, where the IETF Resumable Uploads for HTTP standardisation effort stands, and an effort estimate for closing each gap.

About This Fork

This is a maintained fork of the original tus package. The package was renamed to Tussle to allow publishing updated versions to Hex without conflicting with the original (now unmaintained) package.

Acknowledgments

Thank you to the original author of this library and all the people who graciously published their improvements that I have integrated into this fork.

Contributors

jvantuyl
Jayson Vantuyl
jpsca
Juan-Pablo Scaletti
gottfrois
Pierre-Louis Gottfrois
marcinkoziej
Marcin Koziej
ringods
Ringo De Smet
joeljuca
Joel Jucá
feng19
Kevin Pan
zkessin
Zachary Kessin
github-actions[bot]
github-actions[bot]

License

BSD-3-Clause. See LICENSE for details.