rc: ensure rclone fails to start up if the --rc port is in use already

This commit is contained in:
Nick Craig-Wood 2018-11-01 17:20:04 +00:00
parent 0b80d1481a
commit b961e07c57
4 changed files with 44 additions and 28 deletions

View file

@ -353,7 +353,10 @@ func initConfig() {
fs.Debugf("rclone", "Version %q starting with parameters %q", fs.Version, os.Args) fs.Debugf("rclone", "Version %q starting with parameters %q", fs.Version, os.Args)
// Start the remote control server if configured // Start the remote control server if configured
rcserver.Start(&rcflags.Opt) _, err = rcserver.Start(&rcflags.Opt)
if err != nil {
log.Fatalf("Failed to start remote control: %v", err)
}
// Setup CPU profiling if desired // Setup CPU profiling if desired
if *cpuProfile != "" { if *cpuProfile != "" {

View file

@ -37,8 +37,13 @@ See the [rc documentation](/rc/) for more info on the rc flags.
if len(args) > 0 { if len(args) > 0 {
rcflags.Opt.Files = args[0] rcflags.Opt.Files = args[0]
} }
rcserver.Start(&rcflags.Opt) s, err := rcserver.Start(&rcflags.Opt)
// Run the rc forever if err != nil {
select {} log.Fatalf("Failed to start remote control: %v", err)
}
if s == nil {
log.Fatal("rc server not configured")
}
s.Wait()
}, },
} }

View file

@ -21,24 +21,27 @@ import (
) )
// Start the remote control server if configured // Start the remote control server if configured
func Start(opt *rc.Options) { //
// If the server wasn't configured the *Server returned may be nil
func Start(opt *rc.Options) (*Server, error) {
if opt.Enabled { if opt.Enabled {
// Serve on the DefaultServeMux so can have global registrations appear // Serve on the DefaultServeMux so can have global registrations appear
s := newServer(opt, http.DefaultServeMux) s := newServer(opt, http.DefaultServeMux)
go s.serve() return s, s.Serve()
} }
return nil, nil
} }
// server contains everything to run the server // Server contains everything to run the rc server
type server struct { type Server struct {
srv *httplib.Server *httplib.Server
files http.Handler files http.Handler
opt *rc.Options opt *rc.Options
} }
func newServer(opt *rc.Options, mux *http.ServeMux) *server { func newServer(opt *rc.Options, mux *http.ServeMux) *Server {
s := &server{ s := &Server{
srv: httplib.NewServer(mux, &opt.HTTPOptions), Server: httplib.NewServer(mux, &opt.HTTPOptions),
opt: opt, opt: opt,
} }
mux.HandleFunc("/", s.handler) mux.HandleFunc("/", s.handler)
@ -55,18 +58,20 @@ func newServer(opt *rc.Options, mux *http.ServeMux) *server {
return s return s
} }
// serve runs the http server - doesn't return // Serve runs the http server in the background.
func (s *server) serve() { //
err := s.srv.Serve() // Use s.Close() and s.Wait() to shutdown server
func (s *Server) Serve() error {
err := s.Server.Serve()
if err != nil { if err != nil {
fs.Errorf(nil, "Opening listener: %v", err) return err
} }
fs.Logf(nil, "Serving remote control on %s", s.srv.URL()) fs.Logf(nil, "Serving remote control on %s", s.URL())
// Open the files in the browser if set // Open the files in the browser if set
if s.files != nil { if s.files != nil {
_ = open.Start(s.srv.URL()) _ = open.Start(s.URL())
} }
s.srv.Wait() return nil
} }
// writeError writes a formatted error to the output // writeError writes a formatted error to the output
@ -94,7 +99,7 @@ func writeError(path string, in rc.Params, w http.ResponseWriter, err error, sta
} }
// handler reads incoming requests and dispatches them // handler reads incoming requests and dispatches them
func (s *server) handler(w http.ResponseWriter, r *http.Request) { func (s *Server) handler(w http.ResponseWriter, r *http.Request) {
path := strings.TrimLeft(r.URL.Path, "/") path := strings.TrimLeft(r.URL.Path, "/")
w.Header().Add("Access-Control-Allow-Origin", "*") w.Header().Add("Access-Control-Allow-Origin", "*")
@ -116,7 +121,7 @@ func (s *server) handler(w http.ResponseWriter, r *http.Request) {
} }
} }
func (s *server) handlePost(w http.ResponseWriter, r *http.Request, path string) { func (s *Server) handlePost(w http.ResponseWriter, r *http.Request, path string) {
contentType := r.Header.Get("Content-Type") contentType := r.Header.Get("Content-Type")
values := r.URL.Query() values := r.URL.Query()
@ -184,11 +189,11 @@ func (s *server) handlePost(w http.ResponseWriter, r *http.Request, path string)
} }
} }
func (s *server) handleOptions(w http.ResponseWriter, r *http.Request, path string) { func (s *Server) handleOptions(w http.ResponseWriter, r *http.Request, path string) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
} }
func (s *server) serveRoot(w http.ResponseWriter, r *http.Request) { func (s *Server) serveRoot(w http.ResponseWriter, r *http.Request) {
remotes := config.FileSections() remotes := config.FileSections()
sort.Strings(remotes) sort.Strings(remotes)
directory := serve.NewDirectory("") directory := serve.NewDirectory("")
@ -201,7 +206,7 @@ func (s *server) serveRoot(w http.ResponseWriter, r *http.Request) {
directory.Serve(w, r) directory.Serve(w, r)
} }
func (s *server) serveRemote(w http.ResponseWriter, r *http.Request, path string, fsName string) { func (s *Server) serveRemote(w http.ResponseWriter, r *http.Request, path string, fsName string) {
f, err := rc.GetCachedFs(fsName) f, err := rc.GetCachedFs(fsName)
if err != nil { 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 make Fs"), http.StatusInternalServerError)
@ -234,7 +239,7 @@ func (s *server) serveRemote(w http.ResponseWriter, r *http.Request, path string
// Match URLS of the form [fs]/remote // Match URLS of the form [fs]/remote
var fsMatch = regexp.MustCompile(`^\[(.*?)\](.*)$`) var fsMatch = regexp.MustCompile(`^\[(.*?)\](.*)$`)
func (s *server) handleGet(w http.ResponseWriter, r *http.Request, path string) { func (s *Server) handleGet(w http.ResponseWriter, r *http.Request, path string) {
// Look to see if this has an fs in the path // Look to see if this has an fs in the path
match := fsMatch.FindStringSubmatch(path) match := fsMatch.FindStringSubmatch(path)
switch { switch {

View file

@ -36,8 +36,11 @@ func TestRcServer(t *testing.T) {
opt.Files = testFs opt.Files = testFs
mux := http.NewServeMux() mux := http.NewServeMux()
rcServer := newServer(&opt, mux) rcServer := newServer(&opt, mux)
go rcServer.serve() assert.NoError(t, rcServer.Serve())
defer rcServer.srv.Close() defer func() {
rcServer.Close()
rcServer.Wait()
}()
// Do the simplest possible test to check the server is alive // Do the simplest possible test to check the server is alive
// Do it a few times to wait for the server to start // Do it a few times to wait for the server to start