Go で multipart/form-data を扱ってみる

この記事は フラー Advent Calendar 2020 の24日目の記事です。 23日目は @tmshr06 さんで

Firestore Emulator を使って GitHub Actions でテストを動かしてみる - Qiita

でした。


今回は Multipart について勉強したときのメモを残しておきます。

multipart とは何か

電子メールにASCII文字データ以外の任意のデータを含めるMIMEの拡張仕様の一つです。 具体的にはHTTPヘッダーに以下のような フォーマットの Content-Type で表現します。

Content-Type: multipart/form-data; boundary=c7245ee369df31f524686275eb89381b30581b1ca5557de2453f9f8cf66c

※ boundary は一例

Content-Type は multipart/{subtype} の形式を取ります。 boundary は複数のコンテンツの区切りを示す文字列です。ランダムな英数文字列*1が用いられることが多いです。

それぞれのsubtypeを以下の用途にしたがって使い分けられることが多いです。

multipart/alternative(内容の同じテキストメールとHTMLメールのように、形式は違っても内容は同じもの)
multipart/mixed(個々の要素はそれぞれ異なる)
multipart/related(関連するものを表現)
multipart/form-data(HTMLフォーム、POSTメソッドから送信する)

www.softel.co.jp

上記の記事が参考になりました。

multipart/form-data とは何か

先述した multipart のsubtype の一つです。

これはHTTPのPOSTメソッドでmultipartで表現したものを送信したいときに使います。

Go の標準パッケージにも multipart パッケージが存在しますが、 HTTP での利用を想定したものです*2APIはそれなりに揃っているので、読み込みや書き込みはこれを使えば大体のことはできると思います。

multipart package (クライアントのサンプルコード)

  file, _ := os.Open(filePath)
  defer file.Close()

  body := &bytes.Buffer{}
  writer := multipart.NewWriter(body)
  part, _ := writer.CreateFormFile("file", filepath.Base(file.Name()))
  io.Copy(part, file)
  writer.Close()

  r, _ := http.NewRequest("POST", "http://example.com", body)
  r.Header.Add("Content-Type", writer.FormDataContentType())
  client := &http.Client{}
  client.Do(r)

上記のように表現することができます。

NewWriter は全体のマルチパートメッセージを持つためのWriterで、生成するときにrandomにboundary(base16)が決定します。

パートを追加するときは、CreatePart() を使って追加することができますが、 より高レベルのAPIとして、CreateFromFile()CreateFromField() が提供されており、これらのメソッドを使うことで各パートのContent-Typeの設定などを省略することが可能です。

送信する前に最後のboundaryを挿入するために writer.Close() を呼び出す必要があります。


明日は okuzawats さんで 「Androidアプリ開発入門2020」 です。お楽しみに。

合わせて読みたい

RFC 7578 - Returning Values from Forms: multipart/form-data

RFC 2046 - Multipurpose Internet Mail Extensions (MIME) Part Two: Media Types

multipart - The Go Programming Language

Go言語のio.Pipeでファイルを効率よくアップロードする方法. io.Pipeと非同期処理を活かし、ファイルアップロードのメモリ使用量を減らす | by James Kirk | Eureka Engineering | Medium

Go言語のmime/multipartパッケージでファイルをアップロードしましょう | by James Kirk | Eureka Engineering | Medium

golang multipart file upload example

html - HTMLマルチパートフォーム- “boundary”文字列の最大長?

*1:具体的なフォーマットはここでは割愛させていただきますが、

RFC 2046 - Multipurpose Internet Mail Extensions (MIME) Part Two: Media Types

に記載されています。

*2:FormDataContentType() を呼び出したときに Content-Type は必ず multipart/form-data になります。