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 }