Skip to content

Instantly share code, notes, and snippets.

@amlwwalker
Created March 16, 2019 18:00
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save amlwwalker/8424b849b65f0c098b9174725b90d315 to your computer and use it in GitHub Desktop.
Save amlwwalker/8424b849b65f0c098b9174725b90d315 to your computer and use it in GitHub Desktop.
Golang Multipart upload to S3 over HTTP using tusd
package main
import (
"bytes"
"errors"
"fmt"
"io"
"log"
"mime/multipart"
"net/http"
"os"
"path/filepath"
"regexp"
"strconv"
)
/*
Demonstration of using the tusd as a client to send file uploads
with just multiparts being written to the server (acting as a proxy between client and s3)
*/
func main() {
SendPostRequest("http://localhost:8080/files/upload/", "test.txt", "txt")
}
func SendPostRequest(url string, filename string, filetype string) (string, error) {
file, err := os.Open(filename)
if err != nil {
log.Fatal(err)
}
defer file.Close()
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
part, err := writer.CreateFormFile(filetype, filepath.Base(file.Name()))
if err != nil {
log.Fatal(err)
}
io.Copy(part, file)
writer.Close()
request, err := http.NewRequest("POST", url, body)
if err != nil {
log.Fatal(err)
}
fileStats, err := file.Stat()
if err != nil {
// Could not obtain stat, handle error
}
fileSize := strconv.FormatInt(fileStats.Size(), 10)
request.Header.Add("Tus-Resumable", "1.0.0")
request.Header.Add("Upload-Offset", "0")
request.Header.Add("Content-Length", fileSize)
request.Header.Add("Upload-Length", fileSize)
request.Header.Add("Content-Type", writer.FormDataContentType())
client := &http.Client{}
response, err := client.Do(request)
if err != nil {
log.Fatal(err)
}
defer response.Body.Close()
if response.StatusCode != 201 {
fmt.Println("There was an error with the request ", response.StatusCode)
} else {
r := regexp.MustCompile(`files\/upload\/(.*?)\+`)
subStrings := r.FindStringSubmatch(response.Header["Location"][0])
// [0] is the full string
storedFileName := subStrings[1]
fmt.Println("stored File Name "+storedFileName, " status code: ", response.StatusCode)
return storedFileName, nil
}
return "", errors.New("The file upload failed")
}
package main
import (
"fmt"
"log"
"net/http"
"os"
"regexp"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/tus/tusd"
"github.com/tus/tusd/filestore"
"github.com/tus/tusd/s3store"
)
/*
Demonstration of using the tusd as a server to accept file uploads direct to s3
with just multiparts being written to the server (acting as a proxy between client and s3)
*/
func main() {
fStore := filestore.FileStore{
Path: "./uploads",
}
S3_ACCESS_KEY := os.Getenv("S3_ACCESS_KEY")
S3_SECRET_KEY := os.Getenv("S3_SECRET_KEY")
s3Config := &aws.Config{
Region: aws.String("eu-west-2"),
Credentials: credentials.NewStaticCredentials(S3_ACCESS_KEY, S3_SECRET_KEY, ""),
}
s3Locker := s3store.New("app.bucket", s3.New(session.Must(session.NewSession()), s3Config))
composer := tusd.NewStoreComposer()
fStore.UseIn(composer)
s3Locker = s3Locker
handler, err := tusd.NewHandler(tusd.Config{
BasePath: "/files/upload/",
StoreComposer: composer,
NotifyCompleteUploads: true,
NotifyTerminatedUploads: true,
NotifyUploadProgress: true,
NotifyCreatedUploads: true,
})
if err != nil {
panic(fmt.Errorf("Unable to create handler: %s", err))
}
http.Handle("/files/upload/", http.StripPrefix("/files/upload/", handler))
err = http.ListenAndServe(":8080", nil)
if err != nil {
panic(fmt.Errorf("Unable to listen: %s", err))
}
}
func setupPostHooks(handler *tusd.Handler) {
go func() {
select {
case info := <-handler.CompleteUploads:
log.Println("upload complete:", info)
case info := <-handler.TerminatedUploads:
log.Println("upload terminated:", info)
case info := <-handler.UploadProgress:
log.Println("upload progress:", info)
case info := <-handler.CreatedUploads:
log.Println("create upload:", info)
processStoredFile(info)
}
}()
}
func processStoredFile(info tusd.FileInfo) {
fmt.Printf("info %+v\r\n", info)
r := regexp.MustCompile(`^(.*?)\+`)
subStrings := r.FindStringSubmatch(info.ID)
storedFileName := subStrings[1]
fmt.Println("stored File Name " + storedFileName)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment