【Go】レスポンス

# はじめに
前回はリクエストの`*http.Request`に焦点を当てて、どのようにリクエストの中身を読み取るのかについてまとめました。

https://qiita.com/YokoYokoko/items/04afe5ff82a4f73909c3

今回は`http.ResponseWriter`とJSONに焦点を当ててまとめてみたいと思います。

# ResponseWriter
サーバーを立てるときには`http.ResponseWriter`と`*http.Request`を引数とした関数を用意します。

:::note info
余談ですが、http.ResponseWriterはインターフェースなので一見値渡しのように見えますが、実際はresponseの参照渡しです。
:::

ResponseWriterインターフェースには次の3つのメソッドがあります。
| メソッド | 説明 |
| --- | --- |
| Write | レスポンスボディにデータを書き込みます。引数としてバイトスライスを受け取り、書き込んだバイト数とエラーを返します。 |
| WriteHeader | レスポンスのステータスコードを設定します。通常、ステータスコードは最初の書き込みが行われる前に設定します。 |
| Header | HTTPレスポンスヘッダを返します。レスポンスヘッダを設定するために使用します。 |

## ResponseWriterへの書き込み
では、`Write`メソッドを使用して以下のように書き込んでみます。
`Write`は引数にバイトスライスを受け取り、それをHTTPレスポンスのボディに書き込みます。
```go
package main

import (
	"fmt"
	"net/http"
)

func writeExample(w http.ResponseWriter, r *http.Request) {
	// レスポンスボディにHTMLを書き込む
	html := `<html>
<head><title>Example</title></head>
<body><h1>Hello, World!</h1></body>
</html>`

	w.Write([]byte(html))
}

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

	http.HandleFunc("/write", writeExample)
	fmt.Println("Starting server at http://127.0.0.1:8080")
	err := server.ListenAndServe()
	if err != nil {
		fmt.Println("Error starting server:", err)
	}
}

```
リクエストを投げてみると、
```terminal
%curl -i http://127.0.0.1:8080/write
```
以下のようにhtmlが返却されます。
```terminal
HTTP/1.1 200 OK
Date: Tue, 09 Jul 2024 14:16:24 GMT
Content-Length: 86
Content-Type: text/html; charset=utf-8

<html>
<head><title>Example</title></head>
<body><h1>Hello, World!</h1></body>
</html>%     
```

:::note info
これまた余談ですが、なぜhtmlをバイトスライスに変換しているんだろうと気になり調べたところシリアライズというのがキーワードなのですね。お恥ずかしいですが、知りませんでした。。。

シリアライズすることで異なる環境やシステム間でデータを一貫して扱うことができるので、裏を返せばバイトスライスにしないと他の環境で読み込めない、となり得るということです。
:::

`io.WriteString`というものも用意されていて、こちらは引数に文字列を渡すことでレスポンスにデータを書き込めます。

## WriteHeader
次に`WriteHeader`ですが、これはHTTPレスポンスのステータスコードを設定するためのメソッドです。
何も指定しない場合はデフォルトで`200 OK`をセットします。

また、`WriteHeaderは呼び出された後にヘッダが変更されるのを防ぐので、コードの順番には注意が必要です。`

例えば以下のように`WriteHeader(501)`とすることでステータスコードを501としてレスポンスを返すことができます。
```go
...
func WriteHeaderExample(w http.ResponseWriter, r *http.Request){
	w.WriteHeader(501)
	fmt.Fprintln(w, "そのようなサービスはありません。")
}


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

	http.HandleFunc("/writeheader", WriteHeaderExample)
	err := server.ListenAndServe()
	if err != nil {
		fmt.Println("Error starting server:", err)
	}
}

```
リクエストを投げると、
```terminal
%curl -i http://127.0.0.1:8080/writeheader
```
次のようにステータスコードが501で返却されます。
```terminal
HTTP/1.1 501 Not Implemented
Date: Tue, 09 Jul 2024 14:21:25 GMT
Content-Length: 49
Content-Type: text/plain; charset=utf-8

そのようなサービスはありません。
```

それにしても名前がややこしいです。。。`WriteHeader`はヘッダーの設定っぽい名前ですが、ステータスコードをセットするメソッドです。

ヘッダーをセットするのは`Header`メソッドの方なので勘違いしないよう注意が必要ですね。

# Header
最後に`Header`メソッドを見てみます。
`Header`メソッドは、HTTPレスポンスのヘッダを操作するために使用されます。このメソッドは、レスポンスヘッダを設定するために必要なヘッダオブジェクトを返します。

定義を一部抜粋しましたが、`Header`には`Add`や`Set`,`Get`などのメソッドが用意されています。（個人的には`has`が気になりました。）
```go
func (h Header) Add(key, value string) {
	textproto.MIMEHeader(h).Add(key, value)
}

func (h Header) Set(key, value string) {
	textproto.MIMEHeader(h).Set(key, value)
}

func (h Header) Get(key string) string {
	return textproto.MIMEHeader(h).Get(key)
}
...
```

今回はこの中の`Set`を使用してヘッダーに追加してみたいと思います。ちなみに`Set`は`Add`と違って既存の値がある場合は値を置き換えるので注意が必要です。追加したいなら`Add`を使用しましょう。

先ほどのコードに追加します。
```go
...
func HeaderExample(w http.ResponseWriter, r *http.Request){
	w.Header().Set("Location", "http://google.com")
	w.WriteHeader(302)
}
...
func main() {

...
	http.HandleFunc("/redirect", HeaderExample)
 ...
```
これでリクエストを投げると、
```terminal
%curl -i http://127.0.0.1:8080/redirect   
```
Googleにリダイレクトされます。
```terminal
HTTP/1.1 302 Found
Location: http://google.com
Date: Tue, 09 Jul 2024 14:34:58 GMT
Content-Length: 0
```

# JSONの書き込み
最後にJSONの書き込み方についてです。
先ほどのコードに追加します。

まず、JSONは`Content-Type`が`application/json`なので、`w.Header.Set(application/json)`でセットする必要があります。

そして、`Post`構造体を用意し、使用します。これを`json.Marshal`でJSON形式のバイトスライスに変換して、`w.Write()`で書き込みます。
```go
...
type Post struct{
	User 		string
	Threads []string
}
...
func JsonExample(w http.ResponseWriter, r *http.Request){
	w.Header().Set("Content-Type", "application/json")
	post := &Post{
		User: "TestUser",
		Threads: []string{"1", "2", "3"},
	}
	json, _ := json.Marshal(post)
	w.Write(json)
}

func main() {
...
	http.HandleFunc("/json", JsonExample)
...
```

リクエストを投げると、
```terminal
%curl -i http://127.0.0.1:8080/json   
```
以下のようにJSONが返ってきます。
```terminal
HTTP/1.1 200 OK
Content-Type: application/json
Date: Tue, 09 Jul 2024 14:53:52 GMT
Content-Length: 43

{"User":"TestUser","Threads":["1","2","3"]}
```

# まとめ
今回はhttp.ResponseWriter+JSONの書き込みについて以下の項目でまとめてみました。
- Write()
- WriteHeader()
- Header()
- json.Marshal

個人的にWriteHeader()の名前が分かりづらいから変えてほしい。。。