From e4da373b41042f15d6ec8e7a6d3f9c5ebdbefc12 Mon Sep 17 00:00:00 2001 From: Chapuis Bertil Date: Fri, 7 Aug 2015 11:18:36 +0200 Subject: [PATCH] improved status code and ordered json response --- backend/interface.go | 27 +++++++++++- backend/rest/rest.go | 26 ++++------- backend/rest_test.go | 102 +++++++++++++++++++++++++++---------------- doc/REST_backend.md | 14 +++--- 4 files changed, 107 insertions(+), 62 deletions(-) diff --git a/backend/interface.go b/backend/interface.go index 63a3a95f8..926fe5caf 100644 --- a/backend/interface.go +++ b/backend/interface.go @@ -1,6 +1,9 @@ package backend -import "io" +import ( + "errors" + "io" +) // Type is the type of a Blob. type Type string @@ -14,6 +17,28 @@ const ( Config = "config" ) +func ParseType(s string) (Type, error) { + if s == string(Data) { + return Data, nil + } + if s == string(Key) { + return Key, nil + } + if s == string(Lock) { + return Lock, nil + } + if s == string(Snapshot) { + return Snapshot, nil + } + if s == string(Index) { + return Index, nil + } + if s == string(Config) { + return Config, nil + } + return "", errors.New("invalid type") +} + // A Backend manages data stored somewhere. type Backend interface { // Location returns a string that specifies the location of the repository, diff --git a/backend/rest/rest.go b/backend/rest/rest.go index 27c3de9a2..4db542d74 100644 --- a/backend/rest/rest.go +++ b/backend/rest/rest.go @@ -4,10 +4,12 @@ import ( "bytes" "encoding/json" "errors" + "fmt" "io" "io/ioutil" "net/http" "net/url" + "sort" "strings" "github.com/restic/restic/backend" @@ -59,26 +61,15 @@ func (rb *RestBlob) Finalize(t backend.Type, name string) error { rb.final = true - // Check key does not already exist. - req, err := http.NewRequest("HEAD", restPath(rb.b.url, t, name), nil) - if err != nil { - return err - } - - resp, errh := http.DefaultClient.Do(req) - if errh != nil { - return errh - } - if resp.StatusCode == 200 { - return errors.New("key already exists") - } - <-rb.b.connChan - _, errp := http.Post(restPath(rb.b.url, t, name), "binary/octet-stream", rb.buf) + resp, err := http.Post(restPath(rb.b.url, t, name), "binary/octet-stream", rb.buf) + if resp.StatusCode == 409 { + err = errors.New("already exists") + } rb.b.connChan <- struct{}{} rb.buf.Reset() - return errp + return err } // Size returns the number of bytes written to the backend so far. @@ -133,7 +124,7 @@ func (b *Rest) GetReader(t backend.Type, name string, offset, length uint) (io.R return nil, err } - req.Header.Add("Range", "bytes="+string(offset)+"-"+string(offset+length)) + req.Header.Add("Range", fmt.Sprintf("bytes=%d-%d", offset, offset+length)) resp, errg := http.DefaultClient.Do(req) if errg != nil { @@ -202,6 +193,7 @@ func (b *Rest) List(t backend.Type, done <-chan struct{}) <-chan string { go func() { defer close(ch) + sort.Strings(list) for _, m := range list { select { case ch <- m: diff --git a/backend/rest_test.go b/backend/rest_test.go index f58f3e432..66404b8e3 100644 --- a/backend/rest_test.go +++ b/backend/rest_test.go @@ -16,12 +16,6 @@ import ( "github.com/restic/restic/backend/rest" ) -func setupRestBackend(t *testing.T) *rest.Rest { - url, _ := url.Parse("http://localhost:8000") - backend, _ := rest.Open(url) - return backend -} - func TestRestBackend(t *testing.T) { // Initializing a temporary direcory for the rest backend. @@ -30,45 +24,43 @@ func TestRestBackend(t *testing.T) { dirs := []string{ path, - filepath.Join(path, "data"), - filepath.Join(path, "snapshot"), - filepath.Join(path, "index"), - filepath.Join(path, "lock"), - filepath.Join(path, "key"), - filepath.Join(path, "temp"), + filepath.Join(path, string(backend.Data)), + filepath.Join(path, string(backend.Snapshot)), + filepath.Join(path, string(backend.Index)), + filepath.Join(path, string(backend.Lock)), + filepath.Join(path, string(backend.Key)), } for _, d := range dirs { os.MkdirAll(d, backend.Modes.Dir) } - // Initialize the router for the repository requests. r := mux.NewRouter() - // Check if the repository has already been initialized. + // Check if a configuration exists. r.HandleFunc("/config", func(w http.ResponseWriter, r *http.Request) { file := filepath.Join(path, "config") if _, err := os.Stat(file); err != nil { - http.Error(w, "Repository not found", 404) + http.Error(w, "404 repository not found", 404) } }).Methods("HEAD") - // Get the configuration of the repository. + // Get the configuration. r.HandleFunc("/config", func(w http.ResponseWriter, r *http.Request) { file := filepath.Join(path, "config") if _, err := os.Stat(file); err == nil { bytes, _ := ioutil.ReadFile(file) w.Write(bytes) } else { - http.Error(w, "Repository not found", 404) + http.Error(w, "404 repository not found", 404) } }).Methods("GET") - // Initialize the repository and save the configuration. + // Save the configuration. r.HandleFunc("/config", func(w http.ResponseWriter, r *http.Request) { file := filepath.Join(path, "config") if _, err := os.Stat(file); err == nil { - http.Error(w, "Repository already initialized", 403) + http.Error(w, "409 repository already initialized", 409) } else { bytes, _ := ioutil.ReadAll(r.Body) ioutil.WriteFile(file, bytes, 0600) @@ -79,8 +71,12 @@ func TestRestBackend(t *testing.T) { // List the blobs of a given type. r.HandleFunc("/{type}/", func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) - blobType := filepath.Clean(vars["type"]) - path := filepath.Join(path, blobType) + blobType, errType := backend.ParseType(filepath.Clean(vars["type"])) + if errType != nil { + http.Error(w, "403 invalid blob type", 403) + return + } + path := filepath.Join(path, string(blobType)) files, _ := ioutil.ReadDir(path) names := make([]string, len(files)) for i, f := range files { @@ -93,35 +89,59 @@ func TestRestBackend(t *testing.T) { // Check if a blob of a given type exists. r.HandleFunc("/{type}/{blob}", func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) - blobType := filepath.Clean(vars["type"]) - blobID := filepath.Clean(vars["blob"]) - blob := filepath.Join(path, blobType, blobID) + blobType, errType := backend.ParseType(filepath.Clean(vars["type"])) + if errType != nil { + http.Error(w, "403 invalid blob type", 403) + return + } + blobID, errID := backend.ParseID(vars["blob"]) + if errID != nil { + http.Error(w, "403 invalid blob ID", 403) + return + } + blob := filepath.Join(path, string(blobType), blobID.String()) if _, err := os.Stat(blob); err != nil { - http.Error(w, "Blob not found", 404) + http.Error(w, "404 blob not found", 404) } }).Methods("HEAD") // Get a blob of a given type. r.HandleFunc("/{type}/{blob}", func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) - blobType := filepath.Clean(vars["type"]) - blobID := filepath.Clean(vars["blob"]) - blob := filepath.Join(path, blobType, blobID) + blobType, errType := backend.ParseType(filepath.Clean(vars["type"])) + if errType != nil { + http.Error(w, "403 invalid blob type", 403) + return + } + blobID, errID := backend.ParseID(vars["blob"]) + if errID != nil { + http.Error(w, "403 invalid blob ID", 403) + return + } + blob := filepath.Join(path, string(blobType), blobID.String()) if file, err := os.Open(blob); err == nil { http.ServeContent(w, r, "", time.Unix(0, 0), file) } else { - http.Error(w, "Blob not found", 404) + http.Error(w, "404 blob not found", 404) } }).Methods("GET") // Save a blob of a given type. r.HandleFunc("/{type}/{blob}", func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) - blobType := filepath.Clean(vars["type"]) - blobID := filepath.Clean(vars["blob"]) - blob := filepath.Join(path, blobType, blobID) + blobType, errType := backend.ParseType(filepath.Clean(vars["type"])) + if errType != nil { + http.Error(w, "403 invalid blob type", 403) + return + } + blobID, errID := backend.ParseID(vars["blob"]) + if errID != nil { + http.Error(w, "403 invalid blob ID", 403) + return + } + blob := filepath.Join(path, string(blobType), blobID.String()) if _, err := os.Stat(blob); err == nil { - http.Error(w, "Blob already uploaded", 403) + http.Error(w, "409 blob already uploaded", 409) } else { bytes, _ := ioutil.ReadAll(r.Body) ioutil.WriteFile(blob, bytes, 0600) @@ -131,13 +151,21 @@ func TestRestBackend(t *testing.T) { // Delete a blob of a given type. r.HandleFunc("/{type}/{blob}", func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) - blobType := filepath.Clean(vars["type"]) - blobID := filepath.Clean(vars["blob"]) - blob := filepath.Join(path, blobType, blobID) + blobType, errType := backend.ParseType(filepath.Clean(vars["type"])) + if errType != nil { + http.Error(w, "403 invalid blob type", 403) + return + } + blobID, errID := backend.ParseID(vars["blob"]) + if errID != nil { + http.Error(w, "403 invalid blob ID", 403) + return + } + blob := filepath.Join(path, string(blobType), blobID.String()) if _, err := os.Stat(blob); err == nil { os.Remove(blob) } else { - http.Error(w, "Blob not found", 404) + http.Error(w, "404 blob not found", 404) } }).Methods("DELETE") diff --git a/doc/REST_backend.md b/doc/REST_backend.md index 91221c785..68e1af638 100644 --- a/doc/REST_backend.md +++ b/doc/REST_backend.md @@ -6,12 +6,12 @@ Restic can interact with HTTP Backend that respects the following REST API. ## HEAD /config Returns "200 OK" if the repository has already been initialized, -"404 Repository not found" otherwise. +"404 repository not found" otherwise. ## GET /config Returns the configuration if the repository has already been initialized, -"404 Repository not found" otherwise. +"404 repository not found" otherwise. Response format: binary/octet-stream @@ -19,7 +19,7 @@ Response format: binary/octet-stream Saves the configuration transmitted in the request body. Returns "200 OK" if the configuration has been saved, -"403 Repository already initialized" if the repository has already been initialized. +"409 repository already initialized" if the repository has already been initialized. Response format: text @@ -32,19 +32,19 @@ Response format: JSON ## HEAD /{type}/{blobID} Returns "200 OK" if the repository contains a blob with the given ID and type, -"404 Blob not found" otherwise. +"404 blob not found" otherwise. ## GET /{type}/{blobID} Returns the content of the blob with the given ID and type, -"404 Blob not found" otherwise. +"404 blob not found" otherwise. Response format: binary/octet-stream ## POST /{type}/{blobID} Saves the content of the request body in a blob with the given ID and type, -"403 Blob already exists" if a blob has already been saved. +"409 blob already exists" if a blob has already been saved. Request format: binary/octet-stream @@ -52,4 +52,4 @@ Request format: binary/octet-stream Deletes the blob with the given ID and type. Returns "200 OK" if the given blob exists and has been deleted, -"404 Blob not found" otherwise. +"404 blob not found" otherwise.