serve http/webdav/restic: implement --prefix - fixes #3398

--prefix enables the servers to serve from a non root prefix.  This
enables easier proxying.
This commit is contained in:
Nick Craig-Wood 2019-08-04 10:56:38 +01:00
parent d51a970932
commit 02eb747d71
5 changed files with 51 additions and 7 deletions

View File

@ -68,7 +68,7 @@ func newServer(f fs.Fs, opt *httplib.Options) *server {
f: f, f: f,
vfs: vfs.New(f, &vfsflags.Opt), vfs: vfs.New(f, &vfsflags.Opt),
} }
mux.HandleFunc("/", s.handler) mux.HandleFunc(s.Opt.Prefix+"/", s.handler)
return s return s
} }
@ -93,7 +93,10 @@ func (s *server) handler(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)
urlPath := r.URL.Path urlPath, ok := s.Path(w, r)
if !ok {
return
}
isDir := strings.HasSuffix(urlPath, "/") isDir := strings.HasSuffix(urlPath, "/")
remote := strings.Trim(urlPath, "/") remote := strings.Trim(urlPath, "/")
if isDir { if isDir {

View File

@ -26,6 +26,9 @@ func AddFlagsPrefix(flagSet *pflag.FlagSet, prefix string, Opt *httplib.Options)
flags.StringVarP(flagSet, &Opt.Realm, prefix+"realm", "", Opt.Realm, "realm for authentication") flags.StringVarP(flagSet, &Opt.Realm, prefix+"realm", "", Opt.Realm, "realm for authentication")
flags.StringVarP(flagSet, &Opt.BasicUser, prefix+"user", "", Opt.BasicUser, "User name for authentication.") flags.StringVarP(flagSet, &Opt.BasicUser, prefix+"user", "", Opt.BasicUser, "User name for authentication.")
flags.StringVarP(flagSet, &Opt.BasicPass, prefix+"pass", "", Opt.BasicPass, "Password for authentication.") flags.StringVarP(flagSet, &Opt.BasicPass, prefix+"pass", "", Opt.BasicPass, "Password for authentication.")
if prefix == "" {
flags.StringVarP(flagSet, &Opt.Prefix, prefix+"prefix", "", Opt.Prefix, "Prefix for URLs.")
}
} }
// AddFlags adds flags for the httplib // AddFlags adds flags for the httplib

View File

@ -44,6 +44,11 @@ for a transfer.
--max-header-bytes controls the maximum number of bytes the server will --max-header-bytes controls the maximum number of bytes the server will
accept in the HTTP header. accept in the HTTP header.
--prefix controls the URL prefix that rclone serves from. By default
rclone will serve from the root. If you used --prefix "rclone" then
rclone would serve from a URL starting with "/rclone/". This is
useful if you wish to proxy rclone serve.
#### Authentication #### Authentication
By default this will serve files without needing a login. By default this will serve files without needing a login.
@ -81,6 +86,7 @@ certificate authority certificate.
// Options contains options for the http Server // Options contains options for the http Server
type Options struct { type Options struct {
ListenAddr string // Port to listen on ListenAddr string // Port to listen on
Prefix string // prefix to strip from URLs
ServerReadTimeout time.Duration // Timeout for server reading data ServerReadTimeout time.Duration // Timeout for server reading data
ServerWriteTimeout time.Duration // Timeout for server writing data ServerWriteTimeout time.Duration // Timeout for server writing data
MaxHeaderBytes int // Maximum size of request header MaxHeaderBytes int // Maximum size of request header
@ -190,6 +196,14 @@ func NewServer(handler http.Handler, opt *Options) *Server {
log.Fatalf("Need both -cert and -key to use SSL") log.Fatalf("Need both -cert and -key to use SSL")
} }
// If a Path is set then serve from there
if strings.HasSuffix(s.Opt.Prefix, "/") {
s.Opt.Prefix = s.Opt.Prefix[:len(s.Opt.Prefix)-1]
}
if s.Opt.Prefix != "" && !strings.HasPrefix(s.Opt.Prefix, "/") {
s.Opt.Prefix = "/" + s.Opt.Prefix
}
// FIXME make a transport? // FIXME make a transport?
s.httpServer = &http.Server{ s.httpServer = &http.Server{
Addr: s.Opt.ListenAddr, Addr: s.Opt.ListenAddr,
@ -299,10 +313,27 @@ func (s *Server) URL() string {
// (i.e. port assigned by operating system) // (i.e. port assigned by operating system)
addr = s.listener.Addr().String() addr = s.listener.Addr().String()
} }
return fmt.Sprintf("%s://%s/", proto, addr) return fmt.Sprintf("%s://%s%s/", proto, addr, s.Opt.Prefix)
} }
// UsingAuth returns true if authentication is required // UsingAuth returns true if authentication is required
func (s *Server) UsingAuth() bool { func (s *Server) UsingAuth() bool {
return s.usingAuth return s.usingAuth
} }
// Path returns the current path with the Prefix stripped
//
// If it returns false, then the path was invalid and the handler
// should exit as the error response has already been sent
func (s *Server) Path(w http.ResponseWriter, r *http.Request) (Path string, ok bool) {
Path = r.URL.Path
if s.Opt.Prefix == "" {
return Path, true
}
if !strings.HasPrefix(Path, s.Opt.Prefix+"/") {
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return Path, false
}
Path = Path[len(s.Opt.Prefix):]
return Path, true
}

View File

@ -171,7 +171,7 @@ func newServer(f fs.Fs, opt *httplib.Options) *server {
Server: httplib.NewServer(mux, opt), Server: httplib.NewServer(mux, opt),
f: f, f: f,
} }
mux.HandleFunc("/", s.handler) mux.HandleFunc(s.Opt.Prefix+"/", s.handler)
return s return s
} }
@ -211,7 +211,10 @@ func (s *server) handler(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)
path := r.URL.Path path, ok := s.Path(w, r)
if !ok {
return
}
remote := makeRemote(path) remote := makeRemote(path)
fs.Debugf(s.f, "%s %s", r.Method, path) fs.Debugf(s.f, "%s %s", r.Method, path)

View File

@ -114,18 +114,22 @@ func newWebDAV(f fs.Fs, opt *httplib.Options) *WebDAV {
f: f, f: f,
vfs: vfs.New(f, &vfsflags.Opt), vfs: vfs.New(f, &vfsflags.Opt),
} }
w.Server = httplib.NewServer(http.HandlerFunc(w.handler), opt)
webdavHandler := &webdav.Handler{ webdavHandler := &webdav.Handler{
Prefix: w.Server.Opt.Prefix,
FileSystem: w, FileSystem: w,
LockSystem: webdav.NewMemLS(), LockSystem: webdav.NewMemLS(),
Logger: w.logRequest, // FIXME Logger: w.logRequest, // FIXME
} }
w.webdavhandler = webdavHandler w.webdavhandler = webdavHandler
w.Server = httplib.NewServer(http.HandlerFunc(w.handler), opt)
return w return w
} }
func (w *WebDAV) handler(rw http.ResponseWriter, r *http.Request) { func (w *WebDAV) handler(rw http.ResponseWriter, r *http.Request) {
urlPath := r.URL.Path urlPath, ok := w.Path(rw, r)
if !ok {
return
}
isDir := strings.HasSuffix(urlPath, "/") isDir := strings.HasSuffix(urlPath, "/")
remote := strings.Trim(urlPath, "/") remote := strings.Trim(urlPath, "/")
if !disableGETDir && (r.Method == "GET" || r.Method == "HEAD") && isDir { if !disableGETDir && (r.Method == "GET" || r.Method == "HEAD") && isDir {