json-rpc整理(JSON remote procedure call)

json rpc 類似xml rpc,透過HTTP的方式進行資料交換來達成remote procedure call,最大的差別在data serialization改成JSON,另外json rpc的規範大約在2005-2010年左右,在底層的傳輸協定也不要求使用HTTP,可以是TCP/IP stream。

spec可參考

概念上跟xml rpc相同,但是將client server的角色模糊了,採用對等peer的概念,當然如果以功能來看,我們可以將make function call那端看成client,function execution那端看成client,只是在json rpc中的連線概念是採用對等,任何一端可能是client,也可能是server,為了清楚的對比xml rpc文章中的範例,以下我將xml rpc中is_even改成在json rpc裡實現,用Node.js當server,python當client

var express = require("express");
var bodyParser = require("body-parser");
var { JSONRPCServer } = require("json-rpc-2.0");
var server = new JSONRPCServer();

server.addMethod("is_even", function(n){
  return n % 2 == 0;
});

var app = express();
app.use(bodyParser.json());

app.post("/jsonrpc", async function(req, res){
  let jreq = req.body;
  let jresp = await server.receive(jreq);
  if(jresp){
    res.json(jresp);
  }else{
    res.sendStatus(204);
  }
});

app.listen(8091);
import requests
import json

def main():
    url = "http://localhost:8091/jsonrpc"
    payload = {
        "method": "is_even",
        "params": [100],
        "jsonrpc": "2.0",
        "id": 0,
    }
    response = requests.post(url, json=payload).json()
    print(response)
if __name__ == "__main__":
    main()

輸入100 回傳結果如下

{u'jsonrpc': u'2.0', u'id': 0, u'result': True}

使用起來簡單直覺,python 可以再利用語言本身的proxy機制,讓呼叫起來比較簡潔

以下整理json rpc 1.0 spec

其中提到 To invoke a remote method, a request is sent. Unless the request is a notification it must be replied to with a response. 在一般情形下,request response模式,但也可以單向client notify server

request body有 method, params, id,其中method 就是method name和xml rpc相同,params就是function parameter,id則是用來識別request / response,id不要求型態類別,可以是sequence id or uuid之類的,spec特別提到 The request id. This can be of any type. It is used to match the response with the request that it is replying to.

Notification: spec: A notification is a special request which does not have a response (並且 id = null)

response body有 result, error, id,其中在error發生時,result為null (spec: This must be null in case there was an error invoking the method. )

在底層的transport,spec分別說明了JSON-RPC over stream connections和JSON-RPC over HTTP的行為

因為HTTP本質是client server的架構,如果要做peer to peer,比較簡單的作法是兩個peer都開http port,分別當server,也當client,在spec中是採用另外一種作法: client會持續的polling (with HTTP POST),server要發request,或是client要回應server來的request,就是透過下一次的HTTP POST夾帶

顯然以spec描述的json rpc over HTTP的方式沒有效率,也因此在spec中說: The use of TCP/IP socket streams is encouraged

v1 spec 第3段 JSON Class hinting主要是描述type的擴展,原因是JSON type就那幾個type,這邊提供了一個擴充機制,這邊必須要接收端和發送端都對type有一致的理解,這就又回到serialization怎麼定的問題了,看起來和單純直接轉成string serialization是沒太多差異

第四段描述communication examples, 比較值得注意的是他的multiple request responses範例 (節錄自spec v1.0)

...

--> {"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 rpc的設計概念上是雙向且asynchronous

因為asynchronous,所以需要用message id來mapping,第一筆postMessage 馬上就有response回來,但不一定如此,在最下面倒數第3筆的postMessage,後面還插入一筆notification (userLeft),然後才是id: 101的response

json rpc 2.0 大約在2010年左右,在1.0的基礎上做了擴展,但spec不要求實作相容1.0,並且在特定詞彙用字上根據RFC 2119的語意,另外以下點出幾個比較重要的差異

jsonrpc field

多了 jsonrpc field,帶版本資訊 “jsonrpc”: “2.0” ,這個方便未來版本相容性處理

params field

支援named parameter,1.0只支援positional parameter

在1.0的spec: params是An Array of objects to pass as arguments to the method., 在2.0中是A Structured value that holds the parameter values to be used during the invocation of the method. 在4.2 Parameter Structures 有更詳細的描述,如果params是array,就如同1.0是按照順序傳入(positional parameter),如果是object,代表是key value,key為parameter name (named parameter)

id field

notification 在1.0 id = null, 2.0 直接拿掉id (注意這邊指的是request id),對於response id來說,是REQUIRED

2.0 spec也提到 If there was an error in detecting the id in the Request object (e.g. Parse error/Invalid Request), it MUST be Null. 所以在response message中id: null是可能的

error、result field

2.0中有error就沒有result,有result就沒有error,在1.0中是要求兩個field都存在,但值可以是null

error object

這是在2.0新定義的,包含code、message、data,code要求為integer,並且spec中參考了  http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php (Specification for Fault Code Interoperability, version 20010516) 的設計

batch request

這是在2.0新定義的,直接將多個request合併在一個array中

整體來說 json rpc 2.0 spec還是保持簡要的風格(相較於W3C的SOAP)
,作為一個簡單的protocol spec設計文件,其spec內容文件的大小大概是一個人日可以做得出來的範圍,值得作為啟發與參考

This entry was posted in Network. Bookmark the permalink.

Leave a Reply