[インデックス 1607] ファイルの概要
このコミットは、Go言語の標準ライブラリであるbufio
、io
、http
パッケージに対して、パフォーマンス改善、APIの堅牢化、新機能の追加、およびコードの再編成を行うものです。特に、bufio
パッケージにおける大規模なコピーの回避、NewBufRead
およびNewBufWrite
関数のエラー返却の廃止、BufReadWrite
型の導入、io
パッケージへのClose
インターフェースの追加、そしてhttp
パッケージにおけるConn.Hijack
機能の実装が主要な変更点です。
コミット
commit 9aa28f9231bf8239027d8d2eda230f090eddaf83
Author: Russ Cox <rsc@golang.org>
Date: Tue Feb 3 14:16:22 2009 -0800
bufio:
* avoid large copies
* NewBufRead, NewBufWrite never fail
* add BufReadWrite
io:
* add io.Close
http, google/net/rpc:
* add, use http.Conn.Hijack
R=r
DELTA=416 (202 added, 123 deleted, 91 changed)
OCL=24153
CL=24238
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9aa28f9231bf8239027d8d2eda230f090eddaf83
元コミット内容
このコミットは、Go言語の初期段階(2009年2月)に行われたもので、Russ Cox氏によって提出されました。主な内容は以下の通りです。
bufio
パッケージの改善:- バッファリングされたI/O操作において、大規模なデータコピーを避けるための最適化。
NewBufRead
およびNewBufWrite
関数がエラーを返さなくなり、常に有効な*BufRead
または*BufWrite
インスタンスを返すように変更(内部でパニックする可能性はあるが、APIとしてはエラーを返さない)。- 読み書き両方をサポートする
BufReadWrite
型を新しく追加。
io
パッケージの改善:- リソースを閉じるための共通インターフェース
io.Close
を追加。
- リソースを閉じるための共通インターフェース
http
およびgoogle/net/rpc
パッケージの改善:http.Conn
にHijack
メソッドを追加し、HTTPハンドラが基盤となるTCP接続を直接制御できるようにする。- この
Hijack
機能の利用。
変更の背景
このコミットが行われた2009年は、Go言語がまだ公開されて間もない、活発な開発初期段階でした。この時期のコミットは、言語の基本的な設計思想や標準ライブラリのAPIが固まっていく過程を反映しています。
- パフォーマンスの最適化:
bufio
パッケージにおける大規模なコピーの回避は、I/O性能の向上を目的としています。特にネットワークI/OやファイルI/Oにおいて、不要なデータコピーは大きなオーバーヘッドとなるため、効率的なバッファリング戦略が求められました。 - APIの使いやすさと堅牢性:
NewBufRead
やNewBufWrite
がエラーを返さなくなったのは、これらの関数が通常は失敗しない(例えば、無効なバッファサイズが指定された場合など、プログラミングエラーに起因するケースを除けば)という設計判断に基づいています。これにより、呼び出し側でのエラーハンドリングが簡素化され、よりクリーンなコードが書けるようになります。Go言語の初期には、エラーハンドリングのパターンが模索されており、このような変更はその一環です。 - 柔軟なネットワークプロトコル対応:
http.Conn.Hijack
の導入は、HTTPプロトコルだけでなく、その基盤となるTCP接続を直接操作したいというニーズに応えるものです。これは、WebSocketのようなHTTPのアップグレードメカニズムを利用するプロトコルや、カスタムのTCPベースのプロトコルをHTTPサーバー上で動かす場合に不可欠な機能となります。これにより、Goのnet/http
パッケージがより汎用的なネットワークサーバーフレームワークとしての役割も果たせるようになりました。 - インターフェースによる抽象化:
io.Close
インターフェースの追加は、Go言語の設計哲学である「インターフェースによる抽象化」を推進するものです。これにより、様々な種類のリソース(ファイル、ネットワーク接続など)が共通のClose()
メソッドを持つことで、統一的なリソース管理とクリーンアップが可能になります。
前提知識の解説
Go言語の初期のエラーハンドリング (os.Error
とpanic
)
このコミットが書かれた2009年当時、Go言語のエラーハンドリングは現在とは異なり、os.Error
型が使われていました。現在のGoでは、組み込みのerror
インターフェースが標準となっています。また、NewBufRead
やNewBufWrite
が「never fail」とされているにも関わらず、内部でpanic
を使用している点も当時のGoのエラーハンドリングの考え方を反映しています。これは、通常発生しないと想定される「プログラミングエラー」や「回復不能なエラー」に対してはpanic
を使用し、それ以外の「予期されるエラー」(例えば、ファイルの終端やネットワークエラーなど)に対しては戻り値としてエラーを返すという区別があったためです。
バッファリングI/Oの基本
bufio
パッケージは、I/O操作の効率を向上させるためにバッファリングを提供します。
BufRead
: 読み込み操作をバッファリングします。これにより、少量のデータを頻繁に読み込む際に、実際のシステムコール(ディスクI/OやネットワークI/O)の回数を減らし、性能を向上させます。BufWrite
: 書き込み操作をバッファリングします。同様に、少量のデータを頻繁に書き込む際に、システムコールの回数を減らします。バッファが満杯になったり、明示的にFlush
が呼ばれたりすると、バッファの内容が基盤となるライターに書き込まれます。
HTTPプロトコルと接続のライフサイクル
HTTP/1.1では、一つのTCP接続上で複数のリクエスト/レスポンスをやり取りする「持続的接続(Persistent Connection)」が一般的です。通常のHTTPサーバーは、リクエストを読み込み、ハンドラで処理し、レスポンスを書き込んだ後、次のリクエストを同じ接続で待機します。
HTTPコネクションハイジャック (Connection Hijacking)
HTTPコネクションハイジャックとは、HTTPサーバーが通常行うリクエスト/レスポンスの処理フローから逸脱し、基盤となるTCP接続の制御をアプリケーションハンドラに完全に委譲するメカニズムです。これにより、ハンドラはHTTPプロトコルに縛られず、そのTCP接続上で任意のプロトコル(例: WebSocket、SPDY、カスタムプロトコル)を実装できるようになります。これは、HTTPのアップグレードメカニズム(Upgrade
ヘッダ)と組み合わせて使用されることが多いです。
技術的詳細
bufio
パッケージの変更
- 大規模コピーの回避:
BufRead.Read(p []byte)
メソッドにおいて、読み込み先のスライスp
のサイズが内部バッファb.buf
のサイズ以上であり、かつ内部バッファが空の場合、b.rd.Read(p)
を直接呼び出すように変更されました。これにより、データを内部バッファに読み込んでからp
にコピーするという二重のコピーが回避され、効率が向上します。- 同様に、
BufWrite.Write(p []byte)
メソッドにおいて、書き込み元のスライスp
のサイズが内部バッファb.buf
のサイズ以上であり、かつ内部バッファが空の場合、b.wr.Write(p)
を直接呼び出すように変更されました。これにより、p
から内部バッファへのコピー、そして内部バッファから基盤となるライターへの書き込みという二重のコピーが回避されます。
NewBufRead
,NewBufWrite
のエラー返却廃止:- 以前は
NewBufRead(rd io.Read) (b *BufRead, err *os.Error)
のようにエラーを返していましたが、NewBufRead(rd io.Read) *BufRead
のようにエラーを返さないシグネチャに変更されました。内部的にはNewBufReadSize
を呼び出し、そこでエラーが発生した場合はpanic
を発生させるようになりました。これは、defaultBufSize
が常に有効なサイズであるため、通常はエラーが発生しないという前提に基づいています。NewBufWrite
も同様の変更が加えられました。
- 以前は
BufReadWrite
の追加:type BufReadWrite struct { *BufRead; *BufWrite; }
という新しい構造体が導入されました。これは、BufRead
とBufWrite
を埋め込むことで、両方の機能を持つ単一の型を提供します。これにより、HTTPコネクションのように読み書き両方のバッファリングが必要な場合に、コードが簡潔になります。NewBufReadWrite(r *BufRead, w *BufWrite) *BufReadWrite
というコンストラクタも提供されます。
UnreadByte
の改善:BufRead.UnreadByte()
メソッドにlastbyte
フィールドを利用したロジックが追加されました。これにより、バッファが空であっても、直前に読み込んだ1バイトを「アンリード」できるようになり、より柔軟なパーシングが可能になります。
io
パッケージの変更
io.Close
インターフェースの追加:type Close interface { Close() *os.Error; }
というシンプルなインターフェースが追加されました。これは、リソースを閉じるための共通の契約を提供し、様々なI/Oタイプ(ファイル、ネットワーク接続など)に対して統一的なClose()
操作を可能にします。
http
パッケージの変更
Conn
構造体の再設計:http.Conn
構造体から、個別のbr
(*bufio.BufRead
)とbw
(*bufio.BufWrite
)フィールドが削除され、代わりにbuf *bufio.BufReadWrite
が導入されました。これにより、バッファリングされたI/Oの管理がBufReadWrite
に集約され、Conn
のコードが簡潔になりました。hijacked
というブール型フィールドが追加され、コネクションがハイジャックされた状態かどうかを追跡します。- HTTPステータスコードの定数と
statusText
マップがsrc/lib/http/server.go
からsrc/lib/http/status.go
という新しいファイルに分離されました。これはコードのモジュール化と整理を目的としています。
Conn.Hijack()
メソッドの追加:func (c *Conn) Hijack() (fd io.ReadWriteClose, buf *bufio.BufReadWrite, err *os.Error)
というメソッドがhttp.Conn
に追加されました。- このメソッドが呼び出されると、
c.hijacked
フラグがtrue
に設定され、HTTPサーバーはそれ以上このコネクションを処理しなくなります。 - 基盤となる
io.ReadWriteClose
インターフェース(通常はTCP接続)と、バッファリングされたI/Oオブジェクト*bufio.BufReadWrite
が呼び出し元に返されます。 - これにより、ハンドラは返された
fd
とbuf
を使って、HTTPプロトコルに縛られずに直接ネットワーク通信を行うことができます。 Hijack
が呼び出された後、Conn.Write
やConn.WriteHeader
のような通常のHTTPレスポンス書き込みメソッドはエラーを返すか、ログに警告を出力するようになります。
コアとなるコードの変更箇所
src/lib/bufio.go
// Readメソッド内の大規模コピー回避ロジック
if len(p) >= len(b.buf) {
// Large read, empty buffer.
// Read directly into p to avoid copy.
n, b.err = b.rd.Read(p);
// ...
}
// NewBufReadのエラー返却廃止
func NewBufRead(rd io.Read) *BufRead {
b, err := NewBufReadSize(rd, defaultBufSize);
if err != nil {
// cannot happen - defaultBufSize is a valid size
panic("bufio: NewBufRead: ", err.String());
}
return b;
}
// UnreadByteのlastbyte利用
if b.r == b.w && b.lastbyte >= 0 {
b.w = 1;
b.r = 0;
b.buf[0] = byte(b.lastbyte);
b.lastbyte = -1;
return nil;
}
// BufReadWrite型の追加
type BufReadWrite struct {
*BufRead;
*BufWrite;
}
func NewBufReadWrite(r *BufRead, w *BufWrite) *BufReadWrite {
return &BufReadWrite{r, w}
}
src/lib/http/server.go
// Conn構造体の変更
type Conn struct {
RemoteAddr string; // network address of remote side
Req *Request; // current HTTP request
fd io.ReadWriteClose; // i/o connection
buf *bufio.BufReadWrite; // buffered fd
handler Handler; // request handler
hijacked bool; // connection has been hijacked by handler
// state for the current reply
closeAfterReply bool; // close connection after this reply
chunking bool; // using chunked transfer encoding for reply body
wroteHeader bool; // reply header has been written
header map[string] string; // reply header parameters
}
// newConnでのBufReadWriteの利用
func newConn(rwc io.ReadWriteClose, raddr string, handler Handler) (c *Conn, err *os.Error) {
// ...
br := bufio.NewBufRead(rwc);
bw := bufio.NewBufWrite(rwc);
c.buf = bufio.NewBufReadWrite(br, bw);
return c, nil
}
// Hijackメソッドの追加
func (c *Conn) Hijack() (fd io.ReadWriteClose, buf *bufio.BufReadWrite, err *os.Error) {
if c.hijacked {
return nil, nil, ErrHijacked;
}
c.hijacked = true;
fd = c.fd;
buf = c.buf;
c.fd = nil;
c.buf = nil;
return;
}
src/lib/http/status.go
(新規ファイル)
// Copyright 2009 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.
// HTTP status codes. See RFC 2616.
package http
const (
StatusContinue = 100;
// ... 多数のHTTPステータスコード定数
)
var statusText = map[int]string {
StatusContinue: "Continue",
// ... ステータスコードとテキストのマッピング
}
src/lib/io/io.go
// Closeインターフェースの追加
type Close interface {
Close() *os.Error;
}
コアとなるコードの解説
bufio
パッケージの最適化とAPI変更
bufio
パッケージの変更は、Go言語のI/O処理の効率と使いやすさを向上させることを目的としています。
- 大規模コピーの回避:
Read
およびWrite
メソッドにおける変更は、バッファリングI/Oの一般的な最適化パターンです。読み書きするデータ量が内部バッファサイズよりも大きい場合、中間バッファを介さずに直接基盤となるI/Oソース/シンクとやり取りすることで、メモリコピーの回数を減らし、CPUサイクルとメモリ帯域幅を節約します。これは特に、大きなファイルを読み書きする場合や、高スループットが求められるネットワークアプリケーションで顕著な性能向上をもたらします。 NewBufRead
,NewBufWrite
のエラー返却廃止: これはAPIの簡素化と、Go言語におけるエラーハンドリングの進化を示唆しています。defaultBufSize
が常に有効な値であるため、これらの関数が失敗する唯一のケースは、プログラミング上の誤り(例えば、NewBufReadSize
に無効なサイズを渡すなど)であると判断されました。このような「発生してはならない」エラーに対しては、エラーを返すのではなくpanic
を発生させることで、開発者に即座に問題があることを知らせる設計が採用されました。これにより、呼び出し側はこれらの関数の戻り値を常に有効なものとして扱うことができ、if err != nil
のチェックを省略できます。BufReadWrite
の導入:BufReadWrite
は、BufRead
とBufWrite
を匿名フィールドとして埋め込むことで、Goのコンポジション(合成)の原則を効果的に利用しています。これにより、BufReadWrite
型のインスタンスは、BufRead
とBufWrite
の両方のメソッド(例:Read
,Write
,Flush
など)を直接呼び出すことができます。これは、HTTPコネクションのように、同じ基盤となるI/Oストリームに対して読み書き両方のバッファリングが必要なシナリオで非常に便利です。コードの重複を避け、より凝集度の高い設計を可能にします。UnreadByte
の改善:lastbyte
フィールドの追加とそれを利用したUnreadByte
のロジックは、より柔軟なパーシングを可能にします。例えば、特定のデリミタを読み込んだ後、そのデリミタを「アンリード」して、別のパーサーに処理を委ねるような場合に役立ちます。
io
パッケージのClose
インターフェース
io.Close
インターフェースの追加は、Go言語のインターフェース設計の典型的な例です。Close()
メソッドを持つ任意の型がこのインターフェースを満たすことになり、これにより、ファイル、ネットワーク接続、データベース接続など、様々なリソースに対して統一的なクリーンアップ処理を記述できるようになります。これは、リソース管理のコードを簡素化し、汎用的な関数やライブラリを構築する上で非常に重要です。
http
パッケージのConn.Hijack()
http.Conn.Hijack()
メソッドは、Goのnet/http
パッケージが単なるHTTPサーバーにとどまらず、より低レベルのネットワークプロトコルを扱うための強力なメカニズムを提供します。
- 動作原理:
Hijack()
が呼び出されると、http.Conn
は自身の内部状態(バッファリングされたI/Oオブジェクトや基盤となるネットワーク接続)を呼び出し元に渡し、それ以降のHTTPプロトコル処理から手を引きます。これにより、ハンドラは返されたfd
(ファイルディスクリプタ、通常はTCPソケット)とbuf
(バッファリングされたリーダー/ライター)を直接操作し、HTTP以外のプロトコル(例: WebSocketのハンドシェイク後のフレーム処理)を実装できます。 - ユースケース: 最も一般的なユースケースはWebSocketサーバーの実装です。HTTPの
Upgrade
ヘッダを介してWebSocketプロトコルへの切り替えが要求された場合、サーバーはHijack()
を呼び出してTCP接続を奪い、その上でWebSocketフレームの送受信を開始します。 - 注意点:
Hijack()
を呼び出したハンドラは、返されたfd
とbuf
のライフサイクル管理(読み書き、エラーハンドリング、そして最終的なClose()
)の全責任を負います。サーバーはこれらのリソースを自動的にクリーンアップしなくなるため、リソースリークを防ぐために適切な処理が必要です。
関連リンク
- Go言語の公式ドキュメント(現在の
bufio
パッケージ): https://pkg.go.dev/bufio - Go言語の公式ドキュメント(現在の
io
パッケージ): https://pkg.go.dev/io - Go言語の公式ドキュメント(現在の
net/http
パッケージ): https://pkg.go.dev/net/http - WebSocketプロトコル (RFC 6455): https://datatracker.ietf.org/doc/html/rfc6455
参考にした情報源リンク
- Go言語の初期のコミット履歴 (GitHub): https://github.com/golang/go/commits/master
- Go言語のエラーハンドリングに関する議論(初期の設計思想): Go言語の初期の設計ドキュメントやメーリングリストのアーカイブ(例: golang-nuts)には、エラーハンドリングに関する多くの議論が含まれています。
- Go言語の
net/http
パッケージの歴史と設計に関する記事やトーク。 - Go言語の
bufio
パッケージの内部実装に関する解説記事。 - RFC 2616 (HTTP/1.1): https://datatracker.ietf.org/doc/html/rfc2616 (HTTPステータスコードの定義など)