1
0
Fork 0
mirror of https://github.com/restic/restic.git synced 2025-03-16 00:00:05 +01:00

improved status code and ordered json response

This commit is contained in:
Chapuis Bertil 2015-08-07 11:18:36 +02:00
parent 6ca450e0b3
commit e4da373b41
4 changed files with 107 additions and 62 deletions

View file

@ -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,

View file

@ -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:

View file

@ -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")

View file

@ -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.