Go における HTTP リクエストの受け取り方

## 前回

https://qiita.com/BitterBamboo/items/6119f7986a04c5a0ac57

## HTTPリクエストの構造

`net/http` におけるハンドラ(関数)は、以下のように `*http.Request` という引数を持っているのでした。

```go:とあるハンドラ(関数)
func hoge(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "hoge")
}
```

このハンドラ(関数)の引数にある `http.Request` の構造は下記のようになっています。

```go:net/http
type Request struct {
	// 省略
	Header Header
	// 省略
	Body io.ReadCloser
	// 省略
	Form url.Values
	PostForm url.Values
	MultipartForm *multipart.Form
	// 省略
}
```

`net/http` を用いた Web アプリでは、
構造体 `Request` を用いて HTTP リクエストのヘッダやボディを取得できます。


## リクエストヘッダの取得

リクエストヘッダを取得するには `http.Request.Header` にアクセスします。

```go:リクエストヘッダを取得
package main

import (
	"fmt"
	"net/http"
)

func process(w http.ResponseWriter, r *http.Request) {
	h := r.Header
	fmt.Fprintln(w, h)
}

func main() {
	server := http.Server{
		Addr: ":8080",
	}
	http.HandleFunc("/process", process)
	server.ListenAndServe()
}
```

結果：

```
map[Accept:[*/*] Accept-Encoding:[gzip, deflate, br] Connection:[keep-alive] Content-Length:[50] Content-Type:[application/json] ...(省略)... User-Agent:[PostmanRuntime/7.29.2]]
```

`Header` 型で返ってきます。

```go:net/http/header.go
type Header map[string][]string
```

`Header` 型はただの map に過ぎないので、

* `r.Header["Accept-Encoding"]` で `[gzip, deflate, br]` (スライス)
* `r.Header["Accept-Encoding"][0]` で `gzip, deflate, br` (文字列)

と、特定のキーの値を取得できますが、

* `r.Header.Get("Accept-Encoding")` で `gzip, deflate, br` (文字列)

とすることもできます。

## リクエストボディの取得

リクエストボディを取得するには `http.Request.Body` にアクセスします。

```go:リクエストボディを取得
package main

import (
	"fmt"
	"net/http"
)

func process(w http.ResponseWriter, r *http.Request) {
	len := r.ContentLength
	body := make([]byte, len) // Content-Length と同じサイズの byte 配列を用意
	r.Body.Read(body)         // byte 配列にリクエストボディを読み込む
	fmt.Fprintln(w, string(body))
}

func main() {
	server := http.Server{
		Addr: ":8080",
	}
	http.HandleFunc("/process", process)
	server.ListenAndServe()
}
```

このコードに対して Content-Type が `application/json` のリクエストを投げると、

```json:
{
    "Cyclone": "Joker",
    "Heat": "Metal",
    "Luna": "Trigger"
}
```

JSON がそのまま返ってきます。（それはそう）

...

もしリクエストの Content-Type が、
`application/x-www-form-urlencoded` や `multipart/form-data` の場合、
別のアプローチをとることができます。

その際 `http.Request.Body` の代わりに、
`http.Request.Form` や `http.Request.MultipartForm` にアクセスします。


### application/x-www-form-urlencoded を処理する

HTML の `<form method="post">` において、
デフォルトで指定される Content-Type が `application/x-www-form-urlencoded` です。
単純なテキストの送信に適しています。

Content-Type が `application/x-www-form-urlencoded` のリクエストを処理するときは、
`http.Request.Form` あるいは `http.Request.PostForm` にアクセスします。

#### http.Request.Form の場合

```go:Form
package main

import (
	"fmt"
	"net/http"
)

func process(w http.ResponseWriter, r *http.Request) {
	r.ParseForm() // リクエスト解析 & http.Request.Form へデータ投入
	fmt.Printf("%T", r.Form) // url.Values
	fmt.Fprintln(w, r.Form)
}

func main() {
	server := http.Server{
		Addr: "0.0.0.0:8080",
	}
	http.HandleFunc("/process", process)
	server.ListenAndServe()
}
```

これに対して、ボディが `application/x-www-form-urlencoded` の POST を送ってみます。

```
http://localhost:8080?left=Shotaro&double=Joker
- right: Philip
- double: Cyclone
```

結果：

```
map[double:[Cyclone Joker] left:[Shotaro] right:[Philip]]
```

ボディだけでなくクエリパラメータも取得できました。

ボディとクエリパラメータでキーが重複する場合、
両方の値がスライスに取り込まれます。（ボディの値が先に来る）

#### http.Request.PostForm の場合

```go:PostForm
package main

import (
	"fmt"
	"net/http"
)

func process(w http.ResponseWriter, r *http.Request) {
	r.ParseForm() // リクエスト解析 & http.Request.PostForm へデータ投入
	fmt.Printf("%T", r.PostForm) // url.Values
	fmt.Fprintln(w, r.PostForm)
}

func main() {
	server := http.Server{
		Addr: ":8080",
	}
	http.HandleFunc("/process", process)
	server.ListenAndServe()
}
```

同様に、ボディが `application/x-www-form-urlencoded` の POST を送ってみます。

```
http://localhost:8080?left=Shotaro&double=Joker
- right: Philip
- double: Cyclone
```

結果：

```
map[double:[Cyclone] right:[Philip]]
```

ボディのみが取得され、クエリパラメータが無視されています。


### キーの値にアクセス！

`http.Request.Form` でも `http.Request.PostForm` でも
`url.Values` 型で結果が返ってきていました。

```go:net/url/url.go
type Values map[string][]string
```

たかが map なので、

* `r.Form["double"]` で `[Cyclone Joker]`
* `r.Form["double"][0]` で `Cyclone`
* `r.Form["double"][1]` で `Joker`

を取得できます。

もしスライスの最初の要素さえ取得できればいいなら、
`url.Values` の `Get()` メソッドも使えます。
すなわち `r.Form.Get("double")` で `Cyclone` を取得できます。

#### FormValue() と PostFormValue()

そして実は、`http.Request.ParseForm()` を我々がわざわざ書かずとも、
いきなりキーの値にアクセスする手段が用意されています。

```go:FormValue()
package main

import (
	"fmt"
	"net/http"
)

func process(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, r.FormValue("double"))
}

func main() {
	server := http.Server{
		Addr: ":8080",
	}
	http.HandleFunc("/process", process)
	server.ListenAndServe()
}
```

結果：`Cyclone`

これもスライスの最初の要素しか取得できないことに注意しましょう。

`http.Request.PostFormValue()` は、
名前から察せるように、クエリパラメータを無視します。


### multipart/form-data を処理する

ブラウザによって必ずサポートされている Content-Type が、
前述の`application/x-www-form-urlencoded` と、
もう一つが `multipart/fomr-data` です。

`multipart/form-data` は、ファイルアップロードのような大量データを送信するのに適しています。

`multipart/form-data` のリクエストを処理するときは、
`http.Request.MultipartForm` にアクセスします。

```go:MultipartForm
package main

import (
	"fmt"
	"net/http"
)

func process(w http.ResponseWriter, r *http.Request) {
	r.ParseMultipartForm(1024)        // リクエスト解析 & http.Request.MultipartForm へデータ投入
                                      // 引数(maxMemory): The whole request body is parsed and up to a total of maxMemory bytes of its file parts are stored in memory, with the remainder stored on disk in temporary files.

	fmt.Printf("%T", r.MultipartForm) // *multipart.Form
	fmt.Fprintln(w, r.MultipartForm)
}

func main() {
	server := http.Server{
		Addr: ":8080",
	}
	http.HandleFunc("/process", process)
	server.ListenAndServe()
}
```

これに対して、ボディが `multipart/form-data` の POST を送ってみます。

```
http://localhost:8080?left=Shotaro&double=Joker
- right: Philip
- double: Cyclone
- cutecat: (かわいい猫の画像)
```

結果：

```
&{map[double:[Cyclone] right:[Philip]] map[cutecat:[0xc0001b8000]]}
```

2つの map を含んだ構造体が得られました。
クエリパラメータは入っていません。

1つ目の map には `multipart/form-data` のファイル以外の部分が、
2つ目の map にはファイルの部分が格納されています。
型を確認してみましょう。

```mime/multipart/formdata.go
type Form struct {
	Value map[string][]string
	File  map[string][]*FileHeader
}

type FileHeader struct {
	Filename string
	Header   textproto.MIMEHeader
	Size     int64

	content []byte
	tmpfile string
}
```

`*multipart.FileHeader` 型には `Open()` メソッドが定義されており、
それを使ってファイルを開くことができます。

```go:multipart/form-data のファイルを取得
package main

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

func process(w http.ResponseWriter, r *http.Request) {
	r.ParseMultipartForm(1024)        // リクエスト解析 & http.Request.MultipartForm へデータ投入
	                                  // 引数(maxMemory): The whole request body is parsed and up to a total of maxMemory bytes of its file parts are stored in memory, with the remainder stored on disk in temporary files.
	fileHeader := r.MultipartForm.File["cutecat"][0]
	file, err := fileHeader.Open()

	if err == nil {
		data, err := io.ReadAll(file)
		if err == nil {
			fmt.Fprint(w, string(data))
		}
	}
}

func main() {
	server := http.Server{
		Addr: ":8080",
	}
	http.HandleFunc("/process", process)
	server.ListenAndServe()
}
```

このコードを実行すると、アップロードした `cutecat` の画像がそのまま返ってきます。

今回のようにアップロードする画像が1つだけなら、
`http.Request.FormFile()` メソッドのを使用する方が断然ラクです。

```go:FormFile()
package main

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

func process(w http.ResponseWriter, r *http.Request) {
	file, _, err := r.FormFile("cutecat")

	if err == nil {
		data, err := io.ReadAll(file)
		if err == nil {
			fmt.Fprint(w, string(data))
		}
	}
}

func main() {
	server := http.Server{
		Addr: ":8080",
	}
	http.HandleFunc("/process", process)
	server.ListenAndServe()
}
```


`http.Request.ParseMultipartForm()` とか `*multipart.FileHeader` を意識しなくてよくなりました！
ちなみに `http.Request.FormFile()` の2つ目の戻り値は `*multipart.FileHeader` です。


## 参考

* https://pkg.go.dev/net/http
* Goプログラミング実践入門

## 次回

Go における HTTP レスポンスの返し方