はじめに
TIG所属の大江です。Go 1.23リリース連載の7本目です。
本記事では以下の内容を取り上げます。
- netでKeep-Aliveの詳細な設定が可能になりました
- net/httpにおけるCookieの扱いのアップデート
1. netでKeep-Aliveの詳細な設定が可能になりました
Keep-Aliveに関してより細かい設定が出来るようになりました。
新しく導入されたtype、KeepAliveConfigによって指定できます。
KeepAliveConfigは以下のように定義されています。
type KeepAliveConfig struct { |
次に各パラメーターについてご紹介します。
Enable
- Keep-Alive Probeを送るかどうかを決定する。trueの場合送信する
検証したところ、Keep-Alive Probeを一切送らないようにするにはDialのフィールド、KeepAliveも負の数にする必要があります。以下のように設定するとKeep-Alive Probeが送信されなくなりました。
kac := net.KeepAliveConfig{ |
その他の場合もどの設定が適用されるかを確認しました。以下の表の通りです。
Dial.KeepAliveの正負 | KeepAliveConfig.Enabled | どちらの設定が適用されるか |
---|---|---|
+ | true | KeepAliveConfig |
+ | false | Dial.KeepAlive |
- | true | KeepAliveConfig |
- | false | 無(Keep-Alive Probeは送られない) |
Idle
- 最初のKeep-Alive Probeを送信するまでにコネクションがアイドル状態である時間
- 0だとデフォルトの15秒に設定される
デフォルトの15秒はGo 1.22までの値と同じです。
Interval
- Keep-Alive Probeが送信される間隔
- 0だとデフォルトの15秒に設定される
デフォルトの15秒はGo 1.22までの値と同じです。
Count
- コネクションを切るまでに送るKeep-Alive Probeの回数
- この回数応答がなければコネクションを閉じる。0だとデフォルトの9回に設定される
デフォルトの9回はLinuxのデフォルト設定と同じです。
パケットキャプチャしてみる
以下のサーバーとクライアントを立て、WiresharkでKeep-Alive Probeをキャプチャしてみます。
サーバー側のコード
package main |
クライアント側のコード
package main |
5秒ごとにKeep-Alive Probeが送られていることが確認できました。
2. net/httpにおけるCookieの扱いのアップデート
今回のアップデートでは、Cookieに関するアップデートがいくつか入りました。
まずはCookie関連のアップデートのポイントを紹介します。
- 新規追加されたCookie.Quotedフィールドにより、Cookieの値が元々クォートされていたかどうかを判定できるようになりました
- 新規追加されたRequest.CookiesNamed()により、指定された名前と一致するすべてのCookieを取得できるようになりました
- 新規追加されたParseSetCookie()、ParseCookie()により、Set-CookieヘッダーからCookieを取得できるようになりました。ParseCookie()では、同じCookie名が複数回現れてもすべてのCookieを取得できます
- 新規追加されたCookie.Partitionedフィールドにより、Partitioned属性を持つCookieを識別できるようになりました
新規追加されたCookie.Quotedフィールドにより、Cookieの値が元々クォートされていたかどうかを判定できるように
まず一点目のアップデートについてご紹介します。
修正の元となったIssueがこちらです。
Cookieについての仕様は、RFC6265の一部として以下のように策定されています。
cookie-pair = cookie-name "=" cookie-value |
またクオーテーションについての扱いについて、RFC6265のドラフトのコメントでは以下のように書かれています。
Per the grammar above, the cookie-value MAY be wrapped in DQUOTE
characters. Note that in this case, the initial and trailing DQUOTE
characters are not stripped. They are part of the Cookie-value, and
will be included in Cookie headers sent to the server.
RFCにはっきり明記されている訳ではないので人によって解釈が分かれるところではありましたが、最終的にはRFCはクオーテーションをCookie-valueの一部としているという解釈に落ち着いたようです。
現状のnet/httpパッケージはCookie-valueをダブルクォーテーションを含まないものとして扱っているので、ダブルクォーテーションを残しておく修正が入りました。
結論として以下のような仕様に落ち着きました。
- http.Cookieに新たなフィールド
Quoted bool
を追加する- Cookieをparseするときに、値からダブルクォートが削除された場合、
Quoted=true
に設定する - Cookieを出力する際、もし
Quoted=true
の場合は、ダブルクォートを値に再度追加する - スペースやカンマを含むCookieについては、Go言語の古いバージョンとの互換性のために、ダブルクォートが暗黙的に追加される
- Cookieをparseするときに、値からダブルクォートが削除された場合、
新旧比較
それでは実際に動かしてどのような点が変わったのか試してみます。
以下のコードをGo 1.23と1.22で動かしてみます。
処理内容としては以下です。
- Set-Cookieヘッダーを返すテストhttpサーバーを立てる
- テストHTTPサーバーに対してGET要求を送る。サーバーが返したSet-Cookieヘッダーに基づきCookieがクライアントのcookiejarに保存される
- 2回目のGet要求でCookieをサーバーに送信する
ここでは以下二点を確認します。
- 2のSet-CookieヘッダーからcookiejarにセットしたCookieインスタンスの中身
- 3でサーバーに送信されたCookieヘッダーの中身
実行したコードは以下です。
package main |
まずSet-CookieヘッダーcookiejarにどのようなCookieが入るかですが、以下のようになりました。
Go 1.23での実行結果
1. |
Go 1.22以前の実行結果(Quotedの部分を削除して実行)
1. |
Go 1.22以前と以降でCookie.Valueの値は変わらず、ダブルクォーテーションが元々ついていた場合はCookie.Quoted=true
になっています。
Cookie.Valueの中身は変えないことで、Go 1.22以前の動作に影響を与えずかつクオーテーションの情報を付与するということが実現できているようです。
次に、サーバーに送信されたCookieヘッダーの中身を比較します。
Go 1.23での実行結果
Cookie: [blank=; no_quotation=value; only_double_quotation=; with_double_quotation="value"; with_space="va lue"; with_comma="va,lue"] |
Go 1.22以前の実行結果(Quotedの部分を削除して実行)
Cookie: [blank=; no_quotation=value; only_double_quotation=; with_double_quotation=value; with_space="va lue"; with_comma="va,lue"] |
cookie.Quoted=true
の場合は、ダブルクォーテーションがCookieヘッダーの前後に追加されて送信されます。
ちなみに、Cookieの値がダブルクォーテーションのみの場合は、ダブルクォーテーションだけになるのではなく空になりました。
新規追加されたRequest.CookiesNamed()により、指定された名前と一致するすべてのCookieを取得できるように
同じNameのCookieがブラウザから送られてきたとき、Request.Cookie()では指定した名前に一致する最初のCookieしか取得できませんでした。
Request.CookiesNamed()によって、同じNameの全てのCookieを取得できるようになりました。
package main |
実行結果
duplicate_name=value <nil> |
CookiesNamed()では同じ名前(“duplicate_name”)の2つの値(“value”,”value2”)を両方取り出すことが出来ました。
新規追加されたParseSetCookie()、ParseCookie()により、Set-CookieヘッダーからCookieを取得できるように
両者ともSet-CookieヘッダーからCookie型を取得するメソッドです。
ParseSetCookieは1つ、ParseCookieは複数のCookieを取得します。
package main |
実行結果
cookie1="value1" |
新規追加されたCookie.Partitionedフィールドにより、Partitioned属性を持つCookieを識別できるように
CHIPSに対応するため、Cookie.Partitionedフィールドが追加されました。
CHIPSとは、サードパーティーCookieを安全に扱えるようにするためGoogleが策定している仕様です。CHIPSでは、Partitioned属性が入っている場合、特定のトップレベルドメインのみに有効なサードパーティーCookieとして保存されます。
Chromeは2025年初頭から、現行のサードパーティCookieの廃止に向けた取り組みを進める予定です。この対応が行われると、CHIPSに対応している場合のみにサードパーティーCookieを使えるようになります。
そちらへ対応するため、今回Go 1.23へのアップデート項目に入りました。
以下が策定された仕様です。
- Cookie parserで、Cookieに”; Partitioned”が入っている場合、boolはtrueに設定されます
- Cookie.Stringでは、Partitionedがtrueの場合、文字列に”; Partitioned”が加えられます
- Cookie.Validでは、Partitionedがtrueで、Cookieがセキュアでない場合、Validはエラーを返します
以下実行したコードです。
package main |
実行結果
※cookie.Valid()
はValidだとnil,Validでないとエラーメッセージが出力されます。
{"String":"parsed_cookie=parsed_cookie; Path=/; HttpOnly; Secure; SameSite=None; Partitioned", "Valid":"<nil>", "Partitioned":"true"} |
おわりに
net,net/httpパッケージの一部アップデート項目についてのご紹介でした。
個人的には、初めて言語仕様変更の議論を追ってみて、当初の提案から議論を経て修正されていく様子を見るのが興味深かったです。
また今回ご紹介したものには数年前に挙がったIssueが発端となり修正されたものが多く、長い綿密な議論を経てアップデートされていることも印象的でした。
次は棚井さんのGo Telemetryです。