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
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. )
  25. type FilerPostResult struct {
  26. Name string `json:"name,omitempty"`
  27. Size uint32 `json:"size,omitempty"`
  28. Error string `json:"error,omitempty"`
  29. Fid string `json:"fid,omitempty"`
  30. Url string `json:"url,omitempty"`
  31. }
  32. var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
  33. func escapeQuotes(s string) string {
  34. return quoteEscaper.Replace(s)
  35. }
  36. func createFormFile(writer *multipart.Writer, fieldname, filename, mime string) (io.Writer, error) {
  37. h := make(textproto.MIMEHeader)
  38. h.Set("Content-Disposition",
  39. fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
  40. escapeQuotes(fieldname), escapeQuotes(filename)))
  41. if len(mime) == 0 {
  42. mime = "application/octet-stream"
  43. }
  44. h.Set("Content-Type", mime)
  45. return writer.CreatePart(h)
  46. }
  47. func makeFormData(filename, mimeType string, content io.Reader) (formData io.Reader, contentType string, err error) {
  48. buf := new(bytes.Buffer)
  49. writer := multipart.NewWriter(buf)
  50. defer writer.Close()
  51. part, err := createFormFile(writer, "file", filename, mimeType)
  52. if err != nil {
  53. glog.V(0).Infoln(err)
  54. return
  55. }
  56. _, err = io.Copy(part, content)
  57. if err != nil {
  58. glog.V(0).Infoln(err)
  59. return
  60. }
  61. formData = buf
  62. contentType = writer.FormDataContentType()
  63. return
  64. }
  65. func (fs *FilerServer) queryFileInfoByPath(w http.ResponseWriter, r *http.Request, path string) (fileId, urlLocation string, err error) {
  66. var found bool
  67. var entry *filer2.Entry
  68. if found, 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 if found {
  72. fileId = string(entry.Chunks[0].Fid)
  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. } else {
  79. w.WriteHeader(http.StatusNotFound)
  80. }
  81. return
  82. }
  83. func (fs *FilerServer) assignNewFileInfo(w http.ResponseWriter, r *http.Request, replication, collection string) (fileId, urlLocation string, err error) {
  84. ar := &operation.VolumeAssignRequest{
  85. Count: 1,
  86. Replication: replication,
  87. Collection: collection,
  88. Ttl: r.URL.Query().Get("ttl"),
  89. }
  90. assignResult, ae := operation.Assign(fs.getMasterNode(), ar)
  91. if ae != nil {
  92. glog.V(0).Infoln("failing to assign a file id", ae.Error())
  93. writeJsonError(w, r, http.StatusInternalServerError, ae)
  94. err = ae
  95. return
  96. }
  97. fileId = assignResult.Fid
  98. urlLocation = "http://" + assignResult.Url + "/" + assignResult.Fid
  99. return
  100. }
  101. func (fs *FilerServer) multipartUploadAnalyzer(w http.ResponseWriter, r *http.Request, replication, collection string) (fileId, urlLocation string, err error) {
  102. //Default handle way for http multipart
  103. if r.Method == "PUT" {
  104. buf, _ := ioutil.ReadAll(r.Body)
  105. r.Body = ioutil.NopCloser(bytes.NewBuffer(buf))
  106. fileName, _, _, _, _, _, _, _, pe := storage.ParseUpload(r)
  107. if pe != nil {
  108. glog.V(0).Infoln("failing to parse post body", pe.Error())
  109. writeJsonError(w, r, http.StatusInternalServerError, pe)
  110. err = pe
  111. return
  112. }
  113. //reconstruct http request body for following new request to volume server
  114. r.Body = ioutil.NopCloser(bytes.NewBuffer(buf))
  115. path := r.URL.Path
  116. if strings.HasSuffix(path, "/") {
  117. if fileName != "" {
  118. path += fileName
  119. }
  120. }
  121. fileId, urlLocation, err = fs.queryFileInfoByPath(w, r, path)
  122. } else {
  123. fileId, urlLocation, err = fs.assignNewFileInfo(w, r, replication, collection)
  124. }
  125. return
  126. }
  127. func multipartHttpBodyBuilder(w http.ResponseWriter, r *http.Request, fileName string) (err error) {
  128. body, contentType, te := makeFormData(fileName, r.Header.Get("Content-Type"), r.Body)
  129. if te != nil {
  130. glog.V(0).Infoln("S3 protocol to raw seaweed protocol failed", te.Error())
  131. writeJsonError(w, r, http.StatusInternalServerError, te)
  132. err = te
  133. return
  134. }
  135. if body != nil {
  136. switch v := body.(type) {
  137. case *bytes.Buffer:
  138. r.ContentLength = int64(v.Len())
  139. case *bytes.Reader:
  140. r.ContentLength = int64(v.Len())
  141. case *strings.Reader:
  142. r.ContentLength = int64(v.Len())
  143. }
  144. }
  145. r.Header.Set("Content-Type", contentType)
  146. rc, ok := body.(io.ReadCloser)
  147. if !ok && body != nil {
  148. rc = ioutil.NopCloser(body)
  149. }
  150. r.Body = rc
  151. return
  152. }
  153. func checkContentMD5(w http.ResponseWriter, r *http.Request) (err error) {
  154. if contentMD5 := r.Header.Get("Content-MD5"); contentMD5 != "" {
  155. buf, _ := ioutil.ReadAll(r.Body)
  156. //checkMD5
  157. sum := md5.Sum(buf)
  158. fileDataMD5 := base64.StdEncoding.EncodeToString(sum[0:len(sum)])
  159. if strings.ToLower(fileDataMD5) != strings.ToLower(contentMD5) {
  160. glog.V(0).Infof("fileDataMD5 [%s] is not equal to Content-MD5 [%s]", fileDataMD5, contentMD5)
  161. err = fmt.Errorf("MD5 check failed")
  162. writeJsonError(w, r, http.StatusNotAcceptable, err)
  163. return
  164. }
  165. //reconstruct http request body for following new request to volume server
  166. r.Body = ioutil.NopCloser(bytes.NewBuffer(buf))
  167. }
  168. return
  169. }
  170. func (fs *FilerServer) monolithicUploadAnalyzer(w http.ResponseWriter, r *http.Request, replication, collection string) (fileId, urlLocation string, err error) {
  171. /*
  172. Amazon S3 ref link:[http://docs.aws.amazon.com/AmazonS3/latest/API/Welcome.html]
  173. There is a long way to provide a completely compatibility against all Amazon S3 API, I just made
  174. a simple data stream adapter between S3 PUT API and seaweedfs's volume storage Write API
  175. 1. The request url format should be http://$host:$port/$bucketName/$objectName
  176. 2. bucketName will be mapped to seaweedfs's collection name
  177. 3. You could customize and make your enhancement.
  178. */
  179. lastPos := strings.LastIndex(r.URL.Path, "/")
  180. if lastPos == -1 || lastPos == 0 || lastPos == len(r.URL.Path)-1 {
  181. glog.V(0).Infoln("URL Path [%s] is invalid, could not retrieve file name", r.URL.Path)
  182. err = fmt.Errorf("URL Path is invalid")
  183. writeJsonError(w, r, http.StatusInternalServerError, err)
  184. return
  185. }
  186. if err = checkContentMD5(w, r); err != nil {
  187. return
  188. }
  189. fileName := r.URL.Path[lastPos+1:]
  190. if err = multipartHttpBodyBuilder(w, r, fileName); err != nil {
  191. return
  192. }
  193. secondPos := strings.Index(r.URL.Path[1:], "/") + 1
  194. collection = r.URL.Path[1:secondPos]
  195. path := r.URL.Path
  196. if fileId, urlLocation, err = fs.queryFileInfoByPath(w, r, path); err == nil && fileId == "" {
  197. fileId, urlLocation, err = fs.assignNewFileInfo(w, r, replication, collection)
  198. }
  199. return
  200. }
  201. func (fs *FilerServer) PostHandler(w http.ResponseWriter, r *http.Request) {
  202. query := r.URL.Query()
  203. replication := query.Get("replication")
  204. if replication == "" {
  205. replication = fs.defaultReplication
  206. }
  207. collection := query.Get("collection")
  208. if collection == "" {
  209. collection = fs.collection
  210. }
  211. if autoChunked := fs.autoChunk(w, r, replication, collection); autoChunked {
  212. return
  213. }
  214. var fileId, urlLocation string
  215. var err error
  216. if strings.HasPrefix(r.Header.Get("Content-Type"), "multipart/form-data; boundary=") {
  217. fileId, urlLocation, err = fs.multipartUploadAnalyzer(w, r, replication, collection)
  218. if err != nil {
  219. return
  220. }
  221. } else {
  222. fileId, urlLocation, err = fs.monolithicUploadAnalyzer(w, r, replication, collection)
  223. if err != nil {
  224. return
  225. }
  226. }
  227. u, _ := url.Parse(urlLocation)
  228. // This allows a client to generate a chunk manifest and submit it to the filer -- it is a little off
  229. // because they need to provide FIDs instead of file paths...
  230. cm, _ := strconv.ParseBool(query.Get("cm"))
  231. if cm {
  232. q := u.Query()
  233. q.Set("cm", "true")
  234. u.RawQuery = q.Encode()
  235. }
  236. glog.V(4).Infoln("post to", u)
  237. request := &http.Request{
  238. Method: r.Method,
  239. URL: u,
  240. Proto: r.Proto,
  241. ProtoMajor: r.ProtoMajor,
  242. ProtoMinor: r.ProtoMinor,
  243. Header: r.Header,
  244. Body: r.Body,
  245. Host: r.Host,
  246. ContentLength: r.ContentLength,
  247. }
  248. resp, do_err := util.Do(request)
  249. if do_err != nil {
  250. glog.V(0).Infoln("failing to connect to volume server", r.RequestURI, do_err.Error())
  251. writeJsonError(w, r, http.StatusInternalServerError, do_err)
  252. return
  253. }
  254. defer resp.Body.Close()
  255. resp_body, ra_err := ioutil.ReadAll(resp.Body)
  256. if ra_err != nil {
  257. glog.V(0).Infoln("failing to upload to volume server", r.RequestURI, ra_err.Error())
  258. writeJsonError(w, r, http.StatusInternalServerError, ra_err)
  259. return
  260. }
  261. glog.V(4).Infoln("post result", string(resp_body))
  262. var ret operation.UploadResult
  263. unmarshal_err := json.Unmarshal(resp_body, &ret)
  264. if unmarshal_err != nil {
  265. glog.V(0).Infoln("failing to read upload resonse", r.RequestURI, string(resp_body))
  266. writeJsonError(w, r, http.StatusInternalServerError, unmarshal_err)
  267. return
  268. }
  269. if ret.Error != "" {
  270. glog.V(0).Infoln("failing to post to volume server", r.RequestURI, ret.Error)
  271. writeJsonError(w, r, http.StatusInternalServerError, errors.New(ret.Error))
  272. return
  273. }
  274. path := r.URL.Path
  275. if strings.HasSuffix(path, "/") {
  276. if ret.Name != "" {
  277. path += ret.Name
  278. } else {
  279. operation.DeleteFile(fs.getMasterNode(), fileId, fs.jwt(fileId)) //clean up
  280. glog.V(0).Infoln("Can not to write to folder", path, "without a file name!")
  281. writeJsonError(w, r, http.StatusInternalServerError,
  282. errors.New("Can not to write to folder "+path+" without a file name"))
  283. return
  284. }
  285. }
  286. // also delete the old fid unless PUT operation
  287. if r.Method != "PUT" {
  288. if found, entry, err := fs.filer.FindEntry(filer2.FullPath(path)); err == nil && found {
  289. oldFid := string(entry.Chunks[0].Fid)
  290. operation.DeleteFile(fs.getMasterNode(), oldFid, fs.jwt(oldFid))
  291. } else if err != nil && err != filer.ErrNotFound {
  292. glog.V(0).Infof("error %v occur when finding %s in filer store", err, path)
  293. }
  294. }
  295. glog.V(4).Infoln("saving", path, "=>", fileId)
  296. entry := &filer2.Entry{
  297. FullPath: filer2.FullPath(path),
  298. Attr: filer2.Attr{
  299. Mode: 0660,
  300. },
  301. Chunks: []filer2.FileChunk{{
  302. Fid: filer2.FileId(fileId),
  303. Size: uint64(r.ContentLength),
  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 []filer2.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. filer2.FileChunk{
  406. Fid: filer2.FileId(fileId),
  407. Offset: chunkOffset,
  408. Size: uint64(chunkBufOffset),
  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 found, entry, err := fs.filer.FindEntry(filer2.FullPath(path)); found && err == nil {
  427. for _, chunk := range entry.Chunks {
  428. oldFid := string(chunk.Fid)
  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 := string(chunk.Fid)
  474. operation.DeleteFile(fs.getMasterNode(), oldFid, fs.jwt(oldFid))
  475. }
  476. }
  477. writeJsonQuiet(w, r, http.StatusAccepted, map[string]string{"error": ""})
  478. }