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

[インデックス 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.RequestBody フィールドが常に 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.Bodynil であるかどうかをチェックすることなく、常に Read メソッドを呼び出すことができます。これは、Goのインターフェース設計における「ゼロ値が有用であるべき」という原則にも合致します。

しかし、FastCGI (FCGI) プロトコルを介してリクエストを処理する net/http/fcgi パッケージでは、リクエストボディが存在しない場合に http.Request.Bodynil になる可能性がありました。この不整合は、net/httpServer の動作を前提として書かれたコードが net/http/fcgi 環境で予期せぬエラーを引き起こす原因となっていました。

コミットメッセージで言及されている「Issue 4183」は、この問題に関連するバグ報告であると考えられます。具体的な内容はコミットメッセージからは読み取れませんが、Request.Bodynil であることによって発生する問題がデバッグ中に発見されたことが示唆されています。開発者は、この不整合を解消し、net/http/fcginet/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.Readerio.Closer の両方のインターフェースを満たす型です。
    • io.ReaderRead([]byte) (n int, err error) メソッドを持ち、データを読み取ります。
    • io.CloserClose() error メソッドを持ち、リソースを閉じます。
    • http.Request.Body は、リクエストボディのストリームを表現するためにこのインターフェースを使用します。
  4. io.Pipe():

    • io.Pipe() は、io.Readerio.Writer のペアを返します。これらはメモリ内で接続されており、Writer に書き込まれたデータは Reader から読み取ることができます。
    • このコミットの文脈では、FastCGIリクエストのボディデータを非同期的に処理するために使用されていました。FCGIの入力ストリームから読み取ったデータを PipeWriter に書き込み、その PipeReaderhttp.Request.Body として提供することで、ハンドラがリクエストボディをストリームとして読み取れるようにします。
  5. io/ioutil パッケージ (Go 1.16でioosに統合):

    • ioutil.NopCloser(r io.Reader): io.Reader を受け取り、Close メソッドが何もしない io.ReadCloser を返します。これは、io.Readerio.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.Bodynil にならないように修正されました。

FastCGIプロトコルでは、リクエストボディは STDIN ストリームとして扱われます。net/http/fcgichild.go は、FastCGIリクエストを処理し、それを http.Request オブジェクトに変換して、ユーザーが登録したHTTPハンドラに渡します。

変更前のコードでは、FastCGIリクエストにボディデータが含まれていない場合、body 変数(http.Request.Body に割り当てられる)が nil のままになる可能性がありました。これは、net/httpServer が常に非 nilRequest.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.Readerio.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() が常に適切に呼び出されることが保証されます。emptyBodyClose() は何もしないため、この変更は安全です。

これらの変更により、net/http/fcgi パッケージは net/http パッケージの Server と同様に、http.Request.Body が常に非 nil であることを保証するようになりました。これにより、GoのHTTPハンドラを記述する際の予測可能性と堅牢性が向上し、開発者は Request.Bodynil チェックを省略できるようになります。

コアとなるコードの変更箇所

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のインターフェースとゼロ値の原則に関する情報