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

[インデックス 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 パッケージの TransportCancelRequest メソッドを追加します。

この変更により、HTTPの各フェーズ(接続確立、ヘッダ受信、ボディ受信など)ごとに新しいタイムアウト期間を追加することなく、あらゆる種類のカスタムHTTPタイムアウトポリシーを許可します。

Transport は、特定のリクエストがどのTCP接続上にあるかを内部的に追跡し、呼び出し元が net.Conn を直接取得することなく、特定のリクエストに対するTCP接続を強制的に閉じることができるようにします。

さらに、将来の変更リスト(CL)では、res.Body.Close (Issue 3672) がこの機能を使って実装される予定です。

関連するIssue: #3362, #3672

変更の背景

この変更の主な背景は、Goの net/http クライアントにおけるHTTPリクエストのタイムアウト処理の柔軟性を向上させることです。以前は、リクエストのライフサイクル全体にわたるきめ細やかなタイムアウト制御が困難でした。例えば、接続確立に時間がかかりすぎた場合、ヘッダの受信に時間がかかりすぎた場合、またはボディの読み込みに時間がかかりすぎた場合など、それぞれ異なるタイムアウトを適用したいというニーズがありました。

TransportCancelRequest メソッドを追加することで、開発者はこれらの異なるフェーズに対して、独自のロジックでリクエストをキャンセルするメカニズムを構築できるようになります。これにより、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 の内部構造に以下の変更を加えることで、リクエストのキャンセル機能を実現しています。

  1. reqConn マップの追加: Transport 構造体に reqConn map[*Request]*persistConn という新しいマップが追加されました。このマップは、現在処理中の各 *http.Request オブジェクトと、それに対応する *persistConn(永続的なTCP接続を管理する内部構造体)を関連付けます。 reqMu sync.Mutex がこのマップへのアクセスを保護するために導入されています。

  2. setReqConn ヘルパーメソッドの追加: TransportsetReqConn(r *Request, pc *persistConn) という内部ヘルパーメソッドが追加されました。このメソッドは、reqConn マップにリクエストと接続のペアを登録したり、リクエストの処理が完了した際にマップからエントリを削除したりするために使用されます。

  3. roundTrip でのリクエスト登録: TransportroundTrip メソッド(実際のHTTPリクエスト処理を行う内部メソッド)の開始時に、pc.t.setReqConn(req.Request, pc) が呼び出されます。これにより、リクエストがどの persistConn を使用しているかが reqConn マップに記録されます。

  4. readLoop およびエラー発生時のリクエスト削除: persistConnreadLoop メソッド(レスポンスボディの読み込みを処理するゴルーチン)が完了した際、または roundTrip でエラーが発生した際に、pc.t.setReqConn(rc.req, nil) が呼び出され、reqConn マップから対応するリクエストのエントリが削除されます。これにより、完了したリクエストがマップに残り続けることを防ぎます。

  5. CancelRequest メソッドの実装: Transport に公開メソッド CancelRequest(req *Request) が追加されました。このメソッドは以下の手順で動作します。

    • t.reqMu.Lock()reqConn マップへのアクセスをロックします。
    • t.reqConn[req] を使用して、引数で渡された *Request に対応する *persistConn をマップから検索します。
    • t.reqMu.Unlock() でロックを解除します。
    • もし persistConn が見つかった場合(つまり、リクエストがまだ処理中である場合)、その persistConn が保持する pc.conn.Close() を呼び出します。これにより、基盤となるTCP接続が強制的に閉じられ、リクエストの処理が中断されます。
  6. ミューテックス名の変更: idleLkidleMu に、altLkaltMu に変更されています。これは、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 構造体への reqMureqConn の追加: reqMureqConn マップへの並行アクセスを安全に管理するためのミューテックスです。reqConn は、現在 Transport によって処理されている各 *http.Request と、そのリクエストが使用している *persistConn(基盤となるTCP接続をカプセル化した内部構造体)をマッピングします。これにより、Transport は特定のリクエストがどの物理的な接続を使用しているかを追跡できます。

  • CancelRequest(req *Request) メソッド: このメソッドは、外部から呼び出されるAPIです。引数としてキャンセルしたい *http.Request オブジェクトを受け取ります。 内部では、reqConn マップを使用して、指定されたリクエストに対応する persistConn を探し出します。 もし persistConn が見つかれば(つまり、リクエストがまだアクティブであれば)、その persistConn が保持する net.ConnClose() メソッドを呼び出します。これにより、基盤となるTCP接続が強制的に閉じられます。接続が閉じられると、その接続に対する読み書き操作は即座にエラーを返し、リクエストの処理が中断されます。

  • setReqConn(r *Request, pc *persistConn) ヘルパーメソッド: この内部メソッドは、reqConn マップのエントリを管理します。 pcnil でない場合、指定された *Request*persistConn のペアをマップに追加します。これは、リクエストが Transport によって処理を開始したときに呼び出されます。 pcnil の場合、指定された *Request のエントリをマップから削除します。これは、リクエストの処理が完了したとき(成功または失敗にかかわらず)に呼び出され、マップが不要なエントリで肥大化するのを防ぎます。

  • roundTripreadLoop での setReqConn の利用: TransportroundTrip メソッドは、リクエストが実際にネットワークに送信される直前に setReqConn を呼び出し、リクエストと接続を関連付けます。 persistConnreadLoop メソッドは、レスポンスボディの読み込みが完了したとき、またはエラーが発生したときに setReqConn を呼び出し、関連付けを解除します。これにより、リクエストが完了した後に reqConn マップからエントリがクリーンアップされます。

このメカニズムにより、開発者は http.Client を通じて発行した特定のリクエストに対して、任意のタイミングで 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.