json-rpc 是 rpc 通信過(guò)程中定義的一套 json 格式標(biāo)準(zhǔn),最早是 json-rpc1.0,最新是 json-rpc2.0裙顽。
使用 json 格式來(lái)通信,通信的雙方 client宣谈,server 必須約定好統(tǒng)一的字段,以便彼此能互相解析键科,這就是 json-rpc 標(biāo)準(zhǔn)的來(lái)由闻丑。
根據(jù) json-rpc 1.0 約定,Request 和 Response 的格式必須符合如下要求:
(1)Request
- method A String containing the name of the method to be invoked.
- params An Array of objects to pass as arguments to the method.
- id The request id. This can be of any type. It is used to match the response with the request that it is replying to.
(2)Response
- result The Object that was returned by the invoked method. This must be null in - - case there was an error invoking the method.
- error An Error object if there was an error invoking the method. It must be null if there was no error.
- id This must be the same id as the request it is responding to.
例子:
request:data sent to service
{ "method": "echo", "params": ["Hello JSON-RPC"], "id": 1}
response:data coming from service
{ "result": "Hello JSON-RPC", "error": null, "id": 1}
json-rpc2.0
(1)Request
- jsonrpc - A String specifying the version of the JSON-RPC protocol. MUST be exactly "2.0".
- method - A String containing the name of the method to be invoked.
- params - An Array of objects to pass as arguments to the method.
- id - The request id. This can be of any type. It is used to match the response with the request that it is replying to.
(2)Response
- jsonrpc - A String specifying the version of the JSON-RPC protocol. MUST be exactly "2.0".
- result - The Object that was returned by the invoked method. This must be null in case there was an error invoking the method.
- error - An Error object if there was an error invoking the method. It must be null if there was no error.
- id - This must be the same id as the request it is responding to.
Notification
A Notification is a Request object without an "id" member. A Request object that is a Notification signifies the Client's lack of interest in the corresponding Response object, and as such no Response object needs to be returned to the client.
The Server MUST NOT reply to a Notification, including those that are within a batch request.
例子:
request:data sent to service
{"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1}
response:data coming from service
{"jsonrpc": "2.0", "result": 19, "id": 1}
a notification request
{"jsonrpc": "2.0", "method": "update", "params": [1,2,3,4,5]}
a notification response
{"jsonrpc": "2.0", "method": "foobar"}
json-rpc1.0 VS json-rpc2.0
- "jsonrpc" field added added a version-field to the Request (and also to the Response) to resolve compatibility issues with JSON-RPC 1.0.
-
client-server instead of peer-to-peer:
JSON-RPC 2.0 uses a client-server-architecture.
V1.0 used a peer-to-peer-architecture where every peer was both server and client. -
Transport independence:
JSON-RPC 2.0 doesn't define any transport-specific issues, since transport and RPC are independent.
V1.0 defined that exceptions must be raised if the connection is closed, and that invalid requests/responses must close the connection (and raise exceptions). - Named parameters added (see Example below)
-
Reduced fields:
- Request: params may be omitted
- Notification: doesn't contain an id anymore
- Response: contains only
result
ORerror
(but not both)
- Optional parameters: defined that unspecified optional parameters SHOULD use a default-value.
- Error-definitions added
Go官方庫(kù)實(shí)現(xiàn)了JSON-RPC 1.0勋颖。JSON-RPC是一個(gè)通過(guò)JSON格式進(jìn)行消息傳輸?shù)腞PC規(guī)范嗦嗡,因此可以進(jìn)行跨語(yǔ)言的調(diào)用。
源文件:rpc/jsonrpc/client.go
type clientRequest struct {
Method string `json:"method"`
Params [1]interface{} `json:"params"`
Id uint64 `json:"id"`
}
func (c *clientCodec) WriteRequest(r *rpc.Request, param interface{}) error {
c.mutex.Lock()
c.pending[r.Seq] = r.ServiceMethod
c.mutex.Unlock()
c.req.Method = r.ServiceMethod
c.req.Params[0] = param
c.req.Id = r.Seq
return c.enc.Encode(&c.req)
}
上面的 enc 就是 json.Encoder()
饭玲,可見(jiàn) WriteRequest()
函數(shù)最終把請(qǐng)求 encode 成 json 格式發(fā)送了侥祭。
調(diào)用流程分析:
func CallRpcService(c *rpc.Client) {
args := &server.Args{7, 8}
var reply int
err := c.Call("Arith.Mult", args, &reply)
if err != nil {
log.Fatal("Arith error: ", err)
}
fmt.Printf("Arith: %d*%d= %d\n", args.A, args.B, reply)
}
這里是調(diào)用的 c.Call("Arith.Mult", args, &reply")
來(lái)實(shí)現(xiàn)發(fā)送 json-rpc request 到服務(wù)器的。
Call() 函數(shù)內(nèi)部的調(diào)用過(guò)程如下:
源文件:rpc/client.go
// (1)
func (client *Client) Call(serviceMethod string, args interface{}, reply interface{}) error {
call := <-client.Go(serviceMethod, args, reply, make(chan *Call, 1)).Done
return call.Error
}
// (2)
func (client *Client) Go(serviceMethod string, args interface{}, reply interface{}, done chan *Call) *Call {
call := new(Call)
call.ServiceMethod = serviceMethod
call.Args = args
call.Reply = reply
...
client.send(call)
return call
}
// (3)
func (client *Client) send(call *Call) {
...
// Encode and send the request.
client.request.Seq = seq
client.request.ServiceMethod = call.ServiceMethod
err := client.codec.WriteRequest(&client.request, call.Args)
...
}
源文件:rpc/jsonrpc/client.go
// (4)
func (c *clientCodec) WriteRequest(r *rpc.Request, param interface{}) error {
c.mutex.Lock()
c.pending[r.Seq] = r.ServiceMethod
c.mutex.Unlock()
c.req.Method = r.ServiceMethod
c.req.Params[0] = param
c.req.Id = r.Seq
return c.enc.Encode(&c.req)
}
Go 的 net/rpc/jsonrpc
庫(kù)可以將 JSON-RPC 的請(qǐng)求轉(zhuǎn)換成自己內(nèi)部的格式,比如 request header 的處理:
func (c *serverCodec) ReadRequestHeader(r *rpc.Request) error {
c.req.reset()
if err := c.dec.Decode(&c.req); err != nil {
return err
}
r.ServiceMethod = c.req.Method
c.mutex.Lock()
c.seq++
c.pending[c.seq] = c.req.Id
c.req.Id = nil
r.Seq = c.seq
c.mutex.Unlock()
return nil
}
Go 語(yǔ)言官方庫(kù)目前不支持 JSON-RPC 2.0 矮冬,但是有第三方開(kāi)發(fā)者提供了實(shí)現(xiàn)谈宛,比如:
https://github.com/powerman/rpc-codec
https://github.com/dwlnetnl/generpc
一些其它的 codec 如 bsonrpc、messagepack胎署、protobuf 等吆录。
如果你使用其它特定的序列化框架,你可以參照這些實(shí)現(xiàn)來(lái)寫(xiě)一個(gè)你自己的 rpc codec琼牧。
關(guān)于 Go 序列化庫(kù)的性能的比較可以參考 gosercomp恢筝。
注意:
Go 語(yǔ)言提供的 jsonrpc 包不支持 json-rpc over HTTP,因此我們不能通過(guò) curl 命令來(lái)給 Server 發(fā)送請(qǐng)求來(lái)測(cè)試巨坊。如果我們真的需要用 http 請(qǐng)求來(lái)測(cè)試的話(huà)撬槽,那么我們就應(yīng)該提供一個(gè) HTTP Hanlder 來(lái)處理 HTTP request/response,然后把他適配到 ServerCodec 函數(shù)中去趾撵,比如:
package main
import (
"io"
"log"
"net"
"net/http"
"net/rpc"
"net/rpc/jsonrpc"
"os"
)
const addr = "localhost:8080"
type HttpConn struct {
in io.Reader
out io.Writer
}
func (c *HttpConn) Read(p []byte) (n int, err error) { return c.in.Read(p) }
func (c *HttpConn) Write(d []byte) (n int, err error) { return c.out.Write(d) }
func (c *HttpConn) Close() error { return nil }
// RPC Api structure
type Test struct{}
// Greet method arguments
type GreetArgs struct {
Name string
}
// Grret message accept object with single param Name
func (test *Test) Greet(args *GreetArgs, result *string) error {
*result = "Hello " + args.Name
return nil
}
// Start server with Test instance as a service
func startServer() {
test := new(Test)
server := rpc.NewServer()
server.Register(test)
listener, err := net.Listen("tcp", addr)
if err != nil {
log.Fatal("listen error:", err)
}
defer listener.Close()
http.Serve(listener, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/test" {
serverCodec := jsonrpc.NewServerCodec(&HttpConn{in: r.Body, out: w})
w.Header().Set("Content-type", "application/json")
w.WriteHeader(200)
err := server.ServeRequest(serverCodec)
if err != nil {
log.Printf("Error while serving JSON request: %v", err)
http.Error(w, "Error while serving JSON request, details have been logged.", 500)
return
}
}
}))
}
func main() {
startServer()
}
現(xiàn)在侄柔,我們就可以用 curl 命令來(lái)測(cè)試了,比如:
$ curl -X POST -H "Content-Type: application/json" -d '{"id": 1, "method": "Test.Greet", "params": [{"name":"world"}]}' http://localhost:8080/test
網(wǎng)上也有 json-rpc over http 的第三方開(kāi)源庫(kù)鼓寺,比如 gorilla/rpc
使用 gorrlla/rpc 的實(shí)例:
https://haisum.github.io/2015/10/13/rpc-jsonrpc-gorilla-example-in-golang/
參考:
http://www.simple-is-better.org/rpc/#differences-between-1-0-and-2-0