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.

334 lines
9.2 KiB

9 years ago
9 years ago
9 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
9 years ago
  1. package weed_server
  2. import (
  3. "bytes"
  4. "context"
  5. "errors"
  6. "io"
  7. "mime"
  8. "mime/multipart"
  9. "net/http"
  10. "net/url"
  11. "path"
  12. "strconv"
  13. "strings"
  14. "time"
  15. "encoding/json"
  16. "github.com/chrislusf/seaweedfs/weed/glog"
  17. "github.com/chrislusf/seaweedfs/weed/images"
  18. "github.com/chrislusf/seaweedfs/weed/operation"
  19. "github.com/chrislusf/seaweedfs/weed/storage/needle"
  20. "github.com/chrislusf/seaweedfs/weed/util"
  21. )
  22. var fileNameEscaper = strings.NewReplacer("\\", "\\\\", "\"", "\\\"")
  23. func (vs *VolumeServer) GetOrHeadHandler(w http.ResponseWriter, r *http.Request) {
  24. n := new(needle.Needle)
  25. vid, fid, filename, ext, _ := parseURLPath(r.URL.Path)
  26. if !vs.maybeCheckJwtAuthorization(r, vid, fid, false) {
  27. writeJsonError(w, r, http.StatusUnauthorized, errors.New("wrong jwt"))
  28. return
  29. }
  30. volumeId, err := needle.NewVolumeId(vid)
  31. if err != nil {
  32. glog.V(2).Infoln("parsing error:", err, r.URL.Path)
  33. w.WriteHeader(http.StatusBadRequest)
  34. return
  35. }
  36. err = n.ParsePath(fid)
  37. if err != nil {
  38. glog.V(2).Infoln("parsing fid error:", err, r.URL.Path)
  39. w.WriteHeader(http.StatusBadRequest)
  40. return
  41. }
  42. glog.V(4).Infoln("volume", volumeId, "reading", n)
  43. hasVolume := vs.store.HasVolume(volumeId)
  44. _, hasEcVolume := vs.store.FindEcVolume(volumeId)
  45. if !hasVolume && !hasEcVolume {
  46. if !vs.ReadRedirect {
  47. glog.V(2).Infoln("volume is not local:", err, r.URL.Path)
  48. w.WriteHeader(http.StatusNotFound)
  49. return
  50. }
  51. lookupResult, err := operation.Lookup(vs.GetMaster(), volumeId.String())
  52. glog.V(2).Infoln("volume", volumeId, "found on", lookupResult, "error", err)
  53. if err == nil && len(lookupResult.Locations) > 0 {
  54. u, _ := url.Parse(util.NormalizeUrl(lookupResult.Locations[0].PublicUrl))
  55. u.Path = r.URL.Path
  56. arg := url.Values{}
  57. if c := r.FormValue("collection"); c != "" {
  58. arg.Set("collection", c)
  59. }
  60. u.RawQuery = arg.Encode()
  61. http.Redirect(w, r, u.String(), http.StatusMovedPermanently)
  62. } else {
  63. glog.V(2).Infoln("lookup error:", err, r.URL.Path)
  64. w.WriteHeader(http.StatusNotFound)
  65. }
  66. return
  67. }
  68. cookie := n.Cookie
  69. var count int
  70. if hasVolume {
  71. count, err = vs.store.ReadVolumeNeedle(volumeId, n)
  72. } else if hasEcVolume {
  73. count, err = vs.store.ReadEcShardNeedle(context.Background(), volumeId, n)
  74. }
  75. glog.V(4).Infoln("read bytes", count, "error", err)
  76. if err != nil || count < 0 {
  77. glog.V(0).Infof("read %s isNormalVolume %v error: %v", r.URL.Path, hasVolume, err)
  78. w.WriteHeader(http.StatusNotFound)
  79. return
  80. }
  81. if n.Cookie != cookie {
  82. glog.V(0).Infof("request %s with cookie:%x expected:%x from %s agent %s", r.URL.Path, cookie, n.Cookie, r.RemoteAddr, r.UserAgent())
  83. w.WriteHeader(http.StatusNotFound)
  84. return
  85. }
  86. if n.LastModified != 0 {
  87. w.Header().Set("Last-Modified", time.Unix(int64(n.LastModified), 0).UTC().Format(http.TimeFormat))
  88. if r.Header.Get("If-Modified-Since") != "" {
  89. if t, parseError := time.Parse(http.TimeFormat, r.Header.Get("If-Modified-Since")); parseError == nil {
  90. if t.Unix() >= int64(n.LastModified) {
  91. w.WriteHeader(http.StatusNotModified)
  92. return
  93. }
  94. }
  95. }
  96. }
  97. if inm := r.Header.Get("If-None-Match"); inm == "\""+n.Etag()+"\"" {
  98. w.WriteHeader(http.StatusNotModified)
  99. return
  100. }
  101. if r.Header.Get("ETag-MD5") == "True" {
  102. setEtag(w, n.MD5())
  103. } else {
  104. setEtag(w, n.Etag())
  105. }
  106. if n.HasPairs() {
  107. pairMap := make(map[string]string)
  108. err = json.Unmarshal(n.Pairs, &pairMap)
  109. if err != nil {
  110. glog.V(0).Infoln("Unmarshal pairs error:", err)
  111. }
  112. for k, v := range pairMap {
  113. w.Header().Set(k, v)
  114. }
  115. }
  116. if vs.tryHandleChunkedFile(n, filename, w, r) {
  117. return
  118. }
  119. if n.NameSize > 0 && filename == "" {
  120. filename = string(n.Name)
  121. if ext == "" {
  122. ext = path.Ext(filename)
  123. }
  124. }
  125. mtype := ""
  126. if n.MimeSize > 0 {
  127. mt := string(n.Mime)
  128. if !strings.HasPrefix(mt, "application/octet-stream") {
  129. mtype = mt
  130. }
  131. }
  132. if ext != ".gz" {
  133. if n.IsGzipped() {
  134. if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
  135. w.Header().Set("Content-Encoding", "gzip")
  136. } else {
  137. if n.Data, err = util.UnGzipData(n.Data); err != nil {
  138. glog.V(0).Infoln("ungzip error:", err, r.URL.Path)
  139. }
  140. }
  141. }
  142. }
  143. rs := conditionallyResizeImages(bytes.NewReader(n.Data), ext, r)
  144. if e := writeResponseContent(filename, mtype, rs, w, r); e != nil {
  145. glog.V(2).Infoln("response write error:", e)
  146. }
  147. }
  148. func (vs *VolumeServer) tryHandleChunkedFile(n *needle.Needle, fileName string, w http.ResponseWriter, r *http.Request) (processed bool) {
  149. if !n.IsChunkedManifest() || r.URL.Query().Get("cm") == "false" {
  150. return false
  151. }
  152. chunkManifest, e := operation.LoadChunkManifest(n.Data, n.IsGzipped())
  153. if e != nil {
  154. glog.V(0).Infof("load chunked manifest (%s) error: %v", r.URL.Path, e)
  155. return false
  156. }
  157. if fileName == "" && chunkManifest.Name != "" {
  158. fileName = chunkManifest.Name
  159. }
  160. ext := path.Ext(fileName)
  161. mType := ""
  162. if chunkManifest.Mime != "" {
  163. mt := chunkManifest.Mime
  164. if !strings.HasPrefix(mt, "application/octet-stream") {
  165. mType = mt
  166. }
  167. }
  168. w.Header().Set("X-File-Store", "chunked")
  169. chunkedFileReader := &operation.ChunkedFileReader{
  170. Manifest: chunkManifest,
  171. Master: vs.GetMaster(),
  172. }
  173. defer chunkedFileReader.Close()
  174. rs := conditionallyResizeImages(chunkedFileReader, ext, r)
  175. if e := writeResponseContent(fileName, mType, rs, w, r); e != nil {
  176. glog.V(2).Infoln("response write error:", e)
  177. }
  178. return true
  179. }
  180. func conditionallyResizeImages(originalDataReaderSeeker io.ReadSeeker, ext string, r *http.Request) io.ReadSeeker {
  181. rs := originalDataReaderSeeker
  182. if len(ext) > 0 {
  183. ext = strings.ToLower(ext)
  184. }
  185. if ext == ".png" || ext == ".jpg" || ext == ".jpeg" || ext == ".gif" {
  186. width, height := 0, 0
  187. if r.FormValue("width") != "" {
  188. width, _ = strconv.Atoi(r.FormValue("width"))
  189. }
  190. if r.FormValue("height") != "" {
  191. height, _ = strconv.Atoi(r.FormValue("height"))
  192. }
  193. rs, _, _ = images.Resized(ext, originalDataReaderSeeker, width, height, r.FormValue("mode"))
  194. }
  195. return rs
  196. }
  197. func writeResponseContent(filename, mimeType string, rs io.ReadSeeker, w http.ResponseWriter, r *http.Request) error {
  198. totalSize, e := rs.Seek(0, 2)
  199. if mimeType == "" {
  200. if ext := path.Ext(filename); ext != "" {
  201. mimeType = mime.TypeByExtension(ext)
  202. }
  203. }
  204. if mimeType != "" {
  205. w.Header().Set("Content-Type", mimeType)
  206. }
  207. if filename != "" {
  208. contentDisposition := "inline"
  209. if r.FormValue("dl") != "" {
  210. if dl, _ := strconv.ParseBool(r.FormValue("dl")); dl {
  211. contentDisposition = "attachment"
  212. }
  213. }
  214. w.Header().Set("Content-Disposition", contentDisposition+`; filename="`+fileNameEscaper.Replace(filename)+`"`)
  215. }
  216. w.Header().Set("Accept-Ranges", "bytes")
  217. if r.Method == "HEAD" {
  218. w.Header().Set("Content-Length", strconv.FormatInt(totalSize, 10))
  219. return nil
  220. }
  221. rangeReq := r.Header.Get("Range")
  222. if rangeReq == "" {
  223. w.Header().Set("Content-Length", strconv.FormatInt(totalSize, 10))
  224. if _, e = rs.Seek(0, 0); e != nil {
  225. return e
  226. }
  227. _, e = io.Copy(w, rs)
  228. return e
  229. }
  230. //the rest is dealing with partial content request
  231. //mostly copy from src/pkg/net/http/fs.go
  232. ranges, err := parseRange(rangeReq, totalSize)
  233. if err != nil {
  234. http.Error(w, err.Error(), http.StatusRequestedRangeNotSatisfiable)
  235. return nil
  236. }
  237. if sumRangesSize(ranges) > totalSize {
  238. // The total number of bytes in all the ranges
  239. // is larger than the size of the file by
  240. // itself, so this is probably an attack, or a
  241. // dumb client. Ignore the range request.
  242. return nil
  243. }
  244. if len(ranges) == 0 {
  245. return nil
  246. }
  247. if len(ranges) == 1 {
  248. // RFC 2616, Section 14.16:
  249. // "When an HTTP message includes the content of a single
  250. // range (for example, a response to a request for a
  251. // single range, or to a request for a set of ranges
  252. // that overlap without any holes), this content is
  253. // transmitted with a Content-Range header, and a
  254. // Content-Length header showing the number of bytes
  255. // actually transferred.
  256. // ...
  257. // A response to a request for a single range MUST NOT
  258. // be sent using the multipart/byteranges media type."
  259. ra := ranges[0]
  260. w.Header().Set("Content-Length", strconv.FormatInt(ra.length, 10))
  261. w.Header().Set("Content-Range", ra.contentRange(totalSize))
  262. w.WriteHeader(http.StatusPartialContent)
  263. if _, e = rs.Seek(ra.start, 0); e != nil {
  264. return e
  265. }
  266. _, e = io.CopyN(w, rs, ra.length)
  267. return e
  268. }
  269. // process multiple ranges
  270. for _, ra := range ranges {
  271. if ra.start > totalSize {
  272. http.Error(w, "Out of Range", http.StatusRequestedRangeNotSatisfiable)
  273. return nil
  274. }
  275. }
  276. sendSize := rangesMIMESize(ranges, mimeType, totalSize)
  277. pr, pw := io.Pipe()
  278. mw := multipart.NewWriter(pw)
  279. w.Header().Set("Content-Type", "multipart/byteranges; boundary="+mw.Boundary())
  280. sendContent := pr
  281. defer pr.Close() // cause writing goroutine to fail and exit if CopyN doesn't finish.
  282. go func() {
  283. for _, ra := range ranges {
  284. part, e := mw.CreatePart(ra.mimeHeader(mimeType, totalSize))
  285. if e != nil {
  286. pw.CloseWithError(e)
  287. return
  288. }
  289. if _, e = rs.Seek(ra.start, 0); e != nil {
  290. pw.CloseWithError(e)
  291. return
  292. }
  293. if _, e = io.CopyN(part, rs, ra.length); e != nil {
  294. pw.CloseWithError(e)
  295. return
  296. }
  297. }
  298. mw.Close()
  299. pw.Close()
  300. }()
  301. if w.Header().Get("Content-Encoding") == "" {
  302. w.Header().Set("Content-Length", strconv.FormatInt(sendSize, 10))
  303. }
  304. w.WriteHeader(http.StatusPartialContent)
  305. _, e = io.CopyN(w, sendContent, sendSize)
  306. return e
  307. }