github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/cmd/serve/http/http.go (about)

     1  package http
     2  
     3  import (
     4  	"net/http"
     5  	"os"
     6  	"path"
     7  	"strconv"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/rclone/rclone/cmd"
    12  	"github.com/rclone/rclone/cmd/serve/httplib"
    13  	"github.com/rclone/rclone/cmd/serve/httplib/httpflags"
    14  	"github.com/rclone/rclone/cmd/serve/httplib/serve"
    15  	"github.com/rclone/rclone/fs"
    16  	"github.com/rclone/rclone/fs/accounting"
    17  	"github.com/rclone/rclone/vfs"
    18  	"github.com/rclone/rclone/vfs/vfsflags"
    19  	"github.com/spf13/cobra"
    20  )
    21  
    22  func init() {
    23  	httpflags.AddFlags(Command.Flags())
    24  	vfsflags.AddFlags(Command.Flags())
    25  }
    26  
    27  // Command definition for cobra
    28  var Command = &cobra.Command{
    29  	Use:   "http remote:path",
    30  	Short: `Serve the remote over HTTP.`,
    31  	Long: `rclone serve http implements a basic web server to serve the remote
    32  over HTTP.  This can be viewed in a web browser or you can make a
    33  remote of type http read from it.
    34  
    35  You can use the filter flags (eg --include, --exclude) to control what
    36  is served.
    37  
    38  The server will log errors.  Use -v to see access logs.
    39  
    40  --bwlimit will be respected for file transfers.  Use --stats to
    41  control the stats printing.
    42  ` + httplib.Help + vfs.Help,
    43  	Run: func(command *cobra.Command, args []string) {
    44  		cmd.CheckArgs(1, 1, command, args)
    45  		f := cmd.NewFsSrc(args)
    46  		cmd.Run(false, true, command, func() error {
    47  			s := newServer(f, &httpflags.Opt)
    48  			err := s.Serve()
    49  			if err != nil {
    50  				return err
    51  			}
    52  			s.Wait()
    53  			return nil
    54  		})
    55  	},
    56  }
    57  
    58  // server contains everything to run the server
    59  type server struct {
    60  	*httplib.Server
    61  	f   fs.Fs
    62  	vfs *vfs.VFS
    63  }
    64  
    65  func newServer(f fs.Fs, opt *httplib.Options) *server {
    66  	mux := http.NewServeMux()
    67  	s := &server{
    68  		Server: httplib.NewServer(mux, opt),
    69  		f:      f,
    70  		vfs:    vfs.New(f, &vfsflags.Opt),
    71  	}
    72  	mux.HandleFunc(s.Opt.BaseURL+"/", s.handler)
    73  	return s
    74  }
    75  
    76  // Serve runs the http server in the background.
    77  //
    78  // Use s.Close() and s.Wait() to shutdown server
    79  func (s *server) Serve() error {
    80  	err := s.Server.Serve()
    81  	if err != nil {
    82  		return err
    83  	}
    84  	fs.Logf(s.f, "Serving on %s", s.URL())
    85  	return nil
    86  }
    87  
    88  // handler reads incoming requests and dispatches them
    89  func (s *server) handler(w http.ResponseWriter, r *http.Request) {
    90  	if r.Method != "GET" && r.Method != "HEAD" {
    91  		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
    92  		return
    93  	}
    94  	w.Header().Set("Accept-Ranges", "bytes")
    95  	w.Header().Set("Server", "rclone/"+fs.Version)
    96  
    97  	urlPath, ok := s.Path(w, r)
    98  	if !ok {
    99  		return
   100  	}
   101  	isDir := strings.HasSuffix(urlPath, "/")
   102  	remote := strings.Trim(urlPath, "/")
   103  	if isDir {
   104  		s.serveDir(w, r, remote)
   105  	} else {
   106  		s.serveFile(w, r, remote)
   107  	}
   108  }
   109  
   110  // serveDir serves a directory index at dirRemote
   111  func (s *server) serveDir(w http.ResponseWriter, r *http.Request, dirRemote string) {
   112  	// List the directory
   113  	node, err := s.vfs.Stat(dirRemote)
   114  	if err == vfs.ENOENT {
   115  		http.Error(w, "Directory not found", http.StatusNotFound)
   116  		return
   117  	} else if err != nil {
   118  		serve.Error(dirRemote, w, "Failed to list directory", err)
   119  		return
   120  	}
   121  	if !node.IsDir() {
   122  		http.Error(w, "Not a directory", http.StatusNotFound)
   123  		return
   124  	}
   125  	dir := node.(*vfs.Dir)
   126  	dirEntries, err := dir.ReadDirAll()
   127  	if err != nil {
   128  		serve.Error(dirRemote, w, "Failed to list directory", err)
   129  		return
   130  	}
   131  
   132  	// Make the entries for display
   133  	directory := serve.NewDirectory(dirRemote, s.HTMLTemplate)
   134  	for _, node := range dirEntries {
   135  		if vfsflags.Opt.NoModTime {
   136  			directory.AddHTMLEntry(node.Path(), node.IsDir(), node.Size(), time.Time{})
   137  		} else {
   138  			directory.AddHTMLEntry(node.Path(), node.IsDir(), node.Size(), node.ModTime().UTC())
   139  		}
   140  	}
   141  
   142  	sortParm := r.URL.Query().Get("sort")
   143  	orderParm := r.URL.Query().Get("order")
   144  	directory.ProcessQueryParams(sortParm, orderParm)
   145  
   146  	// Set the Last-Modified header to the timestamp
   147  	w.Header().Set("Last-Modified", dir.ModTime().UTC().Format(http.TimeFormat))
   148  
   149  	directory.Serve(w, r)
   150  }
   151  
   152  // serveFile serves a file object at remote
   153  func (s *server) serveFile(w http.ResponseWriter, r *http.Request, remote string) {
   154  	node, err := s.vfs.Stat(remote)
   155  	if err == vfs.ENOENT {
   156  		fs.Infof(remote, "%s: File not found", r.RemoteAddr)
   157  		http.Error(w, "File not found", http.StatusNotFound)
   158  		return
   159  	} else if err != nil {
   160  		serve.Error(remote, w, "Failed to find file", err)
   161  		return
   162  	}
   163  	if !node.IsFile() {
   164  		http.Error(w, "Not a file", http.StatusNotFound)
   165  		return
   166  	}
   167  	entry := node.DirEntry()
   168  	if entry == nil {
   169  		http.Error(w, "Can't open file being written", http.StatusNotFound)
   170  		return
   171  	}
   172  	obj := entry.(fs.Object)
   173  	file := node.(*vfs.File)
   174  
   175  	// Set content length since we know how long the object is
   176  	w.Header().Set("Content-Length", strconv.FormatInt(node.Size(), 10))
   177  
   178  	// Set content type
   179  	mimeType := fs.MimeType(r.Context(), obj)
   180  	if mimeType == "application/octet-stream" && path.Ext(remote) == "" {
   181  		// Leave header blank so http server guesses
   182  	} else {
   183  		w.Header().Set("Content-Type", mimeType)
   184  	}
   185  
   186  	// Set the Last-Modified header to the timestamp
   187  	w.Header().Set("Last-Modified", file.ModTime().UTC().Format(http.TimeFormat))
   188  
   189  	// If HEAD no need to read the object since we have set the headers
   190  	if r.Method == "HEAD" {
   191  		return
   192  	}
   193  
   194  	// open the object
   195  	in, err := file.Open(os.O_RDONLY)
   196  	if err != nil {
   197  		serve.Error(remote, w, "Failed to open file", err)
   198  		return
   199  	}
   200  	defer func() {
   201  		err := in.Close()
   202  		if err != nil {
   203  			fs.Errorf(remote, "Failed to close file: %v", err)
   204  		}
   205  	}()
   206  
   207  	// Account the transfer
   208  	tr := accounting.Stats(r.Context()).NewTransfer(obj)
   209  	defer tr.Done(nil)
   210  	// FIXME in = fs.NewAccount(in, obj).WithBuffer() // account the transfer
   211  
   212  	// Serve the file
   213  	http.ServeContent(w, r, remote, node.ModTime(), in)
   214  }