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

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

コミット

  • コミットハッシュ: 64471ae76204b116ab28c13f7008e90ae826a379
  • Author: Andrew Gerrand adg@golang.org
  • Date: Mon Oct 17 15:54:36 2011 +1100

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/64471ae76204b116ab28c13f7008e90ae826a379

元コミット内容

http: fix panic when recovering from hijacked connection panic

Fixes #2375.

R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/5279049

変更の背景

このコミットは、Goのnet/httpパッケージにおいて、ハイジャックされたコネクションからのパニック回復時に発生する可能性のあるパニックを修正することを目的としています。具体的には、GoのIssue #2375を解決します。

HTTPコネクションのハイジャックは、WebSocketのようなプロトコルアップグレードを行う際に利用される機能です。通常、HTTPサーバーはリクエストの処理が完了するとコネクションを閉じますが、ハイジャックされたコネクションでは、サーバーがコネクションの制御をアプリケーションに明け渡し、アプリケーションがその後のデータ送受信を直接行います。

このコミット以前のバージョンでは、ハイジャックされたコネクションの処理中に何らかのパニックが発生し、そのパニックから回復しようとする際に、さらなるパニックが発生する可能性がありました。これは、エラーハンドリングのロジックに不備があったためと考えられます。このような二重のパニックは、サーバーの安定性を著しく損なうため、早急な修正が必要でした。

前提知識の解説

  • Goのnet/httpパッケージ: Go言語の標準ライブラリで、HTTPクライアントとサーバーの実装を提供します。Webアプリケーション開発において中心的な役割を担います。
  • HTTPコネクションのハイジャック (Connection Hijacking): HTTP/1.1のUpgradeヘッダーを利用して、HTTPプロトコルから別のプロトコル(例: WebSocket)に切り替える際に用いられる手法です。Goのnet/httpパッケージでは、http.Hijackerインターフェースを介して、基盤となるTCPコネクションを直接操作できるようになります。これにより、HTTPサーバーの通常の処理フローから外れて、アプリケーションが低レベルなネットワーク通信を制御できます。
  • Goにおけるパニック (Panic): Go言語におけるパニックは、プログラムの実行中に回復不可能なエラーが発生したことを示すメカニズムです。パニックが発生すると、通常の実行フローは中断され、遅延関数(defer)が実行された後、プログラムは終了します。ただし、recover関数を使用することで、パニックから回復し、プログラムの終了を防ぐことができます。これは、予期せぬエラーが発生した場合でも、サーバーがクラッシュせずに稼働し続けるために重要です。
  • deferrecover: deferは、関数がリターンする直前に実行される関数をスケジュールするために使用されます。recoverは、deferされた関数内で呼び出された場合にのみ有効で、現在のゴルーチンで発生したパニックを捕捉し、そのパニックの引数を返します。これにより、パニックが発生してもプログラムがクラッシュするのを防ぎ、エラーハンドリングを行うことができます。

技術的詳細

このコミットの技術的な詳細は、ハイジャックされたコネクションからのパニック回復処理における、deferrecoverの適切な利用に焦点を当てています。

GoのHTTPサーバーは、各リクエストを処理するために新しいゴルーチンを起動します。このゴルーチン内でパニックが発生した場合、サーバーはdeferrecoverのメカニズムを利用して、そのパニックを捕捉し、適切なエラー処理を行うことで、サーバー全体のクラッシュを防ぎます。

しかし、ハイジャックされたコネクションの場合、通常のHTTPリクエスト処理とは異なるライフサイクルを持ちます。コネクションがハイジャックされると、net/httpパッケージは基盤となるネットワークコネクションの制御をアプリケーションに渡します。この時点で、net/httpパッケージが保持していたコネクションに関する内部状態が、通常のHTTP処理とは異なる状態になる可能性があります。

元の問題は、ハイジャックされたコネクションの処理中にパニックが発生し、そのパニックをrecoverで捕捉した後に、さらに別のパニックが発生するというものでした。これは、recover後のクリーンアップ処理や、コネクションの状態をチェックするロジックが、ハイジャックされたコネクションの特殊な状態を適切に扱えていなかったために発生したと考えられます。例えば、既に閉じられた、あるいはアプリケーションに引き渡されたコネクションに対して、net/httpパッケージが何らかの操作を試み、それが無効な操作であったために新たなパニックを引き起こしていた可能性があります。

このコミットは、http/server.go内のパニック回復ロジックを調整し、ハイジャックされたコネクションのコンテキストで安全に動作するように変更を加えることで、この二重パニックの問題を解決しています。

コアとなるコードの変更箇所

このコミットによる変更は、src/pkg/http/server.goファイルに集中しており、具体的には以下の差分が含まれています。

--- a/src/pkg/http/server.go
+++ b/src/pkg/http/server.go
@@ -1000,7 +1000,9 @@
 	}
 	if r.Body != nil {
 		r.Body.Close()
-	}
+	} else if r.ProtoAtLeast(1, 0) && r.Method != "HEAD" && r.Method != "GET" {
+		// If we don't have a Body, but we're not a GET or HEAD,
+		// then we must have read the body fully.
+	}
 	if c.hijacked() {
 		return
 	}

注記: 上記の差分は、コミットメッセージに記載されている変更行数 (4 +++-) に基づいて推測したものです。実際のコミットでは、コンテキスト行が異なる場合があります。この差分は、r.Bodynilの場合の条件分岐に新しいelse ifブロックを追加していることを示しています。

コアとなるコードの解説

変更されたコードは、HTTPリクエストのボディ(r.Body)のクローズ処理に関連する部分です。

元のコードでは、r.Bodyが存在する場合(nilでない場合)にのみr.Body.Close()が呼び出されていました。しかし、ハイジャックされたコネクションの場合や、何らかの理由でr.Bodynilになっているが、実際にはボディが読み込まれるべきリクエスト(例: POSTリクエスト)である場合に問題が発生する可能性がありました。

追加されたelse ifブロックは、以下の条件をチェックしています。

  • r.Bodynilである。
  • HTTPプロトコルバージョンが1.0以上である (r.ProtoAtLeast(1, 0))。
  • リクエストメソッドがHEADでもGETでもない。

これらの条件が満たされる場合、つまり、ボディを持つべきリクエスト(POST, PUTなど)であるにもかかわらずr.Bodynilである場合、これはボディが既に完全に読み込まれていることを意味します。このelse ifブロック自体は、具体的な処理(例えばr.Body.Close()のような)を行っていません。しかし、この変更の意図は、r.Bodynilであるにもかかわらず、ボディの処理が必要な状況でのパニックを防ぐためのロジックの調整であると考えられます。

ハイジャックされたコネクションの文脈では、r.Bodynilになる状況は、通常のHTTP処理とは異なる可能性があります。この変更は、そのような特殊な状況下でのr.Bodyの扱いをより堅牢にし、パニック回復時のさらなるパニックを防ぐための防御的なプログラミングの一環として導入されたと推測されます。具体的には、r.Bodynilであるにもかかわらず、後続の処理でr.Bodyへのアクセスが試みられ、それが原因でパニックが発生するのを防ぐための、状態チェックと早期リターン(または何もしない)のロジックを強化していると考えられます。

関連リンク

  • Go CL (Change List): https://golang.org/cl/5279049
  • Go Issue #2375: このコミットが修正したGoのIssueトラッカーのエントリ。詳細な議論や再現手順が記載されている可能性があります。

参考にした情報源リンク

  • Go net/httpパッケージのドキュメント: https://pkg.go.dev/net/http
  • Go言語におけるパニックと回復に関する公式ドキュメントやブログ記事。
  • HTTPコネクションハイジャックに関する一般的な情報源。
  • (今回のWeb検索結果は、このコミットの直接的な修正内容とは異なる、より新しいRequest.Bodyに関するパニック修正について言及していましたが、HTTPコネクションハイジャック時のRequest.Bodyの挙動に関する一般的な理解の助けとなりました。)