Full-featured Go client for TUS, a protocol for resumable uploads built on HTTP.
Documentation is available at pkg.go.dev
- Resumable Upload writer with chunked and streamed mode support. Conforms the
io.Writer
/io.ReaderFrom
, which allows to use the standard utils such asio.Copy
- Client for Upload manipulation such as creation, deletion, concatenation, etc.
- Intermediate data store (for chunked Uploads) now is only in-memory
- Server extensions are supported:
creation
extension -- upload creationcreation-defer-length
-- upload creation without size. Its size is set on the first data transfercreation-with-upload
-- upload creation and data transferring in one HTTP requestexpiration
-- parsing the upload expiration infochecksum
-- data integrity verification for chunked uploads. Many checksum algorithms from Go stdlib are supportedchecksum-trailer
-- data integrity verification for streamed uploads. Checksum hash is calculated for all data in stream and is put to HTTP trailertermination
-- deleting uploads from serverconcatenation
-- merge finished uploads into oneconcatenation-unfinished
-- merge unfinished uploads (data streams) into one upload
go get github.com/bdragon300/tusgo
Go v1.18 or newer is required
package main
import (
"fmt"
"io"
"net/http"
"net/url"
"os"
)
import "github.com/bdragon300/tusgo"
func main() {
baseURL, _ := url.Parse("http://example.com/files")
cl := tusgo.NewClient(http.DefaultClient, baseURL)
// Assume that the Upload has been created on server earlier with size 1KiB
u := tusgo.Upload{Location: "http://example.com/files/foo/bar", RemoteSize: 1024 * 1024}
// Open a file we want to upload
f, err := os.Open("/tmp/file.txt")
if err != nil {
panic(err)
}
defer f.Close()
s := tusgo.NewUploadStream(cl, &u)
// Set stream and file pointers to be equal to the remote pointer
if _, err = s.Sync(); err != nil {
panic(err)
}
if _, err = f.Seek(s.Tell(), io.SeekStart); err != nil {
panic(err)
}
written, err := io.Copy(s, f)
if err != nil {
panic(fmt.Sprintf("Written %d bytes, error: %s, last response: %v", written, err, s.LastResponse))
}
fmt.Printf("Written %d bytes\n", written)
}
package main
import (
"errors"
"io"
"net"
"net/http"
"net/url"
"os"
"time"
)
import "github.com/bdragon300/tusgo"
func UploadWithRetry(dst *tusgo.UploadStream, src *os.File) error {
// Set stream and file pointer to be equal to the remote pointer
// (if we resume the upload that was interrupted earlier)
if _, err := dst.Sync(); err != nil {
return err
}
if _, err := src.Seek(dst.Tell(), io.SeekStart); err != nil {
return err
}
_, err := io.Copy(dst, src)
attempts := 10
for err != nil && attempts > 0 {
if _, ok := err.(net.Error); !ok && !errors.Is(err, tusgo.ErrChecksumMismatch) {
return err // Permanent error, no luck
}
time.Sleep(5 * time.Second)
attempts--
_, err = io.Copy(dst, src) // Try to resume the transfer again
}
if attempts == 0 {
return errors.New("too many attempts to upload the data")
}
return nil
}
func CreateUploadFromFile(f *os.File, cl *tusgo.Client) *tusgo.Upload {
finfo, err := f.Stat()
if err != nil {
panic(err)
}
u := tusgo.Upload{}
if _, err = cl.CreateUpload(&u, finfo.Size(), false, nil); err != nil {
panic(err)
}
return &u
}
func main() {
baseURL, _ := url.Parse("http://example.com/files")
cl := tusgo.NewClient(http.DefaultClient, baseURL)
f, err := os.Open("/tmp/file.txt")
if err != nil {
panic(err)
}
defer f.Close()
u := CreateUploadFromFile(f, cl)
stream := tusgo.NewUploadStream(cl, u)
if err = UploadWithRetry(stream, f); err != nil {
panic(err)
}
}