1
0
mirror of https://github.com/rclone/rclone.git synced 2025-12-06 00:03:32 +00:00

rcserver: serve directories as well as files

This commit is contained in:
Nick Craig-Wood
2018-10-28 14:31:24 +00:00
parent 370c218c63
commit 89550e7121
9 changed files with 722 additions and 45 deletions

View File

@@ -5,11 +5,16 @@ import (
"encoding/json"
"mime"
"net/http"
"net/url"
"regexp"
"sort"
"strings"
"github.com/ncw/rclone/cmd/serve/httplib"
"github.com/ncw/rclone/cmd/serve/httplib/serve"
"github.com/ncw/rclone/fs"
"github.com/ncw/rclone/fs/config"
"github.com/ncw/rclone/fs/list"
"github.com/ncw/rclone/fs/rc"
"github.com/pkg/errors"
"github.com/skratchdot/open-golang/open"
@@ -18,7 +23,8 @@ import (
// Start the remote control server if configured
func Start(opt *rc.Options) {
if opt.Enabled {
s := newServer(opt)
// Serve on the DefaultServeMux so can have global registrations appear
s := newServer(opt, http.DefaultServeMux)
go s.serve()
}
}
@@ -27,13 +33,13 @@ func Start(opt *rc.Options) {
type server struct {
srv *httplib.Server
files http.Handler
opt *rc.Options
}
func newServer(opt *rc.Options) *server {
// Serve on the DefaultServeMux so can have global registrations appear
mux := http.DefaultServeMux
func newServer(opt *rc.Options, mux *http.ServeMux) *server {
s := &server{
srv: httplib.NewServer(mux, &opt.HTTPOptions),
opt: opt,
}
mux.HandleFunc("/", s.handler)
@@ -89,7 +95,7 @@ func writeError(path string, in rc.Params, w http.ResponseWriter, err error, sta
// handler reads incoming requests and dispatches them
func (s *server) handler(w http.ResponseWriter, r *http.Request) {
path := strings.Trim(r.URL.Path, "/")
path := strings.TrimLeft(r.URL.Path, "/")
w.Header().Add("Access-Control-Allow-Origin", "*")
@@ -102,7 +108,7 @@ func (s *server) handler(w http.ResponseWriter, r *http.Request) {
s.handlePost(w, r, path)
case "OPTIONS":
s.handleOptions(w, r, path)
case "GET":
case "GET", "HEAD":
s.handleGet(w, r, path)
default:
writeError(path, nil, w, errors.Errorf("method %q not allowed", r.Method), http.StatusMethodNotAllowed)
@@ -111,23 +117,29 @@ func (s *server) handler(w http.ResponseWriter, r *http.Request) {
}
func (s *server) handlePost(w http.ResponseWriter, r *http.Request, path string) {
// Parse the POST and URL parameters into r.Form, for others r.Form will be empty value
err := r.ParseForm()
if err != nil {
writeError(path, nil, w, errors.Wrap(err, "failed to parse form/URL parameters"), http.StatusBadRequest)
return
contentType := r.Header.Get("Content-Type")
values := r.URL.Query()
if contentType == "application/x-www-form-urlencoded" {
// Parse the POST and URL parameters into r.Form, for others r.Form will be empty value
err := r.ParseForm()
if err != nil {
writeError(path, nil, w, errors.Wrap(err, "failed to parse form/URL parameters"), http.StatusBadRequest)
return
}
values = r.Form
}
// Read the POST and URL parameters into in
in := make(rc.Params)
for k, vs := range r.Form {
for k, vs := range values {
if len(vs) > 0 {
in[k] = vs[len(vs)-1]
}
}
// Parse a JSON blob from the input
if r.Header.Get("Content-Type") == "application/json" {
if contentType == "application/json" {
err := json.NewDecoder(r.Body).Decode(&in)
if err != nil {
writeError(path, in, w, errors.Wrap(err, "failed to read input JSON"), http.StatusBadRequest)
@@ -138,7 +150,7 @@ func (s *server) handlePost(w http.ResponseWriter, r *http.Request, path string)
// Find the call
call := rc.Calls.Get(path)
if call == nil {
writeError(path, in, w, errors.Errorf("couldn't find method %q", path), http.StatusMethodNotAllowed)
writeError(path, in, w, errors.Errorf("couldn't find method %q", path), http.StatusNotFound)
return
}
@@ -176,24 +188,72 @@ func (s *server) handleOptions(w http.ResponseWriter, r *http.Request, path stri
w.WriteHeader(http.StatusOK)
}
func (s *server) handleGet(w http.ResponseWriter, r *http.Request, path string) {
// if we have an &fs parameter we are serving from a different fs
fsName := r.URL.Query().Get("fs")
if fsName != "" {
f, err := rc.GetCachedFs(fsName)
func (s *server) serveRoot(w http.ResponseWriter, r *http.Request) {
remotes := config.FileSections()
sort.Strings(remotes)
directory := serve.NewDirectory("")
directory.Title = "List of all rclone remotes."
q := url.Values{}
for _, remote := range remotes {
q.Set("fs", remote)
directory.AddEntry("["+remote+":]", true)
}
directory.Serve(w, r)
}
func (s *server) serveRemote(w http.ResponseWriter, r *http.Request, path string, fsName string) {
f, err := rc.GetCachedFs(fsName)
if err != nil {
writeError(path, nil, w, errors.Wrap(err, "failed to make Fs"), http.StatusInternalServerError)
return
}
if path == "" || strings.HasSuffix(path, "/") {
path = strings.Trim(path, "/")
entries, err := list.DirSorted(f, false, path)
if err != nil {
writeError(path, nil, w, errors.Wrap(err, "failed to make Fs"), http.StatusInternalServerError)
writeError(path, nil, w, errors.Wrap(err, "failed to list directory"), http.StatusInternalServerError)
return
}
// Make the entries for display
directory := serve.NewDirectory(path)
for _, entry := range entries {
_, isDir := entry.(fs.Directory)
directory.AddEntry(entry.Remote(), isDir)
}
directory.Serve(w, r)
} else {
o, err := f.NewObject(path)
if err != nil {
writeError(path, nil, w, errors.Wrap(err, "failed to find object"), http.StatusInternalServerError)
return
}
serve.Object(w, r, o)
} else if s.files == nil {
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
} else {
s.files.ServeHTTP(w, r)
}
}
// Match URLS of the form [fs]/remote
var fsMatch = regexp.MustCompile(`^\[(.*?)\](.*)$`)
func (s *server) handleGet(w http.ResponseWriter, r *http.Request, path string) {
// Look to see if this has an fs in the path
match := fsMatch.FindStringSubmatch(path)
switch {
case match != nil && s.opt.Serve:
// Serve /[fs]/remote files
s.serveRemote(w, r, match[2], match[1])
return
case path == "*" && s.opt.Serve:
// Serve /* as the remote listing
s.serveRoot(w, r)
return
case s.files != nil:
// Serve the files
s.files.ServeHTTP(w, r)
return
case path == "" && s.opt.Serve:
// Serve the root as a remote listing
s.serveRoot(w, r)
return
}
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
}