#4.5 File upload Suppose you have a website like Instagram, and you want users to upload their beautiful photos, how you are going to do?
You have to add property enctype
to the form that you want to use for uploading photos, and there are three possibilities for its value:
application/x-www-form-urlencoded Trans-coding all characters before upload(default).
multipart/form-data No trans-coding, you have to use this value when your form have file upload controls.
text/plain Convert spaces to "+", but no trans-coding for special characters.
Therefore, 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 in server side to handle this affair.
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 argument means the maxMemory
. After you called ParseMultipartForm
, file will be saved in your server memory with maxMemory
size, if the file size is larger than maxMemory
, rest of data will be saved in system temporary file. You can use r.FormFile
to get 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 call ParseMultipartForm
once is enough, and no differences for multiple calls.
We use three steps for uploading files as follows:
- Add
enctype="multipart/form-data"
to your form. - Call
r.ParseMultipartForm
in server side to save file in memory or temporary file. - Call
r.FormFile
to get file handle and save to file system.
The file handler is the multipart.FileHeader
, it uses following struct:
type FileHeader struct {
Filename string
Header textproto.MIMEHeader
// contains filtered or unexported fields
}
Figure 4.5 Print information in server after received file.
##Clients upload files I showed the example of using form to upload file, and we can impersonate a client form to upload file 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
}
//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 client to upload file, it uses multipart.Write
to write file in cache and sends to server through POST method.
If you have other field need to write into data like user name, call multipart.WriteField
as needed.
##Links
- Directory
- Previous section: Duplicate submissions
- Next section: Summary