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.

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