[インデックス 14243] ファイルの概要
このコミットは、misc/dashboard/builder
パッケージ内の HTTP クライアント処理において、HTTP レスポンスのステータスコードが 200 OK
でない場合に、そのレスポンスボディを JSON として処理しないように修正するものです。具体的には、Google App Engine (GAE) がエラー応答として 500 Internal Server Error
を返すことがあるため、そのようなエラーレスポンスを有効な JSON ボディとして扱わないように、ステータスコードのチェックを追加しています。
コミット
- コミットハッシュ:
be6afde348db68dfc9f59f57cbb15ed637158ac0
- Author: Dave Cheney dave@cheney.net
- Date: Wed Oct 31 02:24:08 2012 +1100
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/be6afde348db68dfc9f59f57cbb15ed637158ac0
元コミット内容
misc/dashboard/builder: check http status before processing response
Occasionally GAE will return a 500 error response, don't treat this as a valid JSON body.
R=adg, dsymonds
CC=golang-dev
https://golang.org/cl/6775066
変更の背景
この変更の背景には、Go プロジェクトのダッシュボードビルダが Google App Engine (GAE) 上で動作している際に発生していた問題があります。GAE は、内部エラーが発生した場合などに 500 Internal Server Error
のような HTTP エラーレスポンスを返すことがあります。
元のコードでは、HTTP リクエストが成功したかどうか(HTTP ステータスコードが 200 OK
であるか)を確認せずに、レスポンスボディを直接 JSON としてデコードしようとしていました。このため、GAE から 500
エラーなどの非 200
ステータスコードが返された場合でも、そのエラーページの内容やエラーメッセージが JSON としてパースされようとし、結果としてパースエラーや予期せぬ動作を引き起こしていました。
このコミットは、このような状況を回避し、HTTP リクエストが正常に完了した場合(ステータスコードが 200 OK
の場合)にのみレスポンスボディを JSON として処理するようにすることで、堅牢性を向上させることを目的としています。
前提知識の解説
このコミットを理解するためには、以下の技術的な概念を把握しておく必要があります。
-
HTTP ステータスコード: HTTP (Hypertext Transfer Protocol) は、Web 上でデータをやり取りするためのプロトコルです。HTTP リクエストに対するサーバーの応答には、処理結果を示す3桁の数字である「HTTP ステータスコード」が含まれます。
200 OK
: リクエストが成功し、要求された情報がレスポンスボディに含まれていることを示します。これは通常、期待される正常な応答です。500 Internal Server Error
: サーバーがリクエストを処理する際に予期せぬエラーに遭遇したことを示します。これはサーバー側の問題であり、クライアントが再試行しても解決しない場合があります。- 他にも
404 Not Found
(リソースが見つからない)、403 Forbidden
(アクセス拒否) など、様々なステータスコードが存在します。
-
JSON (JavaScript Object Notation): JSON は、人間が読み書きしやすく、機械が解析しやすいデータ交換フォーマットです。Web API などでデータを送受信する際によく用いられます。JSON データは、キーと値のペアの集合(オブジェクト)や、値の順序付きリスト(配列)で構成されます。
-
Google App Engine (GAE): Google App Engine は、Google が提供する PaaS (Platform as a Service) の一種で、Web アプリケーションやモバイルバックエンドを構築・デプロイするためのプラットフォームです。開発者はインフラの管理を気にすることなく、アプリケーションのコードに集中できます。GAE 上で動作するアプリケーションは、リクエストの処理中にエラーが発生した場合、標準的な HTTP エラーレスポンス(例:
500 Internal Server Error
)を返すことがあります。 -
Go言語の
net/http
パッケージ: Go 言語の標準ライブラリには、HTTP クライアントおよびサーバーを実装するためのnet/http
パッケージが含まれています。http.Response
構造体: HTTP レスポンスを表す構造体で、StatusCode
(ステータスコードの数値)、Status
(ステータスコードのテキスト表現、例: "200 OK")、Body
(レスポンスボディのio.ReadCloser
) などのフィールドを持ちます。http.StatusOK
:200
を表すint
型の定数です。fmt.Errorf
: Go 言語でエラーを生成するための関数です。フォーマット文字列と引数を受け取り、新しいerror
インターフェースの値を返します。
-
エラーハンドリング: Go 言語では、関数がエラーを返す場合、通常は戻り値の最後の要素として
error
型の値を返します。呼び出し元は、このerror
値がnil
でない場合にエラーが発生したと判断し、適切なエラー処理を行います。
技術的詳細
このコミットは、Go 言語の net/http
パッケージを使用して行われる HTTP リクエストのレスポンス処理における、一般的なベストプラクティスを適用しています。
HTTP クライアントがサーバーにリクエストを送信し、レスポンスを受け取った際、そのレスポンスボディを処理する前に、まず HTTP ステータスコードを確認することが重要です。これは、サーバーが正常なデータ(例: JSON)を返しているとは限らないためです。特に、4xx
(クライアントエラー) や 5xx
(サーバーエラー) のステータスコードが返された場合、レスポンスボディにはエラーメッセージや HTML のエラーページが含まれていることが多く、これを期待されるデータ形式(例: JSON)としてパースしようとすると、パースエラーが発生したり、誤ったデータが処理されたりする可能性があります。
このコミットでは、http.Response
オブジェクトの StatusCode
フィールドを http.StatusOK
(つまり 200
) と比較することで、このチェックを実装しています。
if r.StatusCode != http.StatusOK {
return fmt.Errorf("bad http response: %v", r.Status)
}
このコードスニペットは、以下のロジックを表現しています。
r.StatusCode
がhttp.StatusOK
(200) と等しくない場合、つまり HTTP リクエストが成功しなかった場合。fmt.Errorf
を使用して新しいエラーを生成し、そのエラーメッセージには実際の HTTP ステータス(例: "500 Internal Server Error")を含めます。- このエラーを呼び出し元に返します。これにより、後続のレスポンスボディの読み取りや JSON パース処理がスキップされ、エラーが適切に伝播されます。
この変更により、アプリケーションは不正な HTTP レスポンスを早期に検出し、無効なデータを処理しようとする試みを防ぐことができます。これは、アプリケーションの堅牢性と信頼性を向上させる上で非常に重要です。
コアとなるコードの変更箇所
--- a/misc/dashboard/builder/http.go
+++ b/misc/dashboard/builder/http.go
@@ -56,8 +56,10 @@ func dash(meth, cmd string, args url.Values, req, resp interface{}) error {
if err != nil {
return err
}
-
defer r.Body.Close()
+ if r.StatusCode != http.StatusOK {
+ return fmt.Errorf("bad http response: %v", r.Status)
+ }
body := new(bytes.Buffer)
if _, err := body.ReadFrom(r.Body); err != nil {
return err
コアとなるコードの解説
変更は misc/dashboard/builder/http.go
ファイルの dash
関数内で行われています。この dash
関数は、おそらく Go ダッシュボードのビルダが外部サービス(この場合は GAE 上のサービス)と HTTP 通信を行うためのヘルパー関数です。
元のコードでは、HTTP リクエストの実行後にエラーがない場合 (if err != nil
のチェックを通過した後)、すぐに r.Body.Close()
を defer
で呼び出し、その後レスポンスボディを読み取って処理していました。
追加された行は以下の通りです。
+ if r.StatusCode != http.StatusOK {
+ return fmt.Errorf("bad http response: %v", r.Status)
+ }
この if
文は、r.Body.Close()
が defer
された直後、かつレスポンスボディを bytes.Buffer
に読み込む処理の直前に挿入されています。
r.StatusCode
: 受信した HTTP レスポンスの数値ステータスコード(例: 200, 404, 500)です。http.StatusOK
:net/http
パッケージで定義されている定数で、値は200
です。!=
: 等しくないことを意味します。つまり、ステータスコードが200 OK
ではない場合、条件が真となります。return fmt.Errorf("bad http response: %v", r.Status)
: 条件が真の場合、fmt.Errorf
を使って新しいエラーオブジェクトを作成し、それを関数の呼び出し元に返します。エラーメッセージには、r.Status
(例: "500 Internal Server Error")が含まれるため、どのような HTTP エラーが発生したかを明確に伝えることができます。
この変更により、dash
関数は、HTTP リクエストが成功しなかった場合(ステータスコードが 200 OK
以外の場合)には、それ以上レスポンスボディを処理せずに、すぐにエラーを返すようになります。これにより、不正なレスポンスボディを JSON としてパースしようとする無駄な処理や、それに伴うパースエラーを防ぐことができます。
関連リンク
- Go CL (Change List): https://golang.org/cl/6775066
参考にした情報源リンク
- Go 言語
net/http
パッケージのドキュメント: https://pkg.go.dev/net/http - HTTP ステータスコード (MDN Web Docs): https://developer.mozilla.org/ja/docs/Web/HTTP/Status
- JSON (MDN Web Docs): https://developer.mozilla.org/ja/docs/Glossary/JSON
- Google App Engine (GAE) 公式サイト: https://cloud.google.com/appengine
- Go 言語
fmt
パッケージのドキュメント: https://pkg.go.dev/fmt