From d64212d90263236d1ea804192bcd32375e90e57a Mon Sep 17 00:00:00 2001 From: Jack <196648+jdeng@users.noreply.github.com> Date: Sun, 26 Jul 2020 04:06:47 -0700 Subject: [PATCH] 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 --- cmd/serve/restic/restic.go | 31 ++++++++++---------- cmd/serve/restic/restic_appendonly_test.go | 6 ++-- cmd/serve/restic/restic_privaterepos_test.go | 6 ++-- cmd/serve/restic/restic_test.go | 2 +- 4 files changed, 23 insertions(+), 22 deletions(-) diff --git a/cmd/serve/restic/restic.go b/cmd/serve/restic/restic.go index f19b1d37c..1d74ef1d0 100644 --- a/cmd/serve/restic/restic.go +++ b/cmd/serve/restic/restic.go @@ -126,7 +126,7 @@ with a path of ` + "`//`" + `. cmd.CheckArgs(1, 1, command, args) f := cmd.NewFsSrc(args) cmd.Run(false, true, command, func() error { - s := newServer(f, &httpflags.Opt) + s := NewServer(f, &httpflags.Opt) if stdio { if terminal.IsTerminal(int(os.Stdout.Fd())) { 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 ` + "`//`" + `. httpSrv := &http2.Server{} opts := &http2.ServeConnOpts{ - Handler: http.HandlerFunc(s.handler), + Handler: s, } httpSrv.ServeConn(conn, opts) return nil @@ -158,26 +158,27 @@ const ( resticAPIV2 = "application/vnd.x.restic.rest.v2" ) -// server contains everything to run the server -type server struct { +// Server contains everything to run the Server +type Server struct { *httplib.Server 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() - s := &server{ + s := &Server{ Server: httplib.NewServer(mux, opt), f: f, } - mux.HandleFunc(s.Opt.BaseURL+"/", s.handler) + mux.HandleFunc(s.Opt.BaseURL+"/", s.ServeHTTP) return s } // Serve runs the http server in the background. // // Use s.Close() and s.Wait() to shutdown server -func (s *server) Serve() error { +func (s *Server) Serve() error { err := s.Server.Serve() if err != nil { return err @@ -205,8 +206,8 @@ func makeRemote(path string) string { return prefix + fileName[:2] + "/" + fileName } -// handler reads incoming requests and dispatches them -func (s *server) handler(w http.ResponseWriter, r *http.Request) { +// ServeHTTP reads incoming requests and dispatches them +func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Header().Set("Accept-Ranges", "bytes") w.Header().Set("Server", "rclone/"+fs.Version) @@ -248,7 +249,7 @@ func (s *server) handler(w http.ResponseWriter, r *http.Request) { } // 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) if err != nil { 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 -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 { // make sure the file does not exist yet _, 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 -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 { 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. -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") 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. // // 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") if r.URL.Query().Get("create") != "true" { diff --git a/cmd/serve/restic/restic_appendonly_test.go b/cmd/serve/restic/restic_appendonly_test.go index 8658fab95..9087f941d 100644 --- a/cmd/serve/restic/restic_appendonly_test.go +++ b/cmd/serve/restic/restic_appendonly_test.go @@ -126,10 +126,10 @@ func TestResticHandler(t *testing.T) { // make a new file system in the temp dir f := cmd.NewFsSrc([]string{tempdir}) - srv := newServer(f, &httpflags.Opt) + srv := NewServer(f, &httpflags.Opt) // create the repo - checkRequest(t, srv.handler, + checkRequest(t, srv.ServeHTTP, newRequest(t, "POST", "/?create=true", nil), []wantFunc{wantCode(http.StatusOK)}) @@ -137,7 +137,7 @@ func TestResticHandler(t *testing.T) { t.Run("", func(t *testing.T) { for i, seq := range test.seq { 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) } }) } diff --git a/cmd/serve/restic/restic_privaterepos_test.go b/cmd/serve/restic/restic_privaterepos_test.go index 61e7864de..4feeb3d8a 100644 --- a/cmd/serve/restic/restic_privaterepos_test.go +++ b/cmd/serve/restic/restic_privaterepos_test.go @@ -57,7 +57,7 @@ func TestResticPrivateRepositories(t *testing.T) { // make a new file system in the temp dir f := cmd.NewFsSrc([]string{tempdir}) - srv := newServer(f, &httpflags.Opt) + srv := NewServer(f, &httpflags.Opt) // Requesting /test/ should allow access reqs := []*http.Request{ @@ -66,7 +66,7 @@ func TestResticPrivateRepositories(t *testing.T) { newAuthenticatedRequest(t, "GET", "/test/config", nil), } 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 @@ -76,7 +76,7 @@ func TestResticPrivateRepositories(t *testing.T) { newAuthenticatedRequest(t, "GET", "/other_user/config", nil), } for _, req := range reqs { - checkRequest(t, srv.handler, req, []wantFunc{wantCode(http.StatusForbidden)}) + checkRequest(t, srv.ServeHTTP, req, []wantFunc{wantCode(http.StatusForbidden)}) } } diff --git a/cmd/serve/restic/restic_test.go b/cmd/serve/restic/restic_test.go index 23b3c77bb..d9f343ca5 100644 --- a/cmd/serve/restic/restic_test.go +++ b/cmd/serve/restic/restic_test.go @@ -41,7 +41,7 @@ func TestRestic(t *testing.T) { assert.NoError(t, err) // Start the server - w := newServer(fremote, &opt) + w := NewServer(fremote, &opt) assert.NoError(t, w.Serve()) defer func() { w.Close()