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 }