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:
parent
6ca450e0b3
commit
e4da373b41
4 changed files with 107 additions and 62 deletions
|
@ -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,
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Add table
Reference in a new issue