mirror of
https://github.com/rclone/rclone.git
synced 2024-12-23 07:29:35 +01:00
serve/restic: expose interfaces so that rclone can be used as a library from within restic
This patch enables rclone to be used as a library from within restic - exposes NewServer - exposes Server - implements http.RoundTripper Co-authored-by: Jack Deng <jackdeng@gmail.com>
This commit is contained in:
parent
8913679d88
commit
d64212d902
@ -126,7 +126,7 @@ with a path of ` + "`/<username>/`" + `.
|
|||||||
cmd.CheckArgs(1, 1, command, args)
|
cmd.CheckArgs(1, 1, command, args)
|
||||||
f := cmd.NewFsSrc(args)
|
f := cmd.NewFsSrc(args)
|
||||||
cmd.Run(false, true, command, func() error {
|
cmd.Run(false, true, command, func() error {
|
||||||
s := newServer(f, &httpflags.Opt)
|
s := NewServer(f, &httpflags.Opt)
|
||||||
if stdio {
|
if stdio {
|
||||||
if terminal.IsTerminal(int(os.Stdout.Fd())) {
|
if terminal.IsTerminal(int(os.Stdout.Fd())) {
|
||||||
return errors.New("Refusing to run HTTP2 server directly on a terminal, please let restic start rclone")
|
return errors.New("Refusing to run HTTP2 server directly on a terminal, please let restic start rclone")
|
||||||
@ -139,7 +139,7 @@ with a path of ` + "`/<username>/`" + `.
|
|||||||
|
|
||||||
httpSrv := &http2.Server{}
|
httpSrv := &http2.Server{}
|
||||||
opts := &http2.ServeConnOpts{
|
opts := &http2.ServeConnOpts{
|
||||||
Handler: http.HandlerFunc(s.handler),
|
Handler: s,
|
||||||
}
|
}
|
||||||
httpSrv.ServeConn(conn, opts)
|
httpSrv.ServeConn(conn, opts)
|
||||||
return nil
|
return nil
|
||||||
@ -158,26 +158,27 @@ const (
|
|||||||
resticAPIV2 = "application/vnd.x.restic.rest.v2"
|
resticAPIV2 = "application/vnd.x.restic.rest.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// server contains everything to run the server
|
// Server contains everything to run the Server
|
||||||
type server struct {
|
type Server struct {
|
||||||
*httplib.Server
|
*httplib.Server
|
||||||
f fs.Fs
|
f fs.Fs
|
||||||
}
|
}
|
||||||
|
|
||||||
func newServer(f fs.Fs, opt *httplib.Options) *server {
|
// NewServer returns an HTTP server that speaks the rest protocol
|
||||||
|
func NewServer(f fs.Fs, opt *httplib.Options) *Server {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
s := &server{
|
s := &Server{
|
||||||
Server: httplib.NewServer(mux, opt),
|
Server: httplib.NewServer(mux, opt),
|
||||||
f: f,
|
f: f,
|
||||||
}
|
}
|
||||||
mux.HandleFunc(s.Opt.BaseURL+"/", s.handler)
|
mux.HandleFunc(s.Opt.BaseURL+"/", s.ServeHTTP)
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serve runs the http server in the background.
|
// Serve runs the http server in the background.
|
||||||
//
|
//
|
||||||
// Use s.Close() and s.Wait() to shutdown server
|
// Use s.Close() and s.Wait() to shutdown server
|
||||||
func (s *server) Serve() error {
|
func (s *Server) Serve() error {
|
||||||
err := s.Server.Serve()
|
err := s.Server.Serve()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -205,8 +206,8 @@ func makeRemote(path string) string {
|
|||||||
return prefix + fileName[:2] + "/" + fileName
|
return prefix + fileName[:2] + "/" + fileName
|
||||||
}
|
}
|
||||||
|
|
||||||
// handler reads incoming requests and dispatches them
|
// ServeHTTP reads incoming requests and dispatches them
|
||||||
func (s *server) handler(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Accept-Ranges", "bytes")
|
w.Header().Set("Accept-Ranges", "bytes")
|
||||||
w.Header().Set("Server", "rclone/"+fs.Version)
|
w.Header().Set("Server", "rclone/"+fs.Version)
|
||||||
|
|
||||||
@ -248,7 +249,7 @@ func (s *server) handler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get the remote
|
// get the remote
|
||||||
func (s *server) serveObject(w http.ResponseWriter, r *http.Request, remote string) {
|
func (s *Server) serveObject(w http.ResponseWriter, r *http.Request, remote string) {
|
||||||
o, err := s.f.NewObject(r.Context(), remote)
|
o, err := s.f.NewObject(r.Context(), remote)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fs.Debugf(remote, "%s request error: %v", r.Method, err)
|
fs.Debugf(remote, "%s request error: %v", r.Method, err)
|
||||||
@ -259,7 +260,7 @@ func (s *server) serveObject(w http.ResponseWriter, r *http.Request, remote stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
// postObject posts an object to the repository
|
// postObject posts an object to the repository
|
||||||
func (s *server) postObject(w http.ResponseWriter, r *http.Request, remote string) {
|
func (s *Server) postObject(w http.ResponseWriter, r *http.Request, remote string) {
|
||||||
if appendOnly {
|
if appendOnly {
|
||||||
// make sure the file does not exist yet
|
// make sure the file does not exist yet
|
||||||
_, err := s.f.NewObject(r.Context(), remote)
|
_, err := s.f.NewObject(r.Context(), remote)
|
||||||
@ -282,7 +283,7 @@ func (s *server) postObject(w http.ResponseWriter, r *http.Request, remote strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
// delete the remote
|
// delete the remote
|
||||||
func (s *server) deleteObject(w http.ResponseWriter, r *http.Request, remote string) {
|
func (s *Server) deleteObject(w http.ResponseWriter, r *http.Request, remote string) {
|
||||||
if appendOnly {
|
if appendOnly {
|
||||||
parts := strings.Split(r.URL.Path, "/")
|
parts := strings.Split(r.URL.Path, "/")
|
||||||
|
|
||||||
@ -331,7 +332,7 @@ func (ls *listItems) add(entry fs.DirEntry) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// listObjects lists all Objects of a given type in an arbitrary order.
|
// listObjects lists all Objects of a given type in an arbitrary order.
|
||||||
func (s *server) listObjects(w http.ResponseWriter, r *http.Request, remote string) {
|
func (s *Server) listObjects(w http.ResponseWriter, r *http.Request, remote string) {
|
||||||
fs.Debugf(remote, "list request")
|
fs.Debugf(remote, "list request")
|
||||||
|
|
||||||
if r.Header.Get("Accept") != resticAPIV2 {
|
if r.Header.Get("Accept") != resticAPIV2 {
|
||||||
@ -372,7 +373,7 @@ func (s *server) listObjects(w http.ResponseWriter, r *http.Request, remote stri
|
|||||||
// createRepo creates repository directories.
|
// createRepo creates repository directories.
|
||||||
//
|
//
|
||||||
// We don't bother creating the data dirs as rclone will create them on the fly
|
// We don't bother creating the data dirs as rclone will create them on the fly
|
||||||
func (s *server) createRepo(w http.ResponseWriter, r *http.Request, remote string) {
|
func (s *Server) createRepo(w http.ResponseWriter, r *http.Request, remote string) {
|
||||||
fs.Infof(remote, "Creating repository")
|
fs.Infof(remote, "Creating repository")
|
||||||
|
|
||||||
if r.URL.Query().Get("create") != "true" {
|
if r.URL.Query().Get("create") != "true" {
|
||||||
|
@ -126,10 +126,10 @@ func TestResticHandler(t *testing.T) {
|
|||||||
|
|
||||||
// make a new file system in the temp dir
|
// make a new file system in the temp dir
|
||||||
f := cmd.NewFsSrc([]string{tempdir})
|
f := cmd.NewFsSrc([]string{tempdir})
|
||||||
srv := newServer(f, &httpflags.Opt)
|
srv := NewServer(f, &httpflags.Opt)
|
||||||
|
|
||||||
// create the repo
|
// create the repo
|
||||||
checkRequest(t, srv.handler,
|
checkRequest(t, srv.ServeHTTP,
|
||||||
newRequest(t, "POST", "/?create=true", nil),
|
newRequest(t, "POST", "/?create=true", nil),
|
||||||
[]wantFunc{wantCode(http.StatusOK)})
|
[]wantFunc{wantCode(http.StatusOK)})
|
||||||
|
|
||||||
@ -137,7 +137,7 @@ func TestResticHandler(t *testing.T) {
|
|||||||
t.Run("", func(t *testing.T) {
|
t.Run("", func(t *testing.T) {
|
||||||
for i, seq := range test.seq {
|
for i, seq := range test.seq {
|
||||||
t.Logf("request %v: %v %v", i, seq.req.Method, seq.req.URL.Path)
|
t.Logf("request %v: %v %v", i, seq.req.Method, seq.req.URL.Path)
|
||||||
checkRequest(t, srv.handler, seq.req, seq.want)
|
checkRequest(t, srv.ServeHTTP, seq.req, seq.want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ func TestResticPrivateRepositories(t *testing.T) {
|
|||||||
|
|
||||||
// make a new file system in the temp dir
|
// make a new file system in the temp dir
|
||||||
f := cmd.NewFsSrc([]string{tempdir})
|
f := cmd.NewFsSrc([]string{tempdir})
|
||||||
srv := newServer(f, &httpflags.Opt)
|
srv := NewServer(f, &httpflags.Opt)
|
||||||
|
|
||||||
// Requesting /test/ should allow access
|
// Requesting /test/ should allow access
|
||||||
reqs := []*http.Request{
|
reqs := []*http.Request{
|
||||||
@ -66,7 +66,7 @@ func TestResticPrivateRepositories(t *testing.T) {
|
|||||||
newAuthenticatedRequest(t, "GET", "/test/config", nil),
|
newAuthenticatedRequest(t, "GET", "/test/config", nil),
|
||||||
}
|
}
|
||||||
for _, req := range reqs {
|
for _, req := range reqs {
|
||||||
checkRequest(t, srv.handler, req, []wantFunc{wantCode(http.StatusOK)})
|
checkRequest(t, srv.ServeHTTP, req, []wantFunc{wantCode(http.StatusOK)})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Requesting everything else should raise forbidden errors
|
// Requesting everything else should raise forbidden errors
|
||||||
@ -76,7 +76,7 @@ func TestResticPrivateRepositories(t *testing.T) {
|
|||||||
newAuthenticatedRequest(t, "GET", "/other_user/config", nil),
|
newAuthenticatedRequest(t, "GET", "/other_user/config", nil),
|
||||||
}
|
}
|
||||||
for _, req := range reqs {
|
for _, req := range reqs {
|
||||||
checkRequest(t, srv.handler, req, []wantFunc{wantCode(http.StatusForbidden)})
|
checkRequest(t, srv.ServeHTTP, req, []wantFunc{wantCode(http.StatusForbidden)})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ func TestRestic(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
// Start the server
|
// Start the server
|
||||||
w := newServer(fremote, &opt)
|
w := NewServer(fremote, &opt)
|
||||||
assert.NoError(t, w.Serve())
|
assert.NoError(t, w.Serve())
|
||||||
defer func() {
|
defer func() {
|
||||||
w.Close()
|
w.Close()
|
||||||
|
Loading…
Reference in New Issue
Block a user