😽

[Go言語Proposalを読む] net/http: Clientが1xxレスポンスの受信回数を制限しないようにする

2024/10/24に公開

[Go言語Proposalを読む] シリーズについて

  • このシリーズではGo言語に提案されたProposalのうち、Acceptedとなったものについて1つ取り上げて解説します。
    • 「Acceptedになったもの」とは、将来のGoで実装され、リリースされる予定であることを意味します。
    • AcceptされたProposalの結論はissueのタイトルと異なっている場合があるので、記事のタイトルは「最終的な結論を筆者が日本語で書いたもの」になっています。
  • Go言語については一定のハンズオン経験がある読者を仮定しています。
  • 次のようなことがわかるように書くことを目指しています:
    • Proposalの前提となる知識
      • Proposalの対象(また、対象となっているGoパッケージ・型・関数の役割)
      • Proposalの背景となっているソフトウェアエンジニアリングやコンピュータサイエンスのトピック
    • Proposalが解決しようとする問題
    • Proposalが問題を解決する手段
    • 検討された他の解決手段
  • 記事執筆のモチベーションは、特に「Proposalの背景となっているソフトウェアエンジニアリングやコンピュータサイエンスのトピック」について学習することにあります。つまり、前提知識部分がメインディッシュです。

基本資料

Proposalの対象

この記事で扱うProposalは https://github.com/golang/go/issues/65035 です。タイトルは

net/http: customize limit on number of 1xx responses

で、

net/http: 1xxレスポンスの受信回数制限をカスタムする

というような意味ですが、最終的な結論はこれとは異なります。

net/httpパッケージの*http.Client型が対象です。

Proposalが解決しようとする問題

HTTPのレスポンスにはレスポンスコードが含まれています。

そのうち1xxレスポンスつまり100番台のレスポンスは情報レスポンスとして使われます。

この1xxレスポンスは、最終的なレスポンス(200など)の前に任意回数返されることがあります。1回も返されないかもしれないし、複数回返されることもあるということです。ところが現在のhttp.Clientは1回のリクエストにつき5回までしか1xxレスポンスを受け取らず、6回目の1xxレスポンスを受け取った時には*Client.Doメソッドがエラーを返します。

これはHTTPの仕様に従っていないのでHTTPクライアントとしてのバグであるようです。

Proposalが問題を解決する手段

そこで、

  • 5回という恣意的な制限をなくす
  • その代わり、デフォルトでは、受信したすべての1xxレスポンスヘッダーの合計サイズが、所定のサイズを超えたらエラーにする
  • この挙動はClient設定でカスタムできる(httptrace.ClientTrace型のGot1xxResponseを使う)

実装もすでに始まっているようです(HTTP/2):
https://github.com/golang/net/commit/4783315416d92ff3d4664762748bd21776b42b98

背景: Resumable Uploads for HTTP

Proposalで挙げられている他の事情として、IETFに提案されている https://datatracker.ietf.org/doc/draft-ietf-httpbis-resumable-upload/ の存在があります。

この提案の中で、サーバー側からクライアントへアップロードの進捗を繰り返し通知する機能を検討しているので、5回という制限があるとそれがうまくいかない、という事情もあるようです。

検討された他の解決手段

元々のproposalは、5回という恣意的な回数制限をhttp.Clientのフィールドで上書きできるようにすることを提案していました。

しかし、1xxレスポンスが果たしている役割を考えると、回数で制限すること自体が理に適っていません。サーバーがレスポンスに応答するのに時間がかかる場合、1xxレスポンスを複数回返すことが有用であり、しかも応答に時間のかかるリクエストはよくあるためです。また、1xxレスポンスの回数や頻度はクライアント側からはわかりません。

他方で、全く制限がないとするのもよくないので、受信したヘッダーサイズの合計が一定値を超えたらエラーとする方針で合意されたようです。(バイナリプロトコルであるHTTP/2ではヘッダーのデコードも潜在的にコストがかかることも言及されています)

より詳しくは次のコメントが参考になります。

そのほかProposalから得られる知識

net/http/httptraceパッケージ

https://pkg.go.dev/net/http/httptrace

パッケージドキュメントにあるように、HTTP Clientからのリクエストによって起こるイベントをトレースするための機能を提供します。

https://pkg.go.dev/net/http/httptrace#ClientTrace 型が中心となる型で、さまざまなイベントに対するイベントハンドラー的なコールバック関数をフィールドとして設定しておき、これをcontext.Contextに詰めてhttp.Request型に設定するという使い方のようです。

この記事へのフィードバックについて

  • この記事についてフィードバックやご意見がある場合、GitHubリポジトリにissueかPRを立てていただけると助かります。
    • ZennのコメントよりもGitHub上でのやり取りが好ましいです。
    • GitHub上ではissueを立てずにいきなりPRを立てても大丈夫です。
  • Go言語のProposalはソフトウェア開発やコンピュータサイエンスの多くの分野に関わるため、筆者の関連知識が十分ではない場合が多いです。自信のない内容はそれとわかるように書くつもりですが、思い込みなどで誤った内容を断言してしまうこともあるかと思います。その際はぜひフィードバックをお願いいたします。
GitHubで編集を提案

Discussion