Welcome To Golang By Example

HTTP client/server multipart form-data request body example in Go (Golang)

Overview

In HTTP context, the multipart/form-data content-type is used for submitting HTML form. In the case of multipart/form-data, as the name suggests, the body consists of different parts separated by a delimiter or boundary where each part is described by its own headers. The delimiter or boundary is also sent as part of the header only.

When you sending an HTML form through a browser in an HTTP call, the data contents can be sent in as request body in the below two formats.

For most of the cases, application/x-www-form-urlencoded can be used. But if you need to upload files then application/x-www-form-urlencoded is not much efficient.

For example, let’s say that the below data needs to be sent.

Then application/x-www-form-urlencoded can be used to send the above data. But let’s say that you also need to send the profile photo of the user in the request as well. So the data is now as below

In the above case, it will not be efficient to use application/x-www-form-urlencoded content-type. multipart/form-data should be used in this case. So for sending simple form data use application/x-www-form-urlencoded but if the form-data also needs to send binary data then use multipart/form-data.

Now let’s see an example of

HTTP Server Example

For HTTP server an incoming request is represented by the request struct

https://golang.org/src/net/http/request.go

To parse the multipart/form-data request body we need to first call the below function on the request object

request.ParseMultipartForm()

What above function will do is that it will parse the incoming request body and the data will be loaded in the below fields of the request object

type Form struct {
	Value map[string][]string
	File  map[string][]*FileHeader
}

It has two parts. The Value holds all the non-files data. So it will hold the name and age key data.  The File part holds all file data. So it will hold the data for photo key.  Both the parts have their value part as an array because for the same key there can be multiple values.

map[string][]string

It also has the same format as Form

map[string][]string

Other than the above fields, the request object also provides the below utility function

Let’s see different ways in which name, key, and photo values can be accessed

Name Key

request.Form["name"]
request.PostForm["name"] 
request.MultipartForm.Value["name"]

will return

["John"]

It returns an array because for the same key there can be different values.

While

request.PostFormValue("name") 

will return

John

It is not an array because as mentioned above this function returns the first value associated with the key.

Age Key

request.Form["age"]
request.PostForm["age"] 
request.MultipartForm.Value["age"]

will return

["21"]

While

request.PostFormValue("age") 

will return

21

Photo Key

request.MultipartForm.File["photo"]

will return the  FileHeader for the image. Bytes of the image can be accessed using that

[FileHeader_object_of_the_image]

While

request.FormFile("photo") 

will return

FileHeader_object_of_the_image

It is not an array because as mentioned above this function returns the first file associated with the key.

Now let’s see an example

package main
import (
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"os"
)
func main() {
	createEmployeeHanlder := http.HandlerFunc(createEmployee)
	http.Handle("/employee", createEmployeeHanlder)
	http.ListenAndServe(":8080", nil)
}
func createEmployee(w http.ResponseWriter, request *http.Request) {
	err := request.ParseMultipartForm(32 << 20) // maxMemory 32MB
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	//Access the name key - First Approach
	fmt.Println(request.Form["name"])
	//Access the name key - Second Approach
	fmt.Println(request.PostForm["name"])
	//Access the name key - Third Approach
	fmt.Println(request.MultipartForm.Value["name"])
	//Access the name key - Fourth Approach
	fmt.Println(request.PostFormValue("name"))
	//Access the age key - First Approach
	fmt.Println(request.Form["age"])
	//Access the age key - Second Approach
	fmt.Println(request.PostForm["age"])
	//Access the age key - Third Approach
	fmt.Println(request.MultipartForm.Value["age"])
	//Access the age key - Fourth Approach
	fmt.Println(request.PostFormValue("age"))
	//Access the photo key - First Approach
	_, h, err := request.FormFile("photo")
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	saveFile(h, "formfile")
	//Access the photo key - Second Approach
	for _, h := range request.MultipartForm.File["photo"] {
		err := saveFile(h, "mapaccess")
		if err != nil {
			w.WriteHeader(http.StatusBadRequest)
			return
		}
	}
	w.WriteHeader(200)
	return
}

Notice in the above program how are we printing the name key using all the approaches

//Access the name key - First Approach
fmt.Println(request.Form["name"])
//Access the name key - Second Approach
fmt.Println(request.PostForm["name"])
//Access the name key - Third Approach
fmt.Println(request.MultipartForm.Value["name"])
//Access the name key - Fourth Approach
fmt.Println(request.PostFormValue("name"))

And similar for the age key

Then we are saving the file present in the request body first using FormFile method and then using request.MultipartForm.File map. Since we are saving the same file using the two methods, we send a prefix argument to the saveFile function so that it saves files with different names. This is done so that the file saved by FormFile is not overwritten by the file saved by request.MultipartForm.File. This is just for a demonstration that both methods can be used to extract file data from the request. FormFile method uses the prefix formfile while request.MultipartForm.File uses mapaccess prefix for saving the file

Now let's make some curl calls. Create a file named test.png in the same folder from which you will run the curl command

curl --location --request PUT 'http://localhost:8080/employee' \
--header 'Content-Type: multipart/form-data' \
--form 'name=John' \
--form 'age=23' \
--form 'photo=@test.png'

Output

You can notice from the output that it is printing exactly what we have discussed earlier.

[John] 
[John] 
[John] 
John 
[23] 
[23] 
[23] 
23

It will also save two files with the below names

formfile-test.png
mapaccess-test.png

Now let's see the second curl example

curl --location --request PUT 'http://localhost:8080/employee' \
--header 'Content-Type: multipart/form-data' \
--form 'name=John' \
--form 'age=23' \
--form 'photo=@test1.png' \
--form 'photo=@test2.png'

Output

[John] 
[John] 
[John] 
John 
[23] 
[23] 
[23] 
23

This time three files will be saved with the below names. Since FormFile only accesses the first file present with a given key therefore only formfile-test1.png is saved by the FormFile method.

formfile-test1.png
mapaccess-test2.png
mapaccess-test2.png

Let's see one more curl example

curl --location --request PUT 'http://localhost:8080/employee' \
--header 'Content-Type: multipart/form-data' \
--form 'name=John' \
--form 'name=John2' \
--form 'age=23' \
--form 'photo=@test.png'

Output

[John John2] 
[John John2] 
[John John2] 
John 
[23] 
[23] 
[23]
23

rqeuest.PostFormValue returns

John

while all other options return both the values

[John John2]

HTTP Client Example

Below is the code for an example of an HTTP client for the same. It is sending the multipart/form-data request body in an HTTP request to the server created in the above example.

First, we have to create a multipart Writer https://golang.org/pkg/mime/multipart/#Writer

writer := multipart.NewWriter(body)

The multipart writer provides two methods

Now let's see the program

package main
import (
    "bytes"
    "io"
    "log"
    "mime/multipart"
    "net/http"
    "os"
    "strings"
    "time"
)
func main() {
    call("http://localhost:8080/employee", "POST")
}
func call(urlPath, method string) error {
    client := &http.Client{
        Timeout: time.Second * 10,
    }
    // New multipart writer.
    body := &bytes.Buffer{}
    writer := multipart.NewWriter(body)
    fw, err := writer.CreateFormField("name")
    if err != nil {
    }
    _, err = io.Copy(fw, strings.NewReader("John"))
    if err != nil {
        return err
    }
    fw, err = writer.CreateFormField("age")
    if err != nil {
    }
    _, err = io.Copy(fw, strings.NewReader("23"))
    if err != nil {
        return err
    }
    fw, err = writer.CreateFormFile("photo", "test.png")
    if err != nil {
    }
    file, err := os.Open("test.png")
    if err != nil {
        panic(err)
    }
    _, err = io.Copy(fw, file)
    if err != nil {
        return err
    }
    // Close multipart writer.
    writer.Close()
    req, err := http.NewRequest("POST", "http://localhost:8080/employee", bytes.NewReader(body.Bytes()))
    if err != nil {
        return err
    }
    req.Header.Set("Content-Type", writer.FormDataContentType())
    rsp, _ := client.Do(req)
    if rsp.StatusCode != http.StatusOK {
        log.Printf("Request failed with response code: %d", rsp.StatusCode)
    }
    return nil
}

Run this program and see the output at your server end as the above client makes a call to your server created in the first example. The server program will give the below output similar to the curl calls.

[John] 
[John] 
[John] 
John 
[23] 
[23] 
[23] 
23

Also, the server will save two files with the below names similar to the curl calls

formfile-test.png
mapaccess-test.png