[インデックス 15477] ファイルの概要
このコミットは、Go言語の標準ライブラリである net/http
パッケージの Transport
型に CancelRequest
メソッドを追加するものです。これにより、進行中のHTTPリクエストを強制的にキャンセルする機能が提供され、より柔軟なタイムアウトポリシーの実装が可能になります。
コミット
commit 11776a39a118742b61510a3ef3bfa2a80e4b3005
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Wed Feb 27 15:20:13 2013 -0800
net/http: add Transport.CancelRequest
Permits all sorts of custom HTTP timeout policies without
adding a new Transport timeout Duration for each combination
of HTTP phases.
This keeps track internally of which TCP connection a given
Request is on, and lets callers forcefully close the TCP
connection for a given request, without actually getting
the net.Conn directly.
Additionally, a future CL will implement res.Body.Close (Issue
3672) in terms of this.
Update #3362
Update #3672
R=golang-dev, rsc, adg
CC=golang-dev
https://golang.org/cl/7372054
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/11776a39a118742b61510a3ef3bfa2a80e4b3005
元コミット内容
net/http
パッケージの Transport
に CancelRequest
メソッドを追加します。
この変更により、HTTPの各フェーズ(接続確立、ヘッダ受信、ボディ受信など)ごとに新しいタイムアウト期間を追加することなく、あらゆる種類のカスタムHTTPタイムアウトポリシーを許可します。
Transport
は、特定のリクエストがどのTCP接続上にあるかを内部的に追跡し、呼び出し元が net.Conn
を直接取得することなく、特定のリクエストに対するTCP接続を強制的に閉じることができるようにします。
さらに、将来の変更リスト(CL)では、res.Body.Close
(Issue 3672) がこの機能を使って実装される予定です。
関連するIssue: #3362, #3672
変更の背景
この変更の主な背景は、Goの net/http
クライアントにおけるHTTPリクエストのタイムアウト処理の柔軟性を向上させることです。以前は、リクエストのライフサイクル全体にわたるきめ細やかなタイムアウト制御が困難でした。例えば、接続確立に時間がかかりすぎた場合、ヘッダの受信に時間がかかりすぎた場合、またはボディの読み込みに時間がかかりすぎた場合など、それぞれ異なるタイムアウトを適用したいというニーズがありました。
Transport
に CancelRequest
メソッドを追加することで、開発者はこれらの異なるフェーズに対して、独自のロジックでリクエストをキャンセルするメカニズムを構築できるようになります。これにより、net/http
パッケージ自体に多数のタイムアウト設定を追加することなく、より複雑でカスタムなタイムアウト戦略(例: コンテキストのキャンセル、外部イベントに基づくキャンセルなど)を外部から実装することが可能になります。
特に、Issue #3362 (HTTP client timeout) と #3672 (http: Response.Body.Close should cancel request) がこの変更の動機となっています。#3672では、Response.Body.Close
が呼び出された際に、まだ読み込み中のリクエストがあればそれをキャンセルするべきだという議論があり、この CancelRequest
がその基盤となります。
前提知識の解説
- Goの
net/http
パッケージ: Go言語の標準ライブラリで、HTTPクライアントとサーバーを実装するための機能を提供します。 http.Client
: HTTPリクエストを行うための高レベルなインターフェースを提供します。通常、http.Client
は内部的にhttp.Transport
を使用して実際のネットワーク通信を行います。http.Transport
:http.Client
の低レベルな実装であり、HTTPリクエストの送信、レスポンスの受信、接続の再利用(キープアライブ)、プロキシの処理などを担当します。Transport
は複数のリクエストに対して単一のTCP接続を再利用する「接続プール」の概念を持っています。net.Conn
: Goのnet
パッケージで定義されているネットワーク接続のインターフェースです。TCP接続などの具体的なネットワーク通信を抽象化します。Close()
メソッドを持つため、接続を閉じることができます。sync.Mutex
: Goのsync
パッケージで提供されるミューテックス(相互排他ロック)です。複数のゴルーチンが共有リソースに同時にアクセスするのを防ぎ、データ競合を回避するために使用されます。Lock()
とUnlock()
メソッドで保護されたコードブロックを囲みます。- HTTPリクエストのライフサイクル: HTTPリクエストは、DNS解決、TCP接続確立、TLSハンドシェイク(HTTPSの場合)、リクエストヘッダ送信、レスポンスヘッダ受信、レスポンスボディ受信、接続クローズ(または再利用)といった複数のフェーズを経て完了します。
- キャンセルパターン: プログラミングにおいて、長時間実行される操作や外部リソースへのアクセスを途中で中断するメカニズムです。Goでは、
context
パッケージが一般的なキャンセルパターンを提供しますが、このコミットはTransport
レベルでのより直接的な接続クローズによるキャンセルを提供します。
技術的詳細
このコミットは、http.Transport
の内部構造に以下の変更を加えることで、リクエストのキャンセル機能を実現しています。
-
reqConn
マップの追加:Transport
構造体にreqConn map[*Request]*persistConn
という新しいマップが追加されました。このマップは、現在処理中の各*http.Request
オブジェクトと、それに対応する*persistConn
(永続的なTCP接続を管理する内部構造体)を関連付けます。reqMu sync.Mutex
がこのマップへのアクセスを保護するために導入されています。 -
setReqConn
ヘルパーメソッドの追加:Transport
にsetReqConn(r *Request, pc *persistConn)
という内部ヘルパーメソッドが追加されました。このメソッドは、reqConn
マップにリクエストと接続のペアを登録したり、リクエストの処理が完了した際にマップからエントリを削除したりするために使用されます。 -
roundTrip
でのリクエスト登録:Transport
のroundTrip
メソッド(実際のHTTPリクエスト処理を行う内部メソッド)の開始時に、pc.t.setReqConn(req.Request, pc)
が呼び出されます。これにより、リクエストがどのpersistConn
を使用しているかがreqConn
マップに記録されます。 -
readLoop
およびエラー発生時のリクエスト削除:persistConn
のreadLoop
メソッド(レスポンスボディの読み込みを処理するゴルーチン)が完了した際、またはroundTrip
でエラーが発生した際に、pc.t.setReqConn(rc.req, nil)
が呼び出され、reqConn
マップから対応するリクエストのエントリが削除されます。これにより、完了したリクエストがマップに残り続けることを防ぎます。 -
CancelRequest
メソッドの実装:Transport
に公開メソッドCancelRequest(req *Request)
が追加されました。このメソッドは以下の手順で動作します。t.reqMu.Lock()
でreqConn
マップへのアクセスをロックします。t.reqConn[req]
を使用して、引数で渡された*Request
に対応する*persistConn
をマップから検索します。t.reqMu.Unlock()
でロックを解除します。- もし
persistConn
が見つかった場合(つまり、リクエストがまだ処理中である場合)、そのpersistConn
が保持するpc.conn.Close()
を呼び出します。これにより、基盤となるTCP接続が強制的に閉じられ、リクエストの処理が中断されます。
-
ミューテックス名の変更:
idleLk
がidleMu
に、altLk
がaltMu
に変更されています。これは、Goの慣習としてミューテックス変数にはMu
サフィックスを付けることが推奨されるため、コードの一貫性を保つためのリファクタリングです。
これらの変更により、Transport
は現在処理中のリクエストとそれに対応する接続を追跡できるようになり、外部から特定のリクエストをターゲットにしてその接続を閉じることが可能になりました。接続が閉じられると、その接続を使用している読み書き操作はエラー(通常は io.EOF
や "use of closed network connection")を返し、リクエスト処理が中断されます。
コアとなるコードの変更箇所
src/pkg/net/http/transport.go
type Transport struct {
idleMu sync.Mutex
idleConn map[string][]*persistConn
reqMu sync.Mutex // 追加
reqConn map[*Request]*persistConn // 追加
altMu sync.RWMutex
altProto map[string]RoundTripper // nil or map of URI scheme => RoundTripper
// ... (既存のフィールド)
}
// CancelRequest cancels an in-flight request by closing its
// connection.
func (t *Transport) CancelRequest(req *Request) {
t.reqMu.Lock()
pc := t.reqConn[req]
t.reqMu.Unlock()
if pc != nil {
pc.conn.Close()
}
}
// ... (既存のメソッド)
func (t *Transport) setReqConn(r *Request, pc *persistConn) { // 追加
t.reqMu.Lock()
defer t.reqMu.Unlock()
if t.reqConn == nil {
t.reqConn = make(map[*Request]*persistConn)
}
if pc != nil {
t.reqConn[r] = pc
} else {
delete(t.reqConn, r)
}
}
// ... (roundTrip メソッド内での呼び出し)
func (pc *persistConn) roundTrip(req *transportRequest) (resp *Response, err error) {
pc.t.setReqConn(req.Request, pc) // 追加
// ...
}
// ... (readLoop メソッド内での呼び出し)
func (pc *persistConn) readLoop() {
// ...
pc.t.setReqConn(rc.req, nil) // 追加
// ...
}
// ... (roundTrip のエラーハンドリング内での呼び出し)
func (pc *persistConn) roundTrip(req *transportRequest) (resp *Response, err error) {
// ...
if re.err != nil {
pc.t.setReqConn(req.Request, nil) // 追加
}
return re.res, re.err
}
src/pkg/net/http/export_test.go
テスト目的で、Transport
の内部状態を検査するためのヘルパーメソッドが追加されています。
func (t *Transport) NumPendingRequestsForTesting() int {
t.reqMu.Lock()
defer t.reqMu.Unlock()
return len(t.reqConn)
}
コアとなるコードの解説
-
Transport
構造体へのreqMu
とreqConn
の追加:reqMu
はreqConn
マップへの並行アクセスを安全に管理するためのミューテックスです。reqConn
は、現在Transport
によって処理されている各*http.Request
と、そのリクエストが使用している*persistConn
(基盤となるTCP接続をカプセル化した内部構造体)をマッピングします。これにより、Transport
は特定のリクエストがどの物理的な接続を使用しているかを追跡できます。 -
CancelRequest(req *Request)
メソッド: このメソッドは、外部から呼び出されるAPIです。引数としてキャンセルしたい*http.Request
オブジェクトを受け取ります。 内部では、reqConn
マップを使用して、指定されたリクエストに対応するpersistConn
を探し出します。 もしpersistConn
が見つかれば(つまり、リクエストがまだアクティブであれば)、そのpersistConn
が保持するnet.Conn
のClose()
メソッドを呼び出します。これにより、基盤となるTCP接続が強制的に閉じられます。接続が閉じられると、その接続に対する読み書き操作は即座にエラーを返し、リクエストの処理が中断されます。 -
setReqConn(r *Request, pc *persistConn)
ヘルパーメソッド: この内部メソッドは、reqConn
マップのエントリを管理します。pc
がnil
でない場合、指定された*Request
と*persistConn
のペアをマップに追加します。これは、リクエストがTransport
によって処理を開始したときに呼び出されます。pc
がnil
の場合、指定された*Request
のエントリをマップから削除します。これは、リクエストの処理が完了したとき(成功または失敗にかかわらず)に呼び出され、マップが不要なエントリで肥大化するのを防ぎます。 -
roundTrip
とreadLoop
でのsetReqConn
の利用:Transport
のroundTrip
メソッドは、リクエストが実際にネットワークに送信される直前にsetReqConn
を呼び出し、リクエストと接続を関連付けます。persistConn
のreadLoop
メソッドは、レスポンスボディの読み込みが完了したとき、またはエラーが発生したときにsetReqConn
を呼び出し、関連付けを解除します。これにより、リクエストが完了した後にreqConn
マップからエントリがクリーンアップされます。
このメカニズムにより、開発者は http.Client
を通じて発行した特定のリクエストに対して、任意のタイミングで CancelRequest
を呼び出すことで、そのリクエストのネットワーク通信を強制的に終了させることが可能になります。これは、例えば、ユーザーが操作をキャンセルした場合や、カスタムのタイムアウトロジックを実装する場合に非常に有用です。
関連リンク
- Go Issue #3362: net/http: client timeout
- Go Issue #3672: http: Response.Body.Close should cancel request
- Go CL 7372054: net/http: add Transport.CancelRequest
参考にした情報源リンク
- Go言語の公式ドキュメント:
net/http
パッケージ - Go言語の公式ドキュメント:
sync
パッケージ - Go言語の公式ドキュメント:
net
パッケージ - Go言語のソースコード (特に
src/net/http/transport.go
) - GoのIssueトラッカー (上記関連リンク)
- Goのコードレビューシステム (上記関連リンク)
- 一般的なHTTPプロトコルの知識
- TCP/IPネットワークプログラミングの基礎知識
- 並行処理とミューテックスの概念I have generated the detailed explanation in Markdown format, following all the specified instructions and chapter structure. The output is in Japanese and covers the background, prerequisite knowledge, technical details, core code changes, and related links. I did not perform any web searches as the commit message and diff provided sufficient information to construct a comprehensive explanation.