1# JSON RPC
2
3[JSON RPC](http://www.jsonrpc.org) is "A light weight remote procedure call protocol". It allows for the creation of simple RPC-style APIs with human-readable messages that are front-end friendly.
4
5## Using JSON RPC with Go-Kit
6Using JSON RPC and go-kit together is quite simple.
7
8A JSON RPC _server_ acts as an [HTTP Handler](https://godoc.org/net/http#Handler), receiving all requests to the JSON RPC's URL. The server looks at the `method` property of the [Request Object](http://www.jsonrpc.org/specification#request_object), and routes it to the corresponding code.
9
10Each JSON RPC _method_ is implemented as an `EndpointCodec`, a go-kit [Endpoint](https://godoc.org/github.com/go-kit/kit/endpoint#Endpoint), sandwiched between a decoder and encoder. The decoder picks apart the JSON RPC request params, which can be passed to your endpoint. The encoder receives the output from the endpoint and encodes a JSON-RPC result.
11
12## Example — Add Service
13Let's say we want a service that adds two ints together. We'll serve this at `http://localhost/rpc`. So a request to our `sum` method will be a POST to `http://localhost/rpc` with a request body of:
14
15 {
16 "id": 123,
17 "jsonrpc": "2.0",
18 "method": "sum",
19 "params": {
20 "A": 2,
21 "B": 2
22 }
23 }
24
25### `EndpointCodecMap`
26The routing table for incoming JSON RPC requests is the `EndpointCodecMap`. The key of the map is the JSON RPC method name. Here, we're routing the `sum` method to an `EndpointCodec` wrapped around `sumEndpoint`.
27
28 jsonrpc.EndpointCodecMap{
29 "sum": jsonrpc.EndpointCodec{
30 Endpoint: sumEndpoint,
31 Decode: decodeSumRequest,
32 Encode: encodeSumResponse,
33 },
34 }
35
36### Decoder
37 type DecodeRequestFunc func(context.Context, json.RawMessage) (request interface{}, err error)
38
39A `DecodeRequestFunc` is given the raw JSON from the `params` property of the Request object, _not_ the whole request object. It returns an object that will be the input to the Endpoint. For our purposes, the output should be a SumRequest, like this:
40
41 type SumRequest struct {
42 A, B int
43 }
44
45So here's our decoder:
46
47 func decodeSumRequest(ctx context.Context, msg json.RawMessage) (interface{}, error) {
48 var req SumRequest
49 err := json.Unmarshal(msg, &req)
50 if err != nil {
51 return nil, err
52 }
53 return req, nil
54 }
55
56So our `SumRequest` will now be passed to the endpoint. Once the endpoint has done its work, we hand over to the…
57
58### Encoder
59The encoder takes the output of the endpoint, and builds the raw JSON message that will form the `result` field of a [Response Object](http://www.jsonrpc.org/specification#response_object). Our result is going to be a plain int. Here's our encoder:
60
61 func encodeSumResponse(ctx context.Context, result interface{}) (json.RawMessage, error) {
62 sum, ok := result.(int)
63 if !ok {
64 return nil, errors.New("result is not an int")
65 }
66 b, err := json.Marshal(sum)
67 if err != nil {
68 return nil, err
69 }
70 return b, nil
71 }
72
73### Server
74Now that we have an EndpointCodec with decoder, endpoint, and encoder, we can wire up the server:
75
76 handler := jsonrpc.NewServer(jsonrpc.EndpointCodecMap{
77 "sum": jsonrpc.EndpointCodec{
78 Endpoint: sumEndpoint,
79 Decode: decodeSumRequest,
80 Encode: encodeSumResponse,
81 },
82 })
83 http.Handle("/rpc", handler)
84 http.ListenAndServe(":80", nil)
85
86With all of this done, our example request above should result in a response like this:
87
88 {
89 "jsonrpc": "2.0",
90 "result": 4
91 }
View as plain text