如何处理同时具有多个输入和多个文件的请求

huangapple go评论122阅读模式
英文:

How to process a request that has multiple inputs and multiple files at the same time

问题

构建一个后端Go服务器,可以接收具有多个输入字段的表单,其中3个字段具有多个文件输入。我搜索了一下,发现如果要使这样的工作正常,不应该使用典型的方式:

  1. if err := r.ParseMultipartForm(32 << 20); err != nil {
  2. fmt.Println(err)
  3. }
  4. // 获取文件头的引用
  5. files := r.MultipartForm.File["coverArt"]

而应该使用以下方式:

  1. mr, err := r.MultipartReader()
  2. if err != nil {
  3. http.Error(w, err.Error(), http.StatusInternalServerError)
  4. }

标准的表单数据:
1)姓名
2)电子邮件
3)封面艺术照片(多个文件)
4)个人资料照片(多个文件)
5)2个音频文件(2首歌曲)
6)2个视频(个人介绍,人声独唱录音)

HTML表单:

  1. <form method="post" enctype="multipart/form-data" action="/upload">
  2. <input type="text" name="name">
  3. <input type="text" name="email">
  4. <input name="coverArt" type="file" multiple />
  5. <input name="profile" type="file" multiple />
  6. <input type="file" name="songs" multiple />
  7. <input type="file" name="videos" multiple/>
  8. <button type="submit">上传文件</button>
  9. </form>

Go代码:

  1. func FilePOST(w http.ResponseWriter, r *http.Request) error {
  2. fmt.Println("File Upload Endpoint Hit")
  3. mr, err := r.MultipartReader()
  4. if err != nil {
  5. http.Error(w, err.Error(), http.StatusInternalServerError)
  6. }
  7. for {
  8. part, err := mr.NextPart()
  9. // 没有更多的部分,结束循环
  10. if err == io.EOF {
  11. break
  12. }
  13. // 出现错误
  14. if err != nil {
  15. http.Error(w, err.Error(), http.StatusInternalServerError)
  16. }
  17. // 封面艺术照片部分
  18. if part.FormName() == "coverArt" {
  19. name := part.FileName()
  20. outfile, err := os.Create("uploads/" + name)
  21. if err != nil {
  22. http.Error(w, err.Error(), http.StatusInternalServerError)
  23. }
  24. defer outfile.Close()
  25. _, err = io.Copy(outfile, part)
  26. if err != nil {
  27. http.Error(w, err.Error(), http.StatusInternalServerError)
  28. }
  29. }
  30. // 个人资料照片部分
  31. if part.FormName() == "profile" {
  32. name := part.FileName()
  33. outfile, err := os.Create("uploads/" + name)
  34. if err != nil {
  35. http.Error(w, err.Error(), http.StatusInternalServerError)
  36. }
  37. defer outfile.Close()
  38. _, err = io.Copy(outfile, part)
  39. if err != nil {
  40. http.Error(w, err.Error(), http.StatusInternalServerError)
  41. }
  42. }
  43. // 音频文件部分
  44. if part.FormName() == "songs" {
  45. name := part.FileName()
  46. outfile, err := os.Create("uploads/" + name)
  47. if err != nil {
  48. http.Error(w, err.Error(), http.StatusInternalServerError)
  49. }
  50. defer outfile.Close()
  51. _, err = io.Copy(outfile, part)
  52. if err != nil {
  53. http.Error(w, err.Error(), http.StatusInternalServerError)
  54. }
  55. }
  56. // 视频文件部分
  57. if part.FormName() == "videos" {
  58. name := part.FileName()
  59. outfile, err := os.Create("uploads/" + name)
  60. if err != nil {
  61. http.Error(w, err.Error(), http.StatusInternalServerError)
  62. }
  63. defer outfile.Close()
  64. _, err = io.Copy(outfile, part)
  65. if err != nil {
  66. http.Error(w, err.Error(), http.StatusInternalServerError)
  67. }
  68. }
  69. }
  70. fmt.Println("done")
  71. return nil
  72. }

Go服务器错误:
go run main.go
now serving at the following location www.localhost:3000
File Upload Endpoint Hit
INFO[0009] POST /upload elapsed="680.422μs" host= method=POST path=/upload query=
2021/07/14 15:58:32 http: panic serving [::1]:62924: runtime error: invalid memory address or nil pointer dereference

英文:

Building a backend go server that can take a form with multiple inputs and 3 of them have multiple file inputs. I searched and it states that if you want to make something like this work you don't want to use the typical

  1. if err := r.ParseMultipartForm(32 &lt;&lt; 20); err != nil {
  2. fmt.Println(err)
  3. }
  4. // get a reference to the fileHeaders
  5. files := r.MultipartForm.File[&quot;coverArt&quot;]

and instead you should use

  1. mr, err := r.MultipartReader()
  2. if err != nil {
  3. http.Error(w, err.Error(), http.StatusInternalServerError)
  4. }

Standard form-data:

  1. Name
  2. Email
  3. Cover art photos (multiple files)
  4. Profile photos (multiple files)
  5. 2 Audio files (2 songs)
  6. 2 Videos (personal intro, recording of person in a cappella)

HTML Form

  1. &lt;form method=&quot;post&quot; enctype=&quot;multipart/form-data&quot; action=&quot;/upload&quot;&gt;
  2. &lt;input type=&quot;text&quot; name=&quot;name&quot;&gt;
  3. &lt;input type=&quot;text&quot; name=&quot;email&quot;&gt;
  4. &lt;input name=&quot;coverArt&quot; type=&quot;file&quot; multiple /&gt;
  5. &lt;input name=&quot;profile&quot; type=&quot;file&quot; multiple /&gt;
  6. &lt;input type=&quot;file&quot; name=&quot;songs&quot; multiple /&gt;
  7. &lt;input type=&quot;file&quot; name=&quot;videos&quot; multiple/&gt;
  8. &lt;button type=&quot;submit&quot;&gt;Upload File&lt;/button&gt;
  9. &lt;/form&gt;

Go Code:

  1. func FilePOST(w http.ResponseWriter, r *http.Request) error {
  2. fmt.Println(&quot;File Upload Endpoint Hit&quot;)
  3. mr, err := r.MultipartReader()
  4. if err != nil {
  5. http.Error(w, err.Error(), http.StatusInternalServerError)
  6. }
  7. for {
  8. part, err := mr.NextPart()
  9. // This is OK, no more parts
  10. if err == io.EOF {
  11. break
  12. }
  13. // Some error
  14. if err != nil {
  15. http.Error(w, err.Error(), http.StatusInternalServerError)
  16. }
  17. // CoverArt &#39;files&#39; part
  18. if part.FormName() == &quot;coverArt&quot; {
  19. name := part.FileName()
  20. outfile, err := os.Create(&quot;uploads/&quot; + name)
  21. if err != nil {
  22. http.Error(w, err.Error(), http.StatusInternalServerError)
  23. // return
  24. }
  25. defer outfile.Close()
  26. _, err = io.Copy(outfile, part)
  27. if err != nil {
  28. http.Error(w, err.Error(), http.StatusInternalServerError)
  29. // return
  30. }
  31. }
  32. // Profile Pic &#39;files&#39; part
  33. if part.FormName() == &quot;profile&quot; {
  34. name := part.FileName()
  35. outfile, err := os.Create(&quot;uploads/&quot; + name)
  36. if err != nil {
  37. http.Error(w, err.Error(), http.StatusInternalServerError)
  38. // return
  39. }
  40. defer outfile.Close()
  41. _, err = io.Copy(outfile, part)
  42. if err != nil {
  43. http.Error(w, err.Error(), http.StatusInternalServerError)
  44. // return
  45. }
  46. }
  47. // Songs &#39;files&#39; part
  48. if part.FormName() == &quot;songs&quot; {
  49. name := part.FileName()
  50. outfile, err := os.Create(&quot;uploads/&quot; + name)
  51. if err != nil {
  52. http.Error(w, err.Error(), http.StatusInternalServerError)
  53. // return
  54. }
  55. defer outfile.Close()
  56. _, err = io.Copy(outfile, part)
  57. if err != nil {
  58. http.Error(w, err.Error(), http.StatusInternalServerError)
  59. // return
  60. }
  61. }
  62. // Video &#39;files&#39; part
  63. if part.FormName() == &quot;videos&quot; {
  64. name := part.FileName()
  65. outfile, err := os.Create(&quot;uploads/&quot; + name)
  66. if err != nil {
  67. http.Error(w, err.Error(), http.StatusInternalServerError)
  68. // return
  69. }
  70. defer outfile.Close()
  71. _, err = io.Copy(outfile, part)
  72. if err != nil {
  73. http.Error(w, err.Error(), http.StatusInternalServerError)
  74. // return
  75. }
  76. }
  77. }
  78. fmt.Println(&quot;done&quot;)
  79. return nil
  80. }

Go Server Error:
go run main.go [15:58:21]
now serving at the following location www.localhost:3000
File Upload Endpoint Hit
INFO[0009] POST /upload elapsed="680.422µs" host= method=POST path=/upload query=
2021/07/14 15:58:32 http: panic serving [::1]:62924: runtime error: invalid memory address or nil pointer dereference

答案1

得分: 1

很难猜测你的代码在哪里出错。可能的原因是当错误发生时,你的程序继续执行。例如,如果文件创建失败,outfile.Close() 将会出现 panic,因为 outfile 是 nil。

这两种方法都支持单个字段的多个文件。它们的区别在于它们如何处理内存。流式版本从网络中读取小部分数据,并在调用 io.Copy 时将其写入文件。另一种变体在调用 ParseMultiForm() 时将所有数据加载到内存中,因此它需要与要传输的文件大小一样多的内存。下面你将找到这两种变体的工作示例。

流式版本:

  1. func storeFile(part *multipart.Part) error {
  2. name := part.FileName()
  3. outfile, err := os.Create("uploads/" + name)
  4. if err != nil {
  5. return err
  6. }
  7. defer outfile.Close()
  8. _, err = io.Copy(outfile, part)
  9. if err != nil {
  10. return err
  11. }
  12. return nil
  13. }
  14. func filePOST(w http.ResponseWriter, r *http.Request) error {
  15. fmt.Println("File Upload Endpoint Hit")
  16. mr, err := r.MultipartReader()
  17. if err != nil {
  18. return err
  19. }
  20. for {
  21. part, err := mr.NextPart()
  22. // This is OK, no more parts
  23. switch {
  24. case errors.Is(err, io.EOF):
  25. fmt.Println("done")
  26. return nil
  27. case err != nil:
  28. // Some error
  29. return err
  30. default:
  31. switch part.FormName() {
  32. case "coverArt", "profile", "songs", "videos":
  33. if err := storeFile(part); err != nil {
  34. return err
  35. }
  36. }
  37. }
  38. }
  39. }
  40. func main() {
  41. http.HandleFunc("/upload", func(writer http.ResponseWriter, request *http.Request) {
  42. err := filePOST(writer, request)
  43. if err != nil {
  44. http.Error(writer, err.Error(), http.StatusInternalServerError)
  45. log.Println("Error", err)
  46. }
  47. })
  48. if err := http.ListenAndServe(":8080", nil); err != nil {
  49. log.Fatal(err)
  50. }
  51. }

另一个使用 ParseMultipartForm 的版本,它将数据读入内存。

  1. func storeFile(part *multipart.FileHeader) error {
  2. name := part.Filename
  3. infile, err := part.Open()
  4. if err != nil {
  5. return err
  6. }
  7. defer infile.Close()
  8. outfile, err := os.Create("uploads/" + name)
  9. if err != nil {
  10. return err
  11. }
  12. defer outfile.Close()
  13. _, err = io.Copy(outfile, infile)
  14. if err != nil {
  15. return err
  16. }
  17. return nil
  18. }
  19. func FilePOST(w http.ResponseWriter, r *http.Request) error {
  20. fmt.Println("File Upload Endpoint Hit")
  21. if err := r.ParseMultipartForm(2 << 24); err != nil {
  22. return err
  23. }
  24. for _, fileType := range []string{"coverArt", "profile", "songs", "videos"} {
  25. uploadedFiles, exists := r.MultipartForm.File[fileType]
  26. if !exists {
  27. continue
  28. }
  29. for _, file := range uploadedFiles {
  30. if err := storeFile(file); err != nil {
  31. return err
  32. }
  33. }
  34. }
  35. return nil
  36. }
  37. func main() {
  38. http.HandleFunc("/upload", func(writer http.ResponseWriter, request *http.Request) {
  39. err := FilePOST(writer, request)
  40. if err != nil {
  41. http.Error(writer, err.Error(), http.StatusInternalServerError)
  42. log.Println("Error", err)
  43. }
  44. })
  45. if err := http.ListenAndServe(":8080", nil); err != nil {
  46. log.Fatal(err)
  47. }
  48. }
英文:

It is hard to guess where your code panics. Probably the reason is that your program continue to execute when error occurs. For example if creation of file fails, outfile.Close() will panic as the outfile is nil.

Both approaches support multiple files for single field. The difference is in how they handle memory. The streaming version reads small portions of data from the network and writes it to a file when you call io.Copy. The other variant loads all the data into memory when you call ParseMultiForm(), so it requires as much memory as the size of the files you want to transfer. Below you will find working examples for both variants.

Streaming variant:

  1. func storeFile(part *multipart.Part) error {
  2. name := part.FileName()
  3. outfile, err := os.Create(&quot;uploads/&quot; + name)
  4. if err != nil {
  5. return err
  6. }
  7. defer outfile.Close()
  8. _, err = io.Copy(outfile, part)
  9. if err != nil {
  10. return err
  11. }
  12. return nil
  13. }
  14. func filePOST(w http.ResponseWriter, r *http.Request) error {
  15. fmt.Println(&quot;File Upload Endpoint Hit&quot;)
  16. mr, err := r.MultipartReader()
  17. if err != nil {
  18. return err
  19. }
  20. for {
  21. part, err := mr.NextPart()
  22. // This is OK, no more parts
  23. switch {
  24. case errors.Is(err, io.EOF):
  25. fmt.Println(&quot;done&quot;)
  26. return nil
  27. case err != nil:
  28. // Some error
  29. return err
  30. default:
  31. switch part.FormName() {
  32. case &quot;coverArt&quot;, &quot;profile&quot;, &quot;songs&quot;, &quot;videos&quot;:
  33. if err := storeFile(part); err != nil {
  34. return err
  35. }
  36. }
  37. }
  38. }
  39. }
  40. func main() {
  41. http.HandleFunc(&quot;/upload&quot;, func(writer http.ResponseWriter, request *http.Request) {
  42. err := filePOST(writer, request)
  43. if err != nil {
  44. http.Error(writer, err.Error(), http.StatusInternalServerError)
  45. log.Println(&quot;Error&quot;, err)
  46. }
  47. })
  48. if err := http.ListenAndServe(&quot;:8080&quot;, nil); err != nil {
  49. log.Fatal(err)
  50. }
  51. }

And version with ParseMultipartForm, which reads data to memory.

  1. func storeFile(part *multipart.FileHeader) error {
  2. name := part.Filename
  3. infile, err := part.Open()
  4. if err != nil {
  5. return err
  6. }
  7. defer infile.Close()
  8. outfile, err := os.Create(&quot;uploads/&quot; + name)
  9. if err != nil {
  10. return err
  11. }
  12. defer outfile.Close()
  13. _, err = io.Copy(outfile, infile)
  14. if err != nil {
  15. return err
  16. }
  17. return nil
  18. }
  19. func FilePOST(w http.ResponseWriter, r *http.Request) error {
  20. fmt.Println(&quot;File Upload Endpoint Hit&quot;)
  21. if err := r.ParseMultipartForm(2 &lt;&lt; 24); err != nil {
  22. return err
  23. }
  24. for _, fileType := range []string{&quot;coverArt&quot;, &quot;profile&quot;, &quot;songs&quot;, &quot;videos&quot;} {
  25. uploadedFiles, exists := r.MultipartForm.File[fileType]
  26. if !exists {
  27. continue
  28. }
  29. for _, file := range uploadedFiles {
  30. if err := storeFile(file); err != nil {
  31. return err
  32. }
  33. }
  34. }
  35. return nil
  36. }
  37. func main() {
  38. http.HandleFunc(&quot;/upload&quot;, func(writer http.ResponseWriter, request *http.Request) {
  39. err := FilePOST(writer, request)
  40. if err != nil {
  41. http.Error(writer, err.Error(), http.StatusInternalServerError)
  42. log.Println(&quot;Error&quot;, err)
  43. }
  44. })
  45. if err := http.ListenAndServe(&quot;:8080&quot;, nil); err != nil {
  46. log.Fatal(err)
  47. }
  48. }

huangapple
  • 本文由 发表于 2021年7月15日 04:03:38
  • 转载请务必保留本文链接:https://go.coder-hub.com/68384426.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定