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.

545 lines
16 KiB

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