# README
JSON RPC
JSON RPC 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.
Using JSON RPC with Go-Kit
Using JSON RPC and go-kit together is quite simple.
A JSON RPC server acts as an HTTP Handler, receiving all requests to the JSON RPC's URL. The server looks at the method
property of the Request Object, and routes it to the corresponding code.
Each JSON RPC method is implemented as an EndpointCodec
, a go-kit 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.
Example — Add Service
Let'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:
{
"id": 123,
"jsonrpc": "2.0",
"method": "sum",
"params": {
"A": 2,
"B": 2
}
}
EndpointCodecMap
The 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
.
jsonrpc.EndpointCodecMap{
"sum": jsonrpc.EndpointCodec{
Endpoint: sumEndpoint,
Decode: decodeSumRequest,
Encode: encodeSumResponse,
},
}
Decoder
type DecodeRequestFunc func(context.Context, json.RawMessage) (request interface{}, err error)
A 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:
type SumRequest struct {
A, B int
}
So here's our decoder:
func decodeSumRequest(ctx context.Context, msg json.RawMessage) (interface{}, error) {
var req SumRequest
err := json.Unmarshal(msg, &req)
if err != nil {
return nil, err
}
return req, nil
}
So our SumRequest
will now be passed to the endpoint. Once the endpoint has done its work, we hand over to the…
Encoder
The encoder takes the output of the endpoint, and builds the raw JSON message that will form the result
field of a Response Object. Our result is going to be a plain int. Here's our encoder:
func encodeSumResponse(ctx context.Context, result interface{}) (json.RawMessage, error) {
sum, ok := result.(int)
if !ok {
return nil, errors.New("result is not an int")
}
b, err := json.Marshal(sum)
if err != nil {
return nil, err
}
return b, nil
}
Server
Now that we have an EndpointCodec with decoder, endpoint, and encoder, we can wire up the server:
handler := jsonrpc.NewServer(jsonrpc.EndpointCodecMap{
"sum": jsonrpc.EndpointCodec{
Endpoint: sumEndpoint,
Decode: decodeSumRequest,
Encode: encodeSumResponse,
},
})
http.Handle("/rpc", handler)
http.ListenAndServe(":80", nil)
With all of this done, our example request above should result in a response like this:
{
"jsonrpc": "2.0",
"result": 4
}