フォーム
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-Type
がapplication/x-www-form-urlencoded
のフォームデータ - マルチパート:
Content-Type
がmultipart/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
Form
とPostForm
の特徴は次の通りです。
Content-Type
がapplication/x-www-form-urlencoded
のフォームデータに対応Form
とPostForm
の違いは、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-Type
がmultipart/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.txt
とa2.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
FormValue
とPostFormValue
の特徴は次の通りです。
Form
、PostForm
、MultipartForm
と比べて、簡単に使える反面、制約事項があります。
Content-Type
がapplication/x-www-form-urlencoded
とmultipart/form-data
のフォームデータに対応- ファイルは
FormFile
で取得する FormValue
とPostFormValue
の違いは、URLクエリ文字列の有無- 事前に
ParseForm
やParseMultipartForm
を実行する必要がない - キーを列挙できない
- 同じキーに対して、複数の値を設定された場合は、最初の値だけ取得できる
関連コードを抜粋します。
// キーを指定してフォームデータを取得
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-Type
がmultipart/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