github.com/gagliardetto/solana-go@v1.11.0/rpc/jsonrpc/README.md (about)

     1  [![Go Report Card](https://goreportcard.com/badge/github.com/ybbus/jsonrpc)](https://goreportcard.com/report/github.com/ybbus/jsonrpc)
     2  [![GoDoc](https://godoc.org/github.com/ybbus/jsonrpc?status.svg)](https://godoc.org/github.com/ybbus/jsonrpc)
     3  [![GitHub license](https://img.shields.io/github/license/mashape/apistatus.svg)]()
     4  
     5  # JSON-RPC 2.0 Client for golang
     6  A go implementation of an rpc client using json as data format over http.
     7  The implementation is based on the JSON-RPC 2.0 specification: http://www.jsonrpc.org/specification
     8  
     9  Supports:
    10  - requests with arbitrary parameters
    11  - convenient response retrieval
    12  - batch requests
    13  - custom http client (e.g. proxy, tls config)
    14  - custom headers (e.g. basic auth)
    15  
    16  ## Installation
    17  
    18  ```sh
    19  go get -u github.com/ybbus/jsonrpc
    20  ```
    21  
    22  ## Getting started
    23  Let's say we want to retrieve a person struct with a specific id using rpc-json over http.
    24  Then we want to save this person after we changed a property.
    25  (Error handling is omitted here)
    26  
    27  ```go
    28  type Person struct {
    29      Id   int `json:"id"`
    30      Name string `json:"name"`
    31      Age  int `json:"age"`
    32  }
    33  
    34  func main() {
    35      rpcClient := jsonrpc.NewClient("http://my-rpc-service:8080/rpc")
    36  
    37      var person *Person
    38      rpcClient.CallFor(&person, "getPersonById", 4711)
    39  
    40      person.Age = 33
    41      rpcClient.Call("updatePerson", person)
    42  }
    43  ```
    44  
    45  ## In detail
    46  
    47  ### Generating rpc-json requests
    48  
    49  Let's start by executing a simple json-rpc http call:
    50  In production code: Always make sure to check err != nil first!
    51  
    52  This calls generate and send a valid rpc-json object. (see: http://www.jsonrpc.org/specification#request_object)
    53  
    54  ```go
    55  func main() {
    56      rpcClient := jsonrpc.NewClient("http://my-rpc-service:8080/rpc")
    57      rpcClient.Call("getDate")
    58      // generates body: {"method":"getDate","id":1,"jsonrpc":"2.0"}
    59  }
    60  ```
    61  
    62  Call a function with parameter:
    63  
    64  ```go
    65  func main() {
    66      rpcClient := jsonrpc.NewClient("http://my-rpc-service:8080/rpc")
    67      rpcClient.Call("addNumbers", 1, 2)
    68      // generates body: {"method":"addNumbers","params":[1,2],"id":1,"jsonrpc":"2.0"}
    69  }
    70  ```
    71  
    72  Call a function with arbitrary parameters:
    73  
    74  ```go
    75  func main() {
    76      rpcClient := jsonrpc.NewClient("http://my-rpc-service:8080/rpc")
    77      rpcClient.Call("createPerson", "Alex", 33, "Germany")
    78      // generates body: {"method":"createPerson","params":["Alex",33,"Germany"],"id":1,"jsonrpc":"2.0"}
    79  }
    80  ```
    81  
    82  Call a function providing custom data structures as parameters:
    83  
    84  ```go
    85  type Person struct {
    86    Name    string `json:"name"`
    87    Age     int `json:"age"`
    88    Country string `json:"country"`
    89  }
    90  func main() {
    91      rpcClient := jsonrpc.NewClient("http://my-rpc-service:8080/rpc")
    92      rpcClient.Call("createPerson", &Person{"Alex", 33, "Germany"})
    93      // generates body: {"jsonrpc":"2.0","method":"createPerson","params":{"name":"Alex","age":33,"country":"Germany"},"id":1}
    94  }
    95  ```
    96  
    97  Complex example:
    98  
    99  ```go
   100  type Person struct {
   101    Name    string `json:"name"`
   102    Age     int `json:"age"`
   103    Country string `json:"country"`
   104  }
   105  func main() {
   106      rpcClient := jsonrpc.NewClient("http://my-rpc-service:8080/rpc")
   107      rpcClient.Call("createPersonsWithRole", &Person{"Alex", 33, "Germany"}, &Person{"Barney", 38, "Germany"}, []string{"Admin", "User"})
   108      // generates body: {"jsonrpc":"2.0","method":"createPersonsWithRole","params":[{"name":"Alex","age":33,"country":"Germany"},{"name":"Barney","age":38,"country":"Germany"},["Admin","User"]],"id":1}
   109  }
   110  ```
   111  
   112  Some examples and resulting JSON-RPC objects:
   113  
   114  ```go
   115  rpcClient.Call("missingParam")
   116  {"method":"missingParam"}
   117  
   118  rpcClient.Call("nullParam", nil)
   119  {"method":"nullParam","params":[null]}
   120  
   121  rpcClient.Call("boolParam", true)
   122  {"method":"boolParam","params":[true]}
   123  
   124  rpcClient.Call("boolParams", true, false, true)
   125  {"method":"boolParams","params":[true,false,true]}
   126  
   127  rpcClient.Call("stringParam", "Alex")
   128  {"method":"stringParam","params":["Alex"]}
   129  
   130  rpcClient.Call("stringParams", "JSON", "RPC")
   131  {"method":"stringParams","params":["JSON","RPC"]}
   132  
   133  rpcClient.Call("numberParam", 123)
   134  {"method":"numberParam","params":[123]}
   135  
   136  rpcClient.Call("numberParams", 123, 321)
   137  {"method":"numberParams","params":[123,321]}
   138  
   139  rpcClient.Call("floatParam", 1.23)
   140  {"method":"floatParam","params":[1.23]}
   141  
   142  rpcClient.Call("floatParams", 1.23, 3.21)
   143  {"method":"floatParams","params":[1.23,3.21]}
   144  
   145  rpcClient.Call("manyParams", "Alex", 35, true, nil, 2.34)
   146  {"method":"manyParams","params":["Alex",35,true,null,2.34]}
   147  
   148  rpcClient.Call("singlePointerToStruct", &person)
   149  {"method":"singlePointerToStruct","params":{"name":"Alex","age":35,"country":"Germany"}}
   150  
   151  rpcClient.Call("multipleStructs", &person, &drink)
   152  {"method":"multipleStructs","params":[{"name":"Alex","age":35,"country":"Germany"},{"name":"Cuba Libre","ingredients":["rum","cola"]}]}
   153  
   154  rpcClient.Call("singleStructInArray", []*Person{&person})
   155  {"method":"singleStructInArray","params":[{"name":"Alex","age":35,"country":"Germany"}]}
   156  
   157  rpcClient.Call("namedParameters", map[string]interface{}{
   158  	"name": "Alex",
   159  	"age":  35,
   160  })
   161  {"method":"namedParameters","params":{"age":35,"name":"Alex"}}
   162  
   163  rpcClient.Call("anonymousStruct", struct {
   164  	Name string `json:"name"`
   165  	Age  int    `json:"age"`
   166  }{"Alex", 33})
   167  {"method":"anonymousStructWithTags","params":{"name":"Alex","age":33}}
   168  
   169  rpcClient.Call("structWithNullField", struct {
   170  	Name    string  `json:"name"`
   171  	Address *string `json:"address"`
   172  }{"Alex", nil})
   173  {"method":"structWithNullField","params":{"name":"Alex","address":null}}
   174  ```
   175  
   176  ### Working with rpc-json responses
   177  
   178  
   179  Before working with the response object, make sure to check err != nil.
   180  Also keep in mind that the json-rpc result field can be nil even on success.
   181  
   182  ```go
   183  func main() {
   184      rpcClient := jsonrpc.NewClient("http://my-rpc-service:8080/rpc")
   185      response, err := rpcClient.Call("addNumbers", 1, 2)
   186      if err != nil {
   187        // error handling goes here e.g. network / http error
   188      }
   189  }
   190  ```
   191  
   192  If an http error occurred, maybe you are interested in the error code (403 etc.)
   193  ```go
   194  func main() {
   195      rpcClient := jsonrpc.NewClient("http://my-rpc-service:8080/rpc")
   196      response, err := rpcClient.Call("addNumbers", 1, 2)
   197  
   198      switch e := err.(type) {
   199        case nil: // if error is nil, do nothing
   200        case *HTTPError:
   201          // use e.Code here
   202          return
   203        default:
   204          // any other error
   205          return
   206      }
   207  
   208      // no error, go on...
   209  }
   210  ```
   211  
   212  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:
   213  (see: http://www.jsonrpc.org/specification#error_object)
   214  
   215  ```go
   216  func main() {
   217      rpcClient := jsonrpc.NewClient("http://my-rpc-service:8080/rpc")
   218      response, err := rpcClient.Call("addNumbers", 1, 2)
   219      if err != nil {
   220          //error handling goes here
   221      }
   222  
   223      if response.Error != nil {
   224          // rpc error handling goes here
   225          // check response.Error.Code, response.Error.Message and optional response.Error.Data
   226      }
   227  }
   228  ```
   229  
   230  After making sure that no errors occurred you can now examine the RPCResponse object.
   231  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.
   232  (see: http://www.jsonrpc.org/specification#response_object)
   233  The library provides some helper functions to retrieve the result in the data format you are interested in.
   234  Again: check for err != nil here to be sure the expected type was provided in the response and could be parsed.
   235  
   236  ```go
   237  func main() {
   238      rpcClient := jsonrpc.NewClient("http://my-rpc-service:8080/rpc")
   239      response, _ := rpcClient.Call("addNumbers", 1, 2)
   240  
   241      result, err := response.GetInt()
   242      if err != nil {
   243          // result cannot be unmarshalled as integer
   244      }
   245  
   246      // helpers provided for all primitive types:
   247      response.GetInt()
   248      response.GetFloat()
   249      response.GetString()
   250      response.GetBool()
   251  }
   252  ```
   253  
   254  Retrieving arrays and objects is also very simple:
   255  
   256  ```go
   257  // json annotations are only required to transform the structure back to json
   258  type Person struct {
   259      Id   int `json:"id"`
   260      Name string `json:"name"`
   261      Age  int `json:"age"`
   262  }
   263  
   264  func main() {
   265      rpcClient := jsonrpc.NewClient("http://my-rpc-service:8080/rpc")
   266      response, _ := rpcClient.Call("getPersonById", 123)
   267  
   268      var person *Person
   269      err := response.GetObject(&person) // expects a rpc-object result value like: {"id": 123, "name": "alex", "age": 33}
   270      if err != nil || person == nil {
   271          // some error on json unmarshal level or json result field was null
   272      }
   273  
   274      fmt.Println(person.Name)
   275  
   276      // we can also set default values if they are missing from the result, or result == null:
   277      person2 := &Person{
   278          Id: 0,
   279          Name: "<empty>",
   280          Age: -1,
   281      }
   282      err := response.GetObject(&person2) // expects a rpc-object result value like: {"id": 123, "name": "alex", "age": 33}
   283      if err != nil || person2 == nil {
   284          // some error on json unmarshal level or json result field was null
   285      }
   286  
   287      fmt.Println(person2.Name) // prints "<empty>" if "name" field was missing in result-json
   288  }
   289  ```
   290  
   291  Retrieving arrays:
   292  
   293  ```go
   294  func main() {
   295      rpcClient := jsonrpc.NewClient("http://my-rpc-service:8080/rpc")
   296      response, _ := rpcClient.Call("getRandomNumbers", 10)
   297  
   298      rndNumbers := []int{}
   299      err := response.GetObject(&rndNumbers) // expects a rpc-object result value like: [10, 188, 14, 3]
   300      if err != nil {
   301          // do error handling
   302      }
   303  
   304      for _, num := range rndNumbers {
   305          fmt.Printf("%v\n", num)
   306      }
   307  }
   308  ```
   309  
   310  ### Using convenient function CallFor()
   311  A very handy way to quickly invoke methods and retrieve results is by using CallFor()
   312  
   313  You can directly provide an object where the result should be stored. Be sure to provide it be reference.
   314  An error is returned if:
   315  - there was an network / http error
   316  - RPCError object is not nil (err can be casted to this object)
   317  - rpc result could not be parsed into provided object
   318  
   319  One of te above examples could look like this:
   320  
   321  ```go
   322  // json annotations are only required to transform the structure back to json
   323  type Person struct {
   324      Id   int `json:"id"`
   325      Name string `json:"name"`
   326      Age  int `json:"age"`
   327  }
   328  
   329  func main() {
   330      rpcClient := jsonrpc.NewClient("http://my-rpc-service:8080/rpc")
   331  
   332      var person *Person
   333      err := rpcClient.CallFor(&person, "getPersonById", 123)
   334  
   335      if err != nil || person == nil {
   336        // handle error
   337      }
   338  
   339      fmt.Println(person.Name)
   340  }
   341  ```
   342  
   343  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.
   344  But if you want to be sure that a JSON-RPC response field was missing or not, you should use pointers to the fields.
   345  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)
   346  
   347  ```
   348  type Person struct {
   349      Id   *int    `json:"id"`
   350      Name *string `json:"name"`
   351      Age  *int    `json:"age"`
   352  }
   353  
   354  func main() {
   355      rpcClient := jsonrpc.NewClient("http://my-rpc-service:8080/rpc")
   356  
   357      var person *Person
   358      err := rpcClient.CallFor(&person, "getPersonById", 123)
   359  
   360      if err != nil || person == nil {
   361        // handle error
   362      }
   363  
   364      if person.Name == nil {
   365        // json rpc response did not provide a field "name" in the result object
   366      }
   367  }
   368  ```
   369  
   370  ### Using RPC Batch Requests
   371  
   372  You can send multiple RPC-Requests in one single HTTP request using RPC Batch Requests.
   373  
   374  ```
   375  func main() {
   376      rpcClient := jsonrpc.NewClient("http://my-rpc-service:8080/rpc")
   377  
   378      response, _ := rpcClient.CallBatch(RPCRequests{
   379        NewRequest("myMethod1", 1, 2, 3),
   380        NewRequest("anotherMethod", "Alex", 35, true),
   381        NewRequest("myMethod2", &Person{
   382          Name: "Emmy",
   383          Age: 4,
   384        }),
   385      })
   386  }
   387  ```
   388  
   389  Keep the following in mind:
   390  - the request / response id's are important to map the requests to the responses. CallBatch() automatically sets the ids to requests[i].ID == i
   391  - the response can be provided in an unordered and maybe incomplete form
   392  - when you want to set the id yourself use, CallRaw()
   393  
   394  There are some helper methods for batch request results:
   395  ```
   396  func main() {
   397      // [...]
   398  
   399      result.HasErrors() // returns true if one of the rpc response objects has Error field != nil
   400      resultMap := result.AsMap() // returns a map for easier retrieval of requests
   401  
   402      if response123, ok := resultMap[123]; ok {
   403        // response object with id 123 exists, use it here
   404        // response123.ID == 123
   405        response123.GetObjectAs(&person)
   406        // ...
   407      }
   408  
   409  }
   410  ```
   411  
   412  ### Raw functions
   413  There are also Raw function calls. Consider the non Raw functions first, unless you know what you are doing.
   414  You can create invalid json rpc requests and have to take care of id's etc. yourself.
   415  Also check documentation of Params() for raw requests.
   416  
   417  ### Custom Headers, Basic authentication
   418  
   419  If the rpc-service is running behind a basic authentication you can easily set the Authorization header:
   420  
   421  ```go
   422  func main() {
   423      rpcClient := jsonrpc.NewClientWithOpts("http://my-rpc-service:8080/rpc", &jsonrpc.RPCClientOpts{
   424     		CustomHeaders: map[string]string{
   425     			"Authorization": "Basic " + base64.StdEncoding.EncodeToString([]byte("myUser"+":"+"mySecret")),
   426     		},
   427     	})
   428      response, _ := rpcClient.Call("addNumbers", 1, 2) // send with Authorization-Header
   429  }
   430  ```
   431  
   432  ### Using oauth
   433  
   434  Using oauth is also easy, e.g. with clientID and clientSecret authentication
   435  
   436  ```go
   437  func main() {
   438  		credentials := clientcredentials.Config{
   439      		ClientID:     "myID",
   440      		ClientSecret: "mySecret",
   441      		TokenURL:     "http://mytokenurl",
   442      	}
   443  
   444      	rpcClient := jsonrpc.NewClientWithOpts("http://my-rpc-service:8080/rpc", &jsonrpc.RPCClientOpts{
   445      		HTTPClient: credentials.Client(context.Background()),
   446      	})
   447  
   448  	// requests now retrieve and use an oauth token
   449  }
   450  ```
   451  
   452  ### Set a custom httpClient
   453  
   454  If you have some special needs on the http.Client of the standard go library, just provide your own one.
   455  For example to use a proxy when executing json-rpc calls:
   456  
   457  ```go
   458  func main() {
   459  	proxyURL, _ := url.Parse("http://proxy:8080")
   460  	transport := &http.Transport{Proxy: http.ProxyURL(proxyURL)}
   461  
   462  	httpClient := &http.Client{
   463  		Transport: transport,
   464  	}
   465  
   466  	rpcClient := jsonrpc.NewClientWithOpts("http://my-rpc-service:8080/rpc", &jsonrpc.RPCClientOpts{
   467  		HTTPClient: httpClient,
   468  	})
   469  
   470  	// requests now use proxy
   471  }
   472  ```