diff --git a/src/cmd/tusd/main.go b/src/cmd/tusd/main.go index 5b8a619..61da14e 100644 --- a/src/cmd/tusd/main.go +++ b/src/cmd/tusd/main.go @@ -9,6 +9,8 @@ import ( "strconv" ) +const basePath = "/files/" + func main() { log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds) log.Printf("tusd started") @@ -36,20 +38,34 @@ func main() { } } - config := tushttp.HandlerConfig{ - Dir: dir, - MaxSize: maxSize, + tusConfig := tushttp.HandlerConfig{ + Dir: dir, + MaxSize: maxSize, + BasePath: basePath, } - log.Printf("handler config: %+v", config) + log.Printf("handler config: %+v", tusConfig) - handler, err := tushttp.NewHandler(config) + tusHandler, err := tushttp.NewHandler(tusConfig) if err != nil { panic(err) } + http.Handle(basePath, tusHandler) + + go handleUploads(tusHandler) + log.Printf("servering clients at http://localhost%s", addr) - if err := http.ListenAndServe(addr, handler); err != nil { + if err := http.ListenAndServe(addr, nil); err != nil { panic(err) } } + +func handleUploads(tus *tushttp.Handler) { + for { + select { + case err := <-tus.Error: + log.Printf("error: %s", err) + } + } +} diff --git a/src/http/handler.go b/src/http/handler.go index 3204f0d..b70a823 100644 --- a/src/http/handler.go +++ b/src/http/handler.go @@ -1,12 +1,15 @@ package http import ( - "log" + "errors" + "io" "net/http" "os" + "strings" ) -type HandlerConfig struct{ +// HandlerConfig holds the configuration for a tus Handler. +type HandlerConfig struct { // Dir points to a filesystem path used by tus to store uploaded and partial // files. Will be created if does not exist yet. Required. Dir string @@ -15,23 +18,76 @@ type HandlerConfig struct{ // limit will cause the oldest upload files to be deleted until enough space // is available again. Required. MaxSize int64 + + // BasePath defines the url path used for handling uploads, e.g. "/files/". + // Must contain a trailling "/". Requests not matching this base path will + // cause a 404, so make sure you dispatch only appropriate requests to the + // handler. Required. + BasePath string } +// NewHandler returns an initialized Handler. An error may occur if the +// config.Dir is not writable. func NewHandler(config HandlerConfig) (*Handler, error) { // Ensure the data store directory exists if err := os.MkdirAll(config.Dir, 0777); err != nil { return nil, err } + errChan := make(chan error) + return &Handler{ - store: newDataStore(config.Dir, config.MaxSize), + store: newDataStore(config.Dir, config.MaxSize), + basePath: config.BasePath, + Error: errChan, + sendError: errChan, }, nil } -type Handler struct{ - store *DataStore +// Handler is a http.Handler that implements tus resumable upload protocol. +type Handler struct { + store *DataStore + basePath string + + // Error provides error events for logging purposes. + Error <-chan error + // same chan as Error, used for sending. + sendError chan<- error } func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - log.Printf("request: %s %s", r.Method, r.URL.RequestURI()) + absPath := r.URL.Path + if !strings.HasPrefix(absPath, h.basePath) { + err := errors.New("invalid url path: " + absPath + " - does not match basePath: " + h.basePath) + h.err(err, w, http.StatusNotFound) + return + } + + relPath := absPath[len(h.basePath)-1:] + + // File creation request + if relPath == "/" { + // Must use POST method according to tus protocol + if r.Method != "POST" { + w.Header().Set("Allow", "POST") + err := errors.New(r.Method + " used against file creation url. Only POST is allowed.") + h.err(err, w, http.StatusMethodNotAllowed) + return + } + } + + err := errors.New("invalid url path: " + absPath + " - does not match file pattern") + h.err(err, w, http.StatusNotFound) +} + +// err sends a http error response and publishes to the Error channel. +func (h *Handler) err(err error, w http.ResponseWriter, status int) { + w.WriteHeader(status) + io.WriteString(w, err.Error()+"\n") + + // non-blocking send + select { + case h.sendError <- err: + default: + } }