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:

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

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

<html>
<head>
       <title>Upload file</title>
</head>
<body>
<form enctype="multipart/form-data" action="http://127.0.0.1:9090/upload" method="post">
    <input type="file" name="uploadfile" />
    <input type="hidden" name="token" value="{{.}}"/>
    <input type="submit" value="upload" />
</form>
</body>
</html>

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

http.HandleFunc("/upload", upload)

// upload logic
func upload(w http.ResponseWriter, r *http.Request) {
       fmt.Println("method:", r.Method)
       if r.Method == "GET" {
           crutime := time.Now().Unix()
           h := md5.New()
           io.WriteString(h, strconv.FormatInt(crutime, 10))
           token := fmt.Sprintf("%x", h.Sum(nil))

           t, _ := template.ParseFiles("upload.gtpl")
           t.Execute(w, token)
       } else {
           r.ParseMultipartForm(32 << 20)
           file, handler, err := r.FormFile("uploadfile")
           if err != nil {
               fmt.Println(err)
               return
           }
           defer file.Close()
           fmt.Fprintf(w, "%v", handler.Header)
           f, err := os.OpenFile("./test/"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0666)
           if err != nil {
               fmt.Println(err)
               return
           }
           defer f.Close()
           io.Copy(f, file)
       }
}

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:

type FileHeader struct {
       Filename string
       Header   textproto.MIMEHeader
       // contains filtered or unexported fields
}

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.

package main

import (
    "bytes"
    "fmt"
    "io"
    "io/ioutil"
    "mime/multipart"
    "net/http"
    "os"
)

func postFile(filename string, targetUrl string) error {
    bodyBuf := &bytes.Buffer{}
    bodyWriter := multipart.NewWriter(bodyBuf)

    // this step is very important
    fileWriter, err := bodyWriter.CreateFormFile("uploadfile", filename)
    if err != nil {
        fmt.Println("error writing to buffer")
        return err
    }

    // open file handle
    fh, err := os.Open(filename)
    if err != nil {
        fmt.Println("error opening file")
        return err
    }
    defer fh.Close()

    //iocopy
    _, err = io.Copy(fileWriter, fh)
    if err != nil {
        return err
    }

    contentType := bodyWriter.FormDataContentType()
    bodyWriter.Close()

    resp, err := http.Post(targetUrl, contentType, bodyBuf)
    if err != nil {
        return err
    }
    defer resp.Body.Close()
    resp_body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return err
    }
    fmt.Println(resp.Status)
    fmt.Println(string(resp_body))
    return nil
}

// sample usage
func main() {
    target_url := "http://localhost:9090/upload"
    filename := "./astaxie.pdf"
    postFile(filename, target_url)
}

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.

results matching ""

    No results matching ""