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

[インデックス 10237] ファイルの概要

このコミットは、Go言語の標準ライブラリであるnet/httpパッケージのリファクタリングの一環として、ClientConnServerConnという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という新しいパッケージを作成し、ClientConnServerConnをそこへ移動しました。これは「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)やパイプライン処理を内部的に管理するClientConnServerConnが、その対象となりました。これらの構造体は、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ではより効率的な多重化メカニズムが導入されたため、現在ではあまり使われていません。

ClientConnServerConnの役割

コミット当時のnet/httpパッケージには、HTTPの永続的接続とパイプライン処理を低レベルで管理するためのClientConnServerConnという構造体が存在しました。

  • ClientConn: クライアント側で永続的接続を管理し、複数のリクエストを同じTCP接続上で送信・受信する機能を提供します。
  • ServerConn: サーバー側で永続的接続を管理し、クライアントからの複数のリクエストを同じTCP接続上で処理する機能を提供します。

これらの構造体は、HTTPの低レベルなプロトコル処理、特に接続の再利用やパイプラインの順序付けといった複雑なロジックをカプセル化していました。

Goのパッケージ構成とMakefileの役割

Goのプロジェクトでは、通常、src/pkgディレクトリ以下に標準ライブラリのパッケージが配置されます。各パッケージは独自のディレクトリを持ち、その中にGoのソースファイル(.go)が含まれます。

Makefileは、Goのビルドシステムにおいて、パッケージのコンパイル順序や依存関係を定義するために使用されていました(Go Modulesが導入される前の話です)。このコミットでは、新しいパッケージの追加と既存パッケージからのファイルの移動に伴い、関連するMakefileが更新されています。

技術的詳細

このコミットの主要な技術的変更点は、ClientConnServerConnの定義と実装を含むpersist.goファイルを、net/httpパッケージから新設されたnet/http/httputilパッケージへ移動したことです。

  1. 新しいパッケージディレクトリの作成: src/pkg/net/http/httputilという新しいディレクトリが作成されました。

  2. 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/httputilGOFILES=persist.goが指定され、persist.goがこのパッケージの一部としてビルドされることを示しています。
  3. persist.goファイルの移動と内容変更:

    • ファイルパスがsrc/pkg/net/http/persist.goからsrc/pkg/net/http/httputil/persist.goに変更されました。
    • パッケージ宣言の変更: ファイルの先頭にあるpackage httppackage httputilに変更されました。これにより、このファイル内の型や関数がhttputilパッケージに属することになります。
    • httpパッケージのインポート: ClientConnServerConnの内部で、net/httpパッケージで定義されているRequestResponseProtocolErrorなどの型を参照する必要があるため、import "http"が追加されました。
    • 型名の完全修飾: persist.go内でRequestResponseといった型が直接使われていた箇所が、http.Requesthttp.Responseのように完全修飾されるようになりました。これは、persist.gohttpパッケージではなくhttputilパッケージに属するようになったため、httpパッケージの型を参照するには明示的にパッケージ名を指定する必要があるためです。同様に、ReadRequestReadResponseといった関数もhttp.ReadRequesthttp.ReadResponseに変更されました。
    • エラー変数ErrPersistEOFErrPipelineも、ProtocolErrorhttp.ProtocolErrorに修正されました。
  4. serve_test.goの変更:

    • src/pkg/net/http/serve_test.goでは、ClientConnのテストのためにNewClientConn関数が直接呼び出されていました。このコミットによりClientConnhttputilパッケージに移動したため、import "net/http/httputil"が追加され、NewClientConnの呼び出しがhttputil.NewClientConnに修正されました。

これらの変更により、ClientConnServerConnnet/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パッケージへ移動したことに伴う、その内容の調整です。

  1. パッケージ宣言の変更 (package http -> package httputil): これは最も基本的な変更であり、persist.go内のすべての公開された型、変数、関数が、これまでのhttpパッケージではなく、新しくhttputilパッケージに属することを示します。これにより、外部からこれらの要素を参照する際には、httputil.ClientConnのようにhttputilプレフィックスが必要になります。

  2. httpパッケージのインポート (import "http"): persist.goは、ClientConnServerConnの実装において、net/httpパッケージで定義されているhttp.Requesthttp.Responsehttp.ProtocolErrorなどの基本的なHTTPプロトコル関連の型や関数に依存しています。ファイルがhttputilパッケージに移動したことで、これらの型や関数はもはや同じパッケージ内のものではなくなり、明示的にhttpパッケージをインポートして参照する必要が生じました。

  3. 型名の完全修飾 (*Request -> *http.Request, ProtocolError -> http.ProtocolError など): 上記のインポートの変更に伴い、persist.go内のコードでRequestResponseといった型が使われている箇所は、すべてhttp.Requesthttp.Responseのように、httpパッケージの型であることを示すために完全修飾されました。これは、Go言語のパッケージシステムにおける基本的なルールであり、異なるパッケージの要素を参照する際には、そのパッケージ名をプレフィックスとして付ける必要があるためです。同様に、ReadRequestReadResponseといった関数呼び出しもhttp.ReadRequesthttp.ReadResponseに変更されています。

  4. テストコードの修正 (NewClientConn -> httputil.NewClientConn): serve_test.gonet/httpパッケージのテストファイルであり、以前は同じパッケージ内のNewClientConnを直接呼び出していました。しかし、NewClientConnhttputilパッケージに移動したため、テストコードもhttputilパッケージをインポートし、httputil.NewClientConnとして呼び出すように修正されました。これにより、テストが引き続き正しく機能することが保証されます。

これらの変更は、net/httpパッケージの責務を明確にし、HTTPの低レベルな接続管理ロジックをhttputilというユーティリティパッケージに分離することで、net/httpパッケージのAPIをよりクリーンで、一般的なHTTP通信のユースケースに集中させるという「httpダイエット計画」の目標を達成するための重要なステップでした。

関連リンク

参考にした情報源リンク

参考にした情報源リンク