mirror of
https://github.com/restic/rest-server.git
synced 2025-10-19 15:43:21 +00:00
read support for length and offset
This commit is contained in:
parent
71163e75d1
commit
59621ca2d6
6 changed files with 321 additions and 62 deletions
1
auth.go
1
auth.go
|
@ -1,7 +1,6 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
21
context.go
21
context.go
|
@ -1,6 +1,11 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import ()
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/restic/restic/backend"
|
||||||
|
)
|
||||||
|
|
||||||
// A Context specifies the root directory where all repositories are stored
|
// A Context specifies the root directory where all repositories are stored
|
||||||
type Context struct {
|
type Context struct {
|
||||||
|
@ -8,10 +13,18 @@ type Context struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewContext(path string) Context {
|
func NewContext(path string) Context {
|
||||||
return Context{path}
|
return Context{filepath.Clean(path)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates the file structure of the Context
|
// Creates the file structure of the Context
|
||||||
func (c *Context) Init() {
|
func (c *Context) Init() error {
|
||||||
|
if _, err := os.Stat(c.path); err != nil {
|
||||||
|
return os.MkdirAll(c.path, backend.Modes.Dir)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) Repository(name string) (Repository, error) {
|
||||||
|
name, err := ParseRepositoryName(name)
|
||||||
|
return Repository{filepath.Join(c.path, name)}, err
|
||||||
}
|
}
|
||||||
|
|
201
handlers.go
201
handlers.go
|
@ -1,40 +1,223 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Handler func(w http.ResponseWriter, r *http.Request, c *Context)
|
type Handler func(w http.ResponseWriter, r *http.Request, c *Context)
|
||||||
|
|
||||||
func HeadConfig(w http.ResponseWriter, r *http.Request, c *Context) {
|
func HeadConfig(w http.ResponseWriter, r *http.Request, c *Context) {
|
||||||
fmt.Fprintln(w, "head config")
|
uri := r.RequestURI
|
||||||
|
name, errrn := RepositoryName(uri)
|
||||||
|
if errrn != nil {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
repo, errr := c.Repository(name)
|
||||||
|
if errr != nil {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !repo.HasConfig() {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetConfig(w http.ResponseWriter, r *http.Request, c *Context) {
|
func GetConfig(w http.ResponseWriter, r *http.Request, c *Context) {
|
||||||
fmt.Fprintln(w, "get config")
|
uri := r.RequestURI
|
||||||
|
name, errrn := RepositoryName(uri)
|
||||||
|
if errrn != nil {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
repo, errr := c.Repository(name)
|
||||||
|
if errr != nil {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
config, errrc := repo.ReadConfig()
|
||||||
|
if errrc != nil {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Write(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
func PostConfig(w http.ResponseWriter, r *http.Request, c *Context) {
|
func PostConfig(w http.ResponseWriter, r *http.Request, c *Context) {
|
||||||
fmt.Fprintln(w, "post config")
|
uri := r.RequestURI
|
||||||
|
name, errrn := RepositoryName(uri)
|
||||||
|
if errrn != nil {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
repo, errr := c.Repository(name)
|
||||||
|
if errr != nil {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
config, errc := ioutil.ReadAll(r.Body)
|
||||||
|
if errc != nil {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
errwc := repo.WriteConfig(config)
|
||||||
|
if errwc != nil {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ListBlob(w http.ResponseWriter, r *http.Request, c *Context) {
|
func ListBlob(w http.ResponseWriter, r *http.Request, c *Context) {
|
||||||
fmt.Fprintln(w, "list blob")
|
uri := r.RequestURI
|
||||||
|
name, errrn := RepositoryName(uri)
|
||||||
|
if errrn != nil {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
repo, errr := c.Repository(name)
|
||||||
|
if errr != nil {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bt := BackendType(uri)
|
||||||
|
if bt.IsNull() {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
blobs, errb := repo.ListBlob(bt)
|
||||||
|
if errb != nil {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
json, errj := json.Marshal(blobs)
|
||||||
|
if errj != nil {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Write(json)
|
||||||
}
|
}
|
||||||
|
|
||||||
func HeadBlob(w http.ResponseWriter, r *http.Request, c *Context) {
|
func HeadBlob(w http.ResponseWriter, r *http.Request, c *Context) {
|
||||||
fmt.Fprintln(w, "head blob")
|
uri := r.RequestURI
|
||||||
|
name, errrn := RepositoryName(uri)
|
||||||
|
if errrn != nil {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
repo, errr := c.Repository(name)
|
||||||
|
if errr != nil {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bt := BackendType(uri)
|
||||||
|
if bt.IsNull() {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
id := BlobID(uri)
|
||||||
|
if id.IsNull() {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !repo.HasBlob(bt, id) {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetBlob(w http.ResponseWriter, r *http.Request, c *Context) {
|
func GetBlob(w http.ResponseWriter, r *http.Request, c *Context) {
|
||||||
fmt.Fprintln(w, "get blob")
|
uri := r.RequestURI
|
||||||
|
name, errrn := RepositoryName(uri)
|
||||||
|
if errrn != nil {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
repo, errr := c.Repository(name)
|
||||||
|
if errr != nil {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bt := BackendType(uri)
|
||||||
|
if bt.IsNull() {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
id := BlobID(uri)
|
||||||
|
if id.IsNull() {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
blob, errb := repo.ReadBlob(bt, id)
|
||||||
|
if errb != nil {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.ServeContent(w, r, "", time.Unix(0, 0), blob)
|
||||||
}
|
}
|
||||||
|
|
||||||
func PostBlob(w http.ResponseWriter, r *http.Request, c *Context) {
|
func PostBlob(w http.ResponseWriter, r *http.Request, c *Context) {
|
||||||
fmt.Fprintln(w, "post blob")
|
uri := r.RequestURI
|
||||||
|
name, errrn := RepositoryName(uri)
|
||||||
|
if errrn != nil {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
repo, errr := c.Repository(name)
|
||||||
|
if errr != nil {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bt := BackendType(uri)
|
||||||
|
if bt.IsNull() {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
id := BlobID(uri)
|
||||||
|
if id.IsNull() {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
blob, errb := ioutil.ReadAll(r.Body)
|
||||||
|
if errb != nil {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
errwb := repo.WriteBlob(bt, id, blob)
|
||||||
|
if errwb != nil {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteBlob(w http.ResponseWriter, r *http.Request, c *Context) {
|
func DeleteBlob(w http.ResponseWriter, r *http.Request, c *Context) {
|
||||||
fmt.Fprintln(w, "delete blob")
|
uri := r.RequestURI
|
||||||
|
name, errrn := RepositoryName(uri)
|
||||||
|
if errrn != nil {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
repo, errr := c.Repository(name)
|
||||||
|
if errr != nil {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bt := BackendType(uri)
|
||||||
|
if bt.IsNull() {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
id := BlobID(uri)
|
||||||
|
if id.IsNull() {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
errd := repo.DeleteBlob(bt, id)
|
||||||
|
if errd != nil {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import ()
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/restic/restic/backend"
|
||||||
|
)
|
||||||
|
|
||||||
// A Repository is the place where backups are stored
|
// A Repository is the place where backups are stored
|
||||||
type Repository struct {
|
type Repository struct {
|
||||||
|
@ -8,6 +14,81 @@ type Repository struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates the file structure of the Repository
|
// Creates the file structure of the Repository
|
||||||
func (r *Repository) Init() {
|
func (r *Repository) Init() error {
|
||||||
|
dirs := []string{
|
||||||
|
r.path,
|
||||||
|
filepath.Join(r.path, string(backend.Data)),
|
||||||
|
filepath.Join(r.path, string(backend.Snapshot)),
|
||||||
|
filepath.Join(r.path, string(backend.Index)),
|
||||||
|
filepath.Join(r.path, string(backend.Lock)),
|
||||||
|
filepath.Join(r.path, string(backend.Key)),
|
||||||
|
}
|
||||||
|
for _, d := range dirs {
|
||||||
|
if _, errs := os.Stat(d); errs != nil {
|
||||||
|
errmk := os.MkdirAll(d, backend.Modes.Dir)
|
||||||
|
if errmk != nil {
|
||||||
|
return errmk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) HasConfig() bool {
|
||||||
|
file := filepath.Join(r.path, string(backend.Config))
|
||||||
|
if _, err := os.Stat(file); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) ReadConfig() ([]byte, error) {
|
||||||
|
file := filepath.Join(r.path, string(backend.Config))
|
||||||
|
return ioutil.ReadFile(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) WriteConfig(data []byte) error {
|
||||||
|
file := filepath.Join(r.path, string(backend.Config))
|
||||||
|
return ioutil.WriteFile(file, data, backend.Modes.File)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) ListBlob(t backend.Type) ([]string, error) {
|
||||||
|
var blobs []string
|
||||||
|
dir := filepath.Join(r.path, string(t))
|
||||||
|
files, err := ioutil.ReadDir(dir)
|
||||||
|
if err != nil {
|
||||||
|
return blobs, err
|
||||||
|
}
|
||||||
|
blobs = make([]string, len(files))
|
||||||
|
for i, f := range files {
|
||||||
|
blobs[i] = f.Name()
|
||||||
|
}
|
||||||
|
return blobs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) HasBlob(bt backend.Type, id backend.ID) bool {
|
||||||
|
file := filepath.Join(r.path, string(bt), id.String())
|
||||||
|
if _, err := os.Stat(file); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) ReadBlob(bt backend.Type, id backend.ID) (*os.File, error) {
|
||||||
|
file := filepath.Join(r.path, string(bt), id.String())
|
||||||
|
f, err := os.Open(file)
|
||||||
|
if err != nil {
|
||||||
|
return f, err
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) WriteBlob(bt backend.Type, id backend.ID, data []byte) error {
|
||||||
|
file := filepath.Join(r.path, string(bt), id.String())
|
||||||
|
return ioutil.WriteFile(file, data, backend.Modes.File)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) DeleteBlob(bt backend.Type, id backend.ID) error {
|
||||||
|
file := filepath.Join(r.path, string(bt), id.String())
|
||||||
|
return os.Remove(file)
|
||||||
}
|
}
|
||||||
|
|
49
router.go
49
router.go
|
@ -1,25 +1,26 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/restic/restic/backend"
|
"github.com/restic/restic/backend"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Route all the server requests
|
type Router struct {
|
||||||
func Router(w http.ResponseWriter, r *http.Request) {
|
Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func (router Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
m := r.Method
|
m := r.Method
|
||||||
u := r.RequestURI
|
u := r.RequestURI
|
||||||
|
|
||||||
log.Println("%s %s", m, u)
|
log.Printf("%s %s", m, u)
|
||||||
|
|
||||||
if Authorize(r) {
|
if Authorize(r) {
|
||||||
if handler := RestAPI(m, u); handler != nil {
|
if handler := RestAPI(m, u); handler != nil {
|
||||||
handler(w, r, nil)
|
handler(w, r, &router.Context)
|
||||||
} else {
|
} else {
|
||||||
http.Error(w, "not found", 404)
|
http.Error(w, "not found", 404)
|
||||||
}
|
}
|
||||||
|
@ -28,42 +29,6 @@ func Router(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the repository name for a given path
|
|
||||||
func RepositoryName(u string) (string, error) {
|
|
||||||
s := strings.Split(u, "/")
|
|
||||||
if len(s) <= 1 {
|
|
||||||
return "", errors.New("path does not contain repository name")
|
|
||||||
}
|
|
||||||
if len(s[1]) < 1 {
|
|
||||||
return "", errors.New("repository name should contain at least 1 character")
|
|
||||||
}
|
|
||||||
match, err := regexp.MatchString("^[a-zA-Z0-9_-]*$", s[1])
|
|
||||||
if !match || err != nil {
|
|
||||||
return "", errors.New("repository name should not contains special characters")
|
|
||||||
}
|
|
||||||
return s[1], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the backend type for a given path
|
|
||||||
func BackendType(u string) backend.Type {
|
|
||||||
s := strings.Split(u, "/")
|
|
||||||
var bt backend.Type
|
|
||||||
if len(s) > 2 {
|
|
||||||
bt, _ = backend.ParseType(s[2])
|
|
||||||
}
|
|
||||||
return bt
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the blob ID for a given path
|
|
||||||
func BlobID(u string) backend.ID {
|
|
||||||
s := strings.Split(u, "/")
|
|
||||||
var id backend.ID
|
|
||||||
if len(s) > 3 {
|
|
||||||
id, _ = backend.ParseID(s[3])
|
|
||||||
}
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
|
|
||||||
// The Rest API returns a Handler when a match occur or nil.
|
// The Rest API returns a Handler when a match occur or nil.
|
||||||
func RestAPI(m string, u string) Handler {
|
func RestAPI(m string, u string) Handler {
|
||||||
s := strings.Split(u, "/")
|
s := strings.Split(u, "/")
|
||||||
|
|
24
server.go
24
server.go
|
@ -1,10 +1,28 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import ()
|
import (
|
||||||
|
//"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
//path, _ := ioutil.TempDir("", "restic-repository-")
|
//path, _ := ioutil.TempDir("", "restic-repository-")
|
||||||
|
//log.Printf("initialize context at %s", path)
|
||||||
|
|
||||||
//http.ListenAndServe(":8000", r)
|
context := Context{"/tmp/restic"}
|
||||||
|
|
||||||
|
repo, _ := context.Repository("repo")
|
||||||
|
repo.Init()
|
||||||
|
|
||||||
|
errc := context.Init()
|
||||||
|
if errc != nil {
|
||||||
|
log.Println("context initialization failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
router := Router{context}
|
||||||
|
port := ":8000"
|
||||||
|
log.Printf("start server on port %s", port)
|
||||||
|
http.ListenAndServe(port, router)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue