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. )
  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 found bool
  68. var entry *filer2.Entry
  69. if found, 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 if found {
  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. } else {
  80. w.WriteHeader(http.StatusNotFound)
  81. }
  82. return
  83. }
  84. func (fs *FilerServer) assignNewFileInfo(w http.ResponseWriter, r *http.Request, replication, collection string) (fileId, urlLocation string, err error) {
  85. ar := &operation.VolumeAssignRequest{
  86. Count: 1,
  87. Replication: replication,
  88. Collection: collection,
  89. Ttl: r.URL.Query().Get("ttl"),
  90. }
  91. assignResult, ae := operation.Assign(fs.getMasterNode(), ar)
  92. if ae != nil {
  93. glog.V(0).Infoln("failing to assign a file id", ae.Error())
  94. writeJsonError(w, r, http.StatusInternalServerError, ae)
  95. err = ae
  96. return
  97. }
  98. fileId = assignResult.Fid
  99. urlLocation = "http://" + assignResult.Url + "/" + assignResult.Fid
  100. return
  101. }
  102. func (fs *FilerServer) multipartUploadAnalyzer(w http.ResponseWriter, r *http.Request, replication, collection string) (fileId, urlLocation string, err error) {
  103. //Default handle way for http multipart
  104. if r.Method == "PUT" {
  105. buf, _ := ioutil.ReadAll(r.Body)
  106. r.Body = ioutil.NopCloser(bytes.NewBuffer(buf))
  107. fileName, _, _, _, _, _, _, _, pe := storage.ParseUpload(r)
  108. if pe != nil {
  109. glog.V(0).Infoln("failing to parse post body", pe.Error())
  110. writeJsonError(w, r, http.StatusInternalServerError, pe)
  111. err = pe
  112. return
  113. }
  114. //reconstruct http request body for following new request to volume server
  115. r.Body = ioutil.NopCloser(bytes.NewBuffer(buf))
  116. path := r.URL.Path
  117. if strings.HasSuffix(path, "/") {
  118. if fileName != "" {
  119. path += fileName
  120. }
  121. }
  122. fileId, urlLocation, err = fs.queryFileInfoByPath(w, r, path)
  123. } else {
  124. fileId, urlLocation, err = fs.assignNewFileInfo(w, r, replication, collection)
  125. }
  126. return
  127. }
  128. func multipartHttpBodyBuilder(w http.ResponseWriter, r *http.Request, fileName string) (err error) {
  129. body, contentType, te := makeFormData(fileName, r.Header.Get("Content-Type"), r.Body)
  130. if te != nil {
  131. glog.V(0).Infoln("S3 protocol to raw seaweed protocol failed", te.Error())
  132. writeJsonError(w, r, http.StatusInternalServerError, te)
  133. err = te
  134. return
  135. }
  136. if body != nil {
  137. switch v := body.(type) {
  138. case *bytes.Buffer:
  139. r.ContentLength = int64(v.Len())
  140. case *bytes.Reader:
  141. r.ContentLength = int64(v.Len())
  142. case *strings.Reader:
  143. r.ContentLength = int64(v.Len())
  144. }
  145. }
  146. r.Header.Set("Content-Type", contentType)
  147. rc, ok := body.(io.ReadCloser)
  148. if !ok && body != nil {
  149. rc = ioutil.NopCloser(body)
  150. }
  151. r.Body = rc
  152. return
  153. }
  154. func checkContentMD5(w http.ResponseWriter, r *http.Request) (err error) {
  155. if contentMD5 := r.Header.Get("Content-MD5"); contentMD5 != "" {
  156. buf, _ := ioutil.ReadAll(r.Body)
  157. //checkMD5
  158. sum := md5.Sum(buf)
  159. fileDataMD5 := base64.StdEncoding.EncodeToString(sum[0:len(sum)])
  160. if strings.ToLower(fileDataMD5) != strings.ToLower(contentMD5) {
  161. glog.V(0).Infof("fileDataMD5 [%s] is not equal to Content-MD5 [%s]", fileDataMD5, contentMD5)
  162. err = fmt.Errorf("MD5 check failed")
  163. writeJsonError(w, r, http.StatusNotAcceptable, err)
  164. return
  165. }
  166. //reconstruct http request body for following new request to volume server
  167. r.Body = ioutil.NopCloser(bytes.NewBuffer(buf))
  168. }
  169. return
  170. }
  171. func (fs *FilerServer) monolithicUploadAnalyzer(w http.ResponseWriter, r *http.Request, replication, collection string) (fileId, urlLocation string, err error) {
  172. /*
  173. Amazon S3 ref link:[http://docs.aws.amazon.com/AmazonS3/latest/API/Welcome.html]
  174. There is a long way to provide a completely compatibility against all Amazon S3 API, I just made
  175. a simple data stream adapter between S3 PUT API and seaweedfs's volume storage Write API
  176. 1. The request url format should be http://$host:$port/$bucketName/$objectName
  177. 2. bucketName will be mapped to seaweedfs's collection name
  178. 3. You could customize and make your enhancement.
  179. */
  180. lastPos := strings.LastIndex(r.URL.Path, "/")
  181. if lastPos == -1 || lastPos == 0 || lastPos == len(r.URL.Path)-1 {
  182. glog.V(0).Infoln("URL Path [%s] is invalid, could not retrieve file name", r.URL.Path)
  183. err = fmt.Errorf("URL Path is invalid")
  184. writeJsonError(w, r, http.StatusInternalServerError, err)
  185. return
  186. }
  187. if err = checkContentMD5(w, r); err != nil {
  188. return
  189. }
  190. fileName := r.URL.Path[lastPos+1:]
  191. if err = multipartHttpBodyBuilder(w, r, fileName); err != nil {
  192. return
  193. }
  194. secondPos := strings.Index(r.URL.Path[1:], "/") + 1
  195. collection = r.URL.Path[1:secondPos]
  196. path := r.URL.Path
  197. if fileId, urlLocation, err = fs.queryFileInfoByPath(w, r, path); err == nil && fileId == "" {
  198. fileId, urlLocation, err = fs.assignNewFileInfo(w, r, replication, collection)
  199. }
  200. return
  201. }
  202. func (fs *FilerServer) PostHandler(w http.ResponseWriter, r *http.Request) {
  203. query := r.URL.Query()
  204. replication := query.Get("replication")
  205. if replication == "" {
  206. replication = fs.defaultReplication
  207. }
  208. collection := query.Get("collection")
  209. if collection == "" {
  210. collection = fs.collection
  211. }
  212. if autoChunked := fs.autoChunk(w, r, replication, collection); autoChunked {
  213. return
  214. }
  215. var fileId, urlLocation string
  216. var err error
  217. if strings.HasPrefix(r.Header.Get("Content-Type"), "multipart/form-data; boundary=") {
  218. fileId, urlLocation, err = fs.multipartUploadAnalyzer(w, r, replication, collection)
  219. if err != nil {
  220. return
  221. }
  222. } else {
  223. fileId, urlLocation, err = fs.monolithicUploadAnalyzer(w, r, replication, collection)
  224. if err != nil {
  225. return
  226. }
  227. }
  228. u, _ := url.Parse(urlLocation)
  229. // This allows a client to generate a chunk manifest and submit it to the filer -- it is a little off
  230. // because they need to provide FIDs instead of file paths...
  231. cm, _ := strconv.ParseBool(query.Get("cm"))
  232. if cm {
  233. q := u.Query()
  234. q.Set("cm", "true")
  235. u.RawQuery = q.Encode()
  236. }
  237. glog.V(4).Infoln("post to", u)
  238. request := &http.Request{
  239. Method: r.Method,
  240. URL: u,
  241. Proto: r.Proto,
  242. ProtoMajor: r.ProtoMajor,
  243. ProtoMinor: r.ProtoMinor,
  244. Header: r.Header,
  245. Body: r.Body,
  246. Host: r.Host,
  247. ContentLength: r.ContentLength,
  248. }
  249. resp, do_err := util.Do(request)
  250. if do_err != nil {
  251. glog.V(0).Infoln("failing to connect to volume server", r.RequestURI, do_err.Error())
  252. writeJsonError(w, r, http.StatusInternalServerError, do_err)
  253. return
  254. }
  255. defer resp.Body.Close()
  256. resp_body, ra_err := ioutil.ReadAll(resp.Body)
  257. if ra_err != nil {
  258. glog.V(0).Infoln("failing to upload to volume server", r.RequestURI, ra_err.Error())
  259. writeJsonError(w, r, http.StatusInternalServerError, ra_err)
  260. return
  261. }
  262. glog.V(4).Infoln("post result", string(resp_body))
  263. var ret operation.UploadResult
  264. unmarshal_err := json.Unmarshal(resp_body, &ret)
  265. if unmarshal_err != nil {
  266. glog.V(0).Infoln("failing to read upload resonse", r.RequestURI, string(resp_body))
  267. writeJsonError(w, r, http.StatusInternalServerError, unmarshal_err)
  268. return
  269. }
  270. if ret.Error != "" {
  271. glog.V(0).Infoln("failing to post to volume server", r.RequestURI, ret.Error)
  272. writeJsonError(w, r, http.StatusInternalServerError, errors.New(ret.Error))
  273. return
  274. }
  275. path := r.URL.Path
  276. if strings.HasSuffix(path, "/") {
  277. if ret.Name != "" {
  278. path += ret.Name
  279. } else {
  280. operation.DeleteFile(fs.getMasterNode(), fileId, fs.jwt(fileId)) //clean up
  281. glog.V(0).Infoln("Can not to write to folder", path, "without a file name!")
  282. writeJsonError(w, r, http.StatusInternalServerError,
  283. errors.New("Can not to write to folder "+path+" without a file name"))
  284. return
  285. }
  286. }
  287. // also delete the old fid unless PUT operation
  288. if r.Method != "PUT" {
  289. if found, entry, err := fs.filer.FindEntry(filer2.FullPath(path)); err == nil && found {
  290. oldFid := entry.Chunks[0].FileId
  291. operation.DeleteFile(fs.getMasterNode(), oldFid, fs.jwt(oldFid))
  292. } else if err != nil && err != filer.ErrNotFound {
  293. glog.V(0).Infof("error %v occur when finding %s in filer store", err, path)
  294. }
  295. }
  296. glog.V(4).Infoln("saving", path, "=>", fileId)
  297. entry := &filer2.Entry{
  298. FullPath: filer2.FullPath(path),
  299. Attr: filer2.Attr{
  300. Mode: 0660,
  301. },
  302. Chunks: []*filer_pb.FileChunk{{
  303. FileId: fileId,
  304. Size: uint64(r.ContentLength),
  305. }},
  306. }
  307. if db_err := fs.filer.CreateEntry(entry); db_err != nil {
  308. operation.DeleteFile(fs.getMasterNode(), fileId, fs.jwt(fileId)) //clean up
  309. glog.V(0).Infof("failing to write %s to filer server : %v", path, db_err)
  310. writeJsonError(w, r, http.StatusInternalServerError, db_err)
  311. return
  312. }
  313. reply := FilerPostResult{
  314. Name: ret.Name,
  315. Size: ret.Size,
  316. Error: ret.Error,
  317. Fid: fileId,
  318. Url: urlLocation,
  319. }
  320. writeJsonQuiet(w, r, http.StatusCreated, reply)
  321. }
  322. func (fs *FilerServer) autoChunk(w http.ResponseWriter, r *http.Request, replication string, collection string) bool {
  323. if r.Method != "POST" {
  324. glog.V(4).Infoln("AutoChunking not supported for method", r.Method)
  325. return false
  326. }
  327. // autoChunking can be set at the command-line level or as a query param. Query param overrides command-line
  328. query := r.URL.Query()
  329. parsedMaxMB, _ := strconv.ParseInt(query.Get("maxMB"), 10, 32)
  330. maxMB := int32(parsedMaxMB)
  331. if maxMB <= 0 && fs.maxMB > 0 {
  332. maxMB = int32(fs.maxMB)
  333. }
  334. if maxMB <= 0 {
  335. glog.V(4).Infoln("AutoChunking not enabled")
  336. return false
  337. }
  338. glog.V(4).Infoln("AutoChunking level set to", maxMB, "(MB)")
  339. chunkSize := 1024 * 1024 * maxMB
  340. contentLength := int64(0)
  341. if contentLengthHeader := r.Header["Content-Length"]; len(contentLengthHeader) == 1 {
  342. contentLength, _ = strconv.ParseInt(contentLengthHeader[0], 10, 64)
  343. if contentLength <= int64(chunkSize) {
  344. glog.V(4).Infoln("Content-Length of", contentLength, "is less than the chunk size of", chunkSize, "so autoChunking will be skipped.")
  345. return false
  346. }
  347. }
  348. if contentLength <= 0 {
  349. glog.V(4).Infoln("Content-Length value is missing or unexpected so autoChunking will be skipped.")
  350. return false
  351. }
  352. reply, err := fs.doAutoChunk(w, r, contentLength, chunkSize, replication, collection)
  353. if err != nil {
  354. writeJsonError(w, r, http.StatusInternalServerError, err)
  355. } else if reply != nil {
  356. writeJsonQuiet(w, r, http.StatusCreated, reply)
  357. }
  358. return true
  359. }
  360. func (fs *FilerServer) doAutoChunk(w http.ResponseWriter, r *http.Request, contentLength int64, chunkSize int32, replication string, collection string) (filerResult *FilerPostResult, replyerr error) {
  361. multipartReader, multipartReaderErr := r.MultipartReader()
  362. if multipartReaderErr != nil {
  363. return nil, multipartReaderErr
  364. }
  365. part1, part1Err := multipartReader.NextPart()
  366. if part1Err != nil {
  367. return nil, part1Err
  368. }
  369. fileName := part1.FileName()
  370. if fileName != "" {
  371. fileName = path.Base(fileName)
  372. }
  373. var fileChunks []*filer_pb.FileChunk
  374. totalBytesRead := int64(0)
  375. tmpBufferSize := int32(1024 * 1024)
  376. tmpBuffer := bytes.NewBuffer(make([]byte, 0, tmpBufferSize))
  377. chunkBuf := make([]byte, chunkSize+tmpBufferSize, chunkSize+tmpBufferSize) // chunk size plus a little overflow
  378. chunkBufOffset := int32(0)
  379. chunkOffset := int64(0)
  380. writtenChunks := 0
  381. filerResult = &FilerPostResult{
  382. Name: fileName,
  383. }
  384. for totalBytesRead < contentLength {
  385. tmpBuffer.Reset()
  386. bytesRead, readErr := io.CopyN(tmpBuffer, part1, int64(tmpBufferSize))
  387. readFully := readErr != nil && readErr == io.EOF
  388. tmpBuf := tmpBuffer.Bytes()
  389. bytesToCopy := tmpBuf[0:int(bytesRead)]
  390. copy(chunkBuf[chunkBufOffset:chunkBufOffset+int32(bytesRead)], bytesToCopy)
  391. chunkBufOffset = chunkBufOffset + int32(bytesRead)
  392. if chunkBufOffset >= chunkSize || readFully || (chunkBufOffset > 0 && bytesRead == 0) {
  393. writtenChunks = writtenChunks + 1
  394. fileId, urlLocation, assignErr := fs.assignNewFileInfo(w, r, replication, collection)
  395. if assignErr != nil {
  396. return nil, assignErr
  397. }
  398. // upload the chunk to the volume server
  399. chunkName := fileName + "_chunk_" + strconv.FormatInt(int64(len(fileChunks)+1), 10)
  400. uploadErr := fs.doUpload(urlLocation, w, r, chunkBuf[0:chunkBufOffset], chunkName, "application/octet-stream", fileId)
  401. if uploadErr != nil {
  402. return nil, uploadErr
  403. }
  404. // Save to chunk manifest structure
  405. fileChunks = append(fileChunks,
  406. &filer_pb.FileChunk{
  407. FileId: fileId,
  408. Offset: chunkOffset,
  409. Size: uint64(chunkBufOffset),
  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 found, entry, err := fs.filer.FindEntry(filer2.FullPath(path)); found && 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. }