[インデックス 18363] ファイルの概要
このコミットは、Go言語の標準ライブラリ net/http/httputil
パッケージ内の ReverseProxy
において、バックエンドからのレスポンスに含まれるホップバイホップヘッダーを適切に削除する修正を導入しています。これにより、リバースプロキシがHTTPプロトコルの仕様に準拠し、予期せぬ動作やセキュリティ上の問題を回避できるようになります。
コミット
commit 96471b65d5cbff81d47288dad5fc49ec136ccc80
Author: Fredrik Enestad <fredrik.enestad@soundtrackyourbrand.com>
Date: Mon Jan 27 15:24:58 2014 -0800
httputil: in ReverseProxy, strip hop-by-hop headers from the backend response
Fixes #5967.
LGTM=bradfitz
R=golang-codereviews, bradfitz
CC=golang-codereviews
https://golang.org/cl/57370043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/96471b65d5cbff81d47288dad5fc49ec136ccc80
元コミット内容
httputil: in ReverseProxy, strip hop-by-hop headers from the backend response
このコミットは、ReverseProxy
がバックエンドからのレスポンスをクライアントに転送する際に、ホップバイホップヘッダーを削除することを目的としています。これは、GoのIssue #5967を修正するものです。
変更の背景
この変更の背景には、HTTPプロトコルの「ホップバイホップヘッダー」の特性と、リバースプロキシの役割があります。
リバースプロキシは、クライアントからのリクエストを受け取り、それをバックエンドサーバーに転送し、バックエンドからのレスポンスをクライアントに返すという仲介役を担います。この際、HTTPヘッダーには「エンドツーエンドヘッダー」と「ホップバイホップヘッダー」の2種類が存在します。
- エンドツーエンドヘッダー: リクエストの送信元から最終的な受信者まで、メッセージ全体に適用されるヘッダーです。例えば、
Content-Type
,Content-Length
,Authorization
などがあります。これらはプロキシを通過しても変更されずに転送されるべきです。 - ホップバイホップヘッダー: 単一のTCP接続、つまり隣接するノード間(クライアントとプロキシ、またはプロキシとバックエンドサーバーなど)でのみ意味を持つヘッダーです。これらはプロキシを越えて転送されるべきではありません。例えば、
Connection
,Keep-Alive
,Proxy-Authenticate
,Proxy-Authorization
,TE
,Trailers
,Transfer-Encoding
,Upgrade
などがあります。
Goの net/http/httputil.ReverseProxy
は、リバースプロキシとして機能しますが、以前のバージョンではバックエンドからのレスポンスに含まれるホップバイホップヘッダーを適切に削除していませんでした。このため、バックエンドから受け取ったホップバイホップヘッダーがそのままクライアントに転送されてしまい、以下のような問題を引き起こす可能性がありました。
- プロトコル違反: ホップバイホップヘッダーは、プロキシを越えて転送されるべきではないというHTTPの仕様に違反します。
- 予期せぬ動作: クライアントがこれらのヘッダーを受け取ると、誤った解釈をしたり、予期せぬ動作を引き起こしたりする可能性があります。例えば、
Connection: close
ヘッダーが転送されると、クライアントが接続を早期に閉じてしまうなどです。 - セキュリティリスク: 特定のホップバイホップヘッダーが不適切に転送されることで、情報漏洩やプロキシのバイパスなどのセキュリティ上の脆弱性につながる可能性もゼロではありません。
このコミットは、これらの問題を解決し、ReverseProxy
がHTTPプロトコルの仕様に厳密に準拠するようにするために行われました。
前提知識の解説
HTTPヘッダーの種類(エンドツーエンド vs ホップバイホップ)
前述の通り、HTTPヘッダーはエンドツーエンドヘッダーとホップバイホップヘッダーに分類されます。この区別は、HTTP通信におけるプロキシの役割を理解する上で非常に重要です。
- エンドツーエンドヘッダー: メッセージのペイロードに関する情報(例:
Content-Type
)、認証情報(例:Authorization
)、キャッシュ制御(例:Cache-Control
)など、メッセージの最終的な受信者にとって意味のある情報を含みます。これらはプロキシによって変更されるべきではありません(ただし、プロキシがキャッシュとして機能する場合など、特定の状況下では変更されることもあります)。 - ホップバイホップヘッダー: 現在の接続に関する情報(例:
Connection
)、プロキシ認証(例:Proxy-Authenticate
)、転送エンコーディング(例:Transfer-Encoding
)など、単一のネットワークホップ(接続)にのみ関連する情報を含みます。これらのヘッダーは、プロキシが次のホップにメッセージを転送する前に削除されるか、適切に処理される必要があります。
HTTP/1.1の仕様(RFC 2616, Section 13.5.1)では、プロキシはホップバイホップヘッダーを転送してはならないと明確に規定されています。
Go言語の net/http/httputil.ReverseProxy
net/http/httputil
パッケージは、HTTPユーティリティ関数を提供します。その中でも ReverseProxy
は、リバースプロキシサーバーを簡単に構築するための構造体です。
ReverseProxy
は、http.Handler
インターフェースを実装しており、ServeHTTP
メソッドを通じてHTTPリクエストを処理します。主な機能は以下の通りです。
- リクエストの転送: クライアントからのHTTPリクエストを、指定されたバックエンドサーバー(ターゲットURL)に転送します。この際、リクエストヘッダーの調整(例:
Host
ヘッダーの書き換え、X-Forwarded-For
ヘッダーの追加など)が行われることがあります。 - レスポンスの転送: バックエンドサーバーからのHTTPレスポンスを受け取り、それをクライアントに転送します。この際も、レスポンスヘッダーの調整や、ボディのストリーミングなどが行われます。
ReverseProxy
は、マイクロサービスアーキテクチャにおけるAPIゲートウェイ、ロードバランシング、SSLオフロードなど、様々な用途で利用されます。
Go言語の http.Header
と http.ResponseWriter
http.Header
: HTTPヘッダーを表すmap[string][]string
型のエイリアスです。ヘッダー名(キー)と、そのヘッダーに関連付けられた値のリスト(スライス)を保持します。Add
,Set
,Get
,Del
などのメソッドを提供し、ヘッダーの操作を容易にします。http.ResponseWriter
: HTTPレスポンスを構築するために使用されるインターフェースです。Header()
メソッドでレスポンスヘッダーを取得し、WriteHeader()
でステータスコードを書き込み、Write()
でレスポンスボディを書き込みます。
技術的詳細
このコミットの技術的詳細は、ReverseProxy
の ServeHTTP
メソッド内で、バックエンドからのレスポンスヘッダーをクライアントにコピーする前に、ホップバイホップヘッダーを明示的に削除する点にあります。
Goの net/http
パッケージには、ホップバイホップヘッダーのリストを定義した内部変数 hopHeaders
が存在します。このコミットでは、バックエンドからのレスポンス (res
) のヘッダー (res.Header
) をループ処理し、hopHeaders
リストに含まれる各ヘッダー名に対応するヘッダーを res.Header.Del(h)
を使って削除しています。
これにより、copyHeader(rw.Header(), res.Header)
が実行される際には、すでにホップバイホップヘッダーが削除された状態のレスポンスヘッダーがクライアントにコピーされることになります。
テストケースでは、fakeHopHeader
というテスト用のホップバイホップヘッダーを hopHeaders
に追加し、バックエンドがこのヘッダーをレスポンスに含めた場合に、リバースプロキシがそれをクライアントに転送しないことを検証しています。
コアとなるコードの変更箇所
変更は主に2つのファイルで行われています。
src/pkg/net/http/httputil/reverseproxy.go
:ReverseProxy
のServeHTTP
メソッドに、ホップバイホップヘッダーを削除するロジックが追加されました。src/pkg/net/http/httputil/reverseproxy_test.go
: ホップバイホップヘッダーの削除が正しく行われることを検証するためのテストケースが追加されました。
src/pkg/net/http/httputil/reverseproxy.go
の変更
--- a/src/pkg/net/http/httputil/reverseproxy.go
+++ b/src/pkg/net/http/httputil/reverseproxy.go
@@ -144,6 +144,10 @@ func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
}\n \tdefer res.Body.Close()\n \n+\tfor _, h := range hopHeaders {\n+\t\tres.Header.Del(h)\n+\t}\n+\n \tcopyHeader(rw.Header(), res.Header)\n \n \trw.WriteHeader(res.StatusCode)\n```
### `src/pkg/net/http/httputil/reverseproxy_test.go` の変更
```diff
--- a/src/pkg/net/http/httputil/reverseproxy_test.go
+++ b/src/pkg/net/http/httputil/reverseproxy_test.go
@@ -16,6 +16,12 @@ import (\
\t\"time\"\n )\n \n+const fakeHopHeader = \"X-Fake-Hop-Header-For-Test\"\n+\n+func init() {\n+\thopHeaders = append(hopHeaders, fakeHopHeader)\n+}\n+\n func TestReverseProxy(t *testing.T) {\n \tconst backendResponse = \"I am the backend\"\n \tconst backendStatus = 404\
@@ -36,6 +42,10 @@ func TestReverseProxy(t *ch.T) {\n \t\t\tt.Errorf(\"backend got Host header %q, want %q\", g, e)\n \t\t}\n \t\tw.Header().Set(\"X-Foo\", \"bar\")\n+\t\tw.Header().Set(\"Upgrade\", \"foo\")\n+\t\tw.Header().Set(fakeHopHeader, \"foo\")\n+\t\tw.Header().Add(\"X-Multi-Value\", \"foo\")\n+\t\tw.Header().Add(\"X-Multi-Value\", \"bar\")\n \t\thttp.SetCookie(w, &http.Cookie{Name: \"flavor\", Value: \"chocolateChip\"})\n \t\tw.WriteHeader(backendStatus)\n \t\tw.Write([]byte(backendResponse))\
@@ -64,6 +74,12 @@ func TestReverseProxy(t *testing.T) {\n \tif g, e := res.Header.Get(\"X-Foo\"), \"bar\"; g != e {\n \t\tt.Errorf(\"got X-Foo %q; expected %q\", g, e)\n \t}\n+\tif c := res.Header.Get(fakeHopHeader); c != \"\" {\n+\t\tt.Errorf(\"got %s header value %q\", fakeHopHeader, c)\n+\t}\n+\tif g, e := len(res.Header[\"X-Multi-Value\"]), 2; g != e {\n+\t\tt.Errorf(\"got %d X-Multi-Value header values; expected %d\", g, e)\n+\t}\n \tif g, e := len(res.Header[\"Set-Cookie\"]), 1; g != e {\n \t\tt.Fatalf(\"got %d SetCookies, want %d\", g, e)\n \t}\n```
## コアとなるコードの解説
### `reverseproxy.go` の変更点
```go
for _, h := range hopHeaders {
res.Header.Del(h)
}
この4行が、このコミットの核心となる変更です。
hopHeaders
: これはnet/http
パッケージ内部で定義されている、ホップバイホップヘッダー名の文字列スライスです。通常、Connection
,Keep-Alive
,Proxy-Authenticate
,Proxy-Authorization
,TE
,Trailers
,Transfer-Encoding
,Upgrade
などが含まれます。for _, h := range hopHeaders
:hopHeaders
スライス内の各ヘッダー名h
に対してループ処理を行います。res.Header.Del(h)
: バックエンドからのレスポンスres
のヘッダーマップ (res.Header
) から、現在のホップバイホップヘッダーh
を削除します。Del
メソッドは、指定されたキー(ヘッダー名)に対応するすべての値を削除します。
このコードブロックは、バックエンドからのレスポンスボディを読み込んだ直後、かつレスポンスヘッダーをクライアントにコピーする copyHeader
関数の呼び出し直前に配置されています。これにより、クライアントにレスポンスが送信される前に、不要なホップバイホップヘッダーが確実に除去されます。
reverseproxy_test.go
の変更点
テストファイルでは、以下の点が重要です。
const fakeHopHeader = "X-Fake-Hop-Header-For-Test"
: テスト専用のカスタムホップバイホップヘッダーを定義しています。func init() { hopHeaders = append(hopHeaders, fakeHopHeader) }
:init
関数内で、このfakeHopHeader
を実際のhopHeaders
リストに追加しています。これにより、テスト中にこのカスタムヘッダーもホップバイホップヘッダーとして扱われるようになります。- バックエンドサーバーのハンドラ内で、
w.Header().Set("Upgrade", "foo")
やw.Header().Set(fakeHopHeader, "foo")
のように、意図的にホップバイホップヘッダーをレスポンスに追加しています。 - クライアント側でのレスポンス検証で、
if c := res.Header.Get(fakeHopHeader); c != "" { ... }
のように、fakeHopHeader
がレスポンスヘッダーに含まれていないことを確認しています。もし含まれていればテストは失敗し、リバースプロキシがホップバイホップヘッダーを正しく削除していないことを示します。
これらのテストの追加により、ReverseProxy
がホップバイホップヘッダーを適切に処理するという振る舞いが保証されるようになりました。
関連リンク
- Go Issue #5967: https://github.com/golang/go/issues/5967
- Go Change List 57370043: https://golang.org/cl/57370043
参考にした情報源リンク
- RFC 2616 - Hypertext Transfer Protocol -- HTTP/1.1 (Section 13.5.1 End-to-end and Hop-by-hop Headers): https://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1
- GoDoc
net/http/httputil
package: https://pkg.go.dev/net/http/httputil - GoDoc
net/http
package: https://pkg.go.dev/net/http - Go言語におけるHTTPリバースプロキシの実装と注意点 (外部記事、概念理解のため): https://zenn.dev/hsaki/articles/go-reverse-proxy (これは一般的な情報源であり、直接このコミットに言及しているわけではありませんが、リバースプロキシの概念理解に役立ちます。)
[インデックス 18363] ファイルの概要
このコミットは、Go言語の標準ライブラリ net/http/httputil
パッケージ内の ReverseProxy
において、バックエンドからのレスポンスに含まれるホップバイホップヘッダーを適切に削除する修正を導入しています。これにより、リバースプロキシがHTTPプロトコルの仕様に準拠し、予期せぬ動作やセキュリティ上の問題を回避できるようになります。
コミット
commit 96471b65d5cbff81d47288dad5fc49ec136ccc80
Author: Fredrik Enestad <fredrik.enestad@soundtrackyourbrand.com>
Date: Mon Jan 27 15:24:58 2014 -0800
httputil: in ReverseProxy, strip hop-by-hop headers from the backend response
Fixes #5967.
LGTM=bradfitz
R=golang-codereviews, bradfitz
CC=golang-codereviews
https://golang.org/cl/57370043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/96471b65d5cbff81d47288dad5fc49ec136ccc80
元コミット内容
httputil: in ReverseProxy, strip hop-by-hop headers from the backend response
このコミットは、ReverseProxy
がバックエンドからのレスポンスをクライアントに転送する際に、ホップバイホップヘッダーを削除することを目的としています。これは、GoのIssue #5967を修正するものです。
変更の背景
この変更の背景には、HTTPプロトコルの「ホップバイホップヘッダー」の特性と、リバースプロキシの役割があります。
リバースプロキシは、クライアントからのリクエストを受け取り、それをバックエンドサーバーに転送し、バックエンドからのレスポンスをクライアントに返すという仲介役を担います。この際、HTTPヘッダーには「エンドツーエンドヘッダー」と「ホップバイホップヘッダー」の2種類が存在します。
- エンドツーエンドヘッダー: リクエストの送信元から最終的な受信者まで、メッセージ全体に適用されるヘッダーです。例えば、
Content-Type
,Content-Length
,Authorization
などがあります。これらはプロキシを通過しても変更されずに転送されるべきです。 - ホップバイホップヘッダー: 単一のTCP接続、つまり隣接するノード間(クライアントとプロキシ、またはプロキシとバックエンドサーバーなど)でのみ意味を持つヘッダーです。これらはプロキシを越えて転送されるべきではありません。例えば、
Connection
,Keep-Alive
,Proxy-Authenticate
,Proxy-Authorization
,TE
,Trailers
,Transfer-Encoding
,Upgrade
などがあります。
Goの net/http/httputil.ReverseProxy
は、リバースプロキシとして機能しますが、以前のバージョンではバックエンドからのレスポンスに含まれるホップバイホップヘッダーを適切に削除していませんでした。このため、バックエンドから受け取ったホップバイホップヘッダーがそのままクライアントに転送されてしまい、以下のような問題を引き起こす可能性がありました。
- プロトコル違反: ホップバイホップヘッダーは、プロキシを越えて転送されるべきではないというHTTPの仕様に違反します。
- 予期せぬ動作: クライアントがこれらのヘッダーを受け取ると、誤った解釈をしたり、予期せぬ動作を引き起こしたりする可能性があります。例えば、
Connection: close
ヘッダーが転送されると、クライアントが接続を早期に閉じてしまうなどです。 - セキュリティリスク: 特定のホップバイホップヘッダーが不適切に転送されることで、情報漏洩やプロキシのバイパスなどのセキュリティ上の脆弱性につながる可能性もゼロではありません。
このコミットは、これらの問題を解決し、ReverseProxy
がHTTPプロトコルの仕様に厳密に準拠するようにするために行われました。
前提知識の解説
HTTPヘッダーの種類(エンドツーエンド vs ホップバイホップ)
前述の通り、HTTPヘッダーはエンドツーエンドヘッダーとホップバイホップヘッダーに分類されます。この区別は、HTTP通信におけるプロキシの役割を理解する上で非常に重要です。
- エンドツーエンドヘッダー: メッセージのペイロードに関する情報(例:
Content-Type
)、認証情報(例:Authorization
)、キャッシュ制御(例:Cache-Control
)など、メッセージの最終的な受信者にとって意味のある情報を含みます。これらはプロキシによって変更されるべきではありません(ただし、プロキシがキャッシュとして機能する場合など、特定の状況下では変更されることもあります)。 - ホップバイホップヘッダー: 現在の接続に関する情報(例:
Connection
)、プロキシ認証(例:Proxy-Authenticate
)、転送エンコーディング(例:Transfer-Encoding
)など、単一のネットワークホップ(接続)にのみ関連する情報を含みます。これらのヘッダーは、プロキシが次のホップにメッセージを転送する前に削除されるか、適切に処理される必要があります。
HTTP/1.1の仕様(RFC 2616, Section 13.5.1)では、プロキシはホップバイホップヘッダーを転送してはならないと明確に規定されています。
Go言語の net/http/httputil.ReverseProxy
net/http/httputil
パッケージは、HTTPユーティリティ関数を提供します。その中でも ReverseProxy
は、リバースプロキシサーバーを簡単に構築するための構造体です。
ReverseProxy
は、http.Handler
インターフェースを実装しており、ServeHTTP
メソッドを通じてHTTPリクエストを処理します。主な機能は以下の通りです。
- リクエストの転送: クライアントからのHTTPリクエストを、指定されたバックエンドサーバー(ターゲットURL)に転送します。この際、リクエストヘッダーの調整(例:
Host
ヘッダーの書き換え、X-Forwarded-For
ヘッダーの追加など)が行われることがあります。 - レスポンスの転送: バックエンドサーバーからのHTTPレスポンスを受け取り、それをクライアントに転送します。この際も、レスポンスヘッダーの調整や、ボディのストリーミングなどが行われます。
ReverseProxy
は、マイクロサービスアーキテクチャにおけるAPIゲートウェイ、ロードバランシング、SSLオフロードなど、様々な用途で利用されます。
Go言語の http.Header
と http.ResponseWriter
http.Header
: HTTPヘッダーを表すmap[string][]string
型のエイリアスです。ヘッダー名(キー)と、そのヘッダーに関連付けられた値のリスト(スライス)を保持します。Add
,Set
,Get
,Del
などのメソッドを提供し、ヘッダーの操作を容易にします。http.ResponseWriter
: HTTPレスポンスを構築するために使用されるインターフェースです。Header()
メソッドでレスポンスヘッダーを取得し、WriteHeader()
でステータスコードを書き込み、Write()
でレスポンスボディを書き込みます。
技術的詳細
このコミットの技術的詳細は、ReverseProxy
の ServeHTTP
メソッド内で、バックエンドからのレスポンスヘッダーをクライアントにコピーする前に、ホップバイホップヘッダーを明示的に削除する点にあります。
Goの net/http
パッケージには、ホップバイホップヘッダーのリストを定義した内部変数 hopHeaders
が存在します。このコミットでは、バックエンドからのレスポンス (res
) のヘッダー (res.Header
) をループ処理し、hopHeaders
リストに含まれる各ヘッダー名に対応するヘッダーを res.Header.Del(h)
を使って削除しています。
これにより、copyHeader(rw.Header(), res.Header)
が実行される際には、すでにホップバイホップヘッダーが削除された状態のレスポンスヘッダーがクライアントにコピーされることになります。
テストケースでは、fakeHopHeader
というテスト用のホップバイホップヘッダーを hopHeaders
に追加し、バックエンドがこのヘッダーをレスポンスに含めた場合に、リバースプロキシがそれをクライアントに転送しないことを検証しています。
コアとなるコードの変更箇所
変更は主に2つのファイルで行われています。
src/pkg/net/http/httputil/reverseproxy.go
:ReverseProxy
のServeHTTP
メソッドに、ホップバイホップヘッダーを削除するロジックが追加されました。src/pkg/net/http/httputil/reverseproxy_test.go
: ホップバイホップヘッダーの削除が正しく行われることを検証するためのテストケースが追加されました。
src/pkg/net/http/httputil/reverseproxy.go
の変更
--- a/src/pkg/net/http/httputil/reverseproxy.go
+++ b/src/pkg/net/http/httputil/reverseproxy.go
@@ -144,6 +144,10 @@ func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
}\n \tdefer res.Body.Close()\n \n+\tfor _, h := range hopHeaders {\n+\t\tres.Header.Del(h)\n+\t}\n+\n \tcopyHeader(rw.Header(), res.Header)\n \n \trw.WriteHeader(res.StatusCode)\
src/pkg/net/http/httputil/reverseproxy_test.go
の変更
--- a/src/pkg/net/http/httputil/reverseproxy_test.go
+++ b/src/pkg/net/http/httputil/reverseproxy_test.go
@@ -16,6 +16,12 @@ import (\
\t\"time\"\n )\n \n+const fakeHopHeader = \"X-Fake-Hop-Header-For-Test\"\n+\n+func init() {\n+\thopHeaders = append(hopHeaders, fakeHopHeader)\n+}\n+\n func TestReverseProxy(t *testing.T) {\n \tconst backendResponse = \"I am the backend\"\n \tconst backendStatus = 404\
@@ -36,6 +42,10 @@ func TestReverseProxy(t *ch.T) {\n \t\t\tt.Errorf(\"backend got Host header %q, want %q\", g, e)\n \t\t}\n \t\tw.Header().Set(\"X-Foo\", \"bar\")\n+\t\tw.Header().Set(\"Upgrade\", \"foo\")\n+\t\tw.Header().Set(fakeHopHeader, \"foo\")\n+\t\tw.Header().Add(\"X-Multi-Value\", \"foo\")\n+\t\tw.Header().Add(\"X-Multi-Value\", \"bar\")\n \t\thttp.SetCookie(w, &http.Cookie{Name: \"flavor\", Value: \"chocolateChip\"})\n \t\tw.WriteHeader(backendStatus)\n \t\tw.Write([]byte(backendResponse))\
@@ -64,6 +74,12 @@ func TestReverseProxy(t *testing.T) {\n \tif g, e := res.Header.Get(\"X-Foo\"), \"bar\"; g != e {\n \t\tt.Errorf(\"got X-Foo %q; expected %q\", g, e)\n \t}\n+\tif c := res.Header.Get(fakeHopHeader); c != \"\" {\n+\t\tt.Errorf(\"got %s header value %q\", fakeHopHeader, c)\n+\t}\n+\tif g, e := len(res.Header[\"X-Multi-Value\"]), 2; g != e {\n+\t\tt.Errorf(\"got %d X-Multi-Value header values; expected %d\", g, e)\n+\t}\n \tif g, e := len(res.Header[\"Set-Cookie\"]), 1; g != e {\n \t\tt.Fatalf(\"got %d SetCookies, want %d\", g, e)\n \t}\n```
## コアとなるコードの解説
### `reverseproxy.go` の変更点
```go
for _, h := range hopHeaders {
res.Header.Del(h)
}
この4行が、このコミットの核心となる変更です。
hopHeaders
: これはnet/http
パッケージ内部で定義されている、ホップバイホップヘッダー名の文字列スライスです。通常、Connection
,Keep-Alive
,Proxy-Authenticate
,Proxy-Authorization
,TE
,Trailers
,Transfer-Encoding
,Upgrade
などが含まれます。for _, h := range hopHeaders
:hopHeaders
スライス内の各ヘッダー名h
に対してループ処理を行います。res.Header.Del(h)
: バックエンドからのレスポンスres
のヘッダーマップ (res.Header
) から、現在のホップバイホップヘッダーh
を削除します。Del
メソッドは、指定されたキー(ヘッダー名)に対応するすべての値を削除します。
このコードブロックは、バックエンドからのレスポンスボディを読み込んだ直後、かつレスポンスヘッダーをクライアントにコピーする copyHeader
関数の呼び出し直前に配置されています。これにより、クライアントにレスポンスが送信される前に、不要なホップバイホップヘッダーが確実に除去されます。
reverseproxy_test.go
の変更点
テストファイルでは、以下の点が重要です。
const fakeHopHeader = "X-Fake-Hop-Header-For-Test"
: テスト専用のカスタムホップバイホップヘッダーを定義しています。func init() { hopHeaders = append(hopHeaders, fakeHopHeader) }
:init
関数内で、このfakeHopHeader
を実際のhopHeaders
リストに追加しています。これにより、テスト中にこのカスタムヘッダーもホップバイホップヘッダーとして扱われるようになります。- バックエンドサーバーのハンドラ内で、
w.Header().Set("Upgrade", "foo")
やw.Header().Set(fakeHopHeader, "foo")
のように、意図的にホップバイホップヘッダーをレスポンスに追加しています。 - クライアント側でのレスポンス検証で、
if c := res.Header.Get(fakeHopHeader); c != "" { ... }
のように、fakeHopHeader
がレスポンスヘッダーに含まれていないことを確認しています。もし含まれていればテストは失敗し、リバースプロキシがホップバイホップヘッダーを正しく削除していないことを示します。
これらのテストの追加により、ReverseProxy
がホップバイホップヘッダーを適切に処理するという振る舞いが保証されるようになりました。
関連リンク
- Go Issue #5967: https://github.com/golang/go/issues/5967
- Go Change List 57370043: https://golang.org/cl/57370043
参考にした情報源リンク
- RFC 2616 - Hypertext Transfer Protocol -- HTTP/1.1 (Section 13.5.1 End-to-end and Hop-by-hop Headers): https://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1
- GoDoc
net/http/httputil
package: https://pkg.go.dev/net/http/httputil - GoDoc
net/http
package: https://pkg.go.dev/net/http - Go言語におけるHTTPリバースプロキシの実装と注意点 (外部記事、概念理解のため): https://zenn.dev/hsaki/articles/go-reverse-proxy (これは一般的な情報源であり、直接このコミットに言及しているわけではありませんが、リバースプロキシの概念理解に役立ちます。)