How to perform multipart requests in Go

In this article, you’ll learn how to make multipart/related requests in Go using standard library packages. I’ll demonstrate this concept using the Google Drive API’s multipart upload method for creating files.

A multipart related request is used for compound documents where you would need to combine the separate body parts to provide the full meaning of the message. For example, the Google Drive API provides a multipart upload method for transferring a small file (5 MB or less) and metadata that describes the file, in a single request. The body of the request should contain two parts:

  • The file’s metadata in JSON format. It must come first, and its Content-Type header must be set to application/json; charset=UTF-8.
  • The file’s data whose Content-Type header is set to the MIME type of the file.

Let’s go ahead and construct the request body using the mime/multipart and net/textproto packages.

main.go
package main

import (
	"bytes"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net/textproto"
)

func upload() error {
	// Metadata content.
	metadata := `{"name": "photo-sample.jpeg"}`

	// New empty buffer
	body := &bytes.Buffer{}
	// Creates a new multipart Writer with a random boundary
	// writing to the empty buffer
	writer := multipart.NewWriter(body)

	// Metadata part
	metadataHeader := textproto.MIMEHeader{}
	// Set the Content-Type header
	metadataHeader.Set("Content-Type", "application/json; charset=UTF-8")
	// Create new multipart part
	part, err := writer.CreatePart(metadataHeader)
	if err != nil {
		return err
	}
	// Write the part body
	part.Write([]byte(metadata))

	// Media part
	// Read the file to memory
	mediaData, err := ioutil.ReadFile("file.jpeg")
	if err != nil {
		return err
	}
	mediaHeader := textproto.MIMEHeader{}
	mediaHeader.Set("Content-Type", "image/jpeg")

	mediaPart, err := writer.CreatePart(mediaHeader)
	if err != nil {
		return err
	}
	io.Copy(mediaPart, bytes.NewReader(mediaData))

	// Finish constructing the multipart request body
	writer.Close()

	return nil
}

The next step after creating the body of the request is to create a POST request to the relevant endpoint, and add the necessary top-level headers which are Content-Type and Content-Length according the Google Drive API documentation.

func upload() error {
	// [..]

	accessToken := "<google drive api access token>"

	endpoint := "https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart"
	req, err := http.NewRequest("POST", endpoint, bytes.NewReader(body.Bytes()))
	if err != nil {
		return err
	}

	// Request Content-Type with boundary parameter.
	contentType := fmt.Sprintf("multipart/related; boundary=%s", writer.Boundary())
	req.Header.Set("Content-Type", contentType)
	// Content-Length must be the total number of bytes in the request body.
	req.Header.Set("Content-Length", fmt.Sprintf("%d", body.Len()))
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken))

	client := &http.Client{Timeout: 120 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return err
	}

	defer resp.Body.Close()
	// Do what you want with the response

	return nil
}

The complete code can be accessed through this GitHub gist. Thanks for reading, and happy coding!