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
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
|
|
21
context.go
21
context.go
|
@ -1,6 +1,11 @@
|
|||
package main
|
||||
|
||||
import ()
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/restic/restic/backend"
|
||||
)
|
||||
|
||||
// A Context specifies the root directory where all repositories are stored
|
||||
type Context struct {
|
||||
|
@ -8,10 +13,18 @@ type Context struct {
|
|||
}
|
||||
|
||||
func NewContext(path string) Context {
|
||||
return Context{path}
|
||||
return Context{filepath.Clean(path)}
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Handler func(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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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
|
||||
|
||||
import ()
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/restic/restic/backend"
|
||||
)
|
||||
|
||||
// A Repository is the place where backups are stored
|
||||
type Repository struct {
|
||||
|
@ -8,6 +14,81 @@ type Repository struct {
|
|||
}
|
||||
|
||||
// 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
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/restic/restic/backend"
|
||||
)
|
||||
|
||||
// Route all the server requests
|
||||
func Router(w http.ResponseWriter, r *http.Request) {
|
||||
type Router struct {
|
||||
Context
|
||||
}
|
||||
|
||||
func (router Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
m := r.Method
|
||||
u := r.RequestURI
|
||||
|
||||
log.Println("%s %s", m, u)
|
||||
log.Printf("%s %s", m, u)
|
||||
|
||||
if Authorize(r) {
|
||||
if handler := RestAPI(m, u); handler != nil {
|
||||
handler(w, r, nil)
|
||||
handler(w, r, &router.Context)
|
||||
} else {
|
||||
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.
|
||||
func RestAPI(m string, u string) Handler {
|
||||
s := strings.Split(u, "/")
|
||||
|
|
24
server.go
24
server.go
|
@ -1,10 +1,28 @@
|
|||
package main
|
||||
|
||||
import ()
|
||||
import (
|
||||
//"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
//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