From 4af7434c10960c00a91c86d5f13fe41dbb806041 Mon Sep 17 00:00:00 2001 From: Marius Date: Sun, 27 Dec 2015 00:44:02 +0100 Subject: [PATCH] Move terminating method in own interface In addition, the DELETE handler is only provided if the TerminaterDataStore interface is implemented. --- cmd/tusd/main.go | 2 +- datastore.go | 8 ++++++++ handler.go | 6 +++++- handler_test.go | 4 ---- limitedstore/limitedstore.go | 20 +++++++++++--------- memorylocker/memorylocker_test.go | 4 ---- options_test.go | 2 +- terminate_test.go | 26 ++++++++++++++++++++++++++ unrouted_handler.go | 19 +++++++++++++++++-- 9 files changed, 69 insertions(+), 22 deletions(-) diff --git a/cmd/tusd/main.go b/cmd/tusd/main.go index 07c09e5..de7ffe9 100644 --- a/cmd/tusd/main.go +++ b/cmd/tusd/main.go @@ -43,7 +43,7 @@ func main() { stderr.Fatalf("Unable to ensure directory exists: %s", err) } - var store tusd.DataStore + var store tusd.TerminaterDataStore store = filestore.New(dir) if storeSize > 0 { diff --git a/datastore.go b/datastore.go index c891c9f..e8ae558 100644 --- a/datastore.go +++ b/datastore.go @@ -51,6 +51,14 @@ type DataStore interface { // If the returned reader also implements the io.Closer interface, the // Close() method will be invoked once everything has been read. GetReader(id string) (io.Reader, error) +} + +// TerminaterDataStore is the interface which must be implemented by DataStores +// if they want to receive DELETE requests using the Handler. If this interface +// is not implemented, no request handler for this method is attached. +type TerminaterDataStore interface { + DataStore + // Terminate an upload so any further requests to the resource, both reading // and writing, must return os.ErrNotExist or similar. Terminate(id string) error diff --git a/handler.go b/handler.go index aafb1e2..0aae179 100644 --- a/handler.go +++ b/handler.go @@ -39,9 +39,13 @@ func NewHandler(config Config) (*Handler, error) { mux.Post("", http.HandlerFunc(handler.PostFile)) mux.Head(":id", http.HandlerFunc(handler.HeadFile)) mux.Get(":id", http.HandlerFunc(handler.GetFile)) - mux.Del(":id", http.HandlerFunc(handler.DelFile)) mux.Add("PATCH", ":id", http.HandlerFunc(handler.PatchFile)) + // Only attach the DELETE handler if the Terminate() method is provided + if _, ok := config.DataStore.(TerminaterDataStore); ok { + mux.Del(":id", http.HandlerFunc(handler.DelFile)) + } + return routedHandler, nil } diff --git a/handler_test.go b/handler_test.go index 54172b1..d919f23 100644 --- a/handler_test.go +++ b/handler_test.go @@ -28,10 +28,6 @@ func (store zeroStore) GetReader(id string) (io.Reader, error) { return nil, ErrNotImplemented } -func (store zeroStore) Terminate(id string) error { - return ErrNotImplemented -} - type httpTest struct { Name string diff --git a/limitedstore/limitedstore.go b/limitedstore/limitedstore.go index 7b6da8c..8a56a45 100644 --- a/limitedstore/limitedstore.go +++ b/limitedstore/limitedstore.go @@ -22,7 +22,7 @@ import ( type LimitedStore struct { StoreSize int64 - tusd.DataStore + tusd.TerminaterDataStore uploads map[string]int64 usedSize int64 @@ -42,13 +42,15 @@ func (p pairlist) Len() int { return len(p) } func (p pairlist) Swap(i, j int) { p[i], p[j] = p[j], p[i] } func (p pairlist) Less(i, j int) bool { return p[i].value < p[j].value } -// Create a new limited store with the given size as the maximum storage size -func New(storeSize int64, dataStore tusd.DataStore) *LimitedStore { +// New creates a new limited store with the given size as the maximum storage +// size. The wrapped data store needs to implement the TerminaterDataStore +// interface, in order to provide the required Terminate method. +func New(storeSize int64, dataStore tusd.TerminaterDataStore) *LimitedStore { return &LimitedStore{ - StoreSize: storeSize, - DataStore: dataStore, - uploads: make(map[string]int64), - mutex: new(sync.Mutex), + StoreSize: storeSize, + TerminaterDataStore: dataStore, + uploads: make(map[string]int64), + mutex: new(sync.Mutex), } } @@ -60,7 +62,7 @@ func (store *LimitedStore) NewUpload(info tusd.FileInfo) (string, error) { return "", err } - id, err := store.DataStore.NewUpload(info) + id, err := store.TerminaterDataStore.NewUpload(info) if err != nil { return "", err } @@ -79,7 +81,7 @@ func (store *LimitedStore) Terminate(id string) error { } func (store *LimitedStore) terminate(id string) error { - err := store.DataStore.Terminate(id) + err := store.TerminaterDataStore.Terminate(id) if err != nil { return err } diff --git a/memorylocker/memorylocker_test.go b/memorylocker/memorylocker_test.go index 6f832b8..1f91573 100644 --- a/memorylocker/memorylocker_test.go +++ b/memorylocker/memorylocker_test.go @@ -24,10 +24,6 @@ func (store zeroStore) GetReader(id string) (io.Reader, error) { return nil, tusd.ErrNotImplemented } -func (store zeroStore) Terminate(id string) error { - return tusd.ErrNotImplemented -} - func TestMemoryLocker(t *testing.T) { var locker tusd.LockerDataStore locker = NewMemoryLocker(&zeroStore{}) diff --git a/options_test.go b/options_test.go index 8c7289b..88f73e9 100644 --- a/options_test.go +++ b/options_test.go @@ -17,7 +17,7 @@ func TestOptions(t *testing.T) { Method: "OPTIONS", Code: http.StatusNoContent, ResHeader: map[string]string{ - "Tus-Extension": "creation,concatenation,termination", + "Tus-Extension": "creation,concatenation", "Tus-Version": "1.0.0", "Tus-Resumable": "1.0.0", "Tus-Max-Size": "400", diff --git a/terminate_test.go b/terminate_test.go index a4a3935..bf858cc 100644 --- a/terminate_test.go +++ b/terminate_test.go @@ -26,6 +26,16 @@ func TestTerminate(t *testing.T) { }, }) + (&httpTest{ + Name: "Successful OPTIONS request", + Method: "OPTIONS", + URL: "", + ResHeader: map[string]string{ + "Tus-Extension": "creation,concatenation,termination", + }, + Code: http.StatusNoContent, + }).Run(handler, t) + (&httpTest{ Name: "Successful request", Method: "DELETE", @@ -36,3 +46,19 @@ func TestTerminate(t *testing.T) { Code: http.StatusNoContent, }).Run(handler, t) } + +func TestTerminateNotImplemented(t *testing.T) { + handler, _ := NewHandler(Config{ + DataStore: zeroStore{}, + }) + + (&httpTest{ + Name: "TerminaterDataStore not implemented", + Method: "DELETE", + URL: "foo", + ReqHeader: map[string]string{ + "Tus-Resumable": "1.0.0", + }, + Code: http.StatusMethodNotAllowed, + }).Run(handler, t) +} diff --git a/unrouted_handler.go b/unrouted_handler.go index 409fb36..920e30a 100644 --- a/unrouted_handler.go +++ b/unrouted_handler.go @@ -76,6 +76,7 @@ type UnroutedHandler struct { isBasePathAbs bool basePath string logger *log.Logger + extensions string // For each finished upload the corresponding info object will be sent using // this unbuffered channel. The NotifyCompleteUploads property in the Config @@ -108,6 +109,12 @@ func NewUnroutedHandler(config Config) (*UnroutedHandler, error) { base = "/" + base } + // Only promote extesions using the Tus-Extension header which are implemented + extensions := "creation,concatenation" + if _, ok := config.DataStore.(TerminaterDataStore); ok { + extensions += ",termination" + } + handler := &UnroutedHandler{ config: config, dataStore: config.DataStore, @@ -115,6 +122,7 @@ func NewUnroutedHandler(config Config) (*UnroutedHandler, error) { isBasePathAbs: uri.IsAbs(), CompleteUploads: make(chan FileInfo), logger: logger, + extensions: extensions, } return handler, nil @@ -167,7 +175,7 @@ func (handler *UnroutedHandler) Middleware(h http.Handler) http.Handler { } header.Set("Tus-Version", "1.0.0") - header.Set("Tus-Extension", "creation,concatenation,termination") + header.Set("Tus-Extension", handler.extensions) w.WriteHeader(http.StatusNoContent) return @@ -429,6 +437,13 @@ func (handler *UnroutedHandler) GetFile(w http.ResponseWriter, r *http.Request) // DelFile terminates an upload permanently. func (handler *UnroutedHandler) DelFile(w http.ResponseWriter, r *http.Request) { + // Abort the request handling if the required interface is not implemented + tstore, ok := handler.config.DataStore.(TerminaterDataStore) + if !ok { + handler.sendError(w, r, ErrNotImplemented) + return + } + id, err := extractIDFromPath(r.URL.Path) if err != nil { handler.sendError(w, r, err) @@ -444,7 +459,7 @@ func (handler *UnroutedHandler) DelFile(w http.ResponseWriter, r *http.Request) defer locker.UnlockUpload(id) } - err = handler.dataStore.Terminate(id) + err = tstore.Terminate(id) if err != nil { handler.sendError(w, r, err) return