You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

212 lines
6.2 KiB

  1. package weed_server
  2. import (
  3. "io"
  4. "mime"
  5. "mime/multipart"
  6. "net/http"
  7. "strconv"
  8. "strings"
  9. "time"
  10. "github.com/chrislusf/weed-fs/go/glog"
  11. "github.com/chrislusf/weed-fs/go/images"
  12. "github.com/chrislusf/weed-fs/go/operation"
  13. "github.com/chrislusf/weed-fs/go/storage"
  14. "github.com/chrislusf/weed-fs/go/util"
  15. )
  16. var fileNameEscaper = strings.NewReplacer("\\", "\\\\", "\"", "\\\"")
  17. func (vs *VolumeServer) GetOrHeadHandler(w http.ResponseWriter, r *http.Request) {
  18. n := new(storage.Needle)
  19. vid, fid, filename, ext, _ := parseURLPath(r.URL.Path)
  20. volumeId, err := storage.NewVolumeId(vid)
  21. if err != nil {
  22. glog.V(2).Infoln("parsing error:", err, r.URL.Path)
  23. w.WriteHeader(http.StatusBadRequest)
  24. return
  25. }
  26. err = n.ParsePath(fid)
  27. if err != nil {
  28. glog.V(2).Infoln("parsing fid error:", err, r.URL.Path)
  29. w.WriteHeader(http.StatusBadRequest)
  30. return
  31. }
  32. glog.V(4).Infoln("volume", volumeId, "reading", n)
  33. if !vs.store.HasVolume(volumeId) {
  34. lookupResult, err := operation.Lookup(vs.GetMasterNode(), volumeId.String())
  35. glog.V(2).Infoln("volume", volumeId, "found on", lookupResult, "error", err)
  36. if err == nil && len(lookupResult.Locations) > 0 {
  37. http.Redirect(w, r, util.NormalizeUrl(lookupResult.Locations[0].PublicUrl)+r.URL.Path, http.StatusMovedPermanently)
  38. } else {
  39. glog.V(2).Infoln("lookup error:", err, r.URL.Path)
  40. w.WriteHeader(http.StatusNotFound)
  41. }
  42. return
  43. }
  44. cookie := n.Cookie
  45. count, e := vs.store.Read(volumeId, n)
  46. glog.V(4).Infoln("read bytes", count, "error", e)
  47. if e != nil || count <= 0 {
  48. glog.V(0).Infoln("read error:", e, r.URL.Path)
  49. w.WriteHeader(http.StatusNotFound)
  50. return
  51. }
  52. if n.Cookie != cookie {
  53. glog.V(0).Infoln("request", r.URL.Path, "with unmaching cookie seen:", cookie, "expected:", n.Cookie, "from", r.RemoteAddr, "agent", r.UserAgent())
  54. w.WriteHeader(http.StatusNotFound)
  55. return
  56. }
  57. if n.LastModified != 0 {
  58. w.Header().Set("Last-Modified", time.Unix(int64(n.LastModified), 0).UTC().Format(http.TimeFormat))
  59. if r.Header.Get("If-Modified-Since") != "" {
  60. if t, parseError := time.Parse(http.TimeFormat, r.Header.Get("If-Modified-Since")); parseError == nil {
  61. if t.Unix() >= int64(n.LastModified) {
  62. w.WriteHeader(http.StatusNotModified)
  63. return
  64. }
  65. }
  66. }
  67. }
  68. etag := n.Etag()
  69. if inm := r.Header.Get("If-None-Match"); inm == etag {
  70. w.WriteHeader(http.StatusNotModified)
  71. return
  72. }
  73. w.Header().Set("Etag", etag)
  74. if n.NameSize > 0 && filename == "" {
  75. filename = string(n.Name)
  76. dotIndex := strings.LastIndex(filename, ".")
  77. if dotIndex > 0 {
  78. ext = filename[dotIndex:]
  79. }
  80. }
  81. mtype := ""
  82. if ext != "" {
  83. mtype = mime.TypeByExtension(ext)
  84. }
  85. if n.MimeSize > 0 {
  86. mt := string(n.Mime)
  87. if mt != "application/octet-stream" {
  88. mtype = mt
  89. }
  90. }
  91. if mtype != "" {
  92. w.Header().Set("Content-Type", mtype)
  93. }
  94. if filename != "" {
  95. w.Header().Set("Content-Disposition", "filename=\""+fileNameEscaper.Replace(filename)+"\"")
  96. }
  97. if ext != ".gz" {
  98. if n.IsGzipped() {
  99. if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
  100. w.Header().Set("Content-Encoding", "gzip")
  101. } else {
  102. if n.Data, err = storage.UnGzipData(n.Data); err != nil {
  103. glog.V(0).Infoln("lookup error:", err, r.URL.Path)
  104. }
  105. }
  106. }
  107. }
  108. if ext == ".png" || ext == ".jpg" || ext == ".gif" {
  109. width, height := 0, 0
  110. if r.FormValue("width") != "" {
  111. width, _ = strconv.Atoi(r.FormValue("width"))
  112. }
  113. if r.FormValue("height") != "" {
  114. height, _ = strconv.Atoi(r.FormValue("height"))
  115. }
  116. n.Data, _, _ = images.Resized(ext, n.Data, width, height)
  117. }
  118. w.Header().Set("Accept-Ranges", "bytes")
  119. if r.Method == "HEAD" {
  120. w.Header().Set("Content-Length", strconv.Itoa(len(n.Data)))
  121. return
  122. }
  123. rangeReq := r.Header.Get("Range")
  124. if rangeReq == "" {
  125. w.Header().Set("Content-Length", strconv.Itoa(len(n.Data)))
  126. if _, e = w.Write(n.Data); e != nil {
  127. glog.V(0).Infoln("response write error:", e)
  128. }
  129. return
  130. }
  131. //the rest is dealing with partial content request
  132. //mostly copy from src/pkg/net/http/fs.go
  133. size := int64(len(n.Data))
  134. ranges, err := parseRange(rangeReq, size)
  135. if err != nil {
  136. http.Error(w, err.Error(), http.StatusRequestedRangeNotSatisfiable)
  137. return
  138. }
  139. if sumRangesSize(ranges) > size {
  140. // The total number of bytes in all the ranges
  141. // is larger than the size of the file by
  142. // itself, so this is probably an attack, or a
  143. // dumb client. Ignore the range request.
  144. ranges = nil
  145. return
  146. }
  147. if len(ranges) == 0 {
  148. return
  149. }
  150. if len(ranges) == 1 {
  151. // RFC 2616, Section 14.16:
  152. // "When an HTTP message includes the content of a single
  153. // range (for example, a response to a request for a
  154. // single range, or to a request for a set of ranges
  155. // that overlap without any holes), this content is
  156. // transmitted with a Content-Range header, and a
  157. // Content-Length header showing the number of bytes
  158. // actually transferred.
  159. // ...
  160. // A response to a request for a single range MUST NOT
  161. // be sent using the multipart/byteranges media type."
  162. ra := ranges[0]
  163. w.Header().Set("Content-Length", strconv.FormatInt(ra.length, 10))
  164. w.Header().Set("Content-Range", ra.contentRange(size))
  165. w.WriteHeader(http.StatusPartialContent)
  166. if _, e = w.Write(n.Data[ra.start : ra.start+ra.length]); e != nil {
  167. glog.V(0).Infoln("response write error:", e)
  168. }
  169. return
  170. }
  171. // process mulitple ranges
  172. for _, ra := range ranges {
  173. if ra.start > size {
  174. http.Error(w, "Out of Range", http.StatusRequestedRangeNotSatisfiable)
  175. return
  176. }
  177. }
  178. sendSize := rangesMIMESize(ranges, mtype, size)
  179. pr, pw := io.Pipe()
  180. mw := multipart.NewWriter(pw)
  181. w.Header().Set("Content-Type", "multipart/byteranges; boundary="+mw.Boundary())
  182. sendContent := pr
  183. defer pr.Close() // cause writing goroutine to fail and exit if CopyN doesn't finish.
  184. go func() {
  185. for _, ra := range ranges {
  186. part, err := mw.CreatePart(ra.mimeHeader(mtype, size))
  187. if err != nil {
  188. pw.CloseWithError(err)
  189. return
  190. }
  191. if _, err = part.Write(n.Data[ra.start : ra.start+ra.length]); err != nil {
  192. pw.CloseWithError(err)
  193. return
  194. }
  195. }
  196. mw.Close()
  197. pw.Close()
  198. }()
  199. if w.Header().Get("Content-Encoding") == "" {
  200. w.Header().Set("Content-Length", strconv.FormatInt(sendSize, 10))
  201. }
  202. w.WriteHeader(http.StatusPartialContent)
  203. io.CopyN(w, sendContent, sendSize)
  204. }