You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Captain Dero 9687ad9cb4 Derosuite Status Update Release 2 6 years ago
..
.gitignore Derosuite Status Update Release 2 6 years ago
LICENSE Derosuite Status Update Release 2 6 years ago
README.md Derosuite Status Update Release 2 6 years ago
examples_test.go Derosuite Status Update Release 2 6 years ago
jsonrpc.go Derosuite Status Update Release 2 6 years ago
jsonrpc_test.go Derosuite Status Update Release 2 6 years ago

README.md

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
  • requests with named parameters
  • 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 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 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)
}