[インデックス 12204] ファイルの概要
このコミットは、Go言語の標準ライブラリ net/http/cgi
パッケージにおける、CGIアプリケーションからの空のレスポンスに関するバグを修正するものです。具体的には、CGIハンドラが何もデータを書き込まなかった場合に、HTTPレスポンスが適切に送信されない問題を解決します。
コミット
- コミットハッシュ:
d8ccebfffa40b016d9e90713ce0430c37d98175c
- 作者: Russ Cox rsc@golang.org
- コミット日時: 2012年2月24日 金曜日 15:10:46 -0500
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d8ccebfffa40b016d9e90713ce0430c37d98175c
元コミット内容
net/http/cgi: fix empty response
R=golang-dev, gri
CC=golang-dev
https://golang.org/cl/5693058
変更の背景
net/http/cgi
パッケージは、GoのHTTPハンドラをCGI (Common Gateway Interface) プロセスとして実行するための機能を提供します。CGIは、Webサーバーが外部プログラム(この場合はGoプログラム)と通信するための標準的な方法です。CGIプログラムは、標準出力にHTTPヘッダとボディを出力することでWebサーバーに応答します。
このコミットが行われる前は、net/http/cgi
を使用してGoのHTTPハンドラを実行した際に、そのハンドラが http.ResponseWriter
に対して何もデータを書き込まなかった場合(例えば、ステータスコードだけを設定してボディを空にする場合など)、CGIインターフェースを通じてWebサーバーに適切なHTTPレスポンスが送信されないという問題がありました。
具体的には、http.ResponseWriter
の実装は、最初の Write
呼び出し時にHTTPヘッダを書き出すという動作をします。もしハンドラが Write
を一度も呼び出さなかった場合、ヘッダもボディも一切出力されず、CGIサーバーはGoプログラムからの応答がないと判断してしまう可能性がありました。これにより、クライアント側ではタイムアウトや不完全なレスポンスとして扱われるなど、予期せぬ動作を引き起こす可能性がありました。
この修正は、ハンドラが明示的にデータを書き込まなかった場合でも、少なくとも空のレスポンス(ヘッダのみ、または空のボディ)が確実に送信されるようにするために導入されました。
前提知識の解説
CGI (Common Gateway Interface)
CGIは、Webサーバーが外部の実行可能プログラム(CGIスクリプトまたはCGIプログラム)と情報をやり取りするための標準的なプロトコルです。WebサーバーはHTTPリクエストの情報を環境変数や標準入力としてCGIプログラムに渡し、CGIプログラムは処理結果をHTTPヘッダとボディとして標準出力に書き出します。Webサーバーはその標準出力を読み取り、クライアントにHTTPレスポンスとして返します。
Goの net/http
パッケージ
Goの net/http
パッケージは、HTTPクライアントとサーバーの実装を提供します。
http.Handler
インターフェース:ServeHTTP(w http.ResponseWriter, r *http.Request)
メソッドを持ち、HTTPリクエストを処理し、レスポンスをhttp.ResponseWriter
に書き込む役割を担います。http.ResponseWriter
インターフェース: HTTPレスポンスを構築するためのインターフェースです。これを通じて、ステータスコードの設定、ヘッダの追加、レスポンスボディの書き込みが行われます。
http.ResponseWriter
の内部動作とヘッダの書き込み
http.ResponseWriter
の典型的な実装では、WriteHeader
メソッドが明示的に呼び出されるか、または Write
メソッドが最初に呼び出されたときに、保留中のHTTPヘッダが実際にクライアント(またはCGIの場合は標準出力)に書き込まれます。これは、ヘッダがボディの前に送信されなければならないというHTTPプロトコルの要件を満たすためです。
もし Write
が一度も呼び出されず、かつ WriteHeader
も明示的に呼び出されなかった場合、レスポンスヘッダは書き込まれないままになる可能性があります。
bufio.Writer
bufio.Writer
は、I/O操作をバッファリングするためのGoの標準ライブラリです。データを直接書き込むのではなく、内部バッファに一時的に保持し、バッファがいっぱいになったとき、または Flush()
メソッドが呼び出されたときに、まとめて実際のI/Oに書き込みます。これにより、I/Oの回数を減らし、パフォーマンスを向上させることができます。
net/http/cgi
の child.go
では、CGIプログラムの標準出力に書き込む際に bufio.Writer
を使用しており、処理の最後に Flush()
を呼び出してバッファの内容を確実に標準出力に書き出しています。
技術的詳細
この問題の核心は、http.ResponseWriter
の Write
メソッドが、たとえ空のバイトスライス (nil
や []byte{}
) を引数として受け取ったとしても、ヘッダがまだ書き込まれていない場合はヘッダを書き出すという副作用を持つ点にあります。
コミット前のコードでは、handler.ServeHTTP(rw, req)
が呼び出された後、もし handler
が rw.Write()
を一度も呼び出さなかった場合、rw
の内部状態は「ヘッダ未書き込み」のままでした。その後の rw.bufw.Flush()
は、バッファにデータがないため、何も書き出さない可能性がありました。CGIの文脈では、これはWebサーバーがGoプログラムから何の応答も受け取らないことを意味し、エラーやタイムアウトの原因となります。
修正では、handler.ServeHTTP(rw, req)
の直後に rw.Write(nil)
を追加しています。
この rw.Write(nil)
の呼び出しは、以下の効果をもたらします。
- ヘッダの強制書き込み:
rw
(http.ResponseWriter のCGI実装) は、Write
メソッドが呼び出された際に、まだヘッダが書き込まれていなければ、デフォルトのステータスコード (200 OK) と共にヘッダを標準出力に書き出します。nil
を書き込んでもボディデータは送信されませんが、ヘッダの送信はトリガーされます。 - CGIプロトコルへの準拠: CGIプロトコルでは、プログラムは少なくともHTTPヘッダを返すことが期待されます。この修正により、ハンドラがボディを生成しなかった場合でも、最低限の有効なHTTPレスポンス(ヘッダのみ)がCGIサーバーに確実に送信されるようになります。
これにより、CGIアプリケーションが空のレスポンスを返す場合でも、Webサーバーがそれを正しく処理できるようになり、クライアント側でのエラーが回避されます。
コアとなるコードの変更箇所
--- a/src/pkg/net/http/cgi/child.go
+++ b/src/pkg/net/http/cgi/child.go
@@ -144,6 +144,7 @@ func Serve(handler http.Handler) error {
bufw: bufio.NewWriter(os.Stdout),
}
handler.ServeHTTP(rw, req)
+ rw.Write(nil) // make sure a response is sent
if err = rw.bufw.Flush(); err != nil {
return err
}
コアとなるコードの解説
変更は src/pkg/net/http/cgi/child.go
ファイルの Serve
関数内で行われています。
Serve
関数は、Goの http.Handler
をCGI環境で実行するためのエントリポイントです。
rw
(http.ResponseWriter のCGI実装) とreq
(http.Request) が準備されます。handler.ServeHTTP(rw, req)
が呼び出され、実際のHTTPリクエスト処理がハンドラに委譲されます。- 追加された行:
rw.Write(nil)
- この行が追加されたことで、
handler.ServeHTTP
が終了した後、たとえハンドラがrw
に何も書き込まなかったとしても、rw.Write(nil)
が呼び出されます。 - 前述の通り、
http.ResponseWriter
の実装は、最初のWrite
呼び出し時にヘッダを書き出すロジックを持っています。nil
を渡してもボディは書き込まれませんが、ヘッダは確実に標準出力にフラッシュされます。これにより、CGIサーバーは有効なHTTPレスポンス(ヘッダのみ)を受け取ることができます。
- この行が追加されたことで、
if err = rw.bufw.Flush(); err != nil { ... }
- 最後に
bufio.Writer
のFlush()
が呼び出され、バッファに残っているデータ(この修正により、少なくともヘッダ)が確実に標準出力に書き出されます。
- 最後に
この小さな変更により、CGIハンドラが明示的にボディを書き込まない場合でも、CGIプロトコルに準拠した最小限のHTTPレスポンスが保証されるようになりました。
関連リンク
- Go Code Review:
https://golang.org/cl/5693058
参考にした情報源リンク
- Go言語の
net/http
パッケージのドキュメント (当時のバージョンに基づく一般的な知識) - CGI (Common Gateway Interface) の一般的な概念
http.ResponseWriter
のWrite
メソッドの挙動に関する一般的な知識bufio.Writer
のドキュメント