json-rpc 1.0規範解讀

JSON可能是這個地球上最簡單的文本數據格式了,可讀、靈活、數據量小,編解碼方便、速度快,對Unicode和特殊字符支持的好。對比下XML,就知道額外的各種標籤節點需要浪費多少字節數。JSON字符默認都要使用Unicode形式,所有非ACSII字符都可以用\uXXXX表示,而不需要額外的轉義。相比之下,XML裏需要使用轉義或是CDATA(類似HTML裏的PRE標籤)、或是Base64才能表示特殊數據。當然缺點也很明顯,比二進制數據結構的數據量大,編解碼慢,沒有完備的類型系統,表達能力有限。

JSON-RPC是一個使用json對象作爲數據載體的遠程過程調用(Remote Process Call,RPC)技術。

"Does distributed computing have to be any harder than this? I don't think so." -- Jan-Klaas Kollhof

JSON-RPC的設計目標就是兩個字:簡單。我們知道一個rpc框架是爲了2個系統間的交互通信,這就需要定義一箇中間的數據傳輸格式。爲了跟系統本身用的平臺數據結構轉換,需要提供一套序列化和反序列化這個數據格式的功能。然後就是需要某種通信協議來傳輸實際遠程調用的數據。最後還需要通信的兩端有實現的代碼樁(stub&skeleton),這一般是基於動態代理或AOP實現的代理,一個可供調用的接口結構,使得框架隱藏了其他所有的技術細節(數據格式、序列化、網絡傳輸等),程序裏能像本地方法調用一樣調用遠程的方法。總結一下:

  1. 數據格式
  2. 序列化功能
  3. 通信協議
  4. 代碼樁

JSON-RPC規範裏只顯式規定了數據格式(即JSON),建議了通信協議(TCP或是HTTP),序列化和代碼樁沒有提及。當然序列化比較好辦,各種語言裏都有豐富的JSON序列化庫(參見參考資料1)。那麼代碼樁就完全留給了實現JSON-RPC規範的框架自己去處理了,通信協議這一塊的具體處理也大部分需要框架自己考慮。

我們知道兩個系統的通信,可能是同步阻塞的(每個請求要等響應完成以後再發下一個請求),也可能是異步非阻塞的(請求1發完,繼續發請求2,哪個響應回來就處理哪個響應);也可能是單向的(one-way,發了就不管了、不要響應信息),也可能是請求-響應的(request-response),每個請求都需要有顯式的回覆。

JSON-RPC1.0裏,把通信的雙方當做對等的兩個端。

  • 定義瞭如何發請求和返回結果、出錯信息,
  • 如何處理單向和請求-響應的交互過程,
  • 以及雙向異步請求如何匹配結果和請求,
  • 最後還添加了一個簡單的類型提示(class hinting)擴展功能(想法很好,但是就兩句話沒細節,挺雞肋的)。

我們來具體看看規範的內容。

請求(request)

請求端發送一個JSON對象表示自己要調用的方法,示例如下:

{ "method": "echo", "params": ["Hello JSON-RPC"], "id": 1}

其中包含三個關鍵元素:

  • method:表示要調用的方法
  • params:表示參數的數組
  • id:表示這次請求是需要響應的,服務方需要提供一個同樣是id爲1的響應信息。

這個id字段非常有意思,一般的同步RPC都不需要考慮這個東西,因爲一般情況下,一個RPC請求只包含一個請求,並且請求會等待響應信息返回,在這之前一直會阻塞調用方的處理線程,這樣整個RPC纔像是調用的本地方法。

響應(response)

一個典型的響應信息如下:

{ "result": "Hello JSON-RPC", "error": null, "id": 1}

關鍵元素也是3個:

  • result:表示返回結果,如果出錯result必須爲null
  • error:表示出錯,如果請求成功,則error必須爲null
  • id:表示爲此響應對應的請求id

通知(notification)

通知信息類似一個請求,但是id必須爲null。通知代表單向的請求,不能回覆,可以由請求方發送給服務方,也可以由服務方發送給請求方(協議原則上要求雙方對等,都可以是請求方或服務方,這一點上HTTP其實是不滿足的)。

通信(transport)

  • TCP

協議推薦通信雙方在TCP下使用字節流(byte stream)的方式交互。此時雙方是對等的。關閉連接前必須給所有未應答的請求方發送一個錯誤信息。無效的請求或響應將會導致連接關閉。

  • HTTP

在一些限制的條件下,也可以使用HTTP作爲通信協議。此時通信雙方明顯不在對等,有了客戶端和服務器端。

考慮到HTTP的開銷問題,協議允許一次POST可以帶上多個rpc請求或通知。由於HTTP下,server不能夠主動訪問client,所以server可以在HTTP響應中附帶上自己的請求和通知。這個地方協議們沒有說清楚具體操作,細節也是個蛋疼的事兒。

類型提示(class hinting)

非常簡單,就是加了個屬性表示構造函數constructor,可以用參數[param1,...]來初始化對象。而且如果對象初始化的時候調用了這個構造器,那麼相應的屬性(比如prop1)也會添加到對象,示例代碼:

{"__jsonclass__":["constructor", [param1,...]], "prop1": ...}

協議沒有具體說清楚這個地方,具體怎麼操作,怎麼對應原生環境裏的類型,蛋疼++。

一個多次通信的例子

JSON-RPC過程如下,其中-->代表發送數據到服務方,<--代表服務方的響應:

--> {"method": "postMessage", "params": ["Hello all!"], "id": 99}
<-- {"result": 1, "error": null, "id": 99}
<-- {"method": "handleMessage", "params": ["user1", "we were just talking"], "id": null}
<-- {"method": "handleMessage", "params": ["user3", "sorry, gotta go now, ttyl"], "id": null}
--> {"method": "postMessage", "params": ["I have a question:"], "id": 101}
<-- {"method": "userLeft", "params": ["user3"], "id": null}
<-- {"result": 1, "error": null, "id": 101}

關於JSON-RPC1.0規範的進一步討論

  • 關於通知

通知機制也可以用於類似FTP協議的NOOP,或是其他通訊協議裏的Keep Alive心跳機制。在具體使用的過程中,定時的發送通知來確認雙方都在線,並且可以捎帶異步處理的信息。

  • 關於id

請求中id字段的作用,就跟JMS協議裏的correlationID一樣,可以起到區分多個請求的作用,這樣又兩個好處:

  1. 多個請求可以打包成一個請求,返回也可以返回多個響應,處理方按照id區分多個數據即可;
  2. 可以做異步的響應,反正我不保證順序了,也能根據id來匹配原來是哪個請求來着。

特別是配合HTTP和通知功能,使得異步調用變得可能:客戶端直接發送請求1後返回,繼續發送請求2,請求3...等等;服務端在處理完成請求1後,可能把響應放到請求2或3的HTTP response報文返回,或者在接下里的某次交互裏返回即可。

  • 關於類型

JSON-RPC跟XML-RPC非常類型,但是藉助於XML本身的schema結構,XML-RPC定義了一套基礎的數據類型、以及在基礎上的構造複雜數據類型的能力,這樣XML-RPC中就可以用於更復雜的業務交互場景。而JSON-RPC使用JS原生的弱類型,只能表示非常簡單和模糊的元數據結構,不利於複雜場景和實現代碼樁的生成工具。如果有一些更有效的schema約束,則可以把JSON-RPC應用得更廣泛。

 

JSON-RPC參見:http://www.jsonrpc.org/specification

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章