Go標準パッケージを利用したリクエスト・レスポンスの処理

# net/http

この記事ではGoの標準パッケージであるnet/httpを利用したリクエスト、レスポンスの処理の方法について解説する。
[「**Goプログラミング実践入門 標準ライブラリでゼロからWebアプリを作る」**](https://amzn.to/3UI1wa3)という書籍を参考にしており、この書籍のまとめでもある。

# リクエストの受信

## ListenAndServe

GoにおいてHTTPサーバーを起動させるためのメソッド。
第一引数：ネットワークアドレス。「””」を指定した場合、80番ポートになる。
第二引数：リクエストを処理するハンドラ。nilを指定した場合、DefaultServeMuxが使われる。

```go
server.ListenAndServe(ネットワークアドレス, ハンドラ)
```

server構造体を使ってサーバーの設定ができる。ネットワークアドレスやハンドラもこの中で指定できるので、これを使った場合引数はいらない。

```go
type Server struct {
	Addr string
	Handler Handler
	TLSConfig *tls.Config
	ReadTimeout time.Duration
	ReadHeaderTimeout time.Duration
	WriteTimeout time.Duration
	IdleTimeout time.Duration
	MaxHeaderBytes int
	TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
	ConnState func(net.Conn, ConnState)
	ErrorLog *log.Logger
	BaseContext func(net.Listener) context.Context
	ConnContext func(ctx context.Context, c net.Conn) context.Context
}
```

こんな感じで使う。

```go
func main() {
	server := http.Server{
		Addr:    "127.0.0.1:8080",
		Handler: nil,
	}
	server.ListenAndServe()
}
```

## ハンドラの設定

GoにおけるハンドラとはServeHTTPといいメソッドを持ったインターフェースのこと。
ServeHTTPh第一引数にインターフェースHTTPResponseWriter、第二引数に構造体Requestへのポインタを取る。

```go
ServeHTTP(w http.ResponseWriter, r *http.Request)
```

### http.Handle

`http.Handle()`というメソッドを使ってハンドラーの設定ができる。以下が使用例。

```go
package main

import (
	"fmt"
	"net/http"
)

type HelloHandler struct{}

func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello!")
}

type WorldHandler struct{}

func (h *WorldHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "World!")
}

func main() {
	hello := HelloHandler{}
	world := WorldHandler{}

	server := http.Server{
		Addr: "127.0.0.1:8080",
	}

	http.Handle("/hello", &hello)
	http.Handle("/world", &world)

	server.ListenAndServe()
}
```

### ポイント

- URLごとにハンドラーを用意する。この場合は`HelloHandler{}`と`WorldHandler{}`を用意している。
- server構造体のHandlerフィールドは設定しない。
- `http.Handle()`を使うことでURLごとのハンドラーを設定する。以下のように使う。

```go
http.Handle(URL, ハンドラーへのポインタ)
```

### http.HandleFunc

`http.HandleFunc()`というメソッドを利用してハンドラーを設定することもできる。
`http.HandleFunc()`を使うとわざわざ構造体を宣言することなく、関数を生成するだけでハンドラーの設定ができる。
以下が使用例。

```go
package main

import (
	"fmt"
	"net/http"
)

func hello(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello!")
}

func world(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "World!")
}

func main() {
	server := http.Server{
		Addr: "127.0.0.1:8080",
	}
	http.HandleFunc("/hello", hello)
	http.HandleFunc("/world", world)

	server.ListenAndServe()
}
```

### ポイント

- `http.HandleFunc()`は第一引数にURLを、第二引数に関数名を渡す。渡された名前の関数がハンドラーに変換される。

```go
http.HandleFunc(URL, 関数名)
```

# レスポンスの送信

## ResponseWriter

レスポンスを送信するために利用するインターフェース。

## Write

HTTPレスポンスのボディに書き込むメソッド。以下の例ではHTMlを書き込む。

```go
func writeExample(w http.ResponseWriter, r *http.Request) {
	str := `<html>
	<head><title>Go Web Programming</title></head>
	<body><h1>Hello World</h1></body>
	</html>`
	w.Write([]byte(str))
}
```

レスポンスは以下のようになる。

```go
$ curl -i 127.0.0.1:8080/write
HTTP/1.1 200 OK
Date: Wed, 23 Nov 2022 02:02:53 GMT
Content-Length: 95
Content-Type: text/html; charset=utf-8

<html>
<head><title>Go Web Programming</title></head>
<body><h1>Hello World</h1></body>
</html>%
```

### ポイント

- レスポンスのボディに書き込むにはバイト配列である必要がある。

## WriteHeader

HTTPレスポンスのステータスコードを書き込む。

```go
func writeHeaderExample(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(501)
	fmt.Fprintln(w, "エラーレスポンスです。")
}
```

レスポンスは以下のようになる。ステータスコードとして501を返せていることがわかる。

```go
$ curl -i 127.0.0.1:8080/writeheader
HTTP/1.1 501 Not Implemented
Date: Wed, 23 Nov 2022 02:15:37 GMT
Content-Length: 34
Content-Type: text/plain; charset=utf-8

エラーレスポンスです。
```

### ポイント

- このメソッドを呼び出した後はヘッダに書き込むことはできない。ResponseWriterに書き込むことはできる。
- このメソッドを呼び出さない場合、ステータスは「200 OK」になる。

## Header

ヘッダーと転送先URLを書き込む。

`w.Header().Set()`の形で使う。第一引数にヘッダーのキー名、第二引数に値を書く。

```go
func headerExample(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Location", "http://google.com")
	w.WriteHeader(302)
}
```

レスポンスは以下のようになる。

```go
$ curl -i 127.0.0.1:8080/redirect   
HTTP/1.1 302 Found
Location: http://google.com
Date: Wed, 23 Nov 2022 02:24:21 GMT
Content-Length: 0
```

ブラウザで127.0.0.1:8080/redirectにアクセスするとGoogleに転送される。
![スクリーンショット 2022-11-23 11.26.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/345720/3bfb0bab-9107-76aa-6f5e-db797eab668f.png)

### ポイント
- `w.Header().Set()`は`w.WriteHeader()`よりも先に書く必要がある。
## JSONを返す

Postという構造体を利用してJSONを返すとする。

```go
type Post struct {
	User    string
	Threads []string
}
```

以下のように関数を定義する。

```go
func jsonExample(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	post := &Post{
		User:    "Goromaru",
		Threads: []string{"1try", "2try", "3try"},
	}
	json, _ := json.Marshal(post)
	w.Write(json)
}
```

レスポンスはこのように返ってくる。

```go
$ curl -i 127.0.0.1:8080/json    
HTTP/1.1 200 OK
Content-Type: application/json
Date: Wed, 23 Nov 2022 02:37:30 GMT
Content-Length: 52

{"User":"Goromaru","Threads":["1try","2try","3try"]}%
```

### ポイント
- まず最初に`w.Header().Set()`を利用してコンテンツタイプを「application/json」に設定する。
- その後、Post構造体のインスタンスpostを作成する。
- `json.Marshal()`を利用してJSONを生成する。
- `w.Write()`を利用してボディに書き込む。