Implementing json-rpc2.0 with Golang

Source: Internet
Author: User
This is a creation in Article, where the information may have evolved or changed.
This article was also posted on my blog Yeqown.github.io

What is RPC?

Remote Procedure Calls (English: remote Procedure Call, abbreviated RPC) is a computer communication protocol. The protocol allows programs running on one computer to invoke subroutines from another computer, and programmers do not need to program this interaction for additional purposes. If the software involved is object-oriented, the remote procedure call can also be called a remote call or a remote method call.

The remote procedure call is an example of a distributed computing client-server (Client/server), which is simple and widely popular. A remote procedure call is always made by the client to a server that executes several process requests and provides parameters to the client. The execution results are returned to the client. Due to a variety of variations and detail differences, various remote procedure call protocols are derived, and they are not compatible with each other.

What is JSON-RPC again?

Json-rpc is a stateless and lightweight remote Procedure call (RPC) Transfer protocol that delivers content that is primarily JSON-based. By invoking a remote server with a URL (such as Get/user), JSON-RPC directly defines the name of the function to invoke in the content (for example {"method": "GetUser"}), which makes the developer not trapped in the problem of using PUT or PATCH.
More JSON-RPC conventions See also: Https://zh.wikipedia.org/wiki/JSON-RPC

Problem

Service-side registration and invocation

Conventions such net/rpc as:

    • The method ' s type is exported.
    • The method is exported.
    • The method has both arguments, both exported (or builtin) types.
    • The method ' s second argument is a pointer.
    • The method has a return type error.
// 这就是约定func (t *T) MethodName(argType T1, replyType *T2) error

So here's the question:

    问题1: Server怎么来注册`t.Methods`?    问题2: JSON-RPC请求参数里面的Params给到args?

Server-side type definition:

type methodType struct {    method     reflect.Method // 用于调用    ArgType    reflect.Type    ReplyType  reflect.Type}type service struct {    name   string                 // 服务的名字, 一般为`T`    rcvr   reflect.Value          // 方法的接受者, 即约定中的 `t`    typ    reflect.Type           // 注册的类型, 即约定中的`T`    method map[string]*methodType // 注册的方法, 即约定中的`MethodName`的集合}// Server represents an RPC Server.type Server struct {    serviceMap sync.Map   // map[string]*service}

To resolve issue 1, refer to the net/rpc registration call in. The main use of reflect this package. The code is as follows:

The 
 //resolves the incoming type and the corresponding exportable method, storing the RCVR type,methods information in SERVER.M. If type is not exportable, it will error func (S *server) Register (RCVR interface{}) Error {_service: = new (Service) _service.typ = REFL Ect. TypeOf (RCVR) _SERVICE.RCVR = reflect. ValueOf (RCVR) sname: = reflect. Indirect (_SERVICE.RCVR). Type (). Name () if sname = = "" {err_s: = "rpc. Register:no Service name for type "+ _service.typ.string () log. Print (err_s) return errors. New (err_s)} if!isexported (sname) {err_s: = "rpc. Register:type "+ sname +" is not exported "log. Print (err_s) return errors. New (err_s)} _service.name = Sname _service.method = Suitablemethods (_service.typ, true)//sync. Map.loadorstore if _, dup: = S.m.loadorstore (sname, _service); DUP {return errors. New ("Rpc:service already defined:" + sname)} return nil}//about Suitablemethods, also using reflect,//to get all the available _service.typ The relevant parameters of the method and method are saved to *methodtype  

Suitablemethods code goes: https://github.com/yeqown/rpc/blob/master/server.go#l105

To solve issue 2, to resolve issue 2, and first see how to call method, the code is as follows:

// 约定:    func (t *T) MethodName(argType T1, replyType *T2) error// s.rcvr: 即约定中的 t// argv:   即约定中的 argType// replyv: 即约定中的 replyTypefunc (s *service) call(mtype *methodType, req *Request, argv, replyv reflect.Value) *Response {    function := mtype.method.Func    returnValues := function.Call([]reflect.Value{s.rcvr, argv, replyv})    errIter := returnValues[0].Interface()    errmsg := ""    if errIter != nil {        errmsg = errIter.(error).Error()        return NewResponse(req.ID, nil, NewJsonrpcErr(InternalErr, errmsg, nil))    }    return NewResponse(req.ID, replyv.Interface(), nil)}

See how to call, plus the JSON-RPC convention, know that to the server is a JSON, and Params it is a JSON-formatted data. Then it becomes: interface{} -req. Params to reflect.Value -argv. So how do you convert it? Look at the code:

Func (S *server) call (req *request) *response {//....//According to Req. method to Query method//Req. Method format such as: "Servicename.methodname"//Mtype *methodtype Mtype: = Svc.method[methodname]//According to the Mtype at the time of registration.    Argtype to generate a reflect.value argisvalue: = False/If true, need to indirect before. var argv reflect. Value if Mtype. Argtype.kind () = = reflect. Ptr {argv = reflect. New (Mtype. Argtype.elem ())} else {argv = reflect. New (Mtype. Argtype) Argisvalue = true} if Argisvalue {argv = argv. Elem ()}//generates a reflect for the argv parameter.    Value, but argv is still 0 values so far. So how to put req.    The Params is copied to argv? I tried, argv = reflect. Value (req.    Params), but at the time of the call, it is called "map[string]interface{} as Main.*args",//This means that the value of the parameter is not correctly assigned to argv. That's the back. Convert function://BS, _: = json. Marshal (req. Params)//JSON. Unmarshal (BS, argv. Interface ())//So there are some restrictions ~, not much to say convert (req. Params, argv.    Interface ())//Note: The Replytype in the Convention is a pointer type that is convenient for assigning values. According to the Mtype at the time of registration. ReplyType to generate a reflect.value replyv: = reflect. New (Mtype. Replytype.elem ()) switch mtype. Replytype.elem (). Kind () {case reflect. Map:replyv. Elem (). Set (reflect. Makemap (Mtype. Replytype.elem ())) case reflect. Slice:replyv. Elem (). Set (reflect. Makeslice (Mtype. Replytype.elem (), 0, 0)} return Svc.call (Mtype, req, argv, REPLYV)}

Support for HTTP calls

Having finished the above section, it is very easy to talk about support for HTTP. Implement the http.Handler interface on the line ~. As follows:

// 支持之POST & jsonfunc (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {    var resp *Response    w.Header().Set("Content-Type", "application/json")    if r.Method != http.MethodPost {        resp = NewResponse("", nil, NewJsonrpcErr(            http.StatusMethodNotAllowed, "HTTP request method must be POST", nil),        )        response(w, resp)        return    }    // 解析请求参数到[]*rpc.Request    reqs, err := getRequestFromBody(r)    if err != nil {        resp = NewResponse("", nil, NewJsonrpcErr(InternalErr, err.Error(), nil))        response(w, resp)        return    }    // 处理请求,包括批量请求    resps := s.handleWithRequests(reqs)    if len(resps) > 1 {        response(w, resps)    } else {        response(w, resps[0])    }    return}

Using the example

Service-side use

// test_server.gopackage mainimport (    // "fmt"    "net/http"    "github.com/yeqown/rpc")type Int inttype Args struct {    A int `json:"a"`    B int `json:"b"`}func (i *Int) Sum(args *Args, reply *int) error {    *reply = args.A + args.B    return nil}type MultyArgs struct {    A *Args `json:"aa"`    B *Args `json:"bb"`}type MultyReply struct {    A int `json:"aa"`    B int `json:"bb"`}func (i *Int) Multy(args *MultyArgs, reply *MultyReply) error {    reply.A = (args.A.A * args.A.B)    reply.B = (args.B.A * args.B.B)    return nil}func main() {    s := rpc.NewServer()    mine_int := new(Int)    s.Register(mine_int)    go s.HandleTCP("127.0.0.1:9999")    // 开启http    http.ListenAndServe(":9998", s)}

Client uses

// test_client.gopackage mainimport (    "github.com/yeqown/rpc")type Args struct {    A int `json:"a"`    B int `json:"b"`}type MultyArgs struct {    A *Args `json:"aa"`    B *Args `json:"bb"`}type MultyReply struct {    A int `json:"aa"`    B int `json:"bb"`}func main() {    c := rpc.NewClient()    c.DialTCP("127.0.0.1:9999")    var sum int    c.Call("1", "Int.Sum", &Args{A: 1, B: 2}, &sum)    println(sum)    c.DialTCP("127.0.0.1:9999")    var reply MultyReply    c.Call("2", "Int.Multy", &MultyArgs{A: &Args{1, 2}, B: &Args{3, 4}}, &reply)    println(reply.A, reply.B)}

Run



Realize

The above only picked up the more important part of me, said the implementation, more like the client support, JSON-RPC request response definition, can be consulted in the project. JSON-RPC is currently implemented based on TCP and HTTP, project address: Github.com/yeqown/rpc

Defects

Only JSON-RPC is supported, and the JSON-RPC Convention is not fully implemented. For example, in a batch call:

if the bulk-call RPC operation itself is not a valid JSON or an array that contains at least one value, the server returns a single Response object instead of an array. If a bulk call does not require a response object to be returned, the server does not need to return any results and must not return an empty array to the client.

Read the two RPC in the reference and discover the ways in which both are used codec to provide extensions. Therefore, you can consider using this method to extend later.

Reference

    • Net/rpc
    • Grollia/rpc
Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.