Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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/cgichild.go では、CGIプログラムの標準出力に書き込む際に bufio.Writer を使用しており、処理の最後に Flush() を呼び出してバッファの内容を確実に標準出力に書き出しています。

技術的詳細

この問題の核心は、http.ResponseWriterWrite メソッドが、たとえ空のバイトスライス (nil[]byte{}) を引数として受け取ったとしても、ヘッダがまだ書き込まれていない場合はヘッダを書き出すという副作用を持つ点にあります。

コミット前のコードでは、handler.ServeHTTP(rw, req) が呼び出された後、もし handlerrw.Write() を一度も呼び出さなかった場合、rw の内部状態は「ヘッダ未書き込み」のままでした。その後の rw.bufw.Flush() は、バッファにデータがないため、何も書き出さない可能性がありました。CGIの文脈では、これはWebサーバーがGoプログラムから何の応答も受け取らないことを意味し、エラーやタイムアウトの原因となります。

修正では、handler.ServeHTTP(rw, req) の直後に rw.Write(nil) を追加しています。 この rw.Write(nil) の呼び出しは、以下の効果をもたらします。

  1. ヘッダの強制書き込み: rw (http.ResponseWriter のCGI実装) は、Write メソッドが呼び出された際に、まだヘッダが書き込まれていなければ、デフォルトのステータスコード (200 OK) と共にヘッダを標準出力に書き出します。nil を書き込んでもボディデータは送信されませんが、ヘッダの送信はトリガーされます。
  2. 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環境で実行するためのエントリポイントです。

  1. rw (http.ResponseWriter のCGI実装) と req (http.Request) が準備されます。
  2. handler.ServeHTTP(rw, req) が呼び出され、実際のHTTPリクエスト処理がハンドラに委譲されます。
  3. 追加された行: rw.Write(nil)
    • この行が追加されたことで、handler.ServeHTTP が終了した後、たとえハンドラが rw に何も書き込まなかったとしても、rw.Write(nil) が呼び出されます。
    • 前述の通り、http.ResponseWriter の実装は、最初の Write 呼び出し時にヘッダを書き出すロジックを持っています。nil を渡してもボディは書き込まれませんが、ヘッダは確実に標準出力にフラッシュされます。これにより、CGIサーバーは有効なHTTPレスポンス(ヘッダのみ)を受け取ることができます。
  4. if err = rw.bufw.Flush(); err != nil { ... }
    • 最後に bufio.WriterFlush() が呼び出され、バッファに残っているデータ(この修正により、少なくともヘッダ)が確実に標準出力に書き出されます。

この小さな変更により、CGIハンドラが明示的にボディを書き込まない場合でも、CGIプロトコルに準拠した最小限のHTTPレスポンスが保証されるようになりました。

関連リンク

  • Go Code Review: https://golang.org/cl/5693058

参考にした情報源リンク

  • Go言語の net/http パッケージのドキュメント (当時のバージョンに基づく一般的な知識)
  • CGI (Common Gateway Interface) の一般的な概念
  • http.ResponseWriterWrite メソッドの挙動に関する一般的な知識
  • bufio.Writer のドキュメント