README

Go Report Card GoDoc GitHub license

JSON-RPC 2.0 Client for golang

A go implementation of an rpc client using json as data format over http. The implementation is based on the JSON-RPC 2.0 specification: http://www.jsonrpc.org/specification

Supports:

  • requests with arbitrary parameters (array)
  • requests with named parameters (object)
  • requests with go JSON structs (object)
  • notifications
  • batch requests
  • convenient response retrieval
  • basic authentication
  • custom headers
  • custom http client

Installation

go get -u github.com/ybbus/jsonrpc

Getting started

Let's say we want to retrieve a person with a specific id using rpc-json over http. Then we want to save this person with new properties. We have to provide basic authentication credentials. (Error handling is omitted here)

type Person struct {
    Id   int `json:"id"`
    Name string `json:"name"`
    Age  int `json:"age"`
}

func main() {
    rpcClient := jsonrpc.NewRPCClient("http://my-rpc-service:8080/rpc")
    rpcClient.SetBasicAuth("alex", "secret")

    response, _ := rpcClient.Call("getPersonById", 123)

    person := Person{}
    response.GetObject(&person)

    person.Age = 33
    rpcClient.Call("updatePerson", person)
}

In detail

Generating rpc-json requests

Let's start by executing a simple json-rpc http call: In production code: Always make sure to check err != nil first!

This calls generate and send a valid rpc-json object. (see: http://www.jsonrpc.org/specification#request_object)

func main() {
    rpcClient := jsonrpc.NewRPCClient("http://my-rpc-service:8080/rpc")
    response, _ := rpcClient.Call("getDate")
    // generates body: {"jsonrpc":"2.0","method":"getDate","id":0}
}

Call a function with parameter:

func main() {
    rpcClient := jsonrpc.NewRPCClient("http://my-rpc-service:8080/rpc")
    response, _ := rpcClient.Call("addNumbers", 1, 2)
    // generates body: {"jsonrpc":"2.0","method":"addNumbers","params":[1,2],"id":0}
}

Call a function with arbitrary array parameters:

func main() {
    rpcClient := jsonrpc.NewRPCClient("http://my-rpc-service:8080/rpc")
    response, _ := rpcClient.Call("createPerson", "Alex", 33, "Germany")
    // generates body: {"jsonrpc":"2.0","method":"createPerson","params":["Alex",33,"Germany"],"id":0}
}

Call a function with named parameters:

func main() {
    rpcClient := jsonrpc.NewRPCClient("http://my-rpc-service:8080/rpc")
	rpcClient.CallNamed("createPerson", map[string]interface{}{
		"name":      "Bartholomew Allen",
		"nicknames": []string{"Barry", "Flash",},
		"male":      true,
		"age":       28,
		"address":   map[string]interface{}{"street": "Main Street", "city": "Central City"},
	})
    // generates body: {"jsonrpc":"2.0","method":"createPerson","params":
	//	{"name": "Bartholomew Allen", "nicknames": ["Barry", "Flash"], "male": true, "age": 28,
	//	"address": {"street": "Main Street", "city": "Central City"}}
	//	,"id":0}
}

Call a function with convenient go JSON struct:

func main() {
    type Person struct {
        Id   int `json:"id"`
        Name string `json:"name"`
        Age  int `json:"age"`
    }

    rpcClient := jsonrpc.NewRPCClient("http://my-rpc-service:8080/rpc")

    // only nil, struct or pointer of struct allowed
	rpcClient.CallNamed("createPerson", &Person{
	  	"Id": 4711,
	  	"Name": "Alex",
	  	"Age": 35,
	})
	// generates body:
    // {"jsonrpc":"2.0","method":"createPerson","params":{"id":4711,"name":"Alex","age":35},"id":0}

	// if no parameter is required better use:
	rpcClient.Call("getInfo")
}

Call a function providing custom data structures as parameters:

type Person struct {
  Name    string `json:"name"`
  Age     int `json:"age"`
  Country string `json:"country"`
}
func main() {
    rpcClient := jsonrpc.NewRPCClient("http://my-rpc-service:8080/rpc")
    response, _ := rpcClient.Call("createPerson", Person{"Alex", 33, "Germany"})
    // generates body: {"jsonrpc":"2.0","method":"createPerson","params":[{"name":"Alex","age":33,"country":"Germany"}],"id":0}
}

Complex example:

type Person struct {
  Name    string `json:"name"`
  Age     int `json:"age"`
  Country string `json:"country"`
}
func main() {
    rpcClient := jsonrpc.NewRPCClient("http://my-rpc-service:8080/rpc")
    response, _ := rpcClient.Call("createPersonsWithRole", []Person{{"Alex", 33, "Germany"}, {"Barney", 38, "Germany"}}, []string{"Admin", "User"})
    // generates body: {"jsonrpc":"2.0","method":"createPersonsWithRole","params":[[{"name":"Alex","age":33,"country":"Germany"},{"name":"Barney","age":38,"country":"Germany"}],["Admin","User"]],"id":0}
}
Notification

A jsonrpc notification is a rpc call to the server without expecting a response. Only an error object is returned in case of networkt / http error. No id field is set in the request json object. (see: http://www.jsonrpc.org/specification#notification)

Execute an simple notification:

func main() {
    rpcClient := jsonrpc.NewRPCClient("http://my-rpc-service:8080/rpc")
    err := rpcClient.Notification("disconnectClient", 123)
    if err != nil {
        //error handling goes here
    }
}
Batch rpcjson calls

A jsonrpc batch call encapsulates multiple json-rpc requests in a single rpc-service call. It returns an array of results (for all non-notification requests). (see: http://www.jsonrpc.org/specification#batch)

Execute two jsonrpc calls and a single notification as batch:

func main() {
    rpcClient := jsonrpc.NewRPCClient(httpServer.URL)

	req1 := rpcClient.NewRPCRequestObject("addNumbers", 1, 2)
	req2 := rpcClient.NewRPCRequestObject("getPersonByName", "alex")
	notify1 := rpcClient.NewRPCNotificationObject("disconnect", true)

	responses, _ := rpcClient.Batch(req1, req2, notify1)

    person := Person{}
    response2, _ := responses.GetResponseOf(req2)
    response2.GetObject(&person)
}

To update the ID of an existing rpcRequest object:

func main() {
    rpcClient := jsonrpc.NewRPCClient(httpServer.URL)

	req1 := rpcClient.NewRPCRequestObject("addNumbers", 1, 2)
	req2 := rpcClient.NewRPCRequestObject("getPersonByName", "alex")
	notify1 := rpcClient.NewRPCNotifyObject("disconnect", true)

	responses, _ := rpcClient.Batch(req1, req2, notify1)

    rpcClient.UpdateRequestID(req1) // updates id to the next valid id if autoincrement is enabled
}
Working with rpc-json responses

Before working with the response object, make sure to check err != nil first. This error indicates problems on the network / http level of an error when parsing the json response.

func main() {
    rpcClient := jsonrpc.NewRPCClient("http://my-rpc-service:8080/rpc")
    response, err := rpcClient.Call("addNumbers", 1, 2)
    if err != nil {
        //error handling goes here
    }
}

The next thing you have to check is if an rpc-json protocol error occoured. This is done by checking if the Error field in the rpc-response != nil: (see: http://www.jsonrpc.org/specification#error_object)

func main() {
    rpcClient := jsonrpc.NewRPCClient("http://my-rpc-service:8080/rpc")
    response, err := rpcClient.Call("addNumbers", 1, 2)
    if err != nil {
        //error handling goes here
    }

    if response.Error != nil {
        // check response.Error.Code, response.Error.Message, response.Error.Data  here
    }
}

After making sure that no errors occoured you can now examine the RPCResponse object. When executing a json-rpc request, most of the time you will be interested in the "result"-property of the returned json-rpc response object. (see: http://www.jsonrpc.org/specification#response_object) The library provides some helper functions to retrieve the result in the data format you are interested in. Again: check for err != nil here to be sure the expected type was provided in the response and could be parsed.

func main() {
    rpcClient := jsonrpc.NewRPCClient("http://my-rpc-service:8080/rpc")
    response, _ := rpcClient.Call("addNumbers", 1, 2)

    result, err := response.GetInt()
    if err != nil {
        // result seems not to be an integer value
    }

    // helpers provided for all primitive types:
    response.GetInt() // int32 or int64 depends on architecture / implementation
    response.GetInt64()
    response.GetFloat64()
    response.GetString()
    response.GetBool()
}

Retrieving arrays and objects is also very simple:

// json annotations are only required to transform the structure back to json
type Person struct {
    Id   int `json:"id"`
    Name string `json:"name"`
    Age  int `json:"age"`
}

func main() {
    rpcClient := jsonrpc.NewRPCClient("http://my-rpc-service:8080/rpc")
    response, _ := rpcClient.Call("getPersonById", 123)

    person := Person{}
    err := response.GetObject(&Person) // expects a rpc-object result value like: {"id": 123, "name": "alex", "age": 33}

    fmt.Println(person.Name)
}

Retrieving arrays e.g. of ints:

func main() {
    rpcClient := jsonrpc.NewRPCClient("http://my-rpc-service:8080/rpc")
    response, _ := rpcClient.Call("getRandomNumbers", 10)

    rndNumbers := []int{}
    err := response.getObject(&rndNumbers) // expects a rpc-object result value like: [10, 188, 14, 3]

    fmt.Println(rndNumbers[0])
}
Basic authentication

If the rpc-service is running behind a basic authentication you can easily set the credentials:

func main() {
    rpcClient := jsonrpc.NewRPCClient("http://my-rpc-service:8080/rpc")
    rpcClient.SetBasicAuth("alex", "secret")
    response, _ := rpcClient.Call("addNumbers", 1, 2) // send with Authorization-Header
}
Set custom headers

Setting some custom headers (e.g. when using another authentication) is simple:

func main() {
    rpcClient := jsonrpc.NewRPCClient("http://my-rpc-service:8080/rpc")
    rpcClient.SetCustomHeader("Authorization", "Bearer abcd1234")
    response, _ := rpcClient.Call("addNumbers", 1, 2) // send with a custom Auth-Header
}
ID auto-increment

Per default the ID of the json-rpc request increments automatically for each request. You can change this behaviour:

func main() {
    rpcClient := jsonrpc.NewRPCClient("http://my-rpc-service:8080/rpc")
    response, _ := rpcClient.Call("addNumbers", 1, 2) // send with ID == 0
    response, _ = rpcClient.Call("addNumbers", 1, 2) // send with ID == 1
    rpcClient.SetNextID(10)
    response, _ = rpcClient.Call("addNumbers", 1, 2) // send with ID == 10
    rpcClient.SetAutoIncrementID(false)
    response, _ = rpcClient.Call("addNumbers", 1, 2) // send with ID == 11
    response, _ = rpcClient.Call("addNumbers", 1, 2) // send with ID == 11
}
Set a custom httpClient

If you have some special needs on the http.Client of the standard go library, just provide your own one. For example to use a proxy when executing json-rpc calls:

func main() {
    rpcClient := jsonrpc.NewRPCClient("http://my-rpc-service:8080/rpc")

    proxyURL, _ := url.Parse("http://proxy:8080")
	transport := &http.Transport{Proxy: http.ProxyURL(proxyURL)}

	httpClient := &http.Client{
		Transport: transport,
	}

	rpcClient.SetHTTPClient(httpClient)
}
Expand ▾ Collapse ▴

Documentation

Overview

    Package jsonrpc provides an jsonrpc 2.0 client that sends jsonrpc requests and receives jsonrpc responses using http.

    Example
    Output:
    
    

    Index

    Examples

    Constants

    This section is empty.

    Variables

    This section is empty.

    Functions

    This section is empty.

    Types

    type BatchResponse

    type BatchResponse struct {
    	// contains filtered or unexported fields
    }

      BatchResponse a list of jsonrpc response objects as a result of a batch request

      if you are interested in the response of a specific request use: GetResponseOf(request)

      func (*BatchResponse) GetResponseOf

      func (batchResponse *BatchResponse) GetResponseOf(request *RPCRequest) (*RPCResponse, error)

        GetResponseOf returns the rpc response of the corresponding request by matching the id.

        For this method to work, autoincrementID should be set to true (default).

        type RPCClient

        type RPCClient struct {
        	// contains filtered or unexported fields
        }

          RPCClient sends jsonrpc requests over http to the provided rpc backend. RPCClient is created using the factory function NewRPCClient().

          func NewRPCClient

          func NewRPCClient(endpoint string) *RPCClient

            NewRPCClient returns a new RPCClient instance with default configuration (no custom headers, default http.Client, autoincrement ids). Endpoint is the rpc-service url to which the rpc requests are sent.

            func (*RPCClient) Batch

            func (client *RPCClient) Batch(requests ...interface{}) (*BatchResponse, error)

              Batch sends a jsonrpc batch request to the rpc-service. The parameter is a list of requests the could be one of:

              RPCRequest
              RPCNotification.
              

              The batch requests returns a list of RPCResponse structs.

              Example
              Output:
              
              

              func (*RPCClient) Call

              func (client *RPCClient) Call(method string, params ...interface{}) (*RPCResponse, error)

                Call sends an jsonrpc request over http to the rpc-service url that was provided on client creation.

                If something went wrong on the network / http level or if json parsing failed it returns an error.

                If something went wrong on the rpc-service / protocol level the Error field of the returned RPCResponse is set and contains information about the error.

                If the request was successful the Error field is nil and the Result field of the RPCRespnse struct contains the rpc result.

                Example
                Output:
                
                

                func (*RPCClient) CallNamed

                func (client *RPCClient) CallNamed(method string, params map[string]interface{}) (*RPCResponse, error)

                  CallNamed sends an jsonrpc request over http to the rpc-service url that was provided on client creation. This differs from Call() by sending named, rather than positional, arguments.

                  If something went wrong on the network / http level or if json parsing failed it returns an error.

                  If something went wrong on the rpc-service / protocol level the Error field of the returned RPCResponse is set and contains information about the error.

                  If the request was successful the Error field is nil and the Result field of the RPCRespnse struct contains the rpc result.

                  Example
                  Output:
                  
                  

                  func (*RPCClient) CallObject

                  func (client *RPCClient) CallObject(method string, jsonStruct interface{}) (*RPCResponse, error)

                    CallObject sends an jsonrpc request over http to the rpc-service url that was provided on client creation. This differs from Call() by directly passing a struct or a pointer to a struct. The method uses reflection to check if the caller provided a struct or something else. Performance of reflection call is not a big deal compared to the other actions this method performs.

                    If the caller did not pass a (pointer) struct an error will be returned.

                    If something went wrong on the network / http level or if json parsing failed it returns an error.

                    If something went wrong on the rpc-service / protocol level the Error field of the returned RPCResponse is set and contains information about the error.

                    If the request was successful the Error field is nil and the Result field of the RPCRespnse struct contains the rpc result.

                    Example
                    Output:
                    
                    

                    func (*RPCClient) NewRPCNotificationObject

                    func (client *RPCClient) NewRPCNotificationObject(method string, params ...interface{}) *RPCNotification

                      NewRPCNotificationObject creates and returns a raw RPCNotification structure. It is mainly used when building batch requests. For single notifications use RPCClient.Notification(). NewRPCNotificationObject struct can also be created directly, but this function sets the ID and the jsonrpc field to the correct values.

                      func (*RPCClient) NewRPCRequestObject

                      func (client *RPCClient) NewRPCRequestObject(method string, params ...interface{}) *RPCRequest

                        NewRPCRequestObject creates and returns a raw RPCRequest structure. It is mainly used when building batch requests. For single requests use RPCClient.Call(). RPCRequest struct can also be created directly, but this function sets the ID and the jsonrpc field to the correct values.

                        func (*RPCClient) Notification

                        func (client *RPCClient) Notification(method string, params ...interface{}) error

                          Notification sends a jsonrpc request to the rpc-service. The difference to Call() is that this request does not expect a response. The ID field of the request is omitted.

                          func (*RPCClient) SetAutoIncrementID

                          func (client *RPCClient) SetAutoIncrementID(flag bool)

                            SetAutoIncrementID if set to true, the id field of an rpcjson request will be incremented automatically

                            func (*RPCClient) SetBasicAuth

                            func (client *RPCClient) SetBasicAuth(username string, password string)

                              SetBasicAuth is a helper function that sets the header for the given basic authentication credentials. To reset / disable authentication just set username or password to an empty string value.

                              func (*RPCClient) SetCustomHeader

                              func (client *RPCClient) SetCustomHeader(key string, value string)

                                SetCustomHeader is used to set a custom header for each rpc request. You could for example set the Authorization Bearer here.

                                func (*RPCClient) SetHTTPClient

                                func (client *RPCClient) SetHTTPClient(httpClient *http.Client)

                                  SetHTTPClient can be used to set a custom http.Client. This can be useful for example if you want to customize the http.Client behaviour (e.g. proxy settings)

                                  func (*RPCClient) SetNextID

                                  func (client *RPCClient) SetNextID(id uint)

                                    SetNextID can be used to manually set the next id / reset the id.

                                    func (*RPCClient) UnsetCustomHeader

                                    func (client *RPCClient) UnsetCustomHeader(key string)

                                      UnsetCustomHeader is used to removes a custom header that was added before.

                                      func (*RPCClient) UpdateRequestID

                                      func (client *RPCClient) UpdateRequestID(rpcRequest *RPCRequest)

                                        UpdateRequestID updates the ID of an RPCRequest structure.

                                        This is used if a request is sent another time and the request should get an updated id.

                                        This does only make sense when used on with Batch() since Call() and Notififcation() do update the id automatically.

                                        type RPCError

                                        type RPCError struct {
                                        	Code    int         `json:"code"`
                                        	Message string      `json:"message"`
                                        	Data    interface{} `json:"data"`
                                        }

                                          RPCError represents a jsonrpc error object if an rpc error occurred.

                                          See: http://www.jsonrpc.org/specification#error_object

                                          func (*RPCError) Error

                                          func (e *RPCError) Error() string

                                          type RPCNotification

                                          type RPCNotification struct {
                                          	JSONRPC string      `json:"jsonrpc"`
                                          	Method  string      `json:"method"`
                                          	Params  interface{} `json:"params,omitempty"`
                                          }

                                            RPCNotification represents a jsonrpc notification object. A notification object omits the id field since there will be no server response.

                                            See: http://www.jsonrpc.org/specification#notification

                                            type RPCRequest

                                            type RPCRequest struct {
                                            	JSONRPC string      `json:"jsonrpc"`
                                            	Method  string      `json:"method"`
                                            	Params  interface{} `json:"params,omitempty"`
                                            	ID      uint        `json:"id"`
                                            }

                                              RPCRequest represents a jsonrpc request object.

                                              See: http://www.jsonrpc.org/specification#request_object

                                              type RPCResponse

                                              type RPCResponse struct {
                                              	JSONRPC string      `json:"jsonrpc"`
                                              	Result  interface{} `json:"result,omitempty"`
                                              	Error   *RPCError   `json:"error,omitempty"`
                                              	ID      uint        `json:"id"`
                                              }

                                                RPCResponse represents a jsonrpc response object. If no rpc specific error occurred Error field is nil.

                                                See: http://www.jsonrpc.org/specification#response_object

                                                Example
                                                Output:
                                                
                                                

                                                func (*RPCResponse) GetBool

                                                func (rpcResponse *RPCResponse) GetBool() (bool, error)

                                                  GetBool converts the rpc response to a bool and returns it.

                                                  If result was not a bool an error is returned.

                                                  func (*RPCResponse) GetFloat64

                                                  func (rpcResponse *RPCResponse) GetFloat64() (float64, error)

                                                    GetFloat64 converts the rpc response to an float64 and returns it.

                                                    If result was not an float64 an error is returned.

                                                    func (*RPCResponse) GetInt

                                                    func (rpcResponse *RPCResponse) GetInt() (int, error)

                                                      GetInt converts the rpc response to an int and returns it.

                                                      This is a convenient function. Int could be 32 or 64 bit, depending on the architecture the code is running on. For a deterministic result use GetInt64().

                                                      If result was not an integer an error is returned.

                                                      func (*RPCResponse) GetInt64

                                                      func (rpcResponse *RPCResponse) GetInt64() (int64, error)

                                                        GetInt64 converts the rpc response to an int64 and returns it.

                                                        If result was not an integer an error is returned.

                                                        func (*RPCResponse) GetObject

                                                        func (rpcResponse *RPCResponse) GetObject(toType interface{}) error

                                                          GetObject converts the rpc response to an object (e.g. a struct) and returns it. The parameter should be a structure that can hold the data of the response object.

                                                          For example if the following json return value is expected: {"name": "alex", age: 33, "country": "Germany"} the struct should look like

                                                          type Person struct {
                                                            Name string
                                                            Age int
                                                            Country string
                                                          }
                                                          

                                                          func (*RPCResponse) GetString

                                                          func (rpcResponse *RPCResponse) GetString() (string, error)

                                                            GetString converts the rpc response to a string and returns it.

                                                            If result was not a string an error is returned.

                                                            Source Files