github.com/xhghs/rclone@v1.51.1-0.20200430155106-e186a28cced8/cmd/serve/http/http.go (about)

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