フォーム

Go標準ライブラリのnet/httpパッケージを利用して、HTTPリクエストのフォーム(Form)を扱う方法を説明します。 通常のフォームに加えて、マルチパートフォームデータ(multipart/form-data)によるファイルのアップロードも扱います。

フォームデータの取得方法

フォームデータを取得する方法はいくつかあります。 数が多く混乱しがちなので、次に取得方法を表で整理しました。 それぞれの違いについては、以降で扱うサンプルと併せて注意深く確認してください。

名前 フォーム マルチパート URLクエリ キーの列挙 Parse関数の実行
Form ○ 1 × ○ 2 複数可 必要(ParseForm)
PostForm × × 複数可 必要(ParseForm)
MultipartForm × × 複数可 必要(ParseMultipartForm)
FormValue ○ 1 ○ 3 ○ 2 単一 × 不要
PostFormValue × 単一 × 不要
FormFile × ○(ファイル) × 単一 × 不要

上記表の用語について補足します。

  • フォーム:Content-Typeapplication/x-www-form-urlencodedのフォームデータ
  • マルチパート:Content-Typemultipart/form-dataのフォームデータ
  • URLクエリ:URLのクエリ文字列(?key1=value1&key2=value2の形式)
  • リクエストのHTTPメソッドはPOST、PUT、PATCHのいずれかとする

次は各種フォームデータ取得のサンプルです。

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/form", form)
    mux.HandleFunc("/post-form", postForm)
    mux.HandleFunc("/multipart-form", multipartForm)
    mux.HandleFunc("/form-value", formValue)
    mux.HandleFunc("/post-form-value", postFormValue)
    mux.HandleFunc("/form-file", formFile)
    server := http.Server{
        Addr:    ":8080",
        Handler: mux,
    }
    server.ListenAndServe()
}

func form(w http.ResponseWriter, r *http.Request) {
    // Formを使うには事前にParseFormを実行する
    err := r.ParseForm()
    if err != nil {
        fmt.Fprintln(w, err)
        return
    }
    // フォームデータの取得
    for k, v := range r.Form {
        fmt.Fprintln(w, k, v)
    }
}

func postForm(w http.ResponseWriter, r *http.Request) {
    // PostFormを使うには事前にParseFormを実行する
    err := r.ParseForm()
    if err != nil {
        fmt.Fprintln(w, err)
        return
    }
    // フォームデータの取得
    for k, v := range r.PostForm {
        fmt.Fprintln(w, k, v)
    }
}

func multipartForm(w http.ResponseWriter, r *http.Request) {
    // MultipartFormを使うには事前にParseMultipartFormを実行する
    // 引数にはメモリに保存する最大バイト長を指定
    // 超過した場合は、一時ファイルに保存される
    err := r.ParseMultipartForm(32 << 20) // 32MB
    if err != nil {
        fmt.Fprintln(w, err)
        return
    }
    // フォームデータの取得
    fmt.Fprintln(w, "# MultipartForm.Value")
    for k, v := range r.MultipartForm.Value {
        fmt.Fprintln(w, k, v)
    }
    // ファイルの取得
    fmt.Fprintln(w, "# MultipartForm.File")
    for k, v := range r.MultipartForm.File {
        fmt.Fprintln(w, "## Key:", k)
        for _, fh := range v {
            fmt.Fprintln(w, "## Filename:", fh.Filename)
            f, err := fh.Open()
            if err != nil {
                fmt.Fprintln(w, err)
                break
            }
            defer f.Close()
            b, err := ioutil.ReadAll(f)
            if err != nil {
                fmt.Fprintln(w, err)
                break
            }
            fmt.Fprintln(w, "## Body:")
            fmt.Fprintln(w, string(b))
        }
    }
}

func formValue(w http.ResponseWriter, r *http.Request) {
    // キーを指定してフォームデータを取得
    fmt.Fprintln(w, r.FormValue("a"))
}

func postFormValue(w http.ResponseWriter, r *http.Request) {
    // キーを指定してフォームデータを取得
    fmt.Fprintln(w, r.PostFormValue("a"))
}

func formFile(w http.ResponseWriter, r *http.Request) {
    // キーを指定してファイルを取得
    f, fh, err := r.FormFile("a")
    if err != nil {
        fmt.Fprintln(w, err)
        return
    }
    defer f.Close()
    b, err := ioutil.ReadAll(f)
    if err != nil {
        fmt.Fprintln(w, err)
        return
    }
    fmt.Fprintln(w, "Filename:", fh.Filename)
    fmt.Fprintln(w, "Body:")
    fmt.Fprintln(w, string(b))
}

以降では、個々の取得方法や実行例について確認します。

FormとPostForm

FormPostFormの特徴は次の通りです。

  • Content-Typeapplication/x-www-form-urlencodedのフォームデータに対応
  • FormPostFormの違いは、URLクエリ文字列の有無
  • 事前にParseFormを実行する必要がある
  • キーを列挙して、どんなキーがあるのか確認できる
  • 同じキーに対して、複数の値を設定された場合に、すべての値を取得できる

関連コードを抜粋します。

// 事前にParseFormを実行する
err := r.ParseForm()
// フォームデータの取得
for k, v := range r.Form {
    fmt.Fprintln(w, k, v)
}
for k, v := range r.PostForm {
    fmt.Fprintln(w, k, v)
}

サンプルコードを実行してみましょう。 Webサーバを起動し、curlコマンドを使ってフォームデータを送信します。

Formの実行例です。URLクエリ文字列を含みます。

curl -X POST "localhost:8080/form?a=q1&a=q2" \
  -d "a=f1" -d "a=f2"
a [f1 f2 q1 q2]

PostFormの実行例です。URLクエリ文字列を含みません。

curl -X POST "localhost:8080/post-form?a=q1&a=q2" \
  -d "a=f1" -d "a=f2"
a [f1 f2]

MultipartForm

MultipartFormの特徴は次の通りです。

  • Content-Typemultipart/form-dataのフォームデータに対応
  • テキストとファイルの両方を取得できる
  • 事前にParseMultipartFormを実行する必要がある
  • キーを列挙して、どんなキーがあるのか確認できる
  • 同じキーに対して、複数の値を設定された場合に、すべての値を取得できる

関連コードを抜粋します。

// MultipartFormを使うには事前にParseMultipartFormを実行する
// 引数にはメモリに保存する最大バイト長を指定
// 超過した場合は、一時ファイルに保存される
err := r.ParseMultipartForm(32 << 20) // 32MB

// フォームデータの取得
// kがキー名、vが値のスライス
for k, v := range r.MultipartForm.Value {
    fmt.Fprintln(w, k, v)
}

// ファイルの取得
// kがキー名、vがファイルのスライス
for k, v := range r.MultipartForm.File {
    fmt.Fprintln(w, "## Key:", k)
    // ファイルは*multipart.FileHeader型
    for _, fh := range v {
        fmt.Fprintln(w, "## Filename:", fh.Filename)
        f, err := fh.Open()
        defer f.Close()
        b, err := ioutil.ReadAll(f)
        // ここではテキストファイルであると仮定し、
        // 文字列として扱う
        fmt.Fprintln(w, "## Body:")
        fmt.Fprintln(w, string(b))
    }
}

MultipartFormの実行例です。 ファイルを添付するため、テキストファイルa1.txta2.txtを用意して実行してください。

curl -X POST "localhost:8080/multipart-form?a=q1&a=q2" \
  -F "a=f1" -F "a=f2" -F "a=@a1.txt" -F "a=@a2.txt"
# MultipartForm.Value
a [f1 f2]
# MultipartForm.File
## Key: a
## Filename: a1.txt
## Body:
file a1

## Filename: a2.txt
## Body:
file a2

FormValueとPostFormValue

FormValuePostFormValueの特徴は次の通りです。 FormPostFormMultipartFormと比べて、簡単に使える反面、制約事項があります。

  • Content-Typeapplication/x-www-form-urlencodedmultipart/form-dataのフォームデータに対応
  • ファイルはFormFileで取得する
  • FormValuePostFormValueの違いは、URLクエリ文字列の有無
  • 事前にParseFormParseMultipartFormを実行する必要がない
  • キーを列挙できない
  • 同じキーに対して、複数の値を設定された場合は、最初の値だけ取得できる

関連コードを抜粋します。

// キーを指定してフォームデータを取得
fmt.Fprintln(w, r.FormValue("a"))
fmt.Fprintln(w, r.PostFormValue("a"))

FormValueの実行例(フォーム)です。URLクエリ文字列よりフォームデータが優先されます。

curl -X POST "localhost:8080/form-value?a=q1&a=q2" \
  -d "a=f1" -d "a=f2"
f1
curl -X POST "localhost:8080/form-value?a=q1&a=q2"
q1

FormValueの実行例(マルチパートフォーム)です。マルチパートフォームデータよりURLクエリ文字列が優先されます。

curl -X POST "localhost:8080/form-value?a=q1&a=q2" \
  -F "a=f1" -F "a=f2"
q1
curl -X POST "localhost:8080/form-value" \
  -F "a=f1" -F "a=f2"
f1

PostFormValueの実行例です。URLクエリ文字列は含みません。

curl -X POST "localhost:8080/post-form-value?a=q1&a=q2" \
  -d "a=f1" -d "a=f2"
f1
curl -X POST "localhost:8080/post-form-value?a=q1&a=q2"
curl -X POST "localhost:8080/post-form-value?a=q1&a=q2" \
  -F "a=f1" -F "a=f2"
f1
curl -X POST "localhost:8080/post-form-value?a=q1&a=q2"

FormFile

FormFileの特徴は次の通りです。 MultipartFormと比べて、簡単に使える反面、制約事項があります。

  • Content-Typemultipart/form-dataのフォームデータに対応
  • 事前にParseMultipartFormを実行する必要がない
  • キーを列挙できない
  • 同じキーに対して、複数の値を設定された場合は、最初の値だけ取得できる

関連コードを抜粋します。

// キーを指定してファイルを取得
// 1つ目の戻り値はmultipart.File型
// 2つ目の戻り値は*multipart.FileHeader型
f, fh, err := r.FormFile("a")
defer f.Close()
b, err := ioutil.ReadAll(f)
fmt.Fprintln(w, "Filename:", fh.Filename)
// ここではテキストファイルであると仮定し、
// 文字列として扱う
fmt.Fprintln(w, "Body:")
fmt.Fprintln(w, string(b))

FormFileの実行例(マルチパートフォーム)です。マルチパートフォームデータよりURLクエリ文字列が優先されます。

curl -X POST "localhost:8080/form-file?a=q1&a=q2" -F "a=f1" -F "a=f2" -F "a=@a1.txt" -F "a=@a2.txt"
Filename: a1.txt
Body:
file a1