因为内置net/rpc包接口设计的缺陷,通过追踪 rpc.HandleHTTP()
方法,找到 ServeHTTP
方法:
func (server *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if req.Method != "CONNECT" {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.WriteHeader(http.StatusMethodNotAllowed)
io.WriteString(w, "405 must CONNECT\n")
return
}
conn, _, err := w.(http.Hijacker).Hijack()
if err != nil {
log.Print("rpc hijacking ", req.RemoteAddr, ": ", err.Error())
return
}
io.WriteString(conn, "HTTP/1.0 "+connected+"\n\n")
server.ServeConn(conn)
}
func (server *Server) ServeConn(conn io.ReadWriteCloser) {
buf := bufio.NewWriter(conn)
srv := &gobServerCodec{
rwc: conn,
dec: gob.NewDecoder(conn),
enc: gob.NewEncoder(buf),
encBuf: buf,
}
server.ServeCodec(srv)
}
根本就没有设置codec的参数,直接写死了 gob ,所以依赖于 net/rpc 包的除了 gob 之外的编码方式都只能使用 tcp 协议。
我们无法使用jsonrpc等定制的编码作为rpc.DialHTTP的底层协议,如果需要让jsonrpc支持rpc.DialHTTP函数,需要调整rpc的接口。
除了传输协议,还有可以指定一个RPC编码协议,用于编码/解码RPC调用的函数参数和返回值,RPC调用不指定编码协议时,默认采用Go语言特有的gob编码协议。
因为,, 其他语言一般都不支持Go语言的gob协议,因此如果需要跨语言RPC调用就需要采用通用的编码协议。
Go的标准库还提供了一个"net/rpc/jsonrpc"
包,用于提供基于JSON编码的RPC支持。
JSON RPC采用JSON进行数据编解码,因而支持跨语言调用。但目前的jsonrpc库是基于tcp协议实现的,暂时不支持使用http进行数据传输。
定义服务
package repo
import (
"errors"
)
type Order struct {}
type OrderInfo struct {
Id string
Price float64
Status int
}
func (o *Order) GetOne(orderId string, orderInfo *OrderInfo) error {
if orderId == "" {
return errors.New("orderId is invalid")
}
*orderInfo = OrderInfo{
Id: orderId,
Price: 100.00,
Status: 1,
}
return nil
}
服务端
package main
import (
"log"
"net"
"net/rpc"
"net/rpc/jsonrpc"
"demo1/go-rpc/repo"
)
func main() {
err := rpc.Register(new(repo.Order))
if err != nil {
log.Fatal(err)
}
l, err := net.Listen("tcp", ":8100")
if err != nil {
log.Fatal(err)
}
for {
conn, e := l.Accept()
if e != nil {
continue
}
go jsonrpc.ServeConn(conn)
}
}
客户端
package main
import (
"fmt"
"log"
"net/rpc/jsonrpc"
"demo1/go-rpc/repo"
)
func main() {
client, err := jsonrpc.Dial("tcp", "127.0.0.1:8100")
if err != nil {
log.Fatal("dialing:", err)
}
orderId := "asddddddd"
var orderInfo repo.OrderInfo
err = client.Call("Order.GetOne", orderId, &orderInfo)
if err != nil {
log.Fatal("Order error:", err)
}
fmt.Println(orderInfo)
}
jsonrpc 中的协议说明
使用tcp工具连接测试,比如nc
1、请求参数列表
id int 编号,默认是0
method string 要调用的方法
params 参数,数组或者是map,如果参数是单个的值应转换成数组。
例如
{"method":"Order.GetOne","params":["asddddddd"],"id":0}
{"method":"Order.GetOne","params":{"A":9,"B":2},"id":0}
2、响应参数
id int 编号,默认是0
result 结果
error string 错误信息
如果成功,返回结果:
{"id":0,"result":{"Id":"asddddddd","Price":100,"Status":1},"error":null}
如果失败,返回结果:
{"id":0,"result":null,"error":"rpc: can't find service Arith.Multiply"}
{"id":0,"result":null,"error":"json: cannot unmarshal string into Go value of type [1]interface {}"}
以上是采用 TCP 来调用的 RPC,就是将传输的格式改成了 JSON 格式,基于此可以实现一个 HTTP+JSON+RPC。我们在 HTTP+GOB+RPC 的基础上,将设置 Handdler 修改一下,设置 Codec 为 JSON 即可。
func main() {
err := rpc.Register(new(repo.Order))
if err != nil {
log.Fatal(err)
}
http.HandleFunc(rpc.DefaultRPCPath, func(writer http.ResponseWriter, request *http.Request) {
var conn io.ReadWriteCloser = struct {
io.Writer
io.ReadCloser
}{
Writer: writer,
ReadCloser: request.Body,
}
rpc.ServeRequest(jsonrpc.NewServerCodec(conn))
})
http.ListenAndServe(":8100", nil)
}
在postman发起请求
GET: http://localhost:8100/_goRPC_
body raw: {"method":"Order.GetOne","params":["asddddddd"],"id":10}
response: {"id":10,"result":{"Id":"asddddddd","Price":100,"Status":1},"error":null}
说明服务端启动成功了。