github.com/gagliardetto/solana-go@v1.11.0/rpc/jsonrpc/README.md (about) 1 [](https://goreportcard.com/report/github.com/ybbus/jsonrpc) 2 [](https://godoc.org/github.com/ybbus/jsonrpc) 3 []() 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 ```