4.5 File upload

Suppose you have a website like Instagram and you want users to upload their beautiful photos. How would you implement that functionality?

You have to add property enctype to the form that you want to use for uploading photos. There are three possible values for this property:

  1. application/x-www-form-urlencoded Transcode all characters before uploading (default).
  2. multipart/form-data No transcoding. You must use this value when your form has file upload controls.
  3. text/plain Convert spaces to "+", but no transcoding for special characters.

Therefore, the HTML content of a file upload form should look like this:

  1. <html>
  2. <head>
  3. <title>Upload file</title>
  4. </head>
  5. <body>
  6. <form enctype="multipart/form-data" action="http://127.0.0.1:9090/upload" method="post">
  7. <input type="file" name="uploadfile" />
  8. <input type="hidden" name="token" value="{{.}}"/>
  9. <input type="submit" value="upload" />
  10. </form>
  11. </body>
  12. </html>

We need to add a function on the server side to handle this form.

  1. http.HandleFunc("/upload", upload)
  2. // upload logic
  3. func upload(w http.ResponseWriter, r *http.Request) {
  4. fmt.Println("method:", r.Method)
  5. if r.Method == "GET" {
  6. crutime := time.Now().Unix()
  7. h := md5.New()
  8. io.WriteString(h, strconv.FormatInt(crutime, 10))
  9. token := fmt.Sprintf("%x", h.Sum(nil))
  10. t, _ := template.ParseFiles("upload.gtpl")
  11. t.Execute(w, token)
  12. } else {
  13. r.ParseMultipartForm(32 << 20)
  14. file, handler, err := r.FormFile("uploadfile")
  15. if err != nil {
  16. fmt.Println(err)
  17. return
  18. }
  19. defer file.Close()
  20. fmt.Fprintf(w, "%v", handler.Header)
  21. f, err := os.OpenFile("./test/"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0666)
  22. if err != nil {
  23. fmt.Println(err)
  24. return
  25. }
  26. defer f.Close()
  27. io.Copy(f, file)
  28. }
  29. }

As you can see, we need to call r.ParseMultipartForm for uploading files. The function ParseMultipartForm takes the maxMemory argument. After you call ParseMultipartForm, the file will be saved in the server memory with maxMemory size. If the file size is larger than maxMemory, the rest of the data will be saved in a system temporary file. You can use r.FormFile to get the file handle and use io.Copy to save to your file system.

You don’t need to call r.ParseForm when you access other non-file fields in the form because Go will call it when it’s necessary. Also, calling ParseMultipartForm once is enough -multiple calls make no difference.

We use three steps for uploading files as follows:

  1. Add enctype="multipart/form-data" to your form.
  2. Call r.ParseMultipartForm on the server side to save the file either to memory or to a temporary file.
  3. Call r.FormFile to get the file handle and save to the file system.

The file handler is the multipart.FileHeader. It uses the following struct:

  1. type FileHeader struct {
  2. Filename string
  3. Header textproto.MIMEHeader
  4. // contains filtered or unexported fields
  5. }

4.5. File upload - 图1

Figure 4.5 Print information on server after receiving file.

Clients upload files

I showed an example of using a form to a upload a file. We can impersonate a client form to upload files in Go as well.

  1. package main
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "io/ioutil"
  7. "mime/multipart"
  8. "net/http"
  9. "os"
  10. )
  11. func postFile(filename string, targetUrl string) error {
  12. bodyBuf := &bytes.Buffer{}
  13. bodyWriter := multipart.NewWriter(bodyBuf)
  14. // this step is very important
  15. fileWriter, err := bodyWriter.CreateFormFile("uploadfile", filename)
  16. if err != nil {
  17. fmt.Println("error writing to buffer")
  18. return err
  19. }
  20. // open file handle
  21. fh, err := os.Open(filename)
  22. if err != nil {
  23. fmt.Println("error opening file")
  24. return err
  25. }
  26. defer fh.Close()
  27. //iocopy
  28. _, err = io.Copy(fileWriter, fh)
  29. if err != nil {
  30. return err
  31. }
  32. contentType := bodyWriter.FormDataContentType()
  33. bodyWriter.Close()
  34. resp, err := http.Post(targetUrl, contentType, bodyBuf)
  35. if err != nil {
  36. return err
  37. }
  38. defer resp.Body.Close()
  39. resp_body, err := ioutil.ReadAll(resp.Body)
  40. if err != nil {
  41. return err
  42. }
  43. fmt.Println(resp.Status)
  44. fmt.Println(string(resp_body))
  45. return nil
  46. }
  47. // sample usage
  48. func main() {
  49. target_url := "http://localhost:9090/upload"
  50. filename := "./astaxie.pdf"
  51. postFile(filename, target_url)
  52. }

The above example shows you how to use a client to upload files. It uses multipart.Write to write files into cache and sends them to the server through the POST method.

If you have other fields that need to write into data, like username, call multipart.WriteField as needed.