add GET route for downloading uploads
This commit is contained in:
parent
04d559dd47
commit
29100e3b5b
|
@ -32,4 +32,10 @@ type DataStore interface {
|
|||
// requests. It may return an os.ErrNotExist which will be interpretet as a
|
||||
// 404 Not Found.
|
||||
GetInfo(id string) (FileInfo, error)
|
||||
// Get an io.Reader to allow downloading the file. This feature is not
|
||||
// part of the official tus specification. If this additional function
|
||||
// should not be enabled any call to GetReader should return
|
||||
// tusd.ErrNotImplemented. The length of the resource is determined by
|
||||
// retrieving the offset using GetInfo.
|
||||
GetReader(id string) (io.Reader, error)
|
||||
}
|
||||
|
|
|
@ -73,6 +73,10 @@ func (store FileStore) GetInfo(id string) (tusd.FileInfo, error) {
|
|||
return info, err
|
||||
}
|
||||
|
||||
func (store FileStore) GetReader(id string) (io.Reader, error) {
|
||||
return os.Open(store.binPath(id))
|
||||
}
|
||||
|
||||
// Return the path to the .bin storing the binary data
|
||||
func (store FileStore) binPath(id string) string {
|
||||
return store.Path + "/" + id + ".bin"
|
||||
|
|
52
handler.go
52
handler.go
|
@ -25,6 +25,7 @@ var (
|
|||
ErrFileLocked = errors.New("file currently locked")
|
||||
ErrIllegalOffset = errors.New("illegal offset")
|
||||
ErrSizeExceeded = errors.New("resource's size exceeded")
|
||||
ErrNotImplemented = errors.New("feature not implemented")
|
||||
)
|
||||
|
||||
// HTTP status codes sent in the response when the specific error is returned.
|
||||
|
@ -37,6 +38,7 @@ var ErrStatusCodes = map[error]int{
|
|||
ErrFileLocked: 423, // Locked (WebDAV) (RFC 4918)
|
||||
ErrIllegalOffset: http.StatusConflict,
|
||||
ErrSizeExceeded: http.StatusRequestEntityTooLarge,
|
||||
ErrNotImplemented: http.StatusNotImplemented,
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
|
@ -92,6 +94,7 @@ 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.Add("PATCH", ":id", http.HandlerFunc(handler.patchFile))
|
||||
|
||||
return handler, nil
|
||||
|
@ -136,7 +139,9 @@ func (handler *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
// Test if the version sent by the client is supported
|
||||
if r.Header.Get("TUS-Resumable") != "1.0.0" {
|
||||
// GET methods are not checked since a browser may visit this URL and does
|
||||
// not include this header. This request is not part of the specification.
|
||||
if r.Method != "GET" && r.Header.Get("TUS-Resumable") != "1.0.0" {
|
||||
handler.sendError(w, ErrUnsupportedVersion)
|
||||
return
|
||||
}
|
||||
|
@ -258,6 +263,51 @@ func (handler *Handler) patchFile(w http.ResponseWriter, r *http.Request) {
|
|||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// Download a file using a GET request. This is not part of the specification.
|
||||
func (handler *Handler) getFile(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.URL.Query().Get(":id")
|
||||
|
||||
// Ensure file is not locked
|
||||
if _, ok := handler.locks[id]; ok {
|
||||
handler.sendError(w, ErrFileLocked)
|
||||
return
|
||||
}
|
||||
|
||||
// Lock file for further writes (heads are allowed)
|
||||
handler.locks[id] = true
|
||||
|
||||
// File will be unlocked regardless of an error or success
|
||||
defer func() {
|
||||
delete(handler.locks, id)
|
||||
}()
|
||||
|
||||
info, err := handler.dataStore.GetInfo(id)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
err = ErrNotFound
|
||||
}
|
||||
handler.sendError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Do not do anything if no data is stored yet.
|
||||
if info.Offset == 0 {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
|
||||
// Get reader
|
||||
src, err := handler.dataStore.GetReader(id)
|
||||
if err != nil {
|
||||
handler.sendError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Length", strconv.FormatInt(info.Offset, 10))
|
||||
w.WriteHeader(http.StatusOK)
|
||||
io.Copy(w, src)
|
||||
}
|
||||
|
||||
// Send the error in the response body. The status code will be looked up in
|
||||
// ErrStatusCodes. If none is found 500 Internal Error will be used.
|
||||
func (handler *Handler) sendError(w http.ResponseWriter, err error) {
|
||||
|
|
|
@ -23,6 +23,10 @@ func (store zeroStore) GetInfo(id string) (FileInfo, error) {
|
|||
return FileInfo{}, nil
|
||||
}
|
||||
|
||||
func (store zeroStore) GetReader(id string) (io.Reader, error) {
|
||||
return nil, ErrNotImplemented
|
||||
}
|
||||
|
||||
func TestCORS(t *testing.T) {
|
||||
handler, _ := NewHandler(Config{})
|
||||
|
||||
|
@ -92,7 +96,7 @@ func TestProtocolDiscovery(t *testing.T) {
|
|||
}
|
||||
|
||||
// Invalid or unsupported version
|
||||
req, _ = http.NewRequest("GET", "", nil)
|
||||
req, _ = http.NewRequest("POST", "", nil)
|
||||
req.Header.Set("TUS-Resumable", "foo")
|
||||
w = httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, req)
|
||||
|
@ -393,3 +397,44 @@ func TestPatchOverflow(t *testing.T) {
|
|||
t.Errorf("Expected %v (got %v)", http.StatusNoContent, w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
type getStore struct {
|
||||
zeroStore
|
||||
}
|
||||
|
||||
func (s getStore) GetInfo(id string) (FileInfo, error) {
|
||||
if id != "yes" {
|
||||
return FileInfo{}, os.ErrNotExist
|
||||
}
|
||||
|
||||
return FileInfo{
|
||||
Offset: 5,
|
||||
Size: 20,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s getStore) GetReader(id string) (io.Reader, error) {
|
||||
return strings.NewReader("hello"), nil
|
||||
}
|
||||
|
||||
func TestGetFile(t *testing.T) {
|
||||
handler, _ := NewHandler(Config{
|
||||
DataStore: getStore{},
|
||||
})
|
||||
|
||||
// Test successfull download
|
||||
req, _ := http.NewRequest("GET", "yes", nil)
|
||||
w := httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("Expected %v (got %v)", http.StatusOK, w.Code)
|
||||
}
|
||||
|
||||
if string(w.Body.Bytes()) != "hello" {
|
||||
t.Errorf("Expected response body to be 'hello'")
|
||||
}
|
||||
|
||||
if w.HeaderMap.Get("Content-Length") != "5" {
|
||||
t.Errorf("Expected Content-Length to be 5")
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue