[インデックス 15850] ファイルの概要
このコミットは、Go言語の標準ライブラリ src/pkg/net/http/fcgi/child.go
ファイルに対する変更です。
コミット
commit bd21f7f1b59325d128bec1d26074aba75dc18b04
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Wed Mar 20 09:06:33 2013 -0700
net/http/fcgi: Request.Body should always be non-nil
Found this inconsistency from net/http's Server while
debugging Issue 4183
Unfortunately this package lacks testing around this,
or most of child.go. :/
R=golang-dev, adg
CC=golang-dev
https://golang.org/cl/7735046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/bd21f7f1b59325d128bec1d26074aba75dc18b04
元コミット内容
net/http/fcgi: Request.Body should always be non-nil
このコミットは、net/http/fcgi
パッケージにおいて、http.Request
の Body
フィールドが常に nil
ではないことを保証するための変更です。この不整合は、net/http
パッケージの Server
の動作との比較から発見され、Issue 4183 のデバッグ中に明らかになりました。コミットメッセージでは、このパッケージ、特に child.go
のテストが不足していることにも言及されています。
変更の背景
この変更の背景には、Go言語の標準ライブラリにおける net/http
パッケージと net/http/fcgi
パッケージ間の http.Request.Body
の振る舞いの不整合がありました。
通常の net/http
パッケージで提供されるHTTPサーバーでは、クライアントからのリクエストにボディが含まれない場合でも、http.Request.Body
フィールドは nil
ではなく、io.NopCloser
でラップされた空の bytes.Reader
など、読み取り可能な空の io.ReadCloser
が設定されます。これにより、アプリケーションコードは req.Body
が nil
であるかどうかをチェックすることなく、常に Read
メソッドを呼び出すことができます。これは、Goのインターフェース設計における「ゼロ値が有用であるべき」という原則にも合致します。
しかし、FastCGI (FCGI) プロトコルを介してリクエストを処理する net/http/fcgi
パッケージでは、リクエストボディが存在しない場合に http.Request.Body
が nil
になる可能性がありました。この不整合は、net/http
の Server
の動作を前提として書かれたコードが net/http/fcgi
環境で予期せぬエラーを引き起こす原因となっていました。
コミットメッセージで言及されている「Issue 4183」は、この問題に関連するバグ報告であると考えられます。具体的な内容はコミットメッセージからは読み取れませんが、Request.Body
が nil
であることによって発生する問題がデバッグ中に発見されたことが示唆されています。開発者は、この不整合を解消し、net/http/fcgi
が net/http
と同様に Request.Body
を常に非 nil
にすることで、より堅牢で予測可能なAPIを提供しようとしました。
また、コミットメッセージには「Unfortunately this package lacks testing around this, or most of child.go. :/」とあり、この変更がテストカバレッジの不足している領域で行われたことが示されています。これは、将来的なバグを防ぐためにも、このような基本的なAPIの振る舞いを統一することの重要性を強調しています。
前提知識の解説
このコミットを理解するためには、以下の概念について知っておく必要があります。
-
FastCGI (FCGI):
- FastCGIは、Webサーバーとアプリケーションプログラムを接続するためのプロトコルです。CGI (Common Gateway Interface) の後継として開発され、CGIのプロセス起動オーバーヘッドを削減し、パフォーマンスを向上させます。
- FastCGIアプリケーションは、リクエストごとにプロセスを起動するのではなく、永続的なプロセスとして動作し、複数のリクエストを処理できます。WebサーバーはFastCGIプロトコルを使ってアプリケーションと通信します。
- Go言語の
net/http/fcgi
パッケージは、GoアプリケーションをFastCGIサーバーとして動作させるための機能を提供します。
-
net/http
パッケージ:- Go言語の標準ライブラリで、HTTPクライアントとサーバーの実装を提供します。Webアプリケーション開発の基盤となります。
http.Request
構造体は、受信したHTTPリクエストを表します。この構造体には、リクエストメソッド、URL、ヘッダー、そしてリクエストボディなどの情報が含まれます。http.Request.Body
フィールドはio.ReadCloser
インターフェース型です。これは、リクエストボディのデータを読み取るためのRead
メソッドと、リソースを解放するためのClose
メソッドを持つことを意味します。
-
io.ReadCloser
インターフェース:io.Reader
とio.Closer
の両方のインターフェースを満たす型です。io.Reader
はRead([]byte) (n int, err error)
メソッドを持ち、データを読み取ります。io.Closer
はClose() error
メソッドを持ち、リソースを閉じます。http.Request.Body
は、リクエストボディのストリームを表現するためにこのインターフェースを使用します。
-
io.Pipe()
:io.Pipe()
は、io.Reader
とio.Writer
のペアを返します。これらはメモリ内で接続されており、Writer
に書き込まれたデータはReader
から読み取ることができます。- このコミットの文脈では、FastCGIリクエストのボディデータを非同期的に処理するために使用されていました。FCGIの入力ストリームから読み取ったデータを
PipeWriter
に書き込み、そのPipeReader
をhttp.Request.Body
として提供することで、ハンドラがリクエストボディをストリームとして読み取れるようにします。
-
io/ioutil
パッケージ (Go 1.16でio
とos
に統合):ioutil.NopCloser(r io.Reader)
:io.Reader
を受け取り、Close
メソッドが何もしないio.ReadCloser
を返します。これは、io.Reader
をio.ReadCloser
が必要な場所に渡す際に便利です。- このコミットでは、空のリクエストボディを表すために
io.Reader
(具体的にはstrings.NewReader("")
) をio.ReadCloser
に変換するために使用されています。
-
strings.NewReader(s string)
:- 文字列
s
を読み取るio.Reader
を返します。このコミットでは、空の文字列""
を読み取るリーダーを作成し、それを空のリクエストボディとして使用しています。
- 文字列
これらの概念を理解することで、コミットがなぜ行われたのか、そしてコードがどのように変更されたのかを深く把握することができます。特に、http.Request.Body
が常に非 nil
であるべきという設計原則は、GoのAPIの使いやすさと堅牢性を高める上で重要です。
技術的詳細
このコミットの技術的な核心は、net/http/fcgi
パッケージが http.Request.Body
を扱う方法を、net/http
パッケージの標準的な振る舞いに合わせることにあります。具体的には、リクエストボディが存在しない場合でも http.Request.Body
が nil
にならないように修正されました。
FastCGIプロトコルでは、リクエストボディは STDIN
ストリームとして扱われます。net/http/fcgi
の child.go
は、FastCGIリクエストを処理し、それを http.Request
オブジェクトに変換して、ユーザーが登録したHTTPハンドラに渡します。
変更前のコードでは、FastCGIリクエストにボディデータが含まれていない場合、body
変数(http.Request.Body
に割り当てられる)が nil
のままになる可能性がありました。これは、net/http
の Server
が常に非 nil
の Request.Body
を提供する(たとえそれが空であっても)という期待と矛盾します。この不整合は、req.Body.Read()
や req.Body.Close()
を呼び出す前に req.Body != nil
のチェックを怠ったアプリケーションコードでパニックやエラーを引き起こす可能性がありました。
この問題を解決するために、以下の変更が導入されました。
-
emptyBody
変数の導入:var emptyBody = ioutil.NopCloser(strings.NewReader(""))
- この行は、グローバル変数
emptyBody
を定義しています。これは、io.ReadCloser
型で、中身が空の読み取り専用ストリームを表します。 strings.NewReader("")
は、空の文字列を読み取るio.Reader
を作成します。ioutil.NopCloser()
は、このio.Reader
をio.ReadCloser
にラップします。Close()
メソッドが呼び出されても何も行わないため、リソースの解放を心配する必要がありません。- この
emptyBody
は、リクエストボディが存在しない場合にhttp.Request.Body
に割り当てられることになります。
-
body
割り当てロジックの変更:- 変更前は、リクエストボディが存在する場合にのみ
body, req.pw = io.Pipe()
が実行され、それ以外の場合はbody
が初期化されないままでした。 - 変更後は、
else
ブロックが追加され、リクエストボディが存在しない場合にbody = emptyBody
が明示的に割り当てられるようになりました。これにより、body
変数は常に有効なio.ReadCloser
インスタンスを持つことが保証されます。
- 変更前は、リクエストボディが存在する場合にのみ
-
body.Close()
の無条件呼び出し:- 変更前は、
if body != nil { body.Close() }
という条件付きのClose()
呼び出しがありました。 - 変更後は、
body
が常に非nil
であることが保証されるため、body.Close()
が無条件に呼び出されるようになりました。これにより、コードが簡素化され、Close()
が常に適切に呼び出されることが保証されます。emptyBody
のClose()
は何もしないため、この変更は安全です。
- 変更前は、
これらの変更により、net/http/fcgi
パッケージは net/http
パッケージの Server
と同様に、http.Request.Body
が常に非 nil
であることを保証するようになりました。これにより、GoのHTTPハンドラを記述する際の予測可能性と堅牢性が向上し、開発者は Request.Body
の nil
チェックを省略できるようになります。
コアとなるコードの変更箇所
diff --git a/src/pkg/net/http/fcgi/child.go b/src/pkg/net/http/fcgi/child.go
index e647f9391e..f36abbcca3 100644
--- a/src/pkg/net/http/fcgi/child.go
+++ b/src/pkg/net/http/fcgi/child.go
@@ -10,10 +10,12 @@ import (
"errors"
"fmt"
"io"
+ "io/ioutil"
"net"
"net/http"
"net/http/cgi"
"os"
+ "strings"
"time"
)
@@ -152,6 +154,8 @@ func (c *child) serve() {
var errCloseConn = errors.New("fcgi: connection should be closed")
+var emptyBody = ioutil.NopCloser(strings.NewReader(""))
+
func (c *child) handleRecord(rec *record) error {
req, ok := c.requests[rec.h.Id]
if !ok && rec.h.Type != typeBeginRequest && rec.h.Type != typeGetValues {
@@ -191,6 +195,8 @@ func (c *child) handleRecord(rec *record) error {\n // body could be an io.LimitReader, but it shouldn't matter\n // as long as both sides are behaving.\n body, req.pw = io.Pipe()\n+ } else {\n+ body = emptyBody\n }\n go c.serveRequest(req, body)\n }\n@@ -232,9 +238,7 @@ func (c *child) serveRequest(req *request, body io.ReadCloser) {\n httpReq.Body = body\n c.handler.ServeHTTP(r, httpReq)\n }\n- if body != nil {\n- body.Close()\n- }\n+ body.Close()\n r.Close()\n c.conn.writeEndRequest(req.reqId, 0, statusRequestComplete)\n if !req.keepConn {\n```
## コアとなるコードの解説
このコミットにおける主要なコード変更は、`src/pkg/net/http/fcgi/child.go` ファイル内の3つの部分に集中しています。
1. **インポートの追加**:
```diff
--- a/src/pkg/net/http/fcgi/child.go
+++ b/src/pkg/net/http/fcgi/child.go
@@ -10,10 +10,12 @@ import (
"errors"
"fmt"
"io"
+ "io/ioutil"
"net"
"net/http"
"net/http/cgi"
"os"
+ "strings"
"time"
)
```
* `io/ioutil` と `strings` パッケージが新しくインポートされています。
* `io/ioutil` は `ioutil.NopCloser` を使用するために必要です。
* `strings` は `strings.NewReader` を使用するために必要です。これらは、空のボディを表す `emptyBody` 変数を定義するために導入されました。
2. **`emptyBody` グローバル変数の定義**:
```diff
--- a/src/pkg/net/http/fcgi/child.go
+++ b/src/pkg/net/http/fcgi/child.go
@@ -152,6 +154,8 @@ func (c *child) serve() {
var errCloseConn = errors.New("fcgi: connection should be closed")
+var emptyBody = ioutil.NopCloser(strings.NewReader(""))
+
func (c *child) handleRecord(rec *record) error {
req, ok := c.requests[rec.h.Id]
if !ok && rec.h.Type != typeBeginRequest && rec.h.Type != typeGetValues {
```
* `var emptyBody = ioutil.NopCloser(strings.NewReader(""))` という行が追加されました。
* これは、リクエストボディが存在しない場合に `http.Request.Body` に割り当てるための、空の `io.ReadCloser` インスタンスを定義しています。
* `strings.NewReader("")` は、空の文字列を読み取る `io.Reader` を作成します。
* `ioutil.NopCloser` は、この `io.Reader` を `io.ReadCloser` インターフェースに適合させます。`NopCloser` は `Close()` メソッドが呼び出されても何もしないため、リソースリークの心配がありません。
3. **`handleRecord` 関数内の `body` 割り当てロジックの変更**:
```diff
--- a/src/pkg/net/http/fcgi/child.go
+++ b/src/pkg/net/http/fcgi/child.go
@@ -191,6 +195,8 @@ func (c *child) handleRecord(rec *record) error {\n // body could be an io.LimitReader, but it shouldn't matter\n // as long as both sides are behaving.\n body, req.pw = io.Pipe()\n + } else {\n + body = emptyBody\n }\n go c.serveRequest(req, body)\n }\n ```
* このコードブロックは、FastCGIリクエストの `FCGI_STDIN` ストリームの処理に関連しています。
* 変更前は、`FCGI_STDIN` の長さが0より大きい(つまりボディデータが存在する)場合にのみ `body, req.pw = io.Pipe()` が実行され、`body` が初期化されていました。ボディが存在しない場合、`body` は `nil` のままでした。
* 追加された `else { body = emptyBody }` ブロックにより、ボディデータが存在しない場合でも、`body` 変数に `emptyBody` が明示的に割り当てられるようになりました。これにより、`body` は常に有効な `io.ReadCloser` インスタンスを持つことが保証されます。
4. **`serveRequest` 関数内の `body.Close()` 呼び出しの変更**:
```diff
--- a/src/pkg/net/http/fcgi/child.go
+++ b/src/pkg/net/http/fcgi/child.go
@@ -232,9 +238,7 @@ func (c *child) serveRequest(req *request, body io.ReadCloser) {\n httpReq.Body = body\n c.handler.ServeHTTP(r, httpReq)\n }\n - if body != nil {\n - body.Close()\n - }\n + body.Close()\n r.Close()\n c.conn.writeEndRequest(req.reqId, 0, statusRequestComplete)\n if !req.keepConn {\n ```
* 変更前は、`body` が `nil` でない場合にのみ `body.Close()` が呼び出されていました。
* 変更後は、`body` が常に非 `nil` であることが保証されるため、条件分岐が削除され、`body.Close()` が無条件に呼び出されるようになりました。これにより、コードが簡潔になり、リソースのクリーンアップが常に適切に行われることが保証されます。`emptyBody` の `Close()` メソッドは何もしないため、この変更は安全です。
これらの変更により、`net/http/fcgi` パッケージは `http.Request.Body` の振る舞いを `net/http` パッケージの標準的な期待に合わせ、より堅牢で予測可能なAPIを提供できるようになりました。
## 関連リンク
* Go Issue 4183 (関連する可能性のあるGoのIssueトラッカー): [https://github.com/golang/go/issues/4183](https://github.com/golang/go/issues/4183) (このコミットの直接のリンクではありませんが、コミットメッセージで言及されているため関連性が高いです)
* Go CL 7735046 (このコミットに対応するGerritの変更リスト): [https://golang.org/cl/7735046](https://golang.org/cl/7735046)
## 参考にした情報源リンク
* Go言語の公式ドキュメント:
* `net/http` パッケージ: [https://pkg.go.dev/net/http](https://pkg.go.dev/net/http)
* `net/http/fcgi` パッケージ: [https://pkg.go.dev/net/http/fcgi](https://pkg.go.dev/net/http/fcgi)
* `io` パッケージ: [https://pkg.go.dev/io](https://pkg.go.dev/io)
* `io/ioutil` パッケージ (Go 1.16以降は `io` と `os` に統合): [https://pkg.go.dev/io/ioutil](https://pkg.go.dev/io/ioutil)
* `strings` パッケージ: [https://pkg.go.dev/strings](https://pkg.go.dev/strings)
* FastCGIの概要: [https://ja.wikipedia.org/wiki/FastCGI](https://ja.wikipedia.org/wiki/FastCGI)
* Go言語における `http.Request.Body` の扱いに関する一般的な情報源 (例: Stack Overflow, Goブログなど)
* `http.Request.Body` が `nil` にならない理由に関する議論: [https://stackoverflow.com/questions/28322055/why-is-http-request-body-never-nil](https://stackoverflow.com/questions/28322055/why-is-http-request-body-never-nil)
* Goのインターフェースとゼロ値の原則に関する情報# [インデックス 15850] ファイルの概要
このコミットは、Go言語の標準ライブラリ `src/pkg/net/http/fcgi/child.go` ファイルに対する変更です。
## コミット
commit bd21f7f1b59325d128bec1d26074aba75dc18b04 Author: Brad Fitzpatrick bradfitz@golang.org Date: Wed Mar 20 09:06:33 2013 -0700
net/http/fcgi: Request.Body should always be non-nil
Found this inconsistency from net/http's Server while
debugging Issue 4183
Unfortunately this package lacks testing around this,
or most of child.go. :/
R=golang-dev, adg
CC=golang-dev
https://golang.org/cl/7735046
## GitHub上でのコミットページへのリンク
[https://github.com/golang/go/commit/bd21f7f1b59325d128bec1d26074aba75dc18b04](https://github.com/golang/go/commit/bd21f7f1b59325d128bec1d26074aba75dc18b04)
## 元コミット内容
`net/http/fcgi: Request.Body should always be non-nil`
このコミットは、`net/http/fcgi` パッケージにおいて、`http.Request` の `Body` フィールドが常に `nil` ではないことを保証するための変更です。この不整合は、`net/http` パッケージの `Server` の動作との比較から発見され、Issue 4183 のデバッグ中に明らかになりました。コミットメッセージでは、このパッケージ、特に `child.go` のテストが不足していることにも言及されています。
## 変更の背景
この変更の背景には、Go言語の標準ライブラリにおける `net/http` パッケージと `net/http/fcgi` パッケージ間の `http.Request.Body` の振る舞いの不整合がありました。
通常の `net/http` パッケージで提供されるHTTPサーバーでは、クライアントからのリクエストにボディが含まれない場合でも、`http.Request.Body` フィールドは `nil` ではなく、`io.NopCloser` でラップされた空の `bytes.Reader` など、読み取り可能な空の `io.ReadCloser` が設定されます。これにより、アプリケーションコードは `req.Body` が `nil` であるかどうかをチェックすることなく、常に `Read` メソッドを呼び出すことができます。これは、Goのインターフェース設計における「ゼロ値が有用であるべき」という原則にも合致します。
しかし、FastCGI (FCGI) プロトコルを介してリクエストを処理する `net/http/fcgi` パッケージでは、リクエストボディが存在しない場合に `http.Request.Body` が `nil` になる可能性がありました。この不整合は、`net/http` の `Server` の動作を前提として書かれたコードが `net/http/fcgi` 環境で予期せぬエラーを引き起こす原因となっていました。
コミットメッセージで言及されている「Issue 4183」は、この問題に関連するバグ報告であると考えられます。具体的な内容はコミットメッセージからは読み取れませんが、`Request.Body` が `nil` であることによって発生する問題がデバッグ中に発見されたことが示唆されています。開発者は、この不整合を解消し、`net/http/fcgi` が `net/http` と同様に `Request.Body` を常に非 `nil` にすることで、より堅牢で予測可能なAPIを提供しようとしました。
また、コミットメッセージには「Unfortunately this package lacks testing around this, or most of child.go. :/」とあり、この変更がテストカバレッジの不足している領域で行われたことが示されています。これは、将来的なバグを防ぐためにも、このような基本的なAPIの振る舞いを統一することの重要性を強調しています。
## 前提知識の解説
このコミットを理解するためには、以下の概念について知っておく必要があります。
1. **FastCGI (FCGI)**:
* FastCGIは、Webサーバーとアプリケーションプログラムを接続するためのプロトコルです。CGI (Common Gateway Interface) の後継として開発され、CGIのプロセス起動オーバーヘッドを削減し、パフォーマンスを向上させます。
* FastCGIアプリケーションは、リクエストごとにプロセスを起動するのではなく、永続的なプロセスとして動作し、複数のリクエストを処理できます。WebサーバーはFastCGIプロトコルを使ってアプリケーションと通信します。
* Go言語の `net/http/fcgi` パッケージは、GoアプリケーションをFastCGIサーバーとして動作させるための機能を提供します。
2. **`net/http` パッケージ**:
* Go言語の標準ライブラリで、HTTPクライアントとサーバーの実装を提供します。Webアプリケーション開発の基盤となります。
* `http.Request` 構造体は、受信したHTTPリクエストを表します。この構造体には、リクエストメソッド、URL、ヘッダー、そしてリクエストボディなどの情報が含まれます。
* `http.Request.Body` フィールドは `io.ReadCloser` インターフェース型です。これは、リクエストボディのデータを読み取るための `Read` メソッドと、リソースを解放するための `Close` メソッドを持つことを意味します。
3. **`io.ReadCloser` インターフェース**:
* `io.Reader` と `io.Closer` の両方のインターフェースを満たす型です。
* `io.Reader` は `Read([]byte) (n int, err error)` メソッドを持ち、データを読み取ります。
* `io.Closer` は `Close() error` メソッドを持ち、リソースを閉じます。
* `http.Request.Body` は、リクエストボディのストリームを表現するためにこのインターフェースを使用します。
4. **`io.Pipe()`**:
* `io.Pipe()` は、`io.Reader` と `io.Writer` のペアを返します。これらはメモリ内で接続されており、`Writer` に書き込まれたデータは `Reader` から読み取ることができます。
* このコミットの文脈では、FastCGIリクエストのボディデータを非同期的に処理するために使用されていました。FCGIの入力ストリームから読み取ったデータを `PipeWriter` に書き込み、その `PipeReader` を `http.Request.Body` として提供することで、ハンドラがリクエストボディをストリームとして読み取れるようにします。
5. **`io/ioutil` パッケージ (Go 1.16で`io`と`os`に統合)**:
* `ioutil.NopCloser(r io.Reader)`: `io.Reader` を受け取り、`Close` メソッドが何もしない `io.ReadCloser` を返します。これは、`io.Reader` を `io.ReadCloser` が必要な場所に渡す際に便利です。
* このコミットでは、空のリクエストボディを表すために `io.Reader` (具体的には `strings.NewReader("")`) を `io.ReadCloser` に変換するために使用されています。
6. **`strings.NewReader(s string)`**:
* 文字列 `s` を読み取る `io.Reader` を返します。このコミットでは、空の文字列 `""` を読み取るリーダーを作成し、それを空のリクエストボディとして使用しています。
これらの概念を理解することで、コミットがなぜ行われたのか、そしてコードがどのように変更されたのかを深く把握することができます。特に、`http.Request.Body` が常に非 `nil` であるべきという設計原則は、GoのAPIの使いやすさと堅牢性を高める上で重要です。
## 技術的詳細
このコミットの技術的な核心は、`net/http/fcgi` パッケージが `http.Request.Body` を扱う方法を、`net/http` パッケージの標準的な振る舞いに合わせることにあります。具体的には、リクエストボディが存在しない場合でも `http.Request.Body` が `nil` にならないように修正されました。
FastCGIプロトコルでは、リクエストボディは `STDIN` ストリームとして扱われます。`net/http/fcgi` の `child.go` は、FastCGIリクエストを処理し、それを `http.Request` オブジェクトに変換して、ユーザーが登録したHTTPハンドラに渡します。
変更前のコードでは、FastCGIリクエストにボディデータが含まれていない場合、`body` 変数(`http.Request.Body` に割り当てられる)が `nil` のままになる可能性がありました。これは、`net/http` の `Server` が常に非 `nil` の `Request.Body` を提供する(たとえそれが空であっても)という期待と矛盾します。この不整合は、`req.Body.Read()` や `req.Body.Close()` を呼び出す前に `req.Body != nil` のチェックを怠ったアプリケーションコードでパニックやエラーを引き起こす可能性がありました。
この問題を解決するために、以下の変更が導入されました。
1. **`emptyBody` 変数の導入**:
* `var emptyBody = ioutil.NopCloser(strings.NewReader(""))`
* この行は、グローバル変数 `emptyBody` を定義しています。これは、`io.ReadCloser` 型で、中身が空の読み取り専用ストリームを表します。
* `strings.NewReader("")` は、空の文字列を読み取る `io.Reader` を作成します。
* `ioutil.NopCloser()` は、この `io.Reader` を `io.ReadCloser` にラップします。`Close()` メソッドが呼び出されても何も行わないため、リソースの解放を心配する必要がありません。
* この `emptyBody` は、リクエストボディが存在しない場合に `http.Request.Body` に割り当てられることになります。
2. **`body` 割り当てロジックの変更**:
* 変更前は、リクエストボディが存在する場合にのみ `body, req.pw = io.Pipe()` が実行され、それ以外の場合は `body` が初期化されないままでした。
* 変更後は、`else` ブロックが追加され、リクエストボディが存在しない場合に `body = emptyBody` が明示的に割り当てられるようになりました。これにより、`body` 変数は常に有効な `io.ReadCloser` インスタンスを持つことが保証されます。
3. **`body.Close()` の無条件呼び出し**:
* 変更前は、`if body != nil { body.Close() }` という条件付きの `Close()` 呼び出しがありました。
* 変更後は、`body` が常に非 `nil` であることが保証されるため、`body.Close()` が無条件に呼び出されるようになりました。これにより、コードが簡素化され、`Close()` が常に適切に呼び出されることが保証されます。`emptyBody` の `Close()` は何もしないため、この変更は安全です。
これらの変更により、`net/http/fcgi` パッケージは `net/http` パッケージの `Server` と同様に、`http.Request.Body` が常に非 `nil` であることを保証するようになりました。これにより、GoのHTTPハンドラを記述する際の予測可能性と堅牢性が向上し、開発者は `Request.Body` の `nil` チェックを省略できるようになります。
## コアとなるコードの変更箇所
```diff
diff --git a/src/pkg/net/http/fcgi/child.go b/src/pkg/net/http/fcgi/child.go
index e647f9391e..f36abbcca3 100644
--- a/src/pkg/net/http/fcgi/child.go
+++ b/src/pkg/net/http/fcgi/child.go
@@ -10,10 +10,12 @@ import (
"errors"
"fmt"
"io"
+ "io/ioutil"
"net"
"net/http"
"net/http/cgi"
"os"
+ "strings"
"time"
)
@@ -152,6 +154,8 @@ func (c *child) serve() {
var errCloseConn = errors.New("fcgi: connection should be closed")
+var emptyBody = ioutil.NopCloser(strings.NewReader(""))
+
func (c *child) handleRecord(rec *record) error {
req, ok := c.requests[rec.h.Id]
if !ok && rec.h.Type != typeBeginRequest && rec.h.Type != typeGetValues {
@@ -191,6 +195,8 @@ func (c *child) handleRecord(rec *record) error {\n // body could be an io.LimitReader, but it shouldn't matter\n // as long as both sides are behaving.\n body, req.pw = io.Pipe()\n+ } else {\n+ body = emptyBody\n }\n go c.serveRequest(req, body)\n }\n@@ -232,9 +238,7 @@ func (c *child) serveRequest(req *request, body io.ReadCloser) {\n httpReq.Body = body\n c.handler.ServeHTTP(r, httpReq)\n }\n- if body != nil {\n- body.Close()\n- }\n+ body.Close()\n r.Close()\n c.conn.writeEndRequest(req.reqId, 0, statusRequestComplete)\n if !req.keepConn {\n```
## コアとなるコードの解説
このコミットにおける主要なコード変更は、`src/pkg/net/http/fcgi/child.go` ファイル内の3つの部分に集中しています。
1. **インポートの追加**:
```diff
--- a/src/pkg/net/http/fcgi/child.go
+++ b/src/pkg/net/http/fcgi/child.go
@@ -10,10 +10,12 @@ import (
"errors"
"fmt"
"io"
+ "io/ioutil"
"net"
"net/http"
"net/http/cgi"
"os"
+ "strings"
"time"
)
```
* `io/ioutil` と `strings` パッケージが新しくインポートされています。
* `io/ioutil` は `ioutil.NopCloser` を使用するために必要です。
* `strings` は `strings.NewReader` を使用するために必要です。これらは、空のボディを表す `emptyBody` 変数を定義するために導入されました。
2. **`emptyBody` グローバル変数の定義**:
```diff
--- a/src/pkg/net/http/fcgi/child.go
+++ b/src/pkg/net/http/fcgi/child.go
@@ -152,6 +154,8 @@ func (c *child) serve() {
var errCloseConn = errors.New("fcgi: connection should be closed")
+var emptyBody = ioutil.NopCloser(strings.NewReader(""))
+
func (c *child) handleRecord(rec *record) error {
req, ok := c.requests[rec.h.Id]
if !ok && rec.h.Type != typeBeginRequest && rec.h.Type != typeGetValues {
```
* `var emptyBody = ioutil.NopCloser(strings.NewReader(""))` という行が追加されました。
* これは、リクエストボディが存在しない場合に `http.Request.Body` に割り当てるための、空の `io.ReadCloser` インスタンスを定義しています。
* `strings.NewReader("")` は、空の文字列を読み取る `io.Reader` を作成します。
* `ioutil.NopCloser` は、この `io.Reader` を `io.ReadCloser` インターフェースに適合させます。`NopCloser` は `Close()` メソッドが呼び出されても何もしないため、リソースリークの心配がありません。
3. **`handleRecord` 関数内の `body` 割り当てロジックの変更**:
```diff
--- a/src/pkg/net/http/fcgi/child.go
+++ b/src/pkg/net/http/fcgi/child.go
@@ -191,6 +195,8 @@ func (c *child) handleRecord(rec *record) error {\n // body could be an io.LimitReader, but it shouldn't matter\n // as long as both sides are behaving.\n body, req.pw = io.Pipe()\n + } else {\n + body = emptyBody\n }\n go c.serveRequest(req, body)\n }\n ```
* このコードブロックは、FastCGIリクエストの `FCGI_STDIN` ストリームの処理に関連しています。
* 変更前は、`FCGI_STDIN` の長さが0より大きい(つまりボディデータが存在する)場合にのみ `body, req.pw = io.Pipe()` が実行され、`body` が初期化されていました。ボディが存在しない場合、`body` は `nil` のままでした。
* 追加された `else { body = emptyBody }` ブロックにより、ボディデータが存在しない場合でも、`body` 変数に `emptyBody` が明示的に割り当てられるようになりました。これにより、`body` は常に有効な `io.ReadCloser` インスタンスを持つことが保証されます。
4. **`serveRequest` 関数内の `body.Close()` 呼び出しの変更**:
```diff
--- a/src/pkg/net/http/fcgi/child.go
+++ b/src/pkg/net/http/fcgi/child.go
@@ -232,9 +238,7 @@ func (c *child) serveRequest(req *request, body io.ReadCloser) {\n httpReq.Body = body\n c.handler.ServeHTTP(r, httpReq)\n }\n- if body != nil {\n- body.Close()\n- }\n+ body.Close()\n r.Close()\n c.conn.writeEndRequest(req.reqId, 0, statusRequestComplete)\n if !req.keepConn {\n ```
* 変更前は、`body` が `nil` でない場合にのみ `body.Close()` が呼び出されていました。
* 変更後は、`body` が常に非 `nil` であることが保証されるため、条件分岐が削除され、`body.Close()` が無条件に呼び出されるようになりました。これにより、コードが簡潔になり、リソースのクリーンアップが常に適切に行われることが保証されます。`emptyBody` の `Close()` メソッドは何もしないため、この変更は安全です。
これらの変更により、`net/http/fcgi` パッケージは `http.Request.Body` の振る舞いを `net/http` パッケージの標準的な期待に合わせ、より堅牢で予測可能なAPIを提供できるようになりました。
## 関連リンク
* Go CL 7735046 (このコミットに対応するGerritの変更リスト): [https://golang.org/cl/7735046](https://golang.org/cl/7735046)
* コミットメッセージで言及されている「Issue 4183」は、Go言語の公式リポジトリでは確認できませんでした。
## 参考にした情報源リンク
* Go言語の公式ドキュメント:
* `net/http` パッケージ: [https://pkg.go.dev/net/http](https://pkg.go.dev/net/http)
* `net/http/fcgi` パッケージ: [https://pkg.go.dev/net/http/fcgi](https://pkg.go.dev/net/http/fcgi)
* `io` パッケージ: [https://pkg.go.dev/io](https://pkg.go.dev/io)
* `io/ioutil` パッケージ (Go 1.16以降は `io` と `os` に統合): [https://pkg.go.dev/io/ioutil](https://pkg.go.dev/io/ioutil)
* `strings` パッケージ: [https://pkg.go.dev/strings](https://pkg.go.dev/strings)
* FastCGIの概要: [https://ja.wikipedia.org/wiki/FastCGI](https://ja.wikipedia.org/wiki/FastCGI)
* Go言語における `http.Request.Body` の扱いに関する一般的な情報源 (例: Stack Overflow, Goブログなど)
* `http.Request.Body` が `nil` にならない理由に関する議論: [https://stackoverflow.com/questions/28322055/why-is-http-request-body-never-nil](https://stackoverflow.com/questions/28322055/why-is-http-request-body-never-nil)
* Goのインターフェースとゼロ値の原則に関する情報