github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/lib/http/serve/serve.go (about) 1 // Package serve deals with serving objects over HTTP 2 package serve 3 4 import ( 5 "fmt" 6 "io" 7 "net/http" 8 "path" 9 "strconv" 10 11 "github.com/rclone/rclone/fs" 12 "github.com/rclone/rclone/fs/accounting" 13 ) 14 15 // Object serves an fs.Object via HEAD or GET 16 func Object(w http.ResponseWriter, r *http.Request, o fs.Object) { 17 if r.Method != "HEAD" && r.Method != "GET" { 18 http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) 19 return 20 } 21 22 // Show that we accept ranges 23 w.Header().Set("Accept-Ranges", "bytes") 24 25 // Set content length since we know how long the object is 26 if o.Size() >= 0 { 27 w.Header().Set("Content-Length", strconv.FormatInt(o.Size(), 10)) 28 } 29 30 // Set content type 31 mimeType := fs.MimeType(r.Context(), o) 32 if mimeType == "application/octet-stream" && path.Ext(o.Remote()) == "" { 33 // Leave header blank so http server guesses 34 } else { 35 w.Header().Set("Content-Type", mimeType) 36 } 37 38 // Set last modified 39 modTime := o.ModTime(r.Context()) 40 w.Header().Set("Last-Modified", modTime.UTC().Format(http.TimeFormat)) 41 42 if r.Method == "HEAD" { 43 return 44 } 45 46 // Decode Range request if present 47 code := http.StatusOK 48 size := o.Size() 49 var options []fs.OpenOption 50 if rangeRequest := r.Header.Get("Range"); rangeRequest != "" { 51 //fs.Debugf(nil, "Range: request %q", rangeRequest) 52 option, err := fs.ParseRangeOption(rangeRequest) 53 if err != nil { 54 fs.Debugf(o, "Get request parse range request error: %v", err) 55 http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 56 return 57 } 58 options = append(options, option) 59 offset, limit := option.Decode(o.Size()) 60 end := o.Size() // exclusive 61 if limit >= 0 { 62 end = offset + limit 63 } 64 if end > o.Size() { 65 end = o.Size() 66 } 67 size = end - offset 68 // fs.Debugf(nil, "Range: offset=%d, limit=%d, end=%d, size=%d (object size %d)", offset, limit, end, size, o.Size()) 69 // Content-Range: bytes 0-1023/146515 70 w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", offset, end-1, o.Size())) 71 // fs.Debugf(nil, "Range: Content-Range: %q", w.Header().Get("Content-Range")) 72 code = http.StatusPartialContent 73 } 74 w.Header().Set("Content-Length", strconv.FormatInt(size, 10)) 75 76 file, err := o.Open(r.Context(), options...) 77 if err != nil { 78 fs.Debugf(o, "Get request open error: %v", err) 79 http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) 80 return 81 } 82 tr := accounting.Stats(r.Context()).NewTransfer(o, nil) 83 defer func() { 84 tr.Done(r.Context(), err) 85 }() 86 in := tr.Account(r.Context(), file) // account the transfer (no buffering) 87 88 w.WriteHeader(code) 89 90 n, err := io.Copy(w, in) 91 if err != nil { 92 fs.Errorf(o, "Didn't finish writing GET request (wrote %d/%d bytes): %v", n, size, err) 93 return 94 } 95 }