[インデックス 16367] ファイルの概要
このコミットは、Go言語の標準ライブラリである net/http パッケージのテストスイートに、Hijacker インターフェースの動作を検証するための新たなテストケースを追加するものです。具体的には、http.ResponseWriter が提供する Hijack メソッドを呼び出した後に、バッファリングされたライターと基盤となるコネクションの両方への書き込みが正しく機能するかを確認します。また、既存のテストコードにおけるHTTPリクエスト文字列の生成方法を共通のヘルパー関数に置き換えることで、コードの重複を排除し、可読性と保守性を向上させています。
コミット
commit 950fa9353126a784ce0f4cde8df1d1c16af0e6c4
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Tue May 21 18:43:28 2013 -0700
net/http: add another Hijacker test
R=dsymonds
CC=gobot, golang-dev
https://golang.org/cl/9570043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/950fa9353126a784ce0f4cde8df1d1c16af0e6c4
元コミット内容
net/http: add another Hijacker test
R=dsymonds
CC=gobot, golang-dev
https://golang.org/cl/9570043
変更の背景
Goの net/http パッケージは、HTTPサーバーとクライアントを構築するための強力な機能を提供します。その中でも Hijacker インターフェースは、HTTPハンドラが基盤となるTCPコネクションを「乗っ取る(hijack)」ことを可能にする重要な機能です。これは、WebSocketのようなHTTP以外のプロトコルに切り替える場合や、カスタムのプロトコルハンドリングを実装する場合に必要となります。
このコミットの背景には、Hijack メソッドが呼び出された後のコネクションの振る舞い、特に Hijack が返す bufio.ReadWriter と net.Conn の両方への書き込みが期待通りに行われることを保証する必要がありました。既存のテストではこの特定のシナリオが十分にカバーされていなかったため、堅牢性を高めるために新たなテストケース TestWriteAfterHijack が追加されました。
また、既存のテストコード内でHTTPリクエストのバイト列を生成する際に、strings.Replace を用いて \n を \r\n に変換する処理が複数箇所で重複していました。これはコードの冗長性を生み、将来的な変更やメンテナンスを困難にする可能性がありました。この重複を解消し、コードの品質を向上させるために、共通のヘルパー関数 reqBytes が導入されました。
前提知識の解説
Go言語の net/http パッケージ
Go言語の net/http パッケージは、HTTPプロトコルを扱うための標準ライブラリです。これには、HTTPサーバー、クライアント、リクエスト、レスポンス、ハンドラなどの機能が含まれます。
http.Handlerインターフェース: HTTPリクエストを処理するためのインターフェースで、ServeHTTP(ResponseWriter, *Request)メソッドを持ちます。http.ResponseWriterインターフェース: HTTPレスポンスをクライアントに書き込むためのインターフェースです。ヘッダーの設定やボディの書き込みを行います。http.Request構造体: クライアントからのHTTPリクエストを表す構造体です。URL、ヘッダー、ボディなどの情報を含みます。
http.Hijacker インターフェース
http.Hijacker は http.ResponseWriter が実装できるオプションのインターフェースです。このインターフェースを実装している ResponseWriter は、Hijack() メソッドを提供します。
Hijack() (net.Conn, *bufio.ReadWriter, error): このメソッドを呼び出すと、HTTPサーバーは基盤となるネットワークコネクションの制御をハンドラに引き渡します。これにより、ハンドラはHTTPプロトコルから離れて、生のTCPコネクションを直接操作できるようになります。net.Conn: 基盤となる生のネットワークコネクションです。これを通じて直接データの読み書きが可能です。*bufio.ReadWriter:Hijack()が呼び出された時点で、http.ResponseWriterが内部的にバッファリングしていたデータ(例えば、部分的に読み込まれたリクエストボディや、まだ送信されていないレスポンスヘッダーなど)を含むbufio.ReadWriterオブジェクトです。このオブジェクトを通じて、バッファリングされていたデータを読み書きしたり、新しいデータをバッファリングしてフラッシュしたりできます。
HTTPプロトコルの行末
HTTP/1.1プロトコルでは、ヘッダーやメッセージボディの各行はCRLF(Carriage Return + Line Feed、\r\n)で区切られることが規定されています。一般的なテキストファイルやプログラミング言語ではLF(Line Feed、\n)が改行コードとして使われることが多いですが、HTTP通信においては \r\n が必須です。
Go言語のテスト
Go言語には、標準でテストフレームワークが組み込まれています。
testingパッケージ: テスト関数(TestXxx)やベンチマーク関数(BenchmarkXxx)を定義するために使用されます。t.Error()/t.Errorf(): テスト失敗を報告するためのメソッドです。bytes.Buffer: バイト列を効率的に操作するためのバッファです。このコミットでは、モックコネクションの出力先として使用されています。bufio.Reader/bufio.Writer: バッファリングされたI/O操作を提供し、効率的な読み書きを可能にします。
技術的詳細
このコミットの技術的詳細は、主に以下の2点に集約されます。
-
reqBytesヘルパー関数の導入:- 目的: HTTPリクエスト文字列(
\n区切り)を、HTTPプロトコルで要求される\r\n区切りのバイト列に変換し、末尾に\r\n\r\nを追加する。 - 実装:
strings.Replace(strings.TrimSpace(req), "\n", "\r\n", -1) + "\r\n\r\n"strings.TrimSpace(req): 入力文字列の先頭と末尾の空白を削除します。これにより、リクエスト文字列の整形が容易になります。strings.Replace(..., "\n", "\r\n", -1): 文字列内のすべての\nを\r\nに置換します。-1はすべての出現箇所を置換することを意味します。+ "\r\n\r\n": HTTPリクエストのヘッダーとボディの区切り、およびリクエスト全体の終端を示すために、末尾に空行(\r\n)を2つ追加します。
- 効果: 既存の複数のテスト(
TestCloseNotifierChanLeak,TestHeaderToWire, 各種BenchmarkServerFakeConnなど)で重複していたリクエストバイト列生成ロジックを共通化し、コードの簡潔性と保守性を向上させました。
- 目的: HTTPリクエスト文字列(
-
TestWriteAfterHijackテストの追加:- 目的:
http.HijackerインターフェースのHijack()メソッドを呼び出した後、返されたbufio.ReadWriterとnet.Connの両方への書き込みが正しく行われることを検証します。 - テストシナリオ:
- モックの
rwTestConnを作成し、そのWriterをbytes.Bufferに設定して、書き込まれたデータをキャプチャできるようにします。 http.HandlerFuncを定義し、その中でrw.(Hijacker).Hijack()を呼び出してコネクションを乗っ取ります。- 新しいゴルーチンを起動し、乗っ取ったコネクションの
bufio.ReadWriter(bufrw) に[hijack-to-bufw]を書き込み、Flush()します。 - 次に、生の
net.Conn(conn) に[hijack-to-conn]を書き込み、Close()します。 - メインのテストゴルーチンは、コネクションが閉じられるのを待ち、その後、
bytes.Bufferに書き込まれた内容が[hijack-to-bufw][hijack-to-conn]となっていることをアサートします。
- モックの
- 検証ポイント:
Hijack()後もbufrwを通じた書き込みが機能し、その内容が基盤のコネクションにフラッシュされること。bufrwのフラッシュ後、connを通じた直接の書き込みも機能すること。- これら2つの書き込みが期待通りの順序で結合されて出力されること。
- 重要性:
Hijack()はHTTPサーバーの制御をアプリケーションに完全に委譲するため、この後のコネクション操作が正しく行われることは、WebSocketサーバーなどのカスタムプロトコル実装において極めて重要です。このテストは、その基本的な動作保証を提供します。
- 目的:
コアとなるコードの変更箇所
変更は src/pkg/net/http/serve_test.go ファイルに集中しています。
-
reqBytes関数の追加:// reqBytes treats req as a request (with \n delimiters) and returns it with \r\n delimiters, // ending in \r\n\r\n func reqBytes(req string) []byte { return []byte(strings.Replace(strings.TrimSpace(req), "\n", "\r\n", -1) + "\r\n\r\n") } -
既存テストでの
reqBytesの利用:TestCloseNotifierChanLeakTestHeaderToWireBenchmarkServerFakeConnNoKeepAliveBenchmarkServerFakeConnWithKeepAliveBenchmarkServerFakeConnWithKeepAliveLitebenchmarkHandlerこれらのテストで、手動でstrings.Replaceを使っていた部分がreqBytesの呼び出しに置き換えられました。
-
TestWriteAfterHijack関数の追加:func TestWriteAfterHijack(t *testing.T) { req := reqBytes("GET / HTTP/1.1\nHost: golang.org") var buf bytes.Buffer wrotec := make(chan bool, 1) conn := &rwTestConn{ Reader: bytes.NewReader(req), Writer: &buf, closec: make(chan bool, 1), } handler := HandlerFunc(func(rw ResponseWriter, r *Request) { conn, bufrw, err := rw.(Hijacker).Hijack() if err != nil { t.Error(err) return } go func() { bufrw.Write([]byte("[hijack-to-bufw]")) bufrw.Flush() conn.Write([]byte("[hijack-to-conn]")) conn.Close() wrotec <- true }() }) ln := &oneConnListener{conn: conn} go Serve(ln, handler) <-conn.closec <-wrotec if g, w := buf.String(), "[hijack-to-bufw][hijack-to-conn]"; g != w { t.Errorf("wrote %q; want %q", g, w) } }
コアとなるコードの解説
reqBytes 関数
この関数は、HTTPリクエストの文字列をバイト列に変換する際の共通処理をカプセル化します。HTTPプロトコルでは行末に \r\n を使用するため、入力文字列の \n をすべて \r\n に置換し、さらにリクエストの終端を示す \r\n\r\n を追加します。これにより、テストコード内でリクエストバイト列を生成する際の冗長な記述を排除し、よりクリーンで読みやすいコードを実現しています。
TestWriteAfterHijack 関数
このテストは、net/http パッケージの Hijacker インターフェースの重要な側面を検証します。
- モックコネクションのセットアップ:
rwTestConnは、テストのためにネットワークコネクションの振る舞いを模倣する構造体です。Writerフィールドにbytes.Bufferを設定することで、ハンドラがコネクションに書き込んだデータをキャプチャし、後で検証できるようにしています。 - ハンドラの定義と
Hijackの呼び出し:http.HandlerFuncが定義され、その中でrw.(Hijacker).Hijack()が呼び出されます。これは、HTTPサーバーがこのコネクションの制御をハンドラに引き渡すことをシミュレートします。 - ゴルーチン内での書き込み:
Hijack()が成功すると、生のnet.Connと、HTTPサーバーが内部的に使用していたbufio.ReadWriterが返されます。テストでは、新しいゴルーチンを起動し、まずbufrwにデータを書き込み、Flush()します。これにより、bufrwにバッファリングされていたデータが基盤のconnに書き込まれます。次に、直接connにデータを書き込みます。 - 結果の検証: テストは、
connが閉じられるのを待ち、その後、bytes.Bufferにキャプチャされた最終的な出力が、bufrwからの書き込みとconnからの書き込みが結合されたものであることをアサートします。この順序と内容の検証は、Hijack後にアプリケーションがコネクションを完全に制御できることを保証するために不可欠です。
このテストは、Hijack 機能が正しく動作し、HTTPサーバーからコネクションの制御を奪った後も、バッファリングされたデータと直接のデータが期待通りに処理されることを確認するものです。これは、WebSocketやその他のカスタムプロトコルをGoの net/http を使って実装する際の基盤となる信頼性を高めます。
関連リンク
- Go Documentation:
net/httppackage: https://pkg.go.dev/net/http - Go Documentation:
http.Hijackerinterface: https://pkg.go.dev/net/http#Hijacker - Go Documentation:
bufiopackage: https://pkg.go.dev/bufio - Go Documentation:
stringspackage: https://pkg.go.dev/strings - Go Documentation:
bytespackage: https://pkg.go.dev/bytes
参考にした情報源リンク
- Go言語の公式ドキュメント
- HTTP/1.1 RFC (RFC 2616, 特にセクション 2.2 Basic Rules)
- Go言語のテストに関する一般的な知識
- Go言語の並行処理(ゴルーチンとチャネル)に関する知識