RPC / JSON-RPC in Golang
RPC(Remote Procedure Call)—— 远程过程调用。通过它可以使函数调用模式网络化。客户端就像调用本地函数一样,客户端把这些参数打包之后通过网络传递到服务端,服务端解包到处理过程中执行,然后将执行的结果反馈给客户端。在 OSI 网络通信模型中,RPC 跨越了传输层和应用层。RPC 使得开发分布式应用程序更加容易。
HTTP RPC server using golang net/rpc package
Go 标准包中已经提供了对 RPC 的支持,而且支持三个级别的 RPC:TCP、HTTP、JSONRPC。但 Go 的 RPC 包是独一无二的 RPC,它和传统的 RPC 系统不同,它只支持 Go 开发的服务器与客户端之间的交互,因为在内部采用了 Gob 来编码。
Go RPC 的函数只有符合下面的条件才能被远程访问:
- 函数必须是导出的(首字母大写)
- 必须有两个参数,并且是导出类型或者内建类型
- 第二个参数必须是指针类型的
- 函数还要有一个返回值 error
举个例子,正确的 RPC 函数格式如下:
func (t *T) MethodName(argType T1, replyType *T2) error
T、T1 和 T2 类型必须能被 encoding/gob
包编解码。
接下来,定义服务对象和方法:
http-rpc/arith.go
package http_rpc
import (
"log"
)
type Arith int
type Args struct {
A, B int
}
func (t *Arith) Multiply(args Args, result *int) error {
log.Printf("Multiply %d with %d\n", args.A, args.B)
*result = args.A * args.B
return nil
}
任何的 RPC 都需要通过网络来传递数据,Go RPC 可以利用 HTTP 和 TCP 来传递数据,利用 HTTP 的好处是可以直接复用 net/http 里面的一些函数。
http-rpc/server/main.go
package main
import (
"log"
"net/http"
"net/rpc"
. "rpc-golang/http-rpc"
)
func main() {
arith := new(Arith)
rpc.Register(arith)
// 把该服务注册到 HTTP 协议上
rpc.HandleHTTP()
log.Println("HTTP RPC service listen and serving on port 1234")
if err := http.ListenAndServe(":1234", nil); err != nil {
log.Fatalf("Error serving: %s", err)
}
}
HTTP RPC client using golang net/rpc package
http-rpc/client/main.go
package main
import (
"log"
"net/rpc"
. "rpc-golang/http-rpc"
)
func main() {
client, err := rpc.DialHTTP("tcp", ":1234")
if err != nil {
log.Fatalf("Error in dialing. %s", err)
}
args := Args{
A: 2,
B: 3,
}
var reply int
err = client.Call("Arith.Multiply", args, &reply)
if err != nil {
log.Fatal("Arith error:", err)
}
log.Printf("%d * %d = %d\n", args.A, args.B, reply)
}
JSON RPC server using gorilla rpc/json
使用 Gorilla 工具包来简化默认的 net/rpc/jsonrpc
. Gorilla 工具包与默认包之间的微小差别在于它需要方法签名来接受 * Request
对象作为第一个参数,并将 Args 参数更改为指针 * Args
。
在 net/rpc
我们的 Multiply 方法看起来是这样的:
func (t *Arith) Multiply(args Args, result *Result) error
对于 gorilla 而言, Multiply 方法看起来是这样的:
func (t *Arith) Multiply(r *http.Request, args *Args, result *Result) error
接下来,定义服务对象和方法:
json-rpc/arith.go
package json_rpc
import (
"log"
"net/http"
)
type Arith int
type Args struct {
A, B int
}
func (t *Arith) Multiply(r *http.Request, args *Args, result *int) error {
log.Printf("Multiply %d with %d\n", args.A, args.B)
*result = args.A * args.B
return nil
}
json-rpc/server/main.go
package main
import (
"github.com/gorilla/mux"
"github.com/gorilla/rpc"
"github.com/gorilla/rpc/json"
"log"
"net/http"
. "rpc-golang/json-rpc"
)
func main() {
server := rpc.NewServer()
server.RegisterCodec(json.NewCodec(), "application/json")
server.RegisterCodec(json.NewCodec(), "application/json;charset=UTF-8")
arith := new(Arith)
server.RegisterService(arith, "")
r := mux.NewRouter()
r.Handle("/rpc", server)
log.Println("JSON RPC service listen and serving on port 1234")
if err := http.ListenAndServe(":1234", r); err != nil {
log.Fatalf("Error serving: %s", err)
}
}
JSON RPC client using gorilla rpc/json
json-rpc/client/main.go
package main
import (
"bytes"
"github.com/gorilla/rpc/json"
"log"
"net/http"
. "rpc-golang/json-rpc"
)
func checkError(err error) {
if err != nil {
log.Fatalf("%s", err)
}
}
func main() {
url := "http://localhost:1234/rpc"
args := Args{
A: 2,
B: 3,
}
message, err := json.EncodeClientRequest("Arith.Multiply", args)
checkError(err)
resp, err := http.Post(url, "application/json", bytes.NewReader(message))
defer resp.Body.Close()
checkError(err)
reply := new(int)
err = json.DecodeClientResponse(resp.Body, reply)
checkError(err)
log.Printf("%d * %d = %d\n", args.A, args.B, *reply)
}
JSON RPC client using Python
我们可以使用 Python 去调用使用 golang + gorilla 工具包写的 JSON RPC server。
json-rpc/client/main.py
# -*- coding: utf-8 -*-
import requests
"""
JSON RPC client using Python
"""
def rpc_call():
url = 'http://localhost:1234/rpc'
payload = {
'id': 1,
'method': 'Arith.Multiply',
'params': [{'A': 2, 'B': 3}]
}
r = requests.post(url, json=payload)
print r.text
if __name__ == '__main__':
rpc_call()
JSON-RPC 简介
In JSON-RPC all messages sent from server or client are valid JSON objects. Client must send JSON object with following keys:
-
method
- Name of method/service -
params
- Array of arguments to be passed -
id
- Id is usually integer and makes it easier for client to know which request it got response to, if RPC calls are done asynchroneously.
Server may reply with JSON object with following keys:
-
result
- Contains return value of method called. It’s null if error ocurred. -
error
- If error occurred, this will indicate error code or error message, otherwise it’s null -
id
- The id of the request it is responding to.
Example:
Request:
{"method": "Arith.Multiply", "params": [{A: 2, B: 3}], "id": 1}
Response:
{"result": 6, "error": null, "id": 1}
JSON-RPC v2 adds support for batch queries and notifications (calls which don’t require response).