Codebase list golang-github-go-kit-kit / 9a1a7ad transport / http / jsonrpc / README.md
9a1a7ad

Tree @9a1a7ad (Download .tar.gz)

README.md @9a1a7adraw · history · blame

# JSON RPC

[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.

## 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](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.

Each 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.

## 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](http://www.jsonrpc.org/specification#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
	}