技術基礎--JSON-RPC2.0

前言

最近剛加入區塊鏈學習的熱潮,從一些基本技術開始學起。本文翻譯自JSON-RPC 2.0 Specification. 其實協議很簡單,本不需要翻譯,主要是為了記錄這個學習過程,以及加深理解。

1. 概覽

JSON是一種輕量級的數據交換格式。它可以地標數字,字符串,有序數組,以及鍵值對。
而JSON-RPC是一種無狀態的,輕量級的遠程程序調用協議。不只一種數據結構及其處理規則可以符合這種定義。數據通過socket, http, 或其他環境傳輸,并不能確定其將在本進程內使用。它使用JSON來座位數據格式。
設計也很簡單。

2. 約定

關鍵詞“MUST”, "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", "OPTIONAL"的解釋可以在RFC2119中找到。
由于采用了JSON協議,其可傳輸的數據類型也和JSON相同。JSON可以表示四種基本類型, Strings, Numbers, Booleans, Null,還有兩種結構類型:Objects, Arrays。當我們提到“基本類型”的時候,是指四種基本的JSON類型,而“結構類型”則指代以上兩種JSON結構類型。當提到JSON類型時,總會把第一個字母大寫,比如Object, Array, String, Number, Boolean, Null. True和False也是如此。

3. 兼容

JSON-RPC 2.0定義的請求對象和響應對象和現有的JSON-RPC 1.0客戶端/服務器有兼容問題。這兩個版本其實很好區分,2.0定義了一個叫"jsonrpc"的成員,其值時2.0,而1.0版本沒有。2.0的實現往往要兼容1.0的對象,即使我們在開發點對點以外或者明顯不是1.0的業務的時候亦是如此。

4. 請求對象

RPC調用是指發送一個請求對象到遠程服務器上。請求對象包括以下成員:
jsonrpc: 用來聲明JSON-RPC協議的版本,固定為“2.0”
method:需要調用的方法。方法名以單詞rpc開頭,后面跟上期限字符,這個字段在rpc交互的方法及其擴展上被存儲起來,并且不能用于其他意義。
params: 結構化的值,用于存儲方法響應需要用到的參數,且不能被省略。
id:客戶端分配的一個標識符,可以包含字符串,數字或者為空。如果沒有id,就會被當成是廣播通知。這個值一般不能為Null,且為數字時不能有小數。如果請求中含有這個字段,服務器在響應時,必須原樣返回該字段,用來關聯以下兩個對象的不同環境:

  1. 請求對象中不推薦使用Null作為id的值,因為2.0聲明中使用Null來響應未知的id。而JSON-RPC 1.0版本則使用Null的id來作為通知,這會引起混淆。
  2. 數字的小數部分也會有問題,因為十進制的小數不能用二進制小數來精確表示。

4.1 通知

當請求參數中不發送id成員時,該請求會被當作通知處理。對于通知,客戶端不關心響應對象,因為服務端也沒必要返回響應對象,確切的說,服務端不準答復一個通知請求,即便這個請求是批處理請求中的一個。
根據這個定義,通知請求并不會被確認,因為客戶端不會收到響應對象,更進一步說,通知請求的客戶端無法感知到錯誤,比如參數錯誤/網絡錯誤等。

4.2 參數結構

RPC調用的參數必須是結構化的值(對象或者數組),對象通過名字遍歷,而數組通過位置可遍歷。
數組參數的遍歷順序必須與服務端順序一致。
對象參數的成員值必須與服務端期望的一致,且在大小寫上精確匹配。一旦有成員缺失,會導致錯誤產生。

  1. 響應對象
    除了通知,服務器上收到RPC調用時,必須做出響應。響應對象時一個JSON對象,包含以下成員:
    jsonrpc: JSON-RPC協議版本,指定"2.0"
    result: 成功響應時包含該字段,有錯誤發生則不包含。其取值取決于服務器上定義的方法的響應。
    error: 當有錯誤發生時含該字段,正確處理時不能含有該字段。其具體值定義在5.1中。
    id: 這個字段必須包含,且取值和請求對象中的id字段相同。如若檢測解析請求對象中的id出錯,則使用Null。
    不管響應中含有result還是error,不能同時含有result和error。

5.1 Error對象

當RPC請求出錯時,服務器響應的Response對象中,必須包含error,且包含以下成員:
code: Number類型,表示出錯類型
message: String類型 簡介的一句話來描述錯誤
data: 可以是基本類型,也可以是結構類型,來表示錯誤的額外信息,且可以缺省。具體取值由服務器自定義,比如錯誤詳情,訪問限制等等。
-32768到-32000之間的錯誤碼是系統保留錯誤碼,協議預定義方便未來使用。錯誤碼的定義和XML-RPC類似:
-32700: 解析錯誤,無效的JSON結構,服務器在解析JSON時出錯
-32600: 請求無效,Request對象不是一個合法的JSON請求
-32601: 未知的方法,服務器未定義該method,或者該方法不可用
-32602: 參數錯誤
-32603: 網絡錯誤
-32000--32099: 服務器錯誤,服務器其他錯誤的保留錯誤碼
上述區間以外的錯誤碼可在應用開發時使用。

6. 批處理

同時發送多個Request對象時, 客戶端可以把請求都放到一個數組里一起發送。
服務端收到Request對象數組并處理完成后,應當以數組的形式返回,且數組中包含了響應的請求的Response對象。每一個請求對應一個響應,如果請求是通知的話,則不包含該Response對象。服務端在批處理請求任務時,可以按任何順序或者并行化處理。
服務端對請求進行批處理時者不是至少長度為1的合法請求對象數組時,服務器響應的對象必須是一個單的Response對象。如果沒有Response數組中不包含Response對象,那也不能返回空數組,而應該什么都不返回。

7. 舉例

7.1. 使用數組參數

--> {"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1}
<-- {"jsonrpc": "2.0", "result": 19, "id": 1}

--> {"jsonrpc": "2.0", "method": "subtract", "params": [23, 42], "id": 2}<-- {"jsonrpc": "2.0", "result": -19, "id": 2}

7.2. 使用對象參數

--> {"jsonrpc": "2.0", "method": "subtract", "params": {"subtrahend": 23, "minuend": 42}, "id": 3}<-- {"jsonrpc": "2.0", "result": 19, "id": 3}--> {"jsonrpc": "2.0", "method": "subtract", "params": {"minuend": 42, "subtrahend": 23}, "id": 4}<-- {"jsonrpc": "2.0", "result": 19, "id": 4}

7.3. 通知

--> {"jsonrpc": "2.0", "method": "update", "params": [1,2,3,4,5]}
--> {"jsonrpc": "2.0", "method": "foobar"}

7.4. 方法不存在

--> {"jsonrpc": "2.0", "method": "foobar", "id": "1"}
<-- {"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found"}, "id": "1"}

7.5. Request對象不是合法的JSON

--> {"jsonrpc": "2.0", "method": "foobar, "params": "bar", "baz]
<-- {"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error"}, "id": null}

7.6. Request請求對象無效(相關字段類型不符合文檔定義)

--> {"jsonrpc": "2.0", "method": 1, "params": "bar"}
<-- {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null}

7.7. Request對象為數組,但不是合法JSON

--> [ {"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"}, {"jsonrpc": "2.0", "method"]<-- {"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error"}, "id": null}

7.8. Request對象為空數組

--> []<-- {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null}

7.9. Request對象為數組,但數組成員不是合法的Request對象:

--> [1]
<-- [ {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null}]

7.10. Request對象為數組,且有多個成員,但部分成員不是合法的Request對象:

--> [1,2,3]
<-- [ {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null}, {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null}, {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null}]

7.11. 正確的批處理調用和響應

--> [
{"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"},
{"jsonrpc": "2.0", "method": "notify_hello", "params": [7]},
{"jsonrpc": "2.0", "method": "subtract", "params": [42,23], "id": "2"},
{"foo": "boo"},
{"jsonrpc": "2.0", "method": "foo.get", "params": {"name": "myself"}, "id": "5"},
{"jsonrpc": "2.0", "method": "get_data", "id": "9"}
]
<-- [
{"jsonrpc": "2.0", "result": 7, "id": "1"},
{"jsonrpc": "2.0", "result": 19, "id": "2"},
{"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null},
{"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found"}, "id": "5"},
{"jsonrpc": "2.0", "result": ["hello", 5], "id": "9"}
]

7.12. 全通知類型的批處理調用

--> [
{"jsonrpc": "2.0", "method": "notify_sum", "params": [1,2,4]},
{"jsonrpc": "2.0", "method": "notify_hello", "params": [7]}
]
<-- //Nothing is returned for all notification batches

8. 擴展

使用rpc開頭的方法名是系統擴展方法,且不能用于其他場合。每一個系統擴展都在相關聲明中定義。系統擴展是可選的。

9. 我們學到什么

1)如何撰寫一個規范,或者說一個規范由哪些部分組成,本規范是一個很好的模版
2)如何做前后兼容。jsonrpc的兼容方式很簡單,在請求頭部擴展一個jsonrpc的版本號即可。如果是良好的設計,在1.0的時候就應該加上此字段。
3)嚴謹性。比如,我們不能簡單的使用Null作為id參數來表示通知,服務端解析id失敗也返回Null的id,無法區分這兩種情形。
4)批處理。一個好的設計必然要考慮多任務的批處理,在設計批處理時,需要考慮數據的解析,服務器可能不按序處理,以及可能并發處理,需要考慮不同的請求在不同的時許下處理可能產生的影響
5)舉例。和測試用例的設計有點類似,盡可能覆蓋全面

10 參考文獻

JSON-RPC 2.0 Specification

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容