A simple go implementation of json rpc 2.0 client over http

Overview

Go Report Card GoDoc GitHub license Mentioned in Awesome Go

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
  • convenient response retrieval
  • batch requests
  • custom http client (e.g. proxy, tls config)
  • custom headers (e.g. basic auth)

Installation

go get -u github.com/ybbus/jsonrpc/v2

Getting started

Let's say we want to retrieve a person struct with a specific id using rpc-json over http. Then we want to save this person after we changed a property. (Error handling is omitted here)

package main

import "github.com/ybbus/jsonrpc/v2"

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

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

    var person *Person
    rpcClient.CallFor(&person, "getPersonById", 4711)

    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.NewClient("http://my-rpc-service:8080/rpc")
    rpcClient.Call("getDate")
    // generates body: {"method":"getDate","id":0,"jsonrpc":"2.0"}
}

Call a function with parameter:

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

Call a function with arbitrary parameters:

func main() {
    rpcClient := jsonrpc.NewClient("http://my-rpc-service:8080/rpc")
    rpcClient.Call("createPerson", "Alex", 33, "Germany")
    // generates body: {"method":"createPerson","params":["Alex",33,"Germany"],"id":0,"jsonrpc":"2.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.NewClient("http://my-rpc-service:8080/rpc")
    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.NewClient("http://my-rpc-service:8080/rpc")
    rpcClient.Call("createPersonsWithRole", &Person{"Alex", 33, "Germany"}, &Person{"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}
}

Some examples and resulting JSON-RPC objects:

rpcClient.Call("missingParam")
{"method":"missingParam"}

rpcClient.Call("nullParam", nil)
{"method":"nullParam","params":[null]}

rpcClient.Call("boolParam", true)
{"method":"boolParam","params":[true]}

rpcClient.Call("boolParams", true, false, true)
{"method":"boolParams","params":[true,false,true]}

rpcClient.Call("stringParam", "Alex")
{"method":"stringParam","params":["Alex"]}

rpcClient.Call("stringParams", "JSON", "RPC")
{"method":"stringParams","params":["JSON","RPC"]}

rpcClient.Call("numberParam", 123)
{"method":"numberParam","params":[123]}

rpcClient.Call("numberParams", 123, 321)
{"method":"numberParams","params":[123,321]}

rpcClient.Call("floatParam", 1.23)
{"method":"floatParam","params":[1.23]}

rpcClient.Call("floatParams", 1.23, 3.21)
{"method":"floatParams","params":[1.23,3.21]}

rpcClient.Call("manyParams", "Alex", 35, true, nil, 2.34)
{"method":"manyParams","params":["Alex",35,true,null,2.34]}

rpcClient.Call("singlePointerToStruct", &person)
{"method":"singlePointerToStruct","params":{"name":"Alex","age":35,"country":"Germany"}}

rpcClient.Call("multipleStructs", &person, &drink)
{"method":"multipleStructs","params":[{"name":"Alex","age":35,"country":"Germany"},{"name":"Cuba Libre","ingredients":["rum","cola"]}]}

rpcClient.Call("singleStructInArray", []*Person{&person})
{"method":"singleStructInArray","params":[{"name":"Alex","age":35,"country":"Germany"}]}

rpcClient.Call("namedParameters", map[string]interface{}{
	"name": "Alex",
	"age":  35,
})
{"method":"namedParameters","params":{"age":35,"name":"Alex"}}

rpcClient.Call("anonymousStruct", struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}{"Alex", 33})
{"method":"anonymousStructWithTags","params":{"name":"Alex","age":33}}

rpcClient.Call("structWithNullField", struct {
	Name    string  `json:"name"`
	Address *string `json:"address"`
}{"Alex", nil})
{"method":"structWithNullField","params":{"name":"Alex","address":null}}

Working with rpc-json responses

Before working with the response object, make sure to check err != nil. Also keep in mind that the json-rpc result field can be nil even on success.

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

If an http error occurred, maybe you are interested in the error code (403 etc.)

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

    switch e := err.(type) {
      case nil: // if error is nil, do nothing
      case *HTTPError:
        // use e.Code here
        return
      default:
        // any other error
        return
    }

    // no error, go on...
}

The next thing you have to check is if an rpc-json protocol error occurred. 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.NewClient("http://my-rpc-service:8080/rpc")
    response, err := rpcClient.Call("addNumbers", 1, 2)
    if err != nil {
        //error handling goes here
    }

    if response.Error != nil {
        // rpc error handling goes here
        // check response.Error.Code, response.Error.Message and optional response.Error.Data
    }
}

After making sure that no errors occurred 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.NewClient("http://my-rpc-service:8080/rpc")
    response, _ := rpcClient.Call("addNumbers", 1, 2)

    result, err := response.GetInt()
    if err != nil {
        // result cannot be unmarshalled as integer
    }

    // helpers provided for all primitive types:
    response.GetInt()
    response.GetFloat()
    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.NewClient("http://my-rpc-service:8080/rpc")
    response, _ := rpcClient.Call("getPersonById", 123)

    var person *Person
    err := response.GetObject(&person) // expects a rpc-object result value like: {"id": 123, "name": "alex", "age": 33}
    if err != nil || person == nil {
        // some error on json unmarshal level or json result field was null
    }

    fmt.Println(person.Name)

    // we can also set default values if they are missing from the result, or result == null:
    person2 := &Person{
        Id: 0,
        Name: "<empty>",
        Age: -1,
    }
    err := response.GetObject(&person2) // expects a rpc-object result value like: {"id": 123, "name": "alex", "age": 33}
    if err != nil || person2 == nil {
        // some error on json unmarshal level or json result field was null
    }

    fmt.Println(person2.Name) // prints "<empty>" if "name" field was missing in result-json
}

Retrieving arrays:

func main() {
    rpcClient := jsonrpc.NewClient("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]
    if err != nil {
        // do error handling
    }

    for _, num := range rndNumbers {
        fmt.Printf("%v\n", num)
    }
}

Using convenient function CallFor()

A very handy way to quickly invoke methods and retrieve results is by using CallFor()

You can directly provide an object where the result should be stored. Be sure to provide it be reference. An error is returned if:

  • there was an network / http error
  • RPCError object is not nil (err can be casted to this object)
  • rpc result could not be parsed into provided object

One of te above examples could look like this:

// 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.NewClient("http://my-rpc-service:8080/rpc")

    var person *Person
    err := rpcClient.CallFor(&person, "getPersonById", 123)

    if err != nil || person == nil {
      // handle error
    }

    fmt.Println(person.Name)
}

Most of the time it is ok to check if a struct field is 0, empty string "" etc. to check if it was provided by the json rpc response. But if you want to be sure that a JSON-RPC response field was missing or not, you should use pointers to the fields. This is just a single example since all this Unmarshaling is standard go json functionality, exactly as if you would call json.Unmarshal(rpcResponse.ResultAsByteArray, &objectToStoreResult)

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

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

    var person *Person
    err := rpcClient.CallFor(&person, "getPersonById", 123)

    if err != nil || person == nil {
      // handle error
    }

    if person.Name == nil {
      // json rpc response did not provide a field "name" in the result object
    }
}

Using RPC Batch Requests

You can send multiple RPC-Requests in one single HTTP request using RPC Batch Requests.

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

    response, _ := rpcClient.CallBatch(RPCRequests{
      NewRequest("myMethod1", 1, 2, 3),
      NewRequest("anotherMethod", "Alex", 35, true),
      NewRequest("myMethod2", &Person{
        Name: "Emmy",
        Age: 4,
      }),
    })
}

Keep the following in mind:

  • the request / response id's are important to map the requests to the responses. CallBatch() automatically sets the ids to requests[i].ID == i
  • the response can be provided in an unordered and maybe incomplete form
  • when you want to set the id yourself use, CallRaw()

There are some helper methods for batch request results:

func main() {
    // [...]

    result.HasErrors() // returns true if one of the rpc response objects has Error field != nil
    resultMap := result.AsMap() // returns a map for easier retrieval of requests

    if response123, ok := resultMap[123]; ok {
      // response object with id 123 exists, use it here
      // response123.ID == 123
      response123.GetObjectAs(&person)
      // ...
    }

}

Raw functions

There are also Raw function calls. Consider the non Raw functions first, unless you know what you are doing. You can create invalid json rpc requests and have to take care of id's etc. yourself. Also check documentation of Params() for raw requests.

Custom Headers, Basic authentication

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

func main() {
    rpcClient := jsonrpc.NewClientWithOpts("http://my-rpc-service:8080/rpc", &jsonrpc.RPCClientOpts{
   		CustomHeaders: map[string]string{
   			"Authorization": "Basic " + base64.StdEncoding.EncodeToString([]byte("myUser"+":"+"mySecret")),
   		},
   	})
    response, _ := rpcClient.Call("addNumbers", 1, 2) // send with Authorization-Header
}

Using oauth

Using oauth is also easy, e.g. with clientID and clientSecret authentication

func main() {
		credentials := clientcredentials.Config{
    		ClientID:     "myID",
    		ClientSecret: "mySecret",
    		TokenURL:     "http://mytokenurl",
    	}

    	rpcClient := jsonrpc.NewClientWithOpts("http://my-rpc-service:8080/rpc", &jsonrpc.RPCClientOpts{
    		HTTPClient: credentials.Client(context.Background()),
    	})

	// requests now retrieve and use an oauth token
}

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() {
	proxyURL, _ := url.Parse("http://proxy:8080")
	transport := &http.Transport{Proxy: http.ProxyURL(proxyURL)}

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

	rpcClient := jsonrpc.NewClientWithOpts("http://my-rpc-service:8080/rpc", &jsonrpc.RPCClientOpts{
		HTTPClient: httpClient,
	})

	// requests now use proxy
}
Comments
  • When passing singleStructInArray params, if there exists map or struct inside the struct, it will be interpret as 'Object'

    When passing singleStructInArray params, if there exists map or struct inside the struct, it will be interpret as 'Object'

    When passing singleStructInArray params, if there exists map or struct inside the struct, the map and inner struct will be interpreted as 'Object'. But what I expected is to generate JSON like keys and values recursively to handle complex JSON request.

    opened by lsls01 8
  • Cannot get latest version: module contains a go.mod file, so module path should be github.com/ybbus/jsonrpc/v2

    Cannot get latest version: module contains a go.mod file, so module path should be github.com/ybbus/jsonrpc/v2

    Background

    The github.com/ybbus/jsonrpc uses Go modules and the current release version is v2. And it’s module path is "github.com/ybbus/jsonrpc", instead of "github.com/ybbus/jsonrpc/v2". It must comply with the specification of "Releasing Modules for v2 or higher" available in the Modules documentation. Quoting the specification:

    A package that has opted in to modules must include the major version in the import path to import any v2+ modules To preserve import compatibility, the go command requires that modules with major version v2 or later use a module path with that major version as the final element. For example, version v2.0.0 of example.com/m must instead use module path example.com/m/v2. https://github.com/golang/go/wiki/Modules#releasing-modules-v2-or-higher

    Steps to Reproduce

    GO111MODULE=on, run go get targeting any version >= v2.1.3 of the ybbus/jsonrpc:

    $ go get github.com/ybbus/[email protected]
    go: finding github.com/ybbus/jsonrpc v2.1.5
    go: finding github.com/ybbus/jsonrpc v2.1.5
    go get github.com/ybbus/[email protected]: github.com/ybbus/[email protected]: invalid version: module contains a go.mod file, so major version must be compatible: should be v0 or v1, not v2
    

    run go get github.com/ybbus/jsonrpc, the version will stuck in v2.1.2:

    $go get github.com/ybbus/jsonrpc
    go: downloading github.com/ybbus/jsonrpc v1.1.1
    go: downloading github.com/ybbus/jsonrpc v2.1.2+incompatible
    go: github.com/ybbus/jsonrpc upgrade => v2.1.2+incompatible 
    

    SO anyone using Go modules will not be able to easily use any newer version of ybbus/jsonrpc.

    Solution

    1. Kill the go.mod files, rolling back to GOPATH.

    This would push them back to not being managed by Go modules (instead of incorrectly using Go modules). Ensure compatibility for downstream module-aware projects and module-unaware projects projects

    2. Fix module path to strictly follow SIV rules.

    Patch the go.mod file to declare the module path as github.com/ybbus/jsonrpc/v2 as per the specs. And adjust all internal imports. The downstream projects might be negatively affected in their building if they are module-unaware (Go versions older than 1.9.7 and 1.10.3; Or use third-party dependency management tools, such as: Dep, glide,govendor…).

    [*] You can see who will be affected here: [9 module-unaware users, i.e., ottenwbe/golook, InWeCrypto/keytool, InWeCrypto/wallet-insight] https://github.com/search?q=ybbus%2Fjsonrpc+filename%3Avendor.conf+filename%3Avendor.json+filename%3Aglide.toml+filename%3AGodep.toml&type=Code

    If you don't want to break the above repos. This method can provides better backwards-compatibility. Release a v2 or higher module through the major subdirectory strategy: Create a new v2 subdirectory (github.com/ybbus/jsonrpc/v2) and place a new go.mod file in that subdirectory. The module path must end with /v2. Copy or move the code into the v2 subdirectory. Update import statements within the module to also use /v2 (import "github.com/ybbus/jsonrpc/v2/…"). Tag the release with v2.x.y.

    3. Suggest your downstream module users use hash instead of a version tag.

    If the standard rule of go modules conflicts with your development mode. Or not intended to be used as a library and does not make any guarantees about the API. So you can’t comply with the specification of "Releasing Modules for v2 or higher" available in the Modules documentation. Regardless, since it's against one of the design choices of Go, it'll be a bit of a hack. Instead of go get github.com/ybbus/[email protected], module users need to use this following way to get the ybbus/jsonrpc: (1) Search for the tag you want (in browser) (2) Get the commit hash for the tag you want (3) Run go get github.com/ybbus/[email protected] (4) Edit the go.mod file to put a comment about which version you actually used This will make it difficult for module users to get and upgrade ybbus/jsonrpc.

    [*] You can see who will be affected here: [44 module users, e.g., yuuki0xff/clustertest, zcash-hackworks/eccfaucet, doubtingben/zfaucet] https://github.com/search?q=ybbus%2Fjsonrpc+filename%3Ago.mod&type=Code

    Summary

    You can make a choice to fix DM issues by balancing your own development schedules/mode against the affects on the downstream projects.

    For this issue, Solution 2 can maximize your benefits and with minimal impacts to your downstream projects the ecosystem.

    References

    • https://github.com/golang/go/wiki/Modules#semantic-import-versioning
    • https://golang.org/cmd/go/#hdr-Module_compatibility_and_semantic_versioning
    • https://github.com/golang/go/wiki/Modules#releasing-modules-v2-or-higher
    opened by KateGo520 6
  • get raw response instead of object

    get raw response instead of object

    Right now, I'm using

    	err = response.GetObject(&variants)
    	if err != nil {
    		fmt.Printf("get object error: %v\n", err)
    		return err
    	}
    

    I get a response, but unmarshaling fails - so - if there is any way to get RAW response in JSON format to use a different unmarshaler ?

    opened by slav123 5
  • Invalid hardcoded id property in request

    Invalid hardcoded id property in request

    Hello.

    Currently, RPCClient.CallFor under the hood creates a RPCRequest with (uninitialized) zero .ID property.

    This behavior introduces some issues when communicating with services that use other jsonrpc-server libraries.

    In my case, I was communicating with NodeJS-based service which uses http-jsonrpc-server package.

    When request ID is zero - server just crashes.

    I don't know if JSONRPC standard allows zero request ID values and probably I need to address the issue to http-jsonrpc-server package author (https://github.com/sangaman/http-jsonrpc-server/issues/19), but this issue might also affect other jsonrpc server implementations.

    Can you please use at least 1 as a default value for requests?

    The fix should be pretty simple:

    jsonrpc.go:327

    func (client *rpcClient) Call(ctx context.Context, method string, params ...interface{}) (*RPCResponse, error) {
    
    	request := &RPCRequest{
    +	ID: 1,
    		Method:  method,
    		Params:  Params(params...),
    		JSONRPC: jsonrpcVersion,
    	}
    
    	return client.doCall(ctx, request)
    }
    
    opened by x1unix 4
  •  Result property type in RPCResponse structure. At least for CallFor method.

    Result property type in RPCResponse structure. At least for CallFor method.

    We have the GetObject method for RPCResponse and here we can find that we do marshal+unmarshal for conversion to custom response structure.

    func (RPCResponse *RPCResponse) GetObject(toType interface{}) error {
    	js, err := json.Marshal(RPCResponse.Result)
    	if err != nil {
    		return err
    	}
    
    	err = json.Unmarshal(js, toType)
    	if err != nil {
    		return err
    	}
    
    	return nil
    }
    

    It could be better if we will use json.RawBytes as a type of Result property. It means that we will have more efficient code:

    func (RPCResponse *RPCResponse) GetObject(toType interface{}) error {
    	err = json.Unmarshal(RPCResponse.Result, toType)
    	if err != nil {
    		return err
    	}
    
    	return nil
    }
    
    opened by cv21 3
  • Changes in code

    Changes in code

    Hi ...

    First, thanks for a really nice jsonrpc module, it has been quite useful for me. I really like to know what your plans are with this module and if you have any plan for versioning it somehow (not the strongest part of go pkg).

    I ran into a situation where some of my code has stopped working as the module changes API, and I like to know if there are more plans for changes in the future ?

    opened by druppy 3
  • [Enhacement] Additional auth parameter in RPC call

    [Enhacement] Additional auth parameter in RPC call

    Hi,

    is it possible to add an optional auth parameter to the RPC call? For example something that would match this JSON Request:

    {
        "jsonrpc": "2.0",
        "method": "do.something",
        "params": {
            "output": [
                "id",
                "name"
            ],
            "interfaces": [
                "id",
                "name"
            ]
        },
        "id": 5,
        "auth": "1234ab123401624291e7d12572075e65"
    }
    
    opened by sikmike 2
  • Support extensions for request object

    Support extensions for request object

    Hi, not sure if this is the right place to ask but i'm not sure what else to do.

    Is it planned to support custom additional members in the request object? Specifically i would like to use this jsonrcp client to communicate with Zabbix (Monitoring Software) since it states that it implement an JSONRPC API (https://www.zabbix.com/documentation/4.2/manual/api).

    From my point of view this is not true since they require an additional 'auth' member in the request object containing a token which is aquired with the 'user.login' method (https://www.zabbix.com/documentation/4.2/manual/api)

    I also found another tool that requires something like that (http://documentation.solarwindsmsp.com/backup/documentation/Content/service-management/json-api/login.htm)

    I got no answer from Zabbix via IRC and forum and found some mentions of jsonrpc extensions, but found no further documentation on how these are implemented.

    Long story short, is it planned to to support custom member in the request object like there is support for custom headers or a custom http client?

    opened by 0x6373 2
  • Client requirement to view exceptions

    Client requirement to view exceptions

    type RPCError struct {
    	Code    int         `json:"code"`
    	Message string      `json:"message"`
    	Data    interface{} `json:"data,omitempty"`
    }
    func (e *RPCError) Error() string {
    	return strconv.Itoa(e.Code) + ":" + e.Message
    }
    

    Maybe I think data is also necessary for client, because code and message is default and not detail, such as 32000 server error, but data in RPCError contains a more detail message of the exceptions raised by server, it has a good help for client to locate the error precisely. so I prefer to change the code as following:

    func (e *RPCError) Error() string {
    	data := fmt.Sprintf("%v", e.Data)
    	return strconv.Itoa(e.Code) + ":" + e.Message+data
    }
    
    opened by Syspretor 2
  • implemented Error interface on RPCError

    implemented Error interface on RPCError

    Seems to make sense for RPCError to implement error. For me it's QoL to be able to bubble it up directly, rather than creating a new error from the message.

    opened by trey-jones 2
  • Added possibility to pass `context.Context` to create `http.Request` object

    Added possibility to pass `context.Context` to create `http.Request` object

    Use Case

    From my application codebase, we assign a unique ID to the request being performed. We also use a custom http.RoundTripper object that intercepts the http.Request and logs it. Within the interceptor, we would like to easily extract the actual request ID from somewhere.

    The best way to do it is through a context.Context that has some custom value set in it. The library does not have any way to forward a customized context.Context

    Proposition

    I think in the ideal world, all methods should use a ctx context.Context as the first argument. Right now to avoid any backward compatibility problem, this PR simply add a CallCtxRaw (name up to all variations/changes) which accepts as its first argument a ctx context.Context.

    I decided to only do CallCtxRaw since it's rather a more advanced use case method and thought we could limit the change to a lower-level call.

    Would be happy to push that to all other methods if interest is expressed in having all of them accepting a ctx context.Context argument.

    opened by maoueh 1
  • [Enhacement] wrap errors with

    [Enhacement] wrap errors with "%w"

    Hi Thanks for your dead simple library. I'm started to using it. I have a suggestion to improve the error handling. If you would wrap all errors (especially the once from third party code), this would help to identify errors. i.e.:

    fmt.Errorf("rpc call: bla bla: %w",err)
    

    In my use-case I try to catch the error context.Canceled to ignore it, because I cancel the context on purpose on a clean shutdown of all my goroutines. If wrapper as in the example above, i could just do:

    if errors.Is(err, context.Canceled) {
       // ignore
    }
    

    But currently I need to parse the message.

    I'm a hobby go dev, but I started also to do this in all potential reusable code to wrap all third-party errors like that and also provide the my own error as variable like the context package to let user of the library use it for the error.Is check:

    var Canceled = errors.New("context canceled")
    

    As a reference, here the blog that explains this "feature": https://go.dev/blog/go1.13-errors Regarding chapter "Whether to Wrap". My interpretation is to wrap errors if you expose anyway the underlying implementation. And if you don't expose, it could make sense to create your own errors without wrapping.

    So for this case the context package implementation is anyway exposed in jsonrpc. So wrapping would make sense. For http.Client propably you could hide the implementation as you expose only the interface (not checked very code line, but looks like only the interface is exposed, not the implementation of the client).

    opened by spali 0
  • strip password when logging URL

    strip password when logging URL

    This makes sure that password doesn't appear in error logs when using basic authentication. (The code that I used in stripPassword is the same that the standard library http client uses)

    opened by areller 3
Releases(v3.1.1)
Owner
Alexander Gehres
Alexander Gehres
The jsonrpc package helps implement of JSON-RPC 2.0

jsonrpc About Simple, Poetic, Pithy. No reflect package. But reflect package is used only when invoke the debug handler. Support GAE/Go Standard Envir

Osamu TONOMORI 172 Nov 20, 2022
A Go library for master-less peer-to-peer autodiscovery and RPC between HTTP services

sleuth sleuth is a Go library that provides master-less peer-to-peer autodiscovery and RPC between HTTP services that reside on the same network. It w

null 360 Nov 17, 2022
Simple, fast and scalable golang rpc library for high load

gorpc Simple, fast and scalable golang RPC library for high load and microservices. Gorpc provides the following features useful for highly loaded pro

Aliaksandr Valialkin 665 Nov 29, 2022
Hprose is a cross-language RPC. This project is Hprose for Golang.

Hprose 3.0 for Golang Introduction Hprose is a High Performance Remote Object Service Engine. It is a modern, lightweight, cross-language, cross-platf

Hprose 1.2k Dec 2, 2022
Distributed Lab 2: RPC in Go

Distributed Lab 2: RPC in Go Using the lab sheet There are two ways to use the lab sheet, you can either: create a new repo from this template - this

null 0 Oct 18, 2021
Like Go channels over the network

libchan: like Go channels over the network Libchan is an ultra-lightweight networking library which lets network services communicate in the same way

Docker 2.5k Nov 15, 2022
Golang implementation of the Raft consensus protocol

raft raft is a Go library that manages a replicated log and can be used with an FSM to manage replicated state machines. It is a library for providing

HashiCorp 6.5k Dec 5, 2022
Simplified distributed locking implementation using Redis

redislock Simplified distributed locking implementation using Redis. For more information, please see examples. Examples import ( "fmt" "time"

Black Square Media 869 Dec 2, 2022
The pure golang implementation of nanomsg (version 1, frozen)

mangos NOTE: This is the legacy version of mangos (v1). Users are encouraged to use mangos v2 instead if possible. No further development is taking pl

nanomsg 1.5k Dec 7, 2022
A Golang implementation of the Umee network, a decentralized universal capital facility in the Cosmos ecosystem.

Umee A Golang implementation of the Umee network, a decentralized universal capital facility in the Cosmos ecosystem. Umee is a Universal Capital Faci

null 164 Dec 6, 2022
An implementation of a distributed access-control server that is based on Google Zanzibar

An implementation of a distributed access-control server that is based on Google Zanzibar - "Google's Consistent, Global Authorization System".

authorizer.tech 64 Nov 26, 2022
Golang implementation of distributed mutex on Azure lease blobs

Distributed Mutex on Azure Lease Blobs This package implements distributed lock available for multiple processes. Possible use-cases include exclusive

YouScan 11 Jul 31, 2022
implementation of some distributed system techniques

Distributed Systems These applications were built with the objective of studding a distributed systems using the most recent technics. The main ideia

Rafael A. C 6 Feb 18, 2022
An implementation of a distributed KV store backed by Raft tolerant of node failures and network partitions 🚣

barge A simple implementation of a consistent, distributed Key:Value store which uses the Raft Concensus Algorithm. This project launches a cluster of

Shehjad Khan 0 Nov 24, 2021
A naive implementation of Raft consensus algorithm.

This implementation is used to learn/understand the Raft consensus algorithm. The code implements the behaviors shown in Figure 2 of the Raft paper wi

Martin 0 Dec 3, 2021
A spinlock implementation for Go.

A spinlock implementation for Go.

Hiroto Funakoshi 0 Dec 20, 2021
This is my implementation of Raft consensus algorithm that I did for own learning.

This is my implementation of Raft consensus algorithm that I did for own learning. Please follow the link to learn more about raft consensus algorithm https://raft.github.io. And Soon, I will be developing same algorithm in Java as well

Umar Tahir 1 Jan 12, 2022
Take control of your data, connect with anything, and expose it anywhere through protocols such as HTTP, GraphQL, and gRPC.

Semaphore Chat: Discord Documentation: Github pages Go package documentation: GoDev Take control of your data, connect with anything, and expose it an

Jexia.com 77 Sep 26, 2022
Lockgate is a cross-platform locking library for Go with distributed locks using Kubernetes or lockgate HTTP lock server as well as the OS file locks support.

Lockgate Lockgate is a locking library for Go. Classical interface: 2 types of locks: shared and exclusive; 2 modes of locking: blocking and non-block

werf 230 Oct 26, 2022