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

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

このコミットは、Go言語の標準ライブラリ net/rpc/jsonrpc パッケージにおける、JSON-RPCレスポンスの構造に関するバグ修正です。具体的には、レスポンスにおいて resulterror のどちらか一方のみが非nullとなるように修正し、JSON-RPCの仕様に厳密に準拠させることを目的としています。

コミット

commit 7718c626926beb0e5fed152d8d1a86be22570a34
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Fri Mar 7 16:59:11 2014 -0800

    net/rpc/jsonrpc: set exactly one of result or error to non-null in response
    
    Fixes #7442
    
    LGTM=gri
    R=golang-codereviews, gri
    CC=golang-codereviews
    https://golang.org/cl/72570044

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

https://github.com/golang/go/commit/7718c626926beb0e5fed152d8d1a86be22570a34

元コミット内容

net/rpc/jsonrpc: set exactly one of result or error to non-null in response

このコミットメッセージは、net/rpc/jsonrpc パッケージにおいて、JSON-RPCのレスポンスで result フィールドまたは error フィールドのいずれか一方のみが非nullになるように設定するという変更の意図を明確に示しています。これはJSON-RPC 2.0の仕様に準拠するための重要な修正です。

Fixes #7442

この行は、このコミットがGoのIssueトラッカーで報告されたIssue 7442を修正するものであることを示しています。Issue 7442は、JSON-RPCレスポンスの result フィールドと error フィールドの扱いに関するバグ、特にエラー発生時に result がnullにならないという問題に関連していると推測されます。

LGTM=gri, R=golang-codereviews, gri, CC=golang-codereviews

これらはコードレビューに関する情報です。LGTM (Looks Good To Me) は、gri (Andrew Gerrand) がこの変更を承認したことを示します。R はレビュー担当者、CC はカーボンコピーの対象を示しており、golang-codereviews はGoプロジェクトのコードレビューメーリングリストを指します。

https://golang.org/cl/72570044

これは、このコミットに対応するGerritのチェンジリスト(Change List)へのリンクです。Goプロジェクトでは、GitHubにプッシュされる前にGerritでコードレビューが行われます。

変更の背景

この変更の背景には、JSON-RPC 2.0の仕様への厳密な準拠があります。JSON-RPC 2.0の仕様では、レスポンスオブジェクトには result または error のどちらか一方のメンバーのみを含めるべきであると明確に規定されています。

従来の net/rpc/jsonrpc の実装では、エラーが発生した場合でも result フィールドが null ではなく、何らかの値(例えば、ゼロ値や以前の値)を持つ可能性がありました。これは、JSON-RPCクライアントがレスポンスを正しくパースする際に混乱を招く可能性があり、特に厳密なJSON-RPCパーサーを使用している場合に問題となります。

Issue 7442は、この仕様不適合を指摘したものであり、このコミットはその問題を解決するために導入されました。具体的には、TestServerErrorHasNullResult というテストが以前はスキップされていた(t.Skip("Known failing test; Issue 7442"))ことから、この問題が認識されており、修正が待たれていたことがわかります。

前提知識の解説

JSON-RPC

JSON-RPCは、リモートプロシージャコール(RPC)プロトコルの一種で、データフォーマットとしてJSONを使用します。HTTPまたはTCP/IPなどのトランスポート層プロトコル上で動作し、クライアントがサーバー上のメソッドを呼び出し、その結果をJSON形式で受け取ることを可能にします。

JSON-RPC 2.0の仕様は、そのシンプルさと柔軟性から広く採用されています。主要な概念は以下の通りです。

  • Request Object: クライアントがサーバーに送信するリクエスト。jsonrpc (バージョン), method (呼び出すメソッド名), params (メソッドの引数), id (リクエストID) を含みます。
  • Response Object: サーバーがクライアントに返すレスポンス。jsonrpc (バージョン), result (成功時の結果), error (エラー時の情報), id (対応するリクエストID) を含みます。
  • Error Object: エラーが発生した場合に error フィールドに含まれるオブジェクト。code (エラーコード), message (エラーメッセージ), data (追加のエラー情報) を含みます。

JSON-RPC 2.0 仕様における resulterror

JSON-RPC 2.0の仕様(https://www.jsonrpc.org/specification)では、レスポンスオブジェクトについて以下のように規定されています。

A Response object MUST contain one of either:

  • result: This member is REQUIRED on success.
  • error: This member is REQUIRED on error.

The Response object MUST NOT contain both a result member and error member.

つまり、成功レスポンスでは result が必須であり error は存在しない、エラーレスポンスでは error が必須であり result は存在しない、という排他的な関係が求められます。このコミットは、この「どちらか一方のみが存在する」というルールを厳密に適用するためのものです。

Go言語の net/rpc および net/rpc/jsonrpc パッケージ

  • net/rpc: Go言語の標準ライブラリに含まれるRPCパッケージです。Goの構造体とメソッドをRPCサービスとして公開し、クライアントから呼び出すための基本的なフレームワークを提供します。エンコーディングには通常、Go独自のgobエンコーディングが使用されます。
  • net/rpc/jsonrpc: net/rpc パッケージのサブパッケージで、JSON-RPCプロトコルを介してRPCサービスを提供するためのコーデック(エンコーダ/デコーダ)を実装しています。これにより、GoのRPCサービスをJSON-RPCクライアントから利用できるようになります。ServerCodecClientCodec が主要なインターフェースです。

技術的詳細

このコミットは、net/rpc/jsonrpc パッケージ内の server.go ファイルにある serverCodec 型の WriteResponse メソッドのロジックを変更しています。このメソッドは、RPC呼び出しの結果をJSON-RPCレスポンスとしてクライアントに書き込む役割を担っています。

変更前は、serverResponse 構造体のインスタンスを宣言し、IdResult を設定した後、エラーの有無に応じて Error フィールドを設定していました。この際、r.Error == "" (エラーがない場合) に resp.Error = nil と設定していましたが、r.Error != "" (エラーがある場合) には resp.Result が明示的に null に設定されるロジックがありませんでした。これにより、エラーレスポンスにもかかわらず result フィールドが何らかの値を持つ可能性がありました。

変更後は、serverResponse 構造体の初期化時に Id のみを設定し、その後の条件分岐で result または error のどちらか一方のみを設定するようにロジックが修正されています。

具体的には、以下のようになります。

  1. resp := serverResponse{Id: b}: serverResponse 構造体を初期化し、Id フィールドのみを設定します。この時点では ResultError はGoのゼロ値(ポインタ型の場合は nil)です。
  2. if r.Error == "":
    • エラーがない場合(成功レスポンス)。
    • resp.Result = x: result フィールドに実際のRPC呼び出し結果 x を設定します。このとき resp.Error は初期値の nil のままです。
  3. else:
    • エラーがある場合(エラーレスポンス)。
    • resp.Error = r.Error: error フィールドにエラーメッセージ r.Error を設定します。このとき resp.Result は初期値の nil のままです。

この修正により、JSON-RPC 2.0の仕様で求められる「resulterror のどちらか一方のみが非nullである」という条件が満たされるようになります。

また、all_test.go の変更は、この修正が正しく機能することを確認するためのテストの有効化です。TestServerErrorHasNullResult というテストが以前はスキップされていましたが、このコミットによってスキップが解除され、テストが実行されるようになります。このテストは、サーバーエラーが発生した場合にレスポンスの result フィールドが null であることを検証するものです。

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

diff --git a/src/pkg/net/rpc/jsonrpc/all_test.go b/src/pkg/net/rpc/jsonrpc/all_test.go
index 07913572aa..a433a365e8 100644
--- a/src/pkg/net/rpc/jsonrpc/all_test.go
+++ b/src/pkg/net/rpc/jsonrpc/all_test.go
@@ -205,7 +205,6 @@ func TestMalformedOutput(t *testing.T) {
 }
 
 func TestServerErrorHasNullResult(t *testing.T) {
-	t.Skip("Known failing test; Issue 7442")
 	var out bytes.Buffer
 	sc := NewServerCodec(struct {
 		io.Reader
diff --git a/src/pkg/net/rpc/jsonrpc/server.go b/src/pkg/net/rpc/jsonrpc/server.go
index 16ec0fe9ad..e6d37cfa64 100644
--- a/src/pkg/net/rpc/jsonrpc/server.go
+++ b/src/pkg/net/rpc/jsonrpc/server.go
@@ -100,7 +100,6 @@ func (c *serverCodec) ReadRequestBody(x interface{}) error {
 var null = json.RawMessage([]byte("null"))
 
 func (c *serverCodec) WriteResponse(r *rpc.Response, x interface{}) error {
-	var resp serverResponse
 	c.mutex.Lock()
 	tb, ok := c.pending[r.Seq]
 	if !ok {
@@ -114,10 +113,9 @@ func (c *serverCodec) WriteResponse(r *rpc.Response, x interface{}) error {
 		// Invalid request so no id.  Use JSON null.
 		tb = &null
 	}
-	resp.Id = tb
-	resp.Result = x
+	resp := serverResponse{Id: tb}
 	if r.Error == "" {
-		resp.Error = nil
+		resp.Result = x
 	} else {
 		resp.Error = r.Error
 	}

コアとなるコードの解説

src/pkg/net/rpc/jsonrpc/all_test.go の変更

 func TestServerErrorHasNullResult(t *testing.T) {
-	t.Skip("Known failing test; Issue 7442")
 	var out bytes.Buffer
 	sc := NewServerCodec(struct {
 		io.Reader

この変更は、TestServerErrorHasNullResult というテスト関数の先頭にあった t.Skip("Known failing test; Issue 7442") の行を削除しています。これは、このテストが以前は既知のバグ(Issue 7442)のためにスキップされていたが、今回の修正によってそのバグが解消されたため、テストを有効化して実行できるようにしたことを意味します。このテストは、JSON-RPCサーバーがエラーレスポンスを返す際に、result フィールドが正しく null になることを検証するものです。

src/pkg/net/rpc/jsonrpc/server.go の変更

 func (c *serverCodec) WriteResponse(r *rpc.Response, x interface{}) error {
-	var resp serverResponse
 	c.mutex.Lock()
 	tb, ok := c.pending[r.Seq]
 	if !ok {
@@ -114,10 +113,9 @@ func (c *serverCodec) WriteResponse(r *rpc.Response, x interface{}) error {
 		// Invalid request so no id.  Use JSON null.
 		tb = &null
 	}
-	resp.Id = tb
-	resp.Result = x
+	resp := serverResponse{Id: tb}
 	if r.Error == "" {
-		resp.Error = nil
+		resp.Result = x
 	} else {
 		resp.Error = r.Error
 	}

この部分が変更の核心です。

変更前:

	var resp serverResponse
	// ...
	resp.Id = tb
	resp.Result = x // ここで常にResultが設定される
	if r.Error == "" {
		resp.Error = nil
	} else {
		resp.Error = r.Error // エラーがあってもResultはxのまま
	}

変更前は、まず serverResponse 型の変数 resp を宣言し、その後に resp.Id = tbresp.Result = x を設定していました。resp.Result = x の行は、RPC呼び出しの結果 xresp.Result に代入しています。その後、r.Error == "" の条件でエラーの有無をチェックし、エラーがない場合は resp.Error = nil と設定していました。しかし、エラーがある場合 (else ブロック) では resp.Error = r.Error と設定するだけで、resp.Resultx の値がそのまま残ってしまいます。これが、エラーレスポンスにもかかわらず result フィールドが null にならない原因でした。

変更後:

	resp := serverResponse{Id: tb} // Idのみを設定して初期化
	// ...
	if r.Error == "" {
		resp.Result = x // 成功時のみResultを設定
	} else {
		resp.Error = r.Error // エラー時のみErrorを設定
	}

変更後では、resp の宣言と初期化が resp := serverResponse{Id: tb} となっています。これにより、respId フィールドのみが設定され、ResultError フィールドはGoのゼロ値(この場合は nil)で初期化されます。

その後の if r.Error == "" の条件分岐で、エラーがない場合は resp.Result = xResult フィールドに結果を代入します。このとき Error フィールドは初期値の nil のままです。

一方、エラーがある場合は else ブロックに入り、resp.Error = r.ErrorError フィールドにエラーメッセージを代入します。このとき Result フィールドは初期値の nil のままです。

この修正により、resulterror のどちらか一方のみが設定され、もう一方は nil のままとなるため、JSON-RPC 2.0の仕様に厳密に準拠したレスポンスが生成されるようになります。

関連リンク

参考にした情報源リンク