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 }