[インデックス 10237] ファイルの概要
このコミットは、Go言語の標準ライブラリであるnet/http
パッケージのリファクタリングの一環として、ClientConn
とServerConn
というHTTP永続的接続を管理する構造体を、新しく作成されたnet/http/httputil
パッケージに移動するものです。これにより、net/http
パッケージの主要部分がよりシンプルになり、利用頻度の低い、あるいは新規ユーザーにとって誤解を招きやすい機能が分離されます。
コミット
commit a7f1141dee05bf889b64401c787f08e1c8643e88
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Thu Nov 3 14:44:29 2011 -0700
net/http/httputil: new package; move ClientConn and ServerConn
Part of http diet plan.
More of the lesser-used and newcomer-misleading parts of http will
move here.
R=r, rsc
CC=golang-dev
https://golang.org/cl/5336049
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a7f1141dee05bf889b64401c787f08e1c8643e88
元コミット内容
net/http/httputil
という新しいパッケージを作成し、ClientConn
とServerConn
をそこへ移動しました。これは「httpダイエット計画」の一環です。net/http
パッケージの利用頻度が低く、新規ユーザーにとって誤解を招きやすい部分が、今後このパッケージに移動される予定です。
変更の背景
このコミットは、Go言語のnet/http
パッケージにおける「httpダイエット計画 (http diet plan)」と呼ばれる大規模なリファクタリングの一環として行われました。当時のnet/http
パッケージは、HTTPプロトコルの基本的な機能に加えて、より低レベルな接続管理やユーティリティ的な機能も含まれており、パッケージの肥大化や複雑化が懸念されていました。
「httpダイエット計画」の目的は、net/http
パッケージをよりシンプルで使いやすいものにし、HTTPのコア機能に焦点を当てることでした。そのために、以下のような方針が取られました。
- 機能の分離: 利用頻度が低い、あるいは特定の高度なユースケースにのみ必要な機能を、別のユーティリティパッケージ(
httputil
など)に移動する。 - APIの明確化: 新規ユーザーが混乱しやすいAPIや、誤用されやすいAPIを整理・再設計する。
- パッケージの軽量化:
net/http
パッケージ自体の依存関係を減らし、コンパイル時間やバイナリサイズを最適化する。
このコミットでは、HTTPの永続的接続(Keep-Alive)やパイプライン処理を内部的に管理するClientConn
とServerConn
が、その対象となりました。これらの構造体は、HTTPクライアントやサーバーの低レベルな実装に深く関わるものであり、一般的なHTTPリクエスト/レスポンスの処理を行うユーザーにとっては直接触れる機会が少ないため、httputil
パッケージへの移動が適切と判断されました。
前提知識の解説
Go言語のnet/http
パッケージ
Go言語の標準ライブラリであるnet/http
パッケージは、HTTPクライアントとサーバーを構築するための包括的な機能を提供します。Webアプリケーションのバックエンド、RESTful API、Webサーバーなど、HTTP通信を扱うほとんどのGoアプリケーションで利用されます。
http.Client
: HTTPリクエストを送信し、レスポンスを受信するクライアント機能を提供します。http.Server
: HTTPリクエストを受け付け、レスポンスを返すサーバー機能を提供します。http.Request
: HTTPリクエストを表す構造体です。メソッド、URL、ヘッダー、ボディなどの情報を含みます。http.Response
: HTTPレスポンスを表す構造体です。ステータスコード、ヘッダー、ボディなどの情報を含みます。
HTTP永続的接続 (Persistent Connections / Keep-Alive)
HTTP/1.1では、デフォルトで永続的接続が有効になっています。これは、一度確立されたTCP接続を複数のHTTPリクエスト/レスポンスの送受信に再利用する仕組みです。これにより、接続の確立・切断にかかるオーバーヘッドが削減され、Webページのロード時間短縮やサーバー負荷軽減に貢献します。
HTTPパイプライン (Pipelining)
HTTPパイプラインは、永続的接続上で複数のHTTPリクエストを連続して送信し、それらのレスポンスを順不同で受信する前に、リクエストを送信し続けることができる技術です。これにより、ネットワークのラウンドトリップタイム(RTT)の影響を軽減し、通信効率を向上させることができます。ただし、HTTP/1.1のパイプラインはHead-of-Line Blockingの問題を抱えており、HTTP/2ではより効率的な多重化メカニズムが導入されたため、現在ではあまり使われていません。
ClientConn
とServerConn
の役割
コミット当時のnet/http
パッケージには、HTTPの永続的接続とパイプライン処理を低レベルで管理するためのClientConn
とServerConn
という構造体が存在しました。
ClientConn
: クライアント側で永続的接続を管理し、複数のリクエストを同じTCP接続上で送信・受信する機能を提供します。ServerConn
: サーバー側で永続的接続を管理し、クライアントからの複数のリクエストを同じTCP接続上で処理する機能を提供します。
これらの構造体は、HTTPの低レベルなプロトコル処理、特に接続の再利用やパイプラインの順序付けといった複雑なロジックをカプセル化していました。
Goのパッケージ構成とMakefile
の役割
Goのプロジェクトでは、通常、src/pkg
ディレクトリ以下に標準ライブラリのパッケージが配置されます。各パッケージは独自のディレクトリを持ち、その中にGoのソースファイル(.go
)が含まれます。
Makefile
は、Goのビルドシステムにおいて、パッケージのコンパイル順序や依存関係を定義するために使用されていました(Go Modulesが導入される前の話です)。このコミットでは、新しいパッケージの追加と既存パッケージからのファイルの移動に伴い、関連するMakefile
が更新されています。
技術的詳細
このコミットの主要な技術的変更点は、ClientConn
とServerConn
の定義と実装を含むpersist.go
ファイルを、net/http
パッケージから新設されたnet/http/httputil
パッケージへ移動したことです。
-
新しいパッケージディレクトリの作成:
src/pkg/net/http/httputil
という新しいディレクトリが作成されました。 -
Makefile
の更新:src/pkg/Makefile
: 全体のビルド対象ディレクトリリスト(DIRS
)とテスト対象外リスト(NOTEST
)にnet/http/httputil
が追加されました。これにより、新しいパッケージがGoのビルドシステムによって認識されるようになります。src/pkg/net/http/Makefile
:GOFILES
リストからpersist.go
が削除されました。これは、このファイルがnet/http
パッケージから移動したためです。src/pkg/net/http/httputil/Makefile
(新規作成): 新しいhttputil
パッケージのビルド設定が定義されました。TARG=net/http/httputil
とGOFILES=persist.go
が指定され、persist.go
がこのパッケージの一部としてビルドされることを示しています。
-
persist.go
ファイルの移動と内容変更:- ファイルパスが
src/pkg/net/http/persist.go
からsrc/pkg/net/http/httputil/persist.go
に変更されました。 - パッケージ宣言の変更: ファイルの先頭にある
package http
がpackage httputil
に変更されました。これにより、このファイル内の型や関数がhttputil
パッケージに属することになります。 http
パッケージのインポート:ClientConn
やServerConn
の内部で、net/http
パッケージで定義されているRequest
、Response
、ProtocolError
などの型を参照する必要があるため、import "http"
が追加されました。- 型名の完全修飾:
persist.go
内でRequest
やResponse
といった型が直接使われていた箇所が、http.Request
やhttp.Response
のように完全修飾されるようになりました。これは、persist.go
がhttp
パッケージではなくhttputil
パッケージに属するようになったため、http
パッケージの型を参照するには明示的にパッケージ名を指定する必要があるためです。同様に、ReadRequest
やReadResponse
といった関数もhttp.ReadRequest
、http.ReadResponse
に変更されました。 - エラー変数
ErrPersistEOF
とErrPipeline
も、ProtocolError
がhttp.ProtocolError
に修正されました。
- ファイルパスが
-
serve_test.go
の変更:src/pkg/net/http/serve_test.go
では、ClientConn
のテストのためにNewClientConn
関数が直接呼び出されていました。このコミットによりClientConn
がhttputil
パッケージに移動したため、import "net/http/httputil"
が追加され、NewClientConn
の呼び出しがhttputil.NewClientConn
に修正されました。
これらの変更により、ClientConn
とServerConn
はnet/http
パッケージの外部に配置され、net/http
パッケージのAPIがよりシンプルで、HTTPの基本的な機能に特化する形になりました。
コアとなるコードの変更箇所
src/pkg/Makefile
--- a/src/pkg/Makefile
+++ b/src/pkg/Makefile
@@ -129,9 +129,10 @@ DIRS=\\\
net/http\\\
net/http/cgi\\\
net/http/fcgi\\\
-\tnet/mail\\\
net/http/pprof\\\
net/http/httptest\\\
+\tnet/http/httputil\\\
+\tnet/mail\\\
net/rpc\\\
net/rpc/jsonrpc\\\
net/smtp\\\
@@ -211,6 +212,7 @@ NOTEST+=\\\
net/dict\\\
net/http/pprof\\\
net/http/httptest\\\
+\tnet/http/httputil\\\
runtime/cgo\\\
syscall\\\
testing\\\
src/pkg/net/http/Makefile
--- a/src/pkg/net/http/Makefile
+++ b/src/pkg/net/http/Makefile
@@ -14,7 +14,6 @@ GOFILES=\\\
fs.go\\\
header.go\\\
lex.go\\\
-\tpersist.go\\\
request.go\\\
response.go\\\
reverseproxy.go\\\
src/pkg/net/http/httputil/Makefile
(新規ファイル)
--- /dev/null
+++ b/src/pkg/net/http/httputil/Makefile
@@ -0,0 +1,11 @@
+# Copyright 2011 The Go Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+
+include ../../../../Make.inc
+
+TARG=net/http/httputil
+GOFILES=\\\
+\tpersist.go\\\
+
+include ../../../../Make.pkg
src/pkg/net/http/persist.go
から src/pkg/net/http/httputil/persist.go
への変更
--- a/src/pkg/net/http/persist.go
+++ b/src/pkg/net/http/httputil/persist.go
@@ -2,11 +2,14 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package http
+// Package httputil provides HTTP utility functions, complementing the
+// more common ones in the net/http package.
+package httputil
import (
"bufio"
"errors"
+ "http"
"io"
"net"
"net/textproto"
@@ -15,8 +18,8 @@ import (
)
var (
- ErrPersistEOF = &ProtocolError{"persistent connection closed"}
- ErrPipeline = &ProtocolError{"pipeline error"}
+ ErrPersistEOF = &http.ProtocolError{"persistent connection closed"}
+ ErrPipeline = &http.ProtocolError{"pipeline error"}
)
// A ServerConn reads requests and sends responses over an underlying
@@ -35,7 +38,7 @@ type ServerConn struct {
re, we error // read/write errors
lastbody io.ReadCloser
nread, nwritten int
- pipereq map[*Request]uint
+ pipereq map[*http.Request]uint
pipe textproto.Pipeline
}
@@ -46,7 +49,7 @@ func NewServerConn(c net.Conn, r *bufio.Reader) *ServerConn {
if r == nil {
r = bufio.NewReader(c)
}
- return &ServerConn{c: c, r: r, pipereq: make(map[*Request]uint)}
+ return &ServerConn{c: c, r: r, pipereq: make(map[*http.Request]uint)}
}
// Hijack detaches the ServerConn and returns the underlying connection as well
@@ -76,7 +79,7 @@ func (sc *ServerConn) Close() error {
// it is gracefully determined that there are no more requests (e.g. after the
// first request on an HTTP/1.0 connection, or after a Connection:close on a
// HTTP/1.1 connection).
-func (sc *ServerConn) Read() (req *Request, err error) {
+func (sc *ServerConn) Read() (req *http.Request, err error) {
// Ensure ordered execution of Reads and Writes
id := sc.pipe.Next()
@@ -126,7 +129,7 @@ func (sc *ServerConn) Read() (req *Request, err error) {
}
}
- req, err = ReadRequest(r)
+ req, err = http.ReadRequest(r)
sc.lk.Lock()
defer sc.lk.Unlock()
if err != nil {
@@ -161,7 +164,7 @@ func (sc *ServerConn) Pending() int {\n // Write writes resp in response to req. To close the connection gracefully, set the\n // Response.Close field to true. Write should be considered operational until\n // it returns an error, regardless of any errors returned on the Read side.\n-func (sc *ServerConn) Write(req *Request, resp *Response) error {\n+func (sc *ServerConn) Write(req *http.Request, resp *http.Response) error {\n \n // Retrieve the pipeline ID of this request/response pair\n sc.lk.Lock()\n@@ -225,10 +228,10 @@ type ClientConn struct {\n re, we error // read/write errors\n lastbody io.ReadCloser\n nread, nwritten int\n- pipereq map[*Request]uint\n+ pipereq map[*http.Request]uint\n \n pipe textproto.Pipeline\n- writeReq func(*Request, io.Writer) error\n+ writeReq func(*http.Request, io.Writer) error\n }\n \n // NewClientConn returns a new ClientConn reading and writing c. If r is not\n@@ -240,8 +243,8 @@ func NewClientConn(c net.Conn, r *bufio.Reader) *ClientConn {\n return &ClientConn{\n c: c,\n r: r,\n- pipereq: make(map[*Request]uint),\n- writeReq: (*Request).Write,\n+ pipereq: make(map[*http.Request]uint),\n+ writeReq: (*http.Request).Write,\n }\n }\n \n@@ -249,7 +252,7 @@ func NewClientConn(c net.Conn, r *bufio.Reader) *ClientConn {\n // using Request's WriteProxy method.\n func NewProxyClientConn(c net.Conn, r *bufio.Reader) *ClientConn {\n cc := NewClientConn(c, r)\n- cc.writeReq = (*Request).WriteProxy\n+ cc.writeReq = (*http.Request).WriteProxy\n return cc\n }\n \n@@ -281,7 +284,7 @@ func (cc *ClientConn) Close() error {\n // keepalive connection is logically closed after this request and the opposing\n // server is informed. An ErrUnexpectedEOF indicates the remote closed the\n // underlying TCP connection, which is usually considered as graceful close.\n-func (cc *ClientConn) Write(req *Request) (err error) {\n+func (cc *ClientConn) Write(req *http.Request) (err error) {\n \n // Ensure ordered execution of Writes\n id := cc.pipe.Next()\n@@ -344,13 +347,7 @@ func (cc *ClientConn) Pending() int {\n // returned together with an ErrPersistEOF, which means that the remote\n // requested that this be the last request serviced. Read can be called\n // concurrently with Write, but not with another Read.\n-func (cc *ClientConn) Read(req *Request) (*Response, error) {\n-\treturn cc.readUsing(req, ReadResponse)\n-}\n-\n-// readUsing is the implementation of Read with a replaceable\n-// ReadResponse-like function, used by the Transport.\n-func (cc *ClientConn) readUsing(req *Request, readRes func(*bufio.Reader, *Request) (*Response, error)) (resp *Response, err error) {\n+func (cc *ClientConn) Read(req *http.Request) (resp *http.Response, err error) {\n \t// Retrieve the pipeline ID of this request/response pair\n \tcc.lk.Lock()\n \tid, ok := cc.pipereq[req]\n@@ -393,7 +390,7 @@ func (cc *ClientConn) readUsing(req *Request, readRes func(*bufio.Reader, *Reque\n \t\t}\n \t}\n \n-\tresp, err = readRes(r, req)\n+\tresp, err = http.ReadResponse(r, req)\n \tcc.lk.Lock()\n \tdefer cc.lk.Unlock()\n \tif err != nil {\n@@ -412,7 +409,7 @@ func (cc *ClientConn) readUsing(req *Request, readRes func(*bufio.Reader, *Reque\n }\n \n // Do is convenience method that writes a request and reads a response.\n-func (cc *ClientConn) Do(req *Request) (resp *Response, err error) {\n+func (cc *ClientConn) Do(req *http.Request) (resp *http.Response, err error) {\n \terr = cc.Write(req)\n \tif err != nil {\n \t\treturn\n```
### `src/pkg/net/http/serve_test.go`
```diff
--- a/src/pkg/net/http/serve_test.go
+++ b/src/pkg/net/http/serve_test.go
@@ -17,6 +17,7 @@ import (\
"io/ioutil"\n
"log"\n
"net"\n
+ "net/http/httputil"\n
"os"\n
"reflect"\n
"strings"\n
@@ -181,7 +182,7 @@ func TestHostHandlers(t *testing.T) {\
t.Fatal(err)\n
}\n
defer conn.Close()\n
-\tcc := NewClientConn(conn, nil)\n
+\tcc := httputil.NewClientConn(conn, nil)\n
for _, vt := range vtests {\n
var r *Response\n
var req Request\n
コアとなるコードの解説
このコミットの核となる変更は、persist.go
ファイルがnet/http
パッケージからnet/http/httputil
パッケージへ移動したことに伴う、その内容の調整です。
-
パッケージ宣言の変更 (
package http
->package httputil
): これは最も基本的な変更であり、persist.go
内のすべての公開された型、変数、関数が、これまでのhttp
パッケージではなく、新しくhttputil
パッケージに属することを示します。これにより、外部からこれらの要素を参照する際には、httputil.ClientConn
のようにhttputil
プレフィックスが必要になります。 -
http
パッケージのインポート (import "http"
):persist.go
は、ClientConn
やServerConn
の実装において、net/http
パッケージで定義されているhttp.Request
、http.Response
、http.ProtocolError
などの基本的なHTTPプロトコル関連の型や関数に依存しています。ファイルがhttputil
パッケージに移動したことで、これらの型や関数はもはや同じパッケージ内のものではなくなり、明示的にhttp
パッケージをインポートして参照する必要が生じました。 -
型名の完全修飾 (
*Request
->*http.Request
,ProtocolError
->http.ProtocolError
など): 上記のインポートの変更に伴い、persist.go
内のコードでRequest
やResponse
といった型が使われている箇所は、すべてhttp.Request
やhttp.Response
のように、http
パッケージの型であることを示すために完全修飾されました。これは、Go言語のパッケージシステムにおける基本的なルールであり、異なるパッケージの要素を参照する際には、そのパッケージ名をプレフィックスとして付ける必要があるためです。同様に、ReadRequest
やReadResponse
といった関数呼び出しもhttp.ReadRequest
、http.ReadResponse
に変更されています。 -
テストコードの修正 (
NewClientConn
->httputil.NewClientConn
):serve_test.go
はnet/http
パッケージのテストファイルであり、以前は同じパッケージ内のNewClientConn
を直接呼び出していました。しかし、NewClientConn
がhttputil
パッケージに移動したため、テストコードもhttputil
パッケージをインポートし、httputil.NewClientConn
として呼び出すように修正されました。これにより、テストが引き続き正しく機能することが保証されます。
これらの変更は、net/http
パッケージの責務を明確にし、HTTPの低レベルな接続管理ロジックをhttputil
というユーティリティパッケージに分離することで、net/http
パッケージのAPIをよりクリーンで、一般的なHTTP通信のユースケースに集中させるという「httpダイエット計画」の目標を達成するための重要なステップでした。
関連リンク
- Go Gerrit Change-Id: https://golang.org/cl/5336049
参考にした情報源リンク
- Go言語の公式ドキュメント (
net/http
パッケージ): https://pkg.go.dev/net/http - Go言語の公式ドキュメント (
net/http/httputil
パッケージ): https://pkg.go.dev/net/http/httputil - HTTP Persistent Connections (Keep-Alive): https://developer.mozilla.org/en-US/docs/Web/HTTP/Connection_management_in_HTTP/Persistent_connections
- HTTP Pipelining: https://developer.mozilla.org/en-US/docs/Web/HTTP/Connection_management_in_HTTP/Pipelining
- Go言語の
net/http
パッケージの歴史とリファクタリングに関する議論(当時のメーリングリストやIssueトラッカーなど、具体的なリンクはコミットから10年以上経過しているため特定が困難ですが、当時のGoコミュニティでは活発に議論されていました。)
参考にした情報源リンク
- Go言語の公式ドキュメント (
net/http
パッケージ): https://pkg.go.dev/net/http - Go言語の公式ドキュメント (
net/http/httputil
パッケージ): https://pkg.go.dev/net/http/httputil - HTTP Persistent Connections (Keep-Alive): https://developer.mozilla.org/en-US/docs/Web/HTTP/Connection_management_in_HTTP/Persistent_connections
- HTTP Pipelining: https://developer.mozilla.org/en-US/docs/Web/HTTP/Connection_management_in_HTTP/Pipelining
- Go
net/http
パッケージの歴史とパフォーマンス最適化に関する情報: