Move terminating method in own interface

In addition, the DELETE handler is only provided if the TerminaterDataStore
interface is implemented.
This commit is contained in:
Marius 2015-12-27 00:44:02 +01:00
parent f96e2614fc
commit 4af7434c10
9 changed files with 69 additions and 22 deletions

View File

@ -43,7 +43,7 @@ func main() {
stderr.Fatalf("Unable to ensure directory exists: %s", err) stderr.Fatalf("Unable to ensure directory exists: %s", err)
} }
var store tusd.DataStore var store tusd.TerminaterDataStore
store = filestore.New(dir) store = filestore.New(dir)
if storeSize > 0 { if storeSize > 0 {

View File

@ -51,6 +51,14 @@ type DataStore interface {
// If the returned reader also implements the io.Closer interface, the // If the returned reader also implements the io.Closer interface, the
// Close() method will be invoked once everything has been read. // Close() method will be invoked once everything has been read.
GetReader(id string) (io.Reader, error) 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 // Terminate an upload so any further requests to the resource, both reading
// and writing, must return os.ErrNotExist or similar. // and writing, must return os.ErrNotExist or similar.
Terminate(id string) error Terminate(id string) error

View File

@ -39,9 +39,13 @@ func NewHandler(config Config) (*Handler, error) {
mux.Post("", http.HandlerFunc(handler.PostFile)) mux.Post("", http.HandlerFunc(handler.PostFile))
mux.Head(":id", http.HandlerFunc(handler.HeadFile)) mux.Head(":id", http.HandlerFunc(handler.HeadFile))
mux.Get(":id", http.HandlerFunc(handler.GetFile)) mux.Get(":id", http.HandlerFunc(handler.GetFile))
mux.Del(":id", http.HandlerFunc(handler.DelFile))
mux.Add("PATCH", ":id", http.HandlerFunc(handler.PatchFile)) 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 return routedHandler, nil
} }

View File

@ -28,10 +28,6 @@ func (store zeroStore) GetReader(id string) (io.Reader, error) {
return nil, ErrNotImplemented return nil, ErrNotImplemented
} }
func (store zeroStore) Terminate(id string) error {
return ErrNotImplemented
}
type httpTest struct { type httpTest struct {
Name string Name string

View File

@ -22,7 +22,7 @@ import (
type LimitedStore struct { type LimitedStore struct {
StoreSize int64 StoreSize int64
tusd.DataStore tusd.TerminaterDataStore
uploads map[string]int64 uploads map[string]int64
usedSize 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) 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 } 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 // New creates a new limited store with the given size as the maximum storage
func New(storeSize int64, dataStore tusd.DataStore) *LimitedStore { // 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{ return &LimitedStore{
StoreSize: storeSize, StoreSize: storeSize,
DataStore: dataStore, TerminaterDataStore: dataStore,
uploads: make(map[string]int64), uploads: make(map[string]int64),
mutex: new(sync.Mutex), mutex: new(sync.Mutex),
} }
} }
@ -60,7 +62,7 @@ func (store *LimitedStore) NewUpload(info tusd.FileInfo) (string, error) {
return "", err return "", err
} }
id, err := store.DataStore.NewUpload(info) id, err := store.TerminaterDataStore.NewUpload(info)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -79,7 +81,7 @@ func (store *LimitedStore) Terminate(id string) error {
} }
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 { if err != nil {
return err return err
} }

View File

@ -24,10 +24,6 @@ func (store zeroStore) GetReader(id string) (io.Reader, error) {
return nil, tusd.ErrNotImplemented return nil, tusd.ErrNotImplemented
} }
func (store zeroStore) Terminate(id string) error {
return tusd.ErrNotImplemented
}
func TestMemoryLocker(t *testing.T) { func TestMemoryLocker(t *testing.T) {
var locker tusd.LockerDataStore var locker tusd.LockerDataStore
locker = NewMemoryLocker(&zeroStore{}) locker = NewMemoryLocker(&zeroStore{})

View File

@ -17,7 +17,7 @@ func TestOptions(t *testing.T) {
Method: "OPTIONS", Method: "OPTIONS",
Code: http.StatusNoContent, Code: http.StatusNoContent,
ResHeader: map[string]string{ ResHeader: map[string]string{
"Tus-Extension": "creation,concatenation,termination", "Tus-Extension": "creation,concatenation",
"Tus-Version": "1.0.0", "Tus-Version": "1.0.0",
"Tus-Resumable": "1.0.0", "Tus-Resumable": "1.0.0",
"Tus-Max-Size": "400", "Tus-Max-Size": "400",

View File

@ -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{ (&httpTest{
Name: "Successful request", Name: "Successful request",
Method: "DELETE", Method: "DELETE",
@ -36,3 +46,19 @@ func TestTerminate(t *testing.T) {
Code: http.StatusNoContent, Code: http.StatusNoContent,
}).Run(handler, t) }).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)
}

View File

@ -76,6 +76,7 @@ type UnroutedHandler struct {
isBasePathAbs bool isBasePathAbs bool
basePath string basePath string
logger *log.Logger logger *log.Logger
extensions string
// For each finished upload the corresponding info object will be sent using // For each finished upload the corresponding info object will be sent using
// this unbuffered channel. The NotifyCompleteUploads property in the Config // this unbuffered channel. The NotifyCompleteUploads property in the Config
@ -108,6 +109,12 @@ func NewUnroutedHandler(config Config) (*UnroutedHandler, error) {
base = "/" + base 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{ handler := &UnroutedHandler{
config: config, config: config,
dataStore: config.DataStore, dataStore: config.DataStore,
@ -115,6 +122,7 @@ func NewUnroutedHandler(config Config) (*UnroutedHandler, error) {
isBasePathAbs: uri.IsAbs(), isBasePathAbs: uri.IsAbs(),
CompleteUploads: make(chan FileInfo), CompleteUploads: make(chan FileInfo),
logger: logger, logger: logger,
extensions: extensions,
} }
return handler, nil 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-Version", "1.0.0")
header.Set("Tus-Extension", "creation,concatenation,termination") header.Set("Tus-Extension", handler.extensions)
w.WriteHeader(http.StatusNoContent) w.WriteHeader(http.StatusNoContent)
return return
@ -429,6 +437,13 @@ func (handler *UnroutedHandler) GetFile(w http.ResponseWriter, r *http.Request)
// DelFile terminates an upload permanently. // DelFile terminates an upload permanently.
func (handler *UnroutedHandler) DelFile(w http.ResponseWriter, r *http.Request) { 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) id, err := extractIDFromPath(r.URL.Path)
if err != nil { if err != nil {
handler.sendError(w, r, err) handler.sendError(w, r, err)
@ -444,7 +459,7 @@ func (handler *UnroutedHandler) DelFile(w http.ResponseWriter, r *http.Request)
defer locker.UnlockUpload(id) defer locker.UnlockUpload(id)
} }
err = handler.dataStore.Terminate(id) err = tstore.Terminate(id)
if err != nil { if err != nil {
handler.sendError(w, r, err) handler.sendError(w, r, err)
return return