メインコンテンツへスキップ

Go Proposal Weekly Digest

Go言語のproposal更新を毎週お届け

#79040active

net/http: drop support for Transport.CancelRequest

新規提案

要約

AIによる要約であり、誤りを含む場合があります。

概要

net/http.Transport.CancelRequest はGo 1.6(2016年2月)から非推奨となっているHTTPリクエストキャンセル用メソッドであり、このproposalはその実質的なサポートを段階的に廃止することを提案するものです。

ステータス変更

(なし)active
2026年6月18日のproposal review meetingで、aclements(コアチームメンバー)によりactiveカラムに追加されました。非推奨から約10年が経過し、より新しいキャンセル機構(コンテキストベース)が定着したことを受けて、正式な廃止プロセスの開始が妥当と判断されたものと考えられます。

技術的背景

現状の問題点

Transport.CancelRequest はHTTP/1リクエストのキャンセルを実装するために、飛行中(in-flight)のリクエストをマップで管理する必要があります。これはリクエストをキャンセルしないコードにも常にオーバーヘッドを課すという問題があります。また、HTTP/2リクエストには動作しない(ドキュメントにも明記済み)、競合状態を起こしやすい(issue #11013)という根本的な設計上の欠陥も抱えています。
キャンセルの歴史的変遷:

  • Go 1.1: Transport.CancelRequest 追加
  • Go 1.5: Request.Cancel チャネルによるキャンセルを追加
  • Go 1.6: Transport.CancelRequestRequest.Cancel を推奨として非推奨化
  • Go 1.13: NewRequestWithContext が追加され、コンテキストベースのキャンセルが唯一の非推奨でないキャンセル手段となる
    現在も CancelRequest は現役の非推奨APIとして10年以上存続しており、内部実装の複雑性を維持したままです。

提案された解決策

Transport.CancelRequest を段階的に廃止します。
第一段階: CancelRequest をno-op(何もしない)に変更し、最初の呼び出し時に1回だけ警告ログを出力する。合わせてGODEBUGフラグ httptransportcancelrequest=1 で旧動作を一時的に復元可能にする。
第二段階: 4リリースサイクル後(issue #76163で提案されたGODEBUGの標準的なライフサイクル方針に従う)、GODEBUGフラグごとサポートを完全削除する。

// 提案されている一時的な実装
var cancelRequestOnce sync.Once
func (*Transport) CancelRequest(req *Request) {
    cancelRequestOnce.Do(func() {
        log.Println("Use of unsupported Transport.CancelRequest. Use NewRequestWithContext instead.")
    })
}

これによって何ができるようになるか

既存コードへの影響はなく、むしろHTTP Transportの内部実装が簡略化されます。
現代的な書き方では以下のようにキャンセルを実装します。

// Before: 旧来のキャンセル方式(非推奨)
transport := &http.Transport{}
req, _ := http.NewRequest("GET", "https://example.com", nil)
go func() {
    time.Sleep(1 * time.Second)
    transport.CancelRequest(req) // HTTP/2では動作しない、競合状態あり
}()
client := &http.Client{Transport: transport}
resp, err := client.Do(req)
// After: コンテキストを使った現代的なキャンセル方式
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
req, _ := http.NewRequestWithContext(ctx, "GET", "https://example.com", nil)
client := &http.Client{}
resp, err := client.Do(req) // HTTP/1・HTTP/2どちらでも正しく動作

議論のハイライト

  • ログ出力の是非: mateusz834 は既に非推奨であることを理由にno-opのみで十分ではないかと指摘した。これに対しissue作者(neild)は「キャンセルの失敗は無声の障害より発見しやすいログのほうが安全」と反論し、ログ出力の意義を主張した
  • ログ出力のリスク: apparentlymartlog パッケージを使った無断のstderr出力がCLIツールの出力フォーマット期待を破壊する可能性を懸念しつつも、より良い代替案がないことを認めた
  • GODEBUGの採用: 互換性のためのGODEBUGライフサイクル方針(issue #76163)に従い、4リリースサイクルの猶予を設けるという段階的廃止プロセスが採用されている
  • 10年越しの廃止: 非推奨宣言から約10年を経てのactiveステータス移行であり、「将来のリリースでno-opになる可能性がある」というドキュメントの文言がついに現実化する動きとなった
  • HTTP/2対応の不完全さ: issue #13540(2015年)以来、HTTP/2への CancelRequest 対応は「導入すべきでない設計ミスをさらに広める」として否定的に評価されており、それが廃止を後押しした

関連リンク