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:
Jack 2020-07-26 04:06:47 -07:00 committed by GitHub
parent 8913679d88
commit d64212d902
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 23 additions and 22 deletions

View File

@ -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" {

View File

@ -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)
} }
}) })
} }

View File

@ -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)})
} }
} }

View File

@ -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()