github.com/ncw/rclone@v1.48.1-0.20190724201158-a35aa1360e3e/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/ncw/rclone/cmd"
    11  	"github.com/ncw/rclone/cmd/serve/httplib"
    12  	"github.com/ncw/rclone/cmd/serve/httplib/httpflags"
    13  	"github.com/ncw/rclone/cmd/serve/httplib/serve"
    14  	"github.com/ncw/rclone/fs"
    15  	"github.com/ncw/rclone/fs/accounting"
    16  	"github.com/ncw/rclone/vfs"
    17  	"github.com/ncw/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.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 := r.URL.Path
    97  	isDir := strings.HasSuffix(urlPath, "/")
    98  	remote := strings.Trim(urlPath, "/")
    99  	if isDir {
   100  		s.serveDir(w, r, remote)
   101  	} else {
   102  		s.serveFile(w, r, remote)
   103  	}
   104  }
   105  
   106  // serveDir serves a directory index at dirRemote
   107  func (s *server) serveDir(w http.ResponseWriter, r *http.Request, dirRemote string) {
   108  	// List the directory
   109  	node, err := s.vfs.Stat(dirRemote)
   110  	if err == vfs.ENOENT {
   111  		http.Error(w, "Directory not found", http.StatusNotFound)
   112  		return
   113  	} else if err != nil {
   114  		serve.Error(dirRemote, w, "Failed to list directory", err)
   115  		return
   116  	}
   117  	if !node.IsDir() {
   118  		http.Error(w, "Not a directory", http.StatusNotFound)
   119  		return
   120  	}
   121  	dir := node.(*vfs.Dir)
   122  	dirEntries, err := dir.ReadDirAll()
   123  	if err != nil {
   124  		serve.Error(dirRemote, w, "Failed to list directory", err)
   125  		return
   126  	}
   127  
   128  	// Make the entries for display
   129  	directory := serve.NewDirectory(dirRemote, s.HTMLTemplate)
   130  	for _, node := range dirEntries {
   131  		directory.AddEntry(node.Path(), node.IsDir())
   132  	}
   133  
   134  	directory.Serve(w, r)
   135  }
   136  
   137  // serveFile serves a file object at remote
   138  func (s *server) serveFile(w http.ResponseWriter, r *http.Request, remote string) {
   139  	node, err := s.vfs.Stat(remote)
   140  	if err == vfs.ENOENT {
   141  		fs.Infof(remote, "%s: File not found", r.RemoteAddr)
   142  		http.Error(w, "File not found", http.StatusNotFound)
   143  		return
   144  	} else if err != nil {
   145  		serve.Error(remote, w, "Failed to find file", err)
   146  		return
   147  	}
   148  	if !node.IsFile() {
   149  		http.Error(w, "Not a file", http.StatusNotFound)
   150  		return
   151  	}
   152  	entry := node.DirEntry()
   153  	if entry == nil {
   154  		http.Error(w, "Can't open file being written", http.StatusNotFound)
   155  		return
   156  	}
   157  	obj := entry.(fs.Object)
   158  	file := node.(*vfs.File)
   159  
   160  	// Set content length since we know how long the object is
   161  	w.Header().Set("Content-Length", strconv.FormatInt(node.Size(), 10))
   162  
   163  	// Set content type
   164  	mimeType := fs.MimeType(r.Context(), obj)
   165  	if mimeType == "application/octet-stream" && path.Ext(remote) == "" {
   166  		// Leave header blank so http server guesses
   167  	} else {
   168  		w.Header().Set("Content-Type", mimeType)
   169  	}
   170  
   171  	// If HEAD no need to read the object since we have set the headers
   172  	if r.Method == "HEAD" {
   173  		return
   174  	}
   175  
   176  	// open the object
   177  	in, err := file.Open(os.O_RDONLY)
   178  	if err != nil {
   179  		serve.Error(remote, w, "Failed to open file", err)
   180  		return
   181  	}
   182  	defer func() {
   183  		err := in.Close()
   184  		if err != nil {
   185  			fs.Errorf(remote, "Failed to close file: %v", err)
   186  		}
   187  	}()
   188  
   189  	// Account the transfer
   190  	accounting.Stats.Transferring(remote)
   191  	defer accounting.Stats.DoneTransferring(remote, true)
   192  	// FIXME in = fs.NewAccount(in, obj).WithBuffer() // account the transfer
   193  
   194  	// Serve the file
   195  	http.ServeContent(w, r, remote, node.ModTime(), in)
   196  }