Skip to content

Latest commit

 

History

History
151 lines (122 loc) · 4.83 KB

File metadata and controls

151 lines (122 loc) · 4.83 KB

#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:

  1. Add enctype="multipart/form-data" to your form.
  2. Call r.ParseMultipartForm in server side to save file in memory or temporary file.
  3. 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