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.

544 lines
16 KiB

9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
7 years ago
9 years ago
7 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
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. "bytes"
  4. "crypto/md5"
  5. "encoding/base64"
  6. "encoding/json"
  7. "errors"
  8. "fmt"
  9. "io"
  10. "io/ioutil"
  11. "mime/multipart"
  12. "net/http"
  13. "net/textproto"
  14. "net/url"
  15. "path"
  16. "strconv"
  17. "strings"
  18. "github.com/chrislusf/seaweedfs/weed/filer2"
  19. "github.com/chrislusf/seaweedfs/weed/glog"
  20. "github.com/chrislusf/seaweedfs/weed/operation"
  21. "github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
  22. "github.com/chrislusf/seaweedfs/weed/storage"
  23. "github.com/chrislusf/seaweedfs/weed/util"
  24. "time"
  25. )
  26. type FilerPostResult struct {
  27. Name string `json:"name,omitempty"`
  28. Size uint32 `json:"size,omitempty"`
  29. Error string `json:"error,omitempty"`
  30. Fid string `json:"fid,omitempty"`
  31. Url string `json:"url,omitempty"`
  32. }
  33. var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
  34. func escapeQuotes(s string) string {
  35. return quoteEscaper.Replace(s)
  36. }
  37. func createFormFile(writer *multipart.Writer, fieldname, filename, mime string) (io.Writer, error) {
  38. h := make(textproto.MIMEHeader)
  39. h.Set("Content-Disposition",
  40. fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
  41. escapeQuotes(fieldname), escapeQuotes(filename)))
  42. if len(mime) == 0 {
  43. mime = "application/octet-stream"
  44. }
  45. h.Set("Content-Type", mime)
  46. return writer.CreatePart(h)
  47. }
  48. func makeFormData(filename, mimeType string, content io.Reader) (formData io.Reader, contentType string, err error) {
  49. buf := new(bytes.Buffer)
  50. writer := multipart.NewWriter(buf)
  51. defer writer.Close()
  52. part, err := createFormFile(writer, "file", filename, mimeType)
  53. if err != nil {
  54. glog.V(0).Infoln(err)
  55. return
  56. }
  57. _, err = io.Copy(part, content)
  58. if err != nil {
  59. glog.V(0).Infoln(err)
  60. return
  61. }
  62. formData = buf
  63. contentType = writer.FormDataContentType()
  64. return
  65. }
  66. func (fs *FilerServer) queryFileInfoByPath(w http.ResponseWriter, r *http.Request, path string) (fileId, urlLocation string, err error) {
  67. var entry *filer2.Entry
  68. if entry, err = fs.filer.FindEntry(filer2.FullPath(path)); err != nil {
  69. glog.V(0).Infoln("failing to find path in filer store", path, err.Error())
  70. writeJsonError(w, r, http.StatusInternalServerError, err)
  71. } else {
  72. fileId = entry.Chunks[0].FileId
  73. urlLocation, err = operation.LookupFileId(fs.getMasterNode(), fileId)
  74. if err != nil {
  75. glog.V(1).Infoln("operation LookupFileId %s failed, err is %s", fileId, err.Error())
  76. w.WriteHeader(http.StatusNotFound)
  77. }
  78. }
  79. return
  80. }
  81. func (fs *FilerServer) assignNewFileInfo(w http.ResponseWriter, r *http.Request, replication, collection string) (fileId, urlLocation string, err error) {
  82. ar := &operation.VolumeAssignRequest{
  83. Count: 1,
  84. Replication: replication,
  85. Collection: collection,
  86. Ttl: r.URL.Query().Get("ttl"),
  87. }
  88. assignResult, ae := operation.Assign(fs.getMasterNode(), ar)
  89. if ae != nil {
  90. glog.V(0).Infoln("failing to assign a file id", ae.Error())
  91. writeJsonError(w, r, http.StatusInternalServerError, ae)
  92. err = ae
  93. return
  94. }
  95. fileId = assignResult.Fid
  96. urlLocation = "http://" + assignResult.Url + "/" + assignResult.Fid
  97. return
  98. }
  99. func (fs *FilerServer) multipartUploadAnalyzer(w http.ResponseWriter, r *http.Request, replication, collection string) (fileId, urlLocation string, err error) {
  100. //Default handle way for http multipart
  101. if r.Method == "PUT" {
  102. buf, _ := ioutil.ReadAll(r.Body)
  103. r.Body = ioutil.NopCloser(bytes.NewBuffer(buf))
  104. fileName, _, _, _, _, _, _, _, pe := storage.ParseUpload(r)
  105. if pe != nil {
  106. glog.V(0).Infoln("failing to parse post body", pe.Error())
  107. writeJsonError(w, r, http.StatusInternalServerError, pe)
  108. err = pe
  109. return
  110. }
  111. //reconstruct http request body for following new request to volume server
  112. r.Body = ioutil.NopCloser(bytes.NewBuffer(buf))
  113. path := r.URL.Path
  114. if strings.HasSuffix(path, "/") {
  115. if fileName != "" {
  116. path += fileName
  117. }
  118. }
  119. fileId, urlLocation, err = fs.queryFileInfoByPath(w, r, path)
  120. } else {
  121. fileId, urlLocation, err = fs.assignNewFileInfo(w, r, replication, collection)
  122. }
  123. return
  124. }
  125. func multipartHttpBodyBuilder(w http.ResponseWriter, r *http.Request, fileName string) (err error) {
  126. body, contentType, te := makeFormData(fileName, r.Header.Get("Content-Type"), r.Body)
  127. if te != nil {
  128. glog.V(0).Infoln("S3 protocol to raw seaweed protocol failed", te.Error())
  129. writeJsonError(w, r, http.StatusInternalServerError, te)
  130. err = te
  131. return
  132. }
  133. if body != nil {
  134. switch v := body.(type) {
  135. case *bytes.Buffer:
  136. r.ContentLength = int64(v.Len())
  137. case *bytes.Reader:
  138. r.ContentLength = int64(v.Len())
  139. case *strings.Reader:
  140. r.ContentLength = int64(v.Len())
  141. }
  142. }
  143. r.Header.Set("Content-Type", contentType)
  144. rc, ok := body.(io.ReadCloser)
  145. if !ok && body != nil {
  146. rc = ioutil.NopCloser(body)
  147. }
  148. r.Body = rc
  149. return
  150. }
  151. func checkContentMD5(w http.ResponseWriter, r *http.Request) (err error) {
  152. if contentMD5 := r.Header.Get("Content-MD5"); contentMD5 != "" {
  153. buf, _ := ioutil.ReadAll(r.Body)
  154. //checkMD5
  155. sum := md5.Sum(buf)
  156. fileDataMD5 := base64.StdEncoding.EncodeToString(sum[0:len(sum)])
  157. if strings.ToLower(fileDataMD5) != strings.ToLower(contentMD5) {
  158. glog.V(0).Infof("fileDataMD5 [%s] is not equal to Content-MD5 [%s]", fileDataMD5, contentMD5)
  159. err = fmt.Errorf("MD5 check failed")
  160. writeJsonError(w, r, http.StatusNotAcceptable, err)
  161. return
  162. }
  163. //reconstruct http request body for following new request to volume server
  164. r.Body = ioutil.NopCloser(bytes.NewBuffer(buf))
  165. }
  166. return
  167. }
  168. func (fs *FilerServer) monolithicUploadAnalyzer(w http.ResponseWriter, r *http.Request, replication, collection string) (fileId, urlLocation string, err error) {
  169. /*
  170. Amazon S3 ref link:[http://docs.aws.amazon.com/AmazonS3/latest/API/Welcome.html]
  171. There is a long way to provide a completely compatibility against all Amazon S3 API, I just made
  172. a simple data stream adapter between S3 PUT API and seaweedfs's volume storage Write API
  173. 1. The request url format should be http://$host:$port/$bucketName/$objectName
  174. 2. bucketName will be mapped to seaweedfs's collection name
  175. 3. You could customize and make your enhancement.
  176. */
  177. lastPos := strings.LastIndex(r.URL.Path, "/")
  178. if lastPos == -1 || lastPos == 0 || lastPos == len(r.URL.Path)-1 {
  179. glog.V(0).Infoln("URL Path [%s] is invalid, could not retrieve file name", r.URL.Path)
  180. err = fmt.Errorf("URL Path is invalid")
  181. writeJsonError(w, r, http.StatusInternalServerError, err)
  182. return
  183. }
  184. if err = checkContentMD5(w, r); err != nil {
  185. return
  186. }
  187. fileName := r.URL.Path[lastPos+1:]
  188. if err = multipartHttpBodyBuilder(w, r, fileName); err != nil {
  189. return
  190. }
  191. secondPos := strings.Index(r.URL.Path[1:], "/") + 1
  192. collection = r.URL.Path[1:secondPos]
  193. path := r.URL.Path
  194. if fileId, urlLocation, err = fs.queryFileInfoByPath(w, r, path); err == nil && fileId == "" {
  195. fileId, urlLocation, err = fs.assignNewFileInfo(w, r, replication, collection)
  196. }
  197. return
  198. }
  199. func (fs *FilerServer) PostHandler(w http.ResponseWriter, r *http.Request) {
  200. query := r.URL.Query()
  201. replication := query.Get("replication")
  202. if replication == "" {
  203. replication = fs.defaultReplication
  204. }
  205. collection := query.Get("collection")
  206. if collection == "" {
  207. collection = fs.collection
  208. }
  209. if autoChunked := fs.autoChunk(w, r, replication, collection); autoChunked {
  210. return
  211. }
  212. var fileId, urlLocation string
  213. var err error
  214. if strings.HasPrefix(r.Header.Get("Content-Type"), "multipart/form-data; boundary=") {
  215. fileId, urlLocation, err = fs.multipartUploadAnalyzer(w, r, replication, collection)
  216. if err != nil {
  217. return
  218. }
  219. } else {
  220. fileId, urlLocation, err = fs.monolithicUploadAnalyzer(w, r, replication, collection)
  221. if err != nil {
  222. return
  223. }
  224. }
  225. u, _ := url.Parse(urlLocation)
  226. // This allows a client to generate a chunk manifest and submit it to the filer -- it is a little off
  227. // because they need to provide FIDs instead of file paths...
  228. cm, _ := strconv.ParseBool(query.Get("cm"))
  229. if cm {
  230. q := u.Query()
  231. q.Set("cm", "true")
  232. u.RawQuery = q.Encode()
  233. }
  234. glog.V(4).Infoln("post to", u)
  235. request := &http.Request{
  236. Method: r.Method,
  237. URL: u,
  238. Proto: r.Proto,
  239. ProtoMajor: r.ProtoMajor,
  240. ProtoMinor: r.ProtoMinor,
  241. Header: r.Header,
  242. Body: r.Body,
  243. Host: r.Host,
  244. ContentLength: r.ContentLength,
  245. }
  246. resp, do_err := util.Do(request)
  247. if do_err != nil {
  248. glog.V(0).Infoln("failing to connect to volume server", r.RequestURI, do_err.Error())
  249. writeJsonError(w, r, http.StatusInternalServerError, do_err)
  250. return
  251. }
  252. defer resp.Body.Close()
  253. resp_body, ra_err := ioutil.ReadAll(resp.Body)
  254. if ra_err != nil {
  255. glog.V(0).Infoln("failing to upload to volume server", r.RequestURI, ra_err.Error())
  256. writeJsonError(w, r, http.StatusInternalServerError, ra_err)
  257. return
  258. }
  259. glog.V(4).Infoln("post result", string(resp_body))
  260. var ret operation.UploadResult
  261. unmarshal_err := json.Unmarshal(resp_body, &ret)
  262. if unmarshal_err != nil {
  263. glog.V(0).Infoln("failing to read upload resonse", r.RequestURI, string(resp_body))
  264. writeJsonError(w, r, http.StatusInternalServerError, unmarshal_err)
  265. return
  266. }
  267. if ret.Error != "" {
  268. glog.V(0).Infoln("failing to post to volume server", r.RequestURI, ret.Error)
  269. writeJsonError(w, r, http.StatusInternalServerError, errors.New(ret.Error))
  270. return
  271. }
  272. path := r.URL.Path
  273. if strings.HasSuffix(path, "/") {
  274. if ret.Name != "" {
  275. path += ret.Name
  276. } else {
  277. operation.DeleteFile(fs.getMasterNode(), fileId, fs.jwt(fileId)) //clean up
  278. glog.V(0).Infoln("Can not to write to folder", path, "without a file name!")
  279. writeJsonError(w, r, http.StatusInternalServerError,
  280. errors.New("Can not to write to folder "+path+" without a file name"))
  281. return
  282. }
  283. }
  284. // also delete the old fid unless PUT operation
  285. if r.Method != "PUT" {
  286. if entry, err := fs.filer.FindEntry(filer2.FullPath(path)); err == nil {
  287. oldFid := entry.Chunks[0].FileId
  288. operation.DeleteFile(fs.getMasterNode(), oldFid, fs.jwt(oldFid))
  289. } else if err != nil && err != filer2.ErrNotFound {
  290. glog.V(0).Infof("error %v occur when finding %s in filer store", err, path)
  291. }
  292. }
  293. glog.V(4).Infoln("saving", path, "=>", fileId)
  294. entry := &filer2.Entry{
  295. FullPath: filer2.FullPath(path),
  296. Attr: filer2.Attr{
  297. Mode: 0660,
  298. },
  299. Chunks: []*filer_pb.FileChunk{{
  300. FileId: fileId,
  301. Size: uint64(r.ContentLength),
  302. Mtime: time.Now().UnixNano(),
  303. }},
  304. }
  305. if db_err := fs.filer.CreateEntry(entry); db_err != nil {
  306. operation.DeleteFile(fs.getMasterNode(), fileId, fs.jwt(fileId)) //clean up
  307. glog.V(0).Infof("failing to write %s to filer server : %v", path, db_err)
  308. writeJsonError(w, r, http.StatusInternalServerError, db_err)
  309. return
  310. }
  311. reply := FilerPostResult{
  312. Name: ret.Name,
  313. Size: ret.Size,
  314. Error: ret.Error,
  315. Fid: fileId,
  316. Url: urlLocation,
  317. }
  318. writeJsonQuiet(w, r, http.StatusCreated, reply)
  319. }
  320. func (fs *FilerServer) autoChunk(w http.ResponseWriter, r *http.Request, replication string, collection string) bool {
  321. if r.Method != "POST" {
  322. glog.V(4).Infoln("AutoChunking not supported for method", r.Method)
  323. return false
  324. }
  325. // autoChunking can be set at the command-line level or as a query param. Query param overrides command-line
  326. query := r.URL.Query()
  327. parsedMaxMB, _ := strconv.ParseInt(query.Get("maxMB"), 10, 32)
  328. maxMB := int32(parsedMaxMB)
  329. if maxMB <= 0 && fs.maxMB > 0 {
  330. maxMB = int32(fs.maxMB)
  331. }
  332. if maxMB <= 0 {
  333. glog.V(4).Infoln("AutoChunking not enabled")
  334. return false
  335. }
  336. glog.V(4).Infoln("AutoChunking level set to", maxMB, "(MB)")
  337. chunkSize := 1024 * 1024 * maxMB
  338. contentLength := int64(0)
  339. if contentLengthHeader := r.Header["Content-Length"]; len(contentLengthHeader) == 1 {
  340. contentLength, _ = strconv.ParseInt(contentLengthHeader[0], 10, 64)
  341. if contentLength <= int64(chunkSize) {
  342. glog.V(4).Infoln("Content-Length of", contentLength, "is less than the chunk size of", chunkSize, "so autoChunking will be skipped.")
  343. return false
  344. }
  345. }
  346. if contentLength <= 0 {
  347. glog.V(4).Infoln("Content-Length value is missing or unexpected so autoChunking will be skipped.")
  348. return false
  349. }
  350. reply, err := fs.doAutoChunk(w, r, contentLength, chunkSize, replication, collection)
  351. if err != nil {
  352. writeJsonError(w, r, http.StatusInternalServerError, err)
  353. } else if reply != nil {
  354. writeJsonQuiet(w, r, http.StatusCreated, reply)
  355. }
  356. return true
  357. }
  358. func (fs *FilerServer) doAutoChunk(w http.ResponseWriter, r *http.Request, contentLength int64, chunkSize int32, replication string, collection string) (filerResult *FilerPostResult, replyerr error) {
  359. multipartReader, multipartReaderErr := r.MultipartReader()
  360. if multipartReaderErr != nil {
  361. return nil, multipartReaderErr
  362. }
  363. part1, part1Err := multipartReader.NextPart()
  364. if part1Err != nil {
  365. return nil, part1Err
  366. }
  367. fileName := part1.FileName()
  368. if fileName != "" {
  369. fileName = path.Base(fileName)
  370. }
  371. var fileChunks []*filer_pb.FileChunk
  372. totalBytesRead := int64(0)
  373. tmpBufferSize := int32(1024 * 1024)
  374. tmpBuffer := bytes.NewBuffer(make([]byte, 0, tmpBufferSize))
  375. chunkBuf := make([]byte, chunkSize+tmpBufferSize, chunkSize+tmpBufferSize) // chunk size plus a little overflow
  376. chunkBufOffset := int32(0)
  377. chunkOffset := int64(0)
  378. writtenChunks := 0
  379. filerResult = &FilerPostResult{
  380. Name: fileName,
  381. }
  382. for totalBytesRead < contentLength {
  383. tmpBuffer.Reset()
  384. bytesRead, readErr := io.CopyN(tmpBuffer, part1, int64(tmpBufferSize))
  385. readFully := readErr != nil && readErr == io.EOF
  386. tmpBuf := tmpBuffer.Bytes()
  387. bytesToCopy := tmpBuf[0:int(bytesRead)]
  388. copy(chunkBuf[chunkBufOffset:chunkBufOffset+int32(bytesRead)], bytesToCopy)
  389. chunkBufOffset = chunkBufOffset + int32(bytesRead)
  390. if chunkBufOffset >= chunkSize || readFully || (chunkBufOffset > 0 && bytesRead == 0) {
  391. writtenChunks = writtenChunks + 1
  392. fileId, urlLocation, assignErr := fs.assignNewFileInfo(w, r, replication, collection)
  393. if assignErr != nil {
  394. return nil, assignErr
  395. }
  396. // upload the chunk to the volume server
  397. chunkName := fileName + "_chunk_" + strconv.FormatInt(int64(len(fileChunks)+1), 10)
  398. uploadErr := fs.doUpload(urlLocation, w, r, chunkBuf[0:chunkBufOffset], chunkName, "application/octet-stream", fileId)
  399. if uploadErr != nil {
  400. return nil, uploadErr
  401. }
  402. // Save to chunk manifest structure
  403. fileChunks = append(fileChunks,
  404. &filer_pb.FileChunk{
  405. FileId: fileId,
  406. Offset: chunkOffset,
  407. Size: uint64(chunkBufOffset),
  408. Mtime: time.Now().UnixNano(),
  409. },
  410. )
  411. // reset variables for the next chunk
  412. chunkBufOffset = 0
  413. chunkOffset = totalBytesRead + int64(bytesRead)
  414. }
  415. totalBytesRead = totalBytesRead + int64(bytesRead)
  416. if bytesRead == 0 || readFully {
  417. break
  418. }
  419. if readErr != nil {
  420. return nil, readErr
  421. }
  422. }
  423. path := r.URL.Path
  424. // also delete the old fid unless PUT operation
  425. if r.Method != "PUT" {
  426. if entry, err := fs.filer.FindEntry(filer2.FullPath(path)); err == nil {
  427. for _, chunk := range entry.Chunks {
  428. oldFid := chunk.FileId
  429. operation.DeleteFile(fs.getMasterNode(), oldFid, fs.jwt(oldFid))
  430. }
  431. } else if err != nil {
  432. glog.V(0).Infof("error %v occur when finding %s in filer store", err, path)
  433. }
  434. }
  435. glog.V(4).Infoln("saving", path)
  436. entry := &filer2.Entry{
  437. FullPath: filer2.FullPath(path),
  438. Attr: filer2.Attr{
  439. Mode: 0660,
  440. },
  441. Chunks: fileChunks,
  442. }
  443. if db_err := fs.filer.CreateEntry(entry); db_err != nil {
  444. replyerr = db_err
  445. filerResult.Error = db_err.Error()
  446. glog.V(0).Infof("failing to write %s to filer server : %v", path, db_err)
  447. return
  448. }
  449. return
  450. }
  451. func (fs *FilerServer) doUpload(urlLocation string, w http.ResponseWriter, r *http.Request, chunkBuf []byte, fileName string, contentType string, fileId string) (err error) {
  452. err = nil
  453. ioReader := ioutil.NopCloser(bytes.NewBuffer(chunkBuf))
  454. uploadResult, uploadError := operation.Upload(urlLocation, fileName, ioReader, false, contentType, nil, fs.jwt(fileId))
  455. if uploadResult != nil {
  456. glog.V(0).Infoln("Chunk upload result. Name:", uploadResult.Name, "Fid:", fileId, "Size:", uploadResult.Size)
  457. }
  458. if uploadError != nil {
  459. err = uploadError
  460. }
  461. return
  462. }
  463. // curl -X DELETE http://localhost:8888/path/to
  464. func (fs *FilerServer) DeleteHandler(w http.ResponseWriter, r *http.Request) {
  465. entry, err := fs.filer.DeleteEntry(filer2.FullPath(r.URL.Path))
  466. if err != nil {
  467. glog.V(4).Infoln("deleting", r.URL.Path, ":", err.Error())
  468. writeJsonError(w, r, http.StatusInternalServerError, err)
  469. return
  470. }
  471. if entry != nil && !entry.IsDirectory() {
  472. for _, chunk := range entry.Chunks {
  473. oldFid := chunk.FileId
  474. operation.DeleteFile(fs.getMasterNode(), oldFid, fs.jwt(oldFid))
  475. }
  476. }
  477. writeJsonQuiet(w, r, http.StatusAccepted, map[string]string{"error": ""})
  478. }