blob: 36aba28911f943c0338dd8251d480728d75af1f7 [file] [log] [blame]
Serge Bazanskicc25bdf2018-10-25 14:02:58 +02001// Package jsonrpc provides a JSON-RPC 2.0 client that sends JSON-RPC requests and receives JSON-RPC responses using HTTP.
2package jsonrpc
3
4import (
5 "bytes"
6 "encoding/json"
7 "errors"
8 "fmt"
9 "net/http"
10 "reflect"
11 "strconv"
12)
13
14const (
15 jsonrpcVersion = "2.0"
16)
17
18// RPCClient sends JSON-RPC requests over HTTP to the provided JSON-RPC backend.
19//
20// RPCClient is created using the factory function NewClient().
21type RPCClient interface {
22 // Call is used to send a JSON-RPC request to the server endpoint.
23 //
24 // The spec states, that params can only be an array or an object, no primitive values.
25 // So there are a few simple rules to notice:
26 //
27 // 1. no params: params field is omitted. e.g. Call("getinfo")
28 //
29 // 2. single params primitive value: value is wrapped in array. e.g. Call("getByID", 1423)
30 //
31 // 3. single params value array or object: value is unchanged. e.g. Call("storePerson", &Person{Name: "Alex"})
32 //
33 // 4. multiple params values: always wrapped in array. e.g. Call("setDetails", "Alex, 35, "Germany", true)
34 //
35 // Examples:
36 // Call("getinfo") -> {"method": "getinfo"}
37 // Call("getPersonId", 123) -> {"method": "getPersonId", "params": [123]}
38 // Call("setName", "Alex") -> {"method": "setName", "params": ["Alex"]}
39 // Call("setMale", true) -> {"method": "setMale", "params": [true]}
40 // Call("setNumbers", []int{1, 2, 3}) -> {"method": "setNumbers", "params": [1, 2, 3]}
41 // Call("setNumbers", 1, 2, 3) -> {"method": "setNumbers", "params": [1, 2, 3]}
42 // Call("savePerson", &Person{Name: "Alex", Age: 35}) -> {"method": "savePerson", "params": {"name": "Alex", "age": 35}}
43 // Call("setPersonDetails", "Alex", 35, "Germany") -> {"method": "setPersonDetails", "params": ["Alex", 35, "Germany"}}
44 //
45 // for more information, see the examples or the unit tests
46 Call(method string, params ...interface{}) (*RPCResponse, error)
47
48 // CallRaw is like Call() but without magic in the requests.Params field.
49 // The RPCRequest object is sent exactly as you provide it.
50 // See docs: NewRequest, RPCRequest, Params()
51 //
52 // It is recommended to first consider Call() and CallFor()
53 CallRaw(request *RPCRequest) (*RPCResponse, error)
54
55 // CallFor is a very handy function to send a JSON-RPC request to the server endpoint
56 // and directly specify an object to store the response.
57 //
58 // out: will store the unmarshaled object, if request was successful.
59 // should always be provided by references. can be nil even on success.
60 // the behaviour is the same as expected from json.Unmarshal()
61 //
62 // method and params: see Call() function
63 //
64 // if the request was not successful (network, http error) or the rpc response returns an error,
65 // an error is returned. if it was an JSON-RPC error it can be casted
66 // to *RPCError.
67 //
68 CallFor(out interface{}, method string, params ...interface{}) error
69
70 // CallBatch invokes a list of RPCRequests in a single batch request.
71 //
72 // Most convenient is to use the following form:
73 // CallBatch(RPCRequests{
74 // Batch("myMethod1", 1, 2, 3),
75 // Batch("myMethod2), "Test"),
76 // })
77 //
78 // You can create the []*RPCRequest array yourself, but it is not recommended and you should notice the following:
79 // - field Params is sent as provided, so Params: 2 forms an invalid json (correct would be Params: []int{2})
80 // - you can use the helper function Params(1, 2, 3) to use the same format as in Call()
81 // - field JSONRPC is overwritten and set to value: "2.0"
82 // - field ID is overwritten and set incrementally and maps to the array position (e.g. requests[5].ID == 5)
83 //
84 //
85 // Returns RPCResponses that is of type []*RPCResponse
86 // - note that a list of RPCResponses can be received unordered so it can happen that: responses[i] != responses[i].ID
87 // - RPCPersponses is enriched with helper functions e.g.: responses.HasError() returns true if one of the responses holds an RPCError
88 CallBatch(requests RPCRequests) (RPCResponses, error)
89
90 // CallBatchRaw invokes a list of RPCRequests in a single batch request.
91 // It sends the RPCRequests parameter is it passed (no magic, no id autoincrement).
92 //
93 // Consider to use CallBatch() instead except you have some good reason not to.
94 //
95 // CallBatchRaw(RPCRequests{
96 // &RPCRequest{
97 // ID: 123, // this won't be replaced in CallBatchRaw
98 // JSONRPC: "wrong", // this won't be replaced in CallBatchRaw
99 // Method: "myMethod1",
100 // Params: []int{1}, // there is no magic, be sure to only use array or object
101 // },
102 // &RPCRequest{
103 // ID: 612,
104 // JSONRPC: "2.0",
105 // Method: "myMethod2",
106 // Params: Params("Alex", 35, true), // you can use helper function Params() (see doc)
107 // },
108 // })
109 //
110 // Returns RPCResponses that is of type []*RPCResponse
111 // - note that a list of RPCResponses can be received unordered
112 // - the id's must be mapped against the id's you provided
113 // - RPCPersponses is enriched with helper functions e.g.: responses.HasError() returns true if one of the responses holds an RPCError
114 CallBatchRaw(requests RPCRequests) (RPCResponses, error)
115}
116
117// RPCRequest represents a JSON-RPC request object.
118//
119// Method: string containing the method to be invoked
120//
121// Params: can be nil. if not must be an json array or object
122//
123// ID: may always set to 1 for single requests. Should be unique for every request in one batch request.
124//
125// JSONRPC: must always be set to "2.0" for JSON-RPC version 2.0
126//
127// See: http://www.jsonrpc.org/specification#request_object
128//
129// Most of the time you shouldn't create the RPCRequest object yourself.
130// The following functions do that for you:
131// Call(), CallFor(), NewRequest()
132//
133// If you want to create it yourself (e.g. in batch or CallRaw()), consider using Params().
134// Params() is a helper function that uses the same parameter syntax as Call().
135//
136// e.g. to manually create an RPCRequest object:
137// request := &RPCRequest{
138// Method: "myMethod",
139// Params: Params("Alex", 35, true),
140// }
141//
142// If you know what you are doing you can omit the Params() call to avoid some reflection but potentially create incorrect rpc requests:
143//request := &RPCRequest{
144// Method: "myMethod",
145// Params: 2, <-- invalid since a single primitive value must be wrapped in an array --> no magic without Params()
146// }
147//
148// correct:
149// request := &RPCRequest{
150// Method: "myMethod",
151// Params: []int{2}, <-- invalid since a single primitive value must be wrapped in an array
152// }
153type RPCRequest struct {
154 Method string `json:"method"`
155 Params interface{} `json:"params,omitempty"`
156 ID int `json:"id"`
157 JSONRPC string `json:"jsonrpc"`
158}
159
160// NewRequest returns a new RPCRequest that can be created using the same convenient parameter syntax as Call()
161//
162// e.g. NewRequest("myMethod", "Alex", 35, true)
163func NewRequest(method string, params ...interface{}) *RPCRequest {
164 request := &RPCRequest{
165 Method: method,
166 Params: Params(params...),
167 JSONRPC: jsonrpcVersion,
168 }
169
170 return request
171}
172
173// RPCResponse represents a JSON-RPC response object.
174//
175// Result: holds the result of the rpc call if no error occurred, nil otherwise. can be nil even on success.
176//
177// Error: holds an RPCError object if an error occurred. must be nil on success.
178//
179// ID: may always be 0 for single requests. is unique for each request in a batch call (see CallBatch())
180//
181// JSONRPC: must always be set to "2.0" for JSON-RPC version 2.0
182//
183// See: http://www.jsonrpc.org/specification#response_object
184type RPCResponse struct {
185 JSONRPC string `json:"jsonrpc"`
186 Result interface{} `json:"result,omitempty"`
187 Error *RPCError `json:"error,omitempty"`
188 ID int `json:"id"`
189}
190
191// RPCError represents a JSON-RPC error object if an RPC error occurred.
192//
193// Code: holds the error code
194//
195// Message: holds a short error message
196//
197// Data: holds additional error data, may be nil
198//
199// See: http://www.jsonrpc.org/specification#error_object
200type RPCError struct {
201 Code int `json:"code"`
202 Message string `json:"message"`
203 Data interface{} `json:"data,omitempty"`
204}
205
206// Error function is provided to be used as error object.
207func (e *RPCError) Error() string {
208 return strconv.Itoa(e.Code) + ":" + e.Message
209}
210
211// HTTPError represents a error that occurred on HTTP level.
212//
213// An error of type HTTPError is returned when a HTTP error occurred (status code)
214// and the body could not be parsed to a valid RPCResponse object that holds a RPCError.
215//
216// Otherwise a RPCResponse object is returned with a RPCError field that is not nil.
217type HTTPError struct {
218 Code int
219 err error
220}
221
222// Error function is provided to be used as error object.
223func (e *HTTPError) Error() string {
224 return e.err.Error()
225}
226
227type rpcClient struct {
228 endpoint string
229 httpClient *http.Client
230 customHeaders map[string]string
231}
232
233// RPCClientOpts can be provided to NewClientWithOpts() to change configuration of RPCClient.
234//
235// HTTPClient: provide a custom http.Client (e.g. to set a proxy, or tls options)
236//
237// CustomHeaders: provide custom headers, e.g. to set BasicAuth
238type RPCClientOpts struct {
239 HTTPClient *http.Client
240 CustomHeaders map[string]string
241}
242
243// RPCResponses is of type []*RPCResponse.
244// This type is used to provide helper functions on the result list
245type RPCResponses []*RPCResponse
246
247// AsMap returns the responses as map with response id as key.
248func (res RPCResponses) AsMap() map[int]*RPCResponse {
249 resMap := make(map[int]*RPCResponse, 0)
250 for _, r := range res {
251 resMap[r.ID] = r
252 }
253
254 return resMap
255}
256
257// GetByID returns the response object of the given id, nil if it does not exist.
258func (res RPCResponses) GetByID(id int) *RPCResponse {
259 for _, r := range res {
260 if r.ID == id {
261 return r
262 }
263 }
264
265 return nil
266}
267
268// HasError returns true if one of the response objects has Error field != nil
269func (res RPCResponses) HasError() bool {
270 for _, res := range res {
271 if res.Error != nil {
272 return true
273 }
274 }
275 return false
276}
277
278// RPCRequests is of type []*RPCRequest.
279// This type is used to provide helper functions on the request list
280type RPCRequests []*RPCRequest
281
282// NewClient returns a new RPCClient instance with default configuration.
283//
284// endpoint: JSON-RPC service URL to which JSON-RPC requests are sent.
285func NewClient(endpoint string) RPCClient {
286 return NewClientWithOpts(endpoint, nil)
287}
288
289// NewClientWithOpts returns a new RPCClient instance with custom configuration.
290//
291// endpoint: JSON-RPC service URL to which JSON-RPC requests are sent.
292//
293// opts: RPCClientOpts provide custom configuration
294func NewClientWithOpts(endpoint string, opts *RPCClientOpts) RPCClient {
295 rpcClient := &rpcClient{
296 endpoint: endpoint,
297 httpClient: &http.Client{},
298 customHeaders: make(map[string]string),
299 }
300
301 if opts == nil {
302 return rpcClient
303 }
304
305 if opts.HTTPClient != nil {
306 rpcClient.httpClient = opts.HTTPClient
307 }
308
309 if opts.CustomHeaders != nil {
310 for k, v := range opts.CustomHeaders {
311 rpcClient.customHeaders[k] = v
312 }
313 }
314
315 return rpcClient
316}
317
318func (client *rpcClient) Call(method string, params ...interface{}) (*RPCResponse, error) {
319
320 request := &RPCRequest{
321 Method: method,
322 Params: Params(params...),
323 JSONRPC: jsonrpcVersion,
324 }
325
326 return client.doCall(request)
327}
328
329func (client *rpcClient) CallRaw(request *RPCRequest) (*RPCResponse, error) {
330
331 return client.doCall(request)
332}
333
334func (client *rpcClient) CallFor(out interface{}, method string, params ...interface{}) error {
335 rpcResponse, err := client.Call(method, params...)
336 if err != nil {
337 return err
338 }
339
340 if rpcResponse.Error != nil {
341 return rpcResponse.Error
342 }
343
344 return rpcResponse.GetObject(out)
345}
346
347func (client *rpcClient) CallBatch(requests RPCRequests) (RPCResponses, error) {
348 if len(requests) == 0 {
349 return nil, errors.New("empty request list")
350 }
351
352 for i, req := range requests {
353 req.ID = i
354 req.JSONRPC = jsonrpcVersion
355 }
356
357 return client.doBatchCall(requests)
358}
359
360func (client *rpcClient) CallBatchRaw(requests RPCRequests) (RPCResponses, error) {
361 if len(requests) == 0 {
362 return nil, errors.New("empty request list")
363 }
364
365 return client.doBatchCall(requests)
366}
367
368func (client *rpcClient) newRequest(req interface{}) (*http.Request, error) {
369
370 body, err := json.Marshal(req)
371 if err != nil {
372 return nil, err
373 }
374
375 request, err := http.NewRequest("POST", client.endpoint, bytes.NewReader(body))
376 if err != nil {
377 return nil, err
378 }
379
380 request.Header.Set("Content-Type", "application/json")
381 request.Header.Set("Accept", "application/json")
382
383 // set default headers first, so that even content type and accept can be overwritten
384 for k, v := range client.customHeaders {
385 request.Header.Set(k, v)
386 }
387
388 return request, nil
389}
390
391func (client *rpcClient) doCall(RPCRequest *RPCRequest) (*RPCResponse, error) {
392
393 httpRequest, err := client.newRequest(RPCRequest)
394 if err != nil {
395 return nil, fmt.Errorf("rpc call %v() on %v: %v", RPCRequest.Method, httpRequest.URL.String(), err.Error())
396 }
397 httpResponse, err := client.httpClient.Do(httpRequest)
398 if err != nil {
399 return nil, fmt.Errorf("rpc call %v() on %v: %v", RPCRequest.Method, httpRequest.URL.String(), err.Error())
400 }
401 defer httpResponse.Body.Close()
402
403 var rpcResponse *RPCResponse
404 decoder := json.NewDecoder(httpResponse.Body)
405 decoder.DisallowUnknownFields()
406 decoder.UseNumber()
407 err = decoder.Decode(&rpcResponse)
408
409 // parsing error
410 if err != nil {
411 // if we have some http error, return it
412 if httpResponse.StatusCode >= 400 {
413 return nil, &HTTPError{
414 Code: httpResponse.StatusCode,
415 err: fmt.Errorf("rpc call %v() on %v status code: %v. could not decode body to rpc response: %v", RPCRequest.Method, httpRequest.URL.String(), httpResponse.StatusCode, err.Error()),
416 }
417 }
418 return nil, fmt.Errorf("rpc call %v() on %v status code: %v. could not decode body to rpc response: %v", RPCRequest.Method, httpRequest.URL.String(), httpResponse.StatusCode, err.Error())
419 }
420
421 // response body empty
422 if rpcResponse == nil {
423 // if we have some http error, return it
424 if httpResponse.StatusCode >= 400 {
425 return nil, &HTTPError{
426 Code: httpResponse.StatusCode,
427 err: fmt.Errorf("rpc call %v() on %v status code: %v. rpc response missing", RPCRequest.Method, httpRequest.URL.String(), httpResponse.StatusCode),
428 }
429 }
430 return nil, fmt.Errorf("rpc call %v() on %v status code: %v. rpc response missing", RPCRequest.Method, httpRequest.URL.String(), httpResponse.StatusCode)
431 }
432
433 return rpcResponse, nil
434}
435
436func (client *rpcClient) doBatchCall(rpcRequest []*RPCRequest) ([]*RPCResponse, error) {
437 httpRequest, err := client.newRequest(rpcRequest)
438 if err != nil {
439 return nil, fmt.Errorf("rpc batch call on %v: %v", httpRequest.URL.String(), err.Error())
440 }
441 httpResponse, err := client.httpClient.Do(httpRequest)
442 if err != nil {
443 return nil, fmt.Errorf("rpc batch call on %v: %v", httpRequest.URL.String(), err.Error())
444 }
445 defer httpResponse.Body.Close()
446
447 var rpcResponse RPCResponses
448 decoder := json.NewDecoder(httpResponse.Body)
449 decoder.DisallowUnknownFields()
450 decoder.UseNumber()
451 err = decoder.Decode(&rpcResponse)
452
453 // parsing error
454 if err != nil {
455 // if we have some http error, return it
456 if httpResponse.StatusCode >= 400 {
457 return nil, &HTTPError{
458 Code: httpResponse.StatusCode,
459 err: fmt.Errorf("rpc batch call on %v status code: %v. could not decode body to rpc response: %v", httpRequest.URL.String(), httpResponse.StatusCode, err.Error()),
460 }
461 }
462 return nil, fmt.Errorf("rpc batch call on %v status code: %v. could not decode body to rpc response: %v", httpRequest.URL.String(), httpResponse.StatusCode, err.Error())
463 }
464
465 // response body empty
466 if rpcResponse == nil || len(rpcResponse) == 0 {
467 // if we have some http error, return it
468 if httpResponse.StatusCode >= 400 {
469 return nil, &HTTPError{
470 Code: httpResponse.StatusCode,
471 err: fmt.Errorf("rpc batch call on %v status code: %v. rpc response missing", httpRequest.URL.String(), httpResponse.StatusCode),
472 }
473 }
474 return nil, fmt.Errorf("rpc batch call on %v status code: %v. rpc response missing", httpRequest.URL.String(), httpResponse.StatusCode)
475 }
476
477 return rpcResponse, nil
478}
479
480// Params is a helper function that uses the same parameter syntax as Call().
481// But you should consider to always use NewRequest() instead.
482//
483// e.g. to manually create an RPCRequest object:
484// request := &RPCRequest{
485// Method: "myMethod",
486// Params: Params("Alex", 35, true),
487// }
488//
489// same with new request:
490// request := NewRequest("myMethod", "Alex", 35, true)
491//
492// If you know what you are doing you can omit the Params() call but potentially create incorrect rpc requests:
493//request := &RPCRequest{
494// Method: "myMethod",
495// Params: 2, <-- invalid since a single primitive value must be wrapped in an array --> no magic without Params()
496// }
497//
498// correct:
499// request := &RPCRequest{
500// Method: "myMethod",
501// Params: []int{2}, <-- invalid since a single primitive value must be wrapped in an array
502// }
503func Params(params ...interface{}) interface{} {
504 var finalParams interface{}
505
506 // if params was nil skip this and p stays nil
507 if params != nil {
508 switch len(params) {
509 case 0: // no parameters were provided, do nothing so finalParam is nil and will be omitted
510 case 1: // one param was provided, use it directly as is, or wrap primitive types in array
511 if params[0] != nil {
512 var typeOf reflect.Type
513
514 // traverse until nil or not a pointer type
515 for typeOf = reflect.TypeOf(params[0]); typeOf != nil && typeOf.Kind() == reflect.Ptr; typeOf = typeOf.Elem() {
516 }
517
518 if typeOf != nil {
519 // now check if we can directly marshal the type or if it must be wrapped in an array
520 switch typeOf.Kind() {
521 // for these types we just do nothing, since value of p is already unwrapped from the array params
522 case reflect.Struct:
523 finalParams = params[0]
524 case reflect.Array:
525 finalParams = params[0]
526 case reflect.Slice:
527 finalParams = params[0]
528 case reflect.Interface:
529 finalParams = params[0]
530 case reflect.Map:
531 finalParams = params[0]
532 default: // everything else must stay in an array (int, string, etc)
533 finalParams = params
534 }
535 }
536 } else {
537 finalParams = params
538 }
539 default: // if more than one parameter was provided it should be treated as an array
540 finalParams = params
541 }
542 }
543
544 return finalParams
545}
546
547// GetInt converts the rpc response to an int64 and returns it.
548//
549// If result was not an integer an error is returned.
550func (RPCResponse *RPCResponse) GetInt() (int64, error) {
551 val, ok := RPCResponse.Result.(json.Number)
552 if !ok {
553 return 0, fmt.Errorf("could not parse int64 from %s", RPCResponse.Result)
554 }
555
556 i, err := val.Int64()
557 if err != nil {
558 return 0, err
559 }
560
561 return i, nil
562}
563
564// GetFloat converts the rpc response to float64 and returns it.
565//
566// If result was not an float64 an error is returned.
567func (RPCResponse *RPCResponse) GetFloat() (float64, error) {
568 val, ok := RPCResponse.Result.(json.Number)
569 if !ok {
570 return 0, fmt.Errorf("could not parse float64 from %s", RPCResponse.Result)
571 }
572
573 f, err := val.Float64()
574 if err != nil {
575 return 0, err
576 }
577
578 return f, nil
579}
580
581// GetBool converts the rpc response to a bool and returns it.
582//
583// If result was not a bool an error is returned.
584func (RPCResponse *RPCResponse) GetBool() (bool, error) {
585 val, ok := RPCResponse.Result.(bool)
586 if !ok {
587 return false, fmt.Errorf("could not parse bool from %s", RPCResponse.Result)
588 }
589
590 return val, nil
591}
592
593// GetString converts the rpc response to a string and returns it.
594//
595// If result was not a string an error is returned.
596func (RPCResponse *RPCResponse) GetString() (string, error) {
597 val, ok := RPCResponse.Result.(string)
598 if !ok {
599 return "", fmt.Errorf("could not parse string from %s", RPCResponse.Result)
600 }
601
602 return val, nil
603}
604
605// GetObject converts the rpc response to an arbitrary type.
606//
607// The function works as you would expect it from json.Unmarshal()
608func (RPCResponse *RPCResponse) GetObject(toType interface{}) error {
609 js, err := json.Marshal(RPCResponse.Result)
610 if err != nil {
611 return err
612 }
613
614 err = json.Unmarshal(js, toType)
615 if err != nil {
616 return err
617 }
618
619 return nil
620}