# 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 Upload file
``` We need to add a function on the server side to handle this form. ```Go 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: ```Go type FileHeader struct { Filename string Header textproto.MIMEHeader // contains filtered or unexported fields } ``` ![](images/4.5.upload2.png?raw=true) 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. ```Go 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](preface.md) - Previous section: [Duplicate submissions](04.4.md) - Next section: [Summary](04.6.md)