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.

364 lines
10 KiB

9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
  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/seaweedfs/go/glog"
  11. "github.com/chrislusf/seaweedfs/go/images"
  12. "github.com/chrislusf/seaweedfs/go/operation"
  13. "github.com/chrislusf/seaweedfs/go/storage"
  14. "github.com/chrislusf/seaweedfs/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. if !vs.ReadRedirect {
  35. glog.V(2).Infoln("volume is not local:", err, r.URL.Path)
  36. w.WriteHeader(http.StatusNotFound)
  37. return
  38. }
  39. lookupResult, err := operation.Lookup(vs.GetMasterNode(), volumeId.String())
  40. glog.V(2).Infoln("volume", volumeId, "found on", lookupResult, "error", err)
  41. if err == nil && len(lookupResult.Locations) > 0 {
  42. http.Redirect(w, r, util.NormalizeUrl(lookupResult.Locations[0].PublicUrl)+r.URL.Path, http.StatusMovedPermanently)
  43. } else {
  44. glog.V(2).Infoln("lookup error:", err, r.URL.Path)
  45. w.WriteHeader(http.StatusNotFound)
  46. }
  47. return
  48. }
  49. cookie := n.Cookie
  50. count, e := vs.store.ReadVolumeNeedle(volumeId, n)
  51. glog.V(4).Infoln("read bytes", count, "error", e)
  52. if e != nil || count <= 0 {
  53. glog.V(0).Infoln("read error:", e, r.URL.Path)
  54. w.WriteHeader(http.StatusNotFound)
  55. return
  56. }
  57. if n.Cookie != cookie {
  58. glog.V(0).Infoln("request", r.URL.Path, "with unmaching cookie seen:", cookie, "expected:", n.Cookie, "from", r.RemoteAddr, "agent", r.UserAgent())
  59. w.WriteHeader(http.StatusNotFound)
  60. return
  61. }
  62. if n.LastModified != 0 {
  63. w.Header().Set("Last-Modified", time.Unix(int64(n.LastModified), 0).UTC().Format(http.TimeFormat))
  64. if r.Header.Get("If-Modified-Since") != "" {
  65. if t, parseError := time.Parse(http.TimeFormat, r.Header.Get("If-Modified-Since")); parseError == nil {
  66. if t.Unix() >= int64(n.LastModified) {
  67. w.WriteHeader(http.StatusNotModified)
  68. return
  69. }
  70. }
  71. }
  72. }
  73. etag := n.Etag()
  74. if inm := r.Header.Get("If-None-Match"); inm == etag {
  75. w.WriteHeader(http.StatusNotModified)
  76. return
  77. }
  78. w.Header().Set("Etag", etag)
  79. if vs.tryHandleChunkedFile(n, filename, w, r) {
  80. return
  81. }
  82. if n.NameSize > 0 && filename == "" {
  83. filename = string(n.Name)
  84. dotIndex := strings.LastIndex(filename, ".")
  85. if dotIndex > 0 {
  86. ext = filename[dotIndex:]
  87. }
  88. }
  89. mtype := ""
  90. if ext != "" {
  91. mtype = mime.TypeByExtension(ext)
  92. }
  93. if n.MimeSize > 0 {
  94. mt := string(n.Mime)
  95. if !strings.HasPrefix(mt, "application/octet-stream") {
  96. mtype = mt
  97. }
  98. }
  99. if mtype != "" {
  100. w.Header().Set("Content-Type", mtype)
  101. }
  102. if filename != "" {
  103. w.Header().Set("Content-Disposition", "filename=\""+fileNameEscaper.Replace(filename)+"\"")
  104. }
  105. if ext != ".gz" {
  106. if n.IsGzipped() {
  107. if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
  108. w.Header().Set("Content-Encoding", "gzip")
  109. } else {
  110. if n.Data, err = operation.UnGzipData(n.Data); err != nil {
  111. glog.V(0).Infoln("lookup error:", err, r.URL.Path)
  112. }
  113. }
  114. }
  115. }
  116. if ext == ".png" || ext == ".jpg" || ext == ".gif" {
  117. width, height := 0, 0
  118. if r.FormValue("width") != "" {
  119. width, _ = strconv.Atoi(r.FormValue("width"))
  120. }
  121. if r.FormValue("height") != "" {
  122. height, _ = strconv.Atoi(r.FormValue("height"))
  123. }
  124. n.Data, _, _ = images.Resized(ext, n.Data, width, height)
  125. }
  126. w.Header().Set("Accept-Ranges", "bytes")
  127. if r.Method == "HEAD" {
  128. w.Header().Set("Content-Length", strconv.Itoa(len(n.Data)))
  129. return
  130. }
  131. rangeReq := r.Header.Get("Range")
  132. if rangeReq == "" {
  133. w.Header().Set("Content-Length", strconv.Itoa(len(n.Data)))
  134. if _, e = w.Write(n.Data); e != nil {
  135. glog.V(4).Infoln("response write error:", e)
  136. }
  137. return
  138. }
  139. //the rest is dealing with partial content request
  140. //mostly copy from src/pkg/net/http/fs.go
  141. size := int64(len(n.Data))
  142. ranges, err := parseRange(rangeReq, size)
  143. if err != nil {
  144. http.Error(w, err.Error(), http.StatusRequestedRangeNotSatisfiable)
  145. return
  146. }
  147. if sumRangesSize(ranges) > size {
  148. // The total number of bytes in all the ranges
  149. // is larger than the size of the file by
  150. // itself, so this is probably an attack, or a
  151. // dumb client. Ignore the range request.
  152. ranges = nil
  153. return
  154. }
  155. if len(ranges) == 0 {
  156. return
  157. }
  158. if len(ranges) == 1 {
  159. // RFC 2616, Section 14.16:
  160. // "When an HTTP message includes the content of a single
  161. // range (for example, a response to a request for a
  162. // single range, or to a request for a set of ranges
  163. // that overlap without any holes), this content is
  164. // transmitted with a Content-Range header, and a
  165. // Content-Length header showing the number of bytes
  166. // actually transferred.
  167. // ...
  168. // A response to a request for a single range MUST NOT
  169. // be sent using the multipart/byteranges media type."
  170. ra := ranges[0]
  171. w.Header().Set("Content-Length", strconv.FormatInt(ra.length, 10))
  172. w.Header().Set("Content-Range", ra.contentRange(size))
  173. w.WriteHeader(http.StatusPartialContent)
  174. if _, e = w.Write(n.Data[ra.start : ra.start+ra.length]); e != nil {
  175. glog.V(2).Infoln("response write error:", e)
  176. }
  177. return
  178. }
  179. // process multiple ranges
  180. for _, ra := range ranges {
  181. if ra.start > size {
  182. http.Error(w, "Out of Range", http.StatusRequestedRangeNotSatisfiable)
  183. return
  184. }
  185. }
  186. sendSize := rangesMIMESize(ranges, mtype, size)
  187. pr, pw := io.Pipe()
  188. mw := multipart.NewWriter(pw)
  189. w.Header().Set("Content-Type", "multipart/byteranges; boundary="+mw.Boundary())
  190. sendContent := pr
  191. defer pr.Close() // cause writing goroutine to fail and exit if CopyN doesn't finish.
  192. go func() {
  193. for _, ra := range ranges {
  194. part, err := mw.CreatePart(ra.mimeHeader(mtype, size))
  195. if err != nil {
  196. pw.CloseWithError(err)
  197. return
  198. }
  199. if _, err = part.Write(n.Data[ra.start : ra.start+ra.length]); err != nil {
  200. pw.CloseWithError(err)
  201. return
  202. }
  203. }
  204. mw.Close()
  205. pw.Close()
  206. }()
  207. if w.Header().Get("Content-Encoding") == "" {
  208. w.Header().Set("Content-Length", strconv.FormatInt(sendSize, 10))
  209. }
  210. w.WriteHeader(http.StatusPartialContent)
  211. io.CopyN(w, sendContent, sendSize)
  212. }
  213. func (vs *VolumeServer) tryHandleChunkedFile(n *storage.Needle, fileName string, w http.ResponseWriter, r *http.Request) (processed bool) {
  214. if !n.IsChunkedManifest() {
  215. return false
  216. }
  217. raw, _ := strconv.ParseBool(r.FormValue("raw"))
  218. if raw {
  219. return false
  220. }
  221. processed = true
  222. chunkManifest, e := operation.LoadChunkManifest(n.Data, n.IsGzipped())
  223. if e != nil {
  224. glog.V(0).Infoln("load chunked manifest error:", e)
  225. return false
  226. }
  227. ext := ""
  228. if fileName == "" && chunkManifest.Name != "" {
  229. fileName = chunkManifest.Name
  230. dotIndex := strings.LastIndex(fileName, ".")
  231. if dotIndex > 0 {
  232. ext = fileName[dotIndex:]
  233. }
  234. }
  235. mtype := ""
  236. if ext != "" {
  237. mtype = mime.TypeByExtension(ext)
  238. }
  239. if chunkManifest.Mime != "" {
  240. mt := chunkManifest.Mime
  241. if !strings.HasPrefix(mt, "application/octet-stream") {
  242. mtype = mt
  243. }
  244. }
  245. if mtype != "" {
  246. w.Header().Set("Content-Type", mtype)
  247. }
  248. if fileName != "" {
  249. w.Header().Set("Content-Disposition", `filename="`+fileNameEscaper.Replace(fileName)+`"`)
  250. }
  251. w.Header().Set("X-File-Store", "chunked")
  252. w.Header().Set("Accept-Ranges", "bytes")
  253. if r.Method == "HEAD" {
  254. w.Header().Set("Content-Length", strconv.FormatInt(chunkManifest.Size, 10))
  255. return true
  256. }
  257. chunkedFileReader := &operation.ChunkedFileReader{
  258. Manifest: chunkManifest,
  259. Master: vs.GetMasterNode(),
  260. }
  261. defer chunkedFileReader.Close()
  262. rangeReq := r.Header.Get("Range")
  263. if rangeReq == "" {
  264. w.Header().Set("Content-Length", strconv.FormatInt(chunkManifest.Size, 10))
  265. if _, e = io.Copy(w, chunkedFileReader); e != nil {
  266. glog.V(2).Infoln("response write error:", e)
  267. }
  268. return true
  269. }
  270. //the rest is dealing with partial content request
  271. //mostly copy from src/pkg/net/http/fs.go
  272. size := chunkManifest.Size
  273. ranges, err := parseRange(rangeReq, size)
  274. if err != nil {
  275. http.Error(w, err.Error(), http.StatusRequestedRangeNotSatisfiable)
  276. return
  277. }
  278. if sumRangesSize(ranges) > size {
  279. // The total number of bytes in all the ranges
  280. // is larger than the size of the file by
  281. // itself, so this is probably an attack, or a
  282. // dumb client. Ignore the range request.
  283. ranges = nil
  284. return
  285. }
  286. if len(ranges) == 0 {
  287. return
  288. }
  289. if len(ranges) == 1 {
  290. // RFC 2616, Section 14.16:
  291. // "When an HTTP message includes the content of a single
  292. // range (for example, a response to a request for a
  293. // single range, or to a request for a set of ranges
  294. // that overlap without any holes), this content is
  295. // transmitted with a Content-Range header, and a
  296. // Content-Length header showing the number of bytes
  297. // actually transferred.
  298. // ...
  299. // A response to a request for a single range MUST NOT
  300. // be sent using the multipart/byteranges media type."
  301. ra := ranges[0]
  302. w.Header().Set("Content-Length", strconv.FormatInt(ra.length, 10))
  303. w.Header().Set("Content-Range", ra.contentRange(size))
  304. w.WriteHeader(http.StatusPartialContent)
  305. if _, e = chunkedFileReader.Seek(ra.start, 0); e != nil {
  306. glog.V(2).Infoln("chunkedFileReader Seek error:", e)
  307. }
  308. if _, e = io.CopyN(w, chunkedFileReader, ra.length); e != nil {
  309. glog.V(2).Infoln("response write error:", e)
  310. }
  311. return
  312. }
  313. // process multiple ranges
  314. for _, ra := range ranges {
  315. if ra.start > size {
  316. http.Error(w, "Out of Range", http.StatusRequestedRangeNotSatisfiable)
  317. return
  318. }
  319. }
  320. sendSize := rangesMIMESize(ranges, mtype, size)
  321. pr, pw := io.Pipe()
  322. mw := multipart.NewWriter(pw)
  323. w.Header().Set("Content-Type", "multipart/byteranges; boundary="+mw.Boundary())
  324. sendContent := pr
  325. defer pr.Close() // cause writing goroutine to fail and exit if CopyN doesn't finish.
  326. go func() {
  327. for _, ra := range ranges {
  328. part, err := mw.CreatePart(ra.mimeHeader(mtype, size))
  329. if err != nil {
  330. pw.CloseWithError(err)
  331. return
  332. }
  333. if _, e = chunkedFileReader.Seek(ra.start, 0); e != nil {
  334. glog.V(2).Infoln("response write error:", e)
  335. }
  336. if _, err = io.CopyN(part, chunkedFileReader, ra.length); err != nil {
  337. pw.CloseWithError(err)
  338. return
  339. }
  340. }
  341. mw.Close()
  342. pw.Close()
  343. }()
  344. if w.Header().Get("Content-Encoding") == "" {
  345. w.Header().Set("Content-Length", strconv.FormatInt(sendSize, 10))
  346. }
  347. w.WriteHeader(http.StatusPartialContent)
  348. io.CopyN(w, sendContent, sendSize)
  349. return
  350. }