【Go】クッキー

# はじめに
前回、レスポンスについてまとめたのですが、長くなるためクッキーは割愛しました。
そこで今回はサーバーからHTTPレスポンスメッセージを通して送られる`クッキー`についてまとめます。

https://qiita.com/YokoYokoko/items/b62f994a5d79da362aed

# クッキー
クッキーは、サーバーがクライアントに送信し、クライアント側で保存される情報の小さな断片です。例えば、ブラウザ（Chrome）で検証すれば簡単に確認できます。（下の画像は何も入っていません。）
![スクリーンショット 2024-07-10 11.32.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3324306/79d4bb97-963b-c076-facb-5a91d17fb70b.png)

サーバーはステートレスであるため、一度クライアントとのやり取りが終わるとその状態を保持しません。そのため、次のリクエストが来たときにそのクライアントが誰なのかを覚えていません。

例えば、ログインしたユーザーが誰であるかを認識するためには、セッション情報をクッキーに保存し、そのクッキーをリクエストのたびに送信することで、サーバーがユーザーを認識することができます。

:::note info
余談ですが、最近はプライバシー保護の観点からサードパーティクッキー廃止の流れとなっています。最近よくクッキーを許可するかどうか確認されますよね。（個人的に面倒なのでやめてほしい。）

これに関して、疑問なのですが、フロントとバックエンドでAPI通信する場合、ドメインが異なる時はクッキーを使えなくなるんでしょうか。分かる方誰か教えてください。
:::

# Goに話を移します
ちょっと前置きが長くなりましたが、`cookie`の扱いをGo言語で確認します。

以下が`Cookie`構造体です。まあ、一般的なcookieの設定項目がありますね。

```go
type Cookie struct {
	Name  string
	Value string

	Path       string    // optional
	Domain     string    // optional
	Expires    time.Time // optional
	RawExpires string    // for reading cookies only

	// MaxAge=0 means no 'Max-Age' attribute specified.
	// MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'
	// MaxAge>0 means Max-Age attribute present and given in seconds
	MaxAge   int
	Secure   bool
	HttpOnly bool
	SameSite SameSite
	Raw      string
	Unparsed []string // Raw text of unparsed attribute-value pairs
}
```
一点補足すると、`MaxAge`と`Expiers`は秒数か日時かの違いなどありますが、期限を決めるという意味では同じものです。ですので、どちらも設定することが可能ですが基本的に`MaxAge`が優先されます。

https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Set-Cookie#max-agenumber:~:text=Max%2DAge%3D%3Cnumber%3E%20%E7%9C%81%E7%95%A5,%E3%81%8C%E5%84%AA%E5%85%88%E3%81%95%E3%82%8C%E3%81%BE%E3%81%99%E3%80%82

# `Cookie`構造体を使用してクッキーをブラウザに登録してみる
ブラウザにクッキーを登録するためには、HTTPレスポンスヘッダーに`Set-Cookieヘッダー`を含めます。ブラウザはこのヘッダーを受け取ると、指定されたクッキーを保存します。

したがって、以下のように`Cookie`構造体に必要な値を設定し、`Set-Cookieをキー`、`c.String()を値`として、ヘッダーに追加します。

こうすると、レスポンスが返ってくるとブラウザのクッキーにセットされます。

Cookie構造体には`String()`がありますが、これは__Cookie構造体をHTTPヘッダー用の文字列に変換し、それを返却してくれます。__

```go
package main

import "net/http"

func CookieSample(w http.ResponseWriter, r *http.Request){
	c := http.Cookie{
		Name: "name",
		Value: "value",
		HttpOnly: true,
	}

	w.Header().Set("Set-Cookie", c.String())
}

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

	http.HandleFunc("/cookie", CookieSample)

	server.ListenAndServe()
}
```
試しに`http://127.0.0.1:8080/cookie`へブラウザでリクエストすると、以下のようにセットされます。
![スクリーンショット 2024-07-10 11.59.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3324306/ed046d24-d376-44b0-bd3e-e35c5f54eb05.png)

# 実はSetCookieメソッドがある
ヘッダーに`Set-Cookie`でクッキーをセットするのでも十分簡単なのですが、実は`http.SetCookie`を使えばより簡潔に書けるみたいです。

先ほどのコードを以下のように変えるだけです。こちらの方が明示的で分かりやすいですね。参照渡しに変わっている点だけ注意が必要です。

```go
	http.SetCookie(w, &c)
	// w.Header().Set("Set-Cookie", c.String())
```

`SetCookie`の定義は以下の通りですが、やっていることは同じですね。
```go
func SetCookie(w ResponseWriter, cookie *Cookie) {
	if v := cookie.String(); v != "" {
		w.Header().Add("Set-Cookie", v)
	}
}
```

# クッキーの取得
では、セットしたクッキーを次はサーバー側でどのように取得するのでしょうか。
あまり複雑ではないので簡潔に書きます。

まず、最初に思いつきそうなのが以下のようにHeaderからキーを指定して取得する方法ですが、これだと文字列のスライスが返ってくるため、自分で解析して必要な名前と値のペアを取得する必要があります。
```
func GetCookie(w http.ResponseWriter, r *http.Request){
    c := r.Header["cookie"]
}
```
ですので、これを避けるために`Cookie`メソッドを使用できます。これは引数でクッキーの名前を指定すると、ヘッダーの中からその名前に一致する値を探して、返却します。
```
func GetCookie(w http.ResponseWriter, r *http.Request){
    c, err := r.Cookie("first_cookie")
}
```

ちなみに`Cookie()`だと単一のクッキーですが、複数のクッキーを取得したい場合には`Cookies()`が用意されています。