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:
- Add
enctype="multipart/form-data"
to your form. - Call
r.ParseMultipartForm
on the server side to save the file either to memory or to a temporary file. - 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.
Links
- Directory
- Previous section: Duplicate submissions
- Next section: Summary