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.

476 lines
14 KiB

  1. // Package jsonrpc provides an jsonrpc 2.0 client that sends jsonrpc requests and receives jsonrpc responses using http.
  2. package jsonrpc
  3. import (
  4. "bytes"
  5. "encoding/base64"
  6. "encoding/json"
  7. "errors"
  8. "fmt"
  9. "net/http"
  10. "strconv"
  11. "sync"
  12. )
  13. // RPCRequest represents a jsonrpc request object.
  14. //
  15. // See: http://www.jsonrpc.org/specification#request_object
  16. type RPCRequest struct {
  17. JSONRPC string `json:"jsonrpc"`
  18. Method string `json:"method"`
  19. Params interface{} `json:"params,omitempty"`
  20. ID uint `json:"id"`
  21. }
  22. // RPCNotification represents a jsonrpc notification object.
  23. // A notification object omits the id field since there will be no server response.
  24. //
  25. // See: http://www.jsonrpc.org/specification#notification
  26. type RPCNotification struct {
  27. JSONRPC string `json:"jsonrpc"`
  28. Method string `json:"method"`
  29. Params interface{} `json:"params,omitempty"`
  30. }
  31. // RPCResponse represents a jsonrpc response object.
  32. // If no rpc specific error occurred Error field is nil.
  33. //
  34. // See: http://www.jsonrpc.org/specification#response_object
  35. type RPCResponse struct {
  36. JSONRPC string `json:"jsonrpc"`
  37. Result interface{} `json:"result,omitempty"`
  38. Error *RPCError `json:"error,omitempty"`
  39. ID uint `json:"id"`
  40. }
  41. // BatchResponse a list of jsonrpc response objects as a result of a batch request
  42. //
  43. // if you are interested in the response of a specific request use: GetResponseOf(request)
  44. type BatchResponse struct {
  45. rpcResponses []RPCResponse
  46. }
  47. // RPCError represents a jsonrpc error object if an rpc error occurred.
  48. //
  49. // See: http://www.jsonrpc.org/specification#error_object
  50. type RPCError struct {
  51. Code int `json:"code"`
  52. Message string `json:"message"`
  53. Data interface{} `json:"data"`
  54. }
  55. func (e *RPCError) Error() string {
  56. return strconv.Itoa(e.Code) + ": " + e.Message
  57. }
  58. // RPCClient sends jsonrpc requests over http to the provided rpc backend.
  59. // RPCClient is created using the factory function NewRPCClient().
  60. type RPCClient struct {
  61. endpoint string
  62. httpClient *http.Client
  63. customHeaders map[string]string
  64. autoIncrementID bool
  65. nextID uint
  66. idMutex sync.Mutex
  67. }
  68. // NewRPCClient returns a new RPCClient instance with default configuration (no custom headers, default http.Client, autoincrement ids).
  69. // Endpoint is the rpc-service url to which the rpc requests are sent.
  70. func NewRPCClient(endpoint string) *RPCClient {
  71. return &RPCClient{
  72. endpoint: endpoint,
  73. httpClient: http.DefaultClient,
  74. autoIncrementID: true,
  75. nextID: 0,
  76. customHeaders: make(map[string]string),
  77. }
  78. }
  79. // NewRPCRequestObject creates and returns a raw RPCRequest structure.
  80. // It is mainly used when building batch requests. For single requests use RPCClient.Call().
  81. // RPCRequest struct can also be created directly, but this function sets the ID and the jsonrpc field to the correct values.
  82. func (client *RPCClient) NewRPCRequestObject(method string, params ...interface{}) *RPCRequest {
  83. client.idMutex.Lock()
  84. rpcRequest := RPCRequest{
  85. ID: client.nextID,
  86. JSONRPC: "2.0",
  87. Method: method,
  88. Params: params,
  89. }
  90. if client.autoIncrementID == true {
  91. client.nextID++
  92. }
  93. client.idMutex.Unlock()
  94. if len(params) == 0 {
  95. rpcRequest.Params = nil
  96. }
  97. return &rpcRequest
  98. }
  99. // NewRPCNotificationObject creates and returns a raw RPCNotification structure.
  100. // It is mainly used when building batch requests. For single notifications use RPCClient.Notification().
  101. // NewRPCNotificationObject struct can also be created directly, but this function sets the ID and the jsonrpc field to the correct values.
  102. func (client *RPCClient) NewRPCNotificationObject(method string, params ...interface{}) *RPCNotification {
  103. rpcNotification := RPCNotification{
  104. JSONRPC: "2.0",
  105. Method: method,
  106. Params: params,
  107. }
  108. if len(params) == 0 {
  109. rpcNotification.Params = nil
  110. }
  111. return &rpcNotification
  112. }
  113. // Call sends an jsonrpc request over http to the rpc-service url that was provided on client creation.
  114. //
  115. // If something went wrong on the network / http level or if json parsing failed it returns an error.
  116. //
  117. // If something went wrong on the rpc-service / protocol level the Error field of the returned RPCResponse is set
  118. // and contains information about the error.
  119. //
  120. // If the request was successful the Error field is nil and the Result field of the RPCRespnse struct contains the rpc result.
  121. func (client *RPCClient) Call(method string, params ...interface{}) (*RPCResponse, error) {
  122. // Ensure that params are nil and will be omitted from JSON if not specified.
  123. var p interface{}
  124. if len(params) != 0 {
  125. p = params
  126. }
  127. httpRequest, err := client.newRequest(false, method, p)
  128. if err != nil {
  129. return nil, err
  130. }
  131. return client.doCall(httpRequest)
  132. }
  133. // CallNamed sends an jsonrpc request over http to the rpc-service url that was provided on client creation.
  134. // This differs from Call() by sending named, rather than positional, arguments.
  135. //
  136. // If something went wrong on the network / http level or if json parsing failed it returns an error.
  137. //
  138. // If something went wrong on the rpc-service / protocol level the Error field of the returned RPCResponse is set
  139. // and contains information about the error.
  140. //
  141. // If the request was successful the Error field is nil and the Result field of the RPCRespnse struct contains the rpc result.
  142. func (client *RPCClient) CallNamed(method string, params map[string]interface{}) (*RPCResponse, error) {
  143. httpRequest, err := client.newRequest(false, method, params)
  144. if err != nil {
  145. return nil, err
  146. }
  147. return client.doCall(httpRequest)
  148. }
  149. func (client *RPCClient) doCall(req *http.Request) (*RPCResponse, error) {
  150. httpResponse, err := client.httpClient.Do(req)
  151. if err != nil {
  152. return nil, err
  153. }
  154. defer httpResponse.Body.Close()
  155. rpcResponse := RPCResponse{}
  156. decoder := json.NewDecoder(httpResponse.Body)
  157. decoder.UseNumber()
  158. err = decoder.Decode(&rpcResponse)
  159. if err != nil {
  160. return nil, err
  161. }
  162. return &rpcResponse, nil
  163. }
  164. // Notification sends a jsonrpc request to the rpc-service. The difference to Call() is that this request does not expect a response.
  165. // The ID field of the request is omitted.
  166. func (client *RPCClient) Notification(method string, params ...interface{}) error {
  167. if len(params) == 0 {
  168. params = nil
  169. }
  170. httpRequest, err := client.newRequest(true, method, params)
  171. if err != nil {
  172. return err
  173. }
  174. httpResponse, err := client.httpClient.Do(httpRequest)
  175. if err != nil {
  176. return err
  177. }
  178. defer httpResponse.Body.Close()
  179. return nil
  180. }
  181. // Batch sends a jsonrpc batch request to the rpc-service.
  182. // The parameter is a list of requests the could be one of:
  183. // RPCRequest
  184. // RPCNotification.
  185. //
  186. // The batch requests returns a list of RPCResponse structs.
  187. func (client *RPCClient) Batch(requests ...interface{}) (*BatchResponse, error) {
  188. for _, r := range requests {
  189. switch r := r.(type) {
  190. default:
  191. return nil, fmt.Errorf("Invalid parameter: %s", r)
  192. case *RPCRequest:
  193. case *RPCNotification:
  194. }
  195. }
  196. httpRequest, err := client.newBatchRequest(requests...)
  197. if err != nil {
  198. return nil, err
  199. }
  200. httpResponse, err := client.httpClient.Do(httpRequest)
  201. if err != nil {
  202. return nil, err
  203. }
  204. defer httpResponse.Body.Close()
  205. rpcResponses := []RPCResponse{}
  206. decoder := json.NewDecoder(httpResponse.Body)
  207. decoder.UseNumber()
  208. err = decoder.Decode(&rpcResponses)
  209. if err != nil {
  210. return nil, err
  211. }
  212. return &BatchResponse{rpcResponses: rpcResponses}, nil
  213. }
  214. // SetAutoIncrementID if set to true, the id field of an rpcjson request will be incremented automatically
  215. func (client *RPCClient) SetAutoIncrementID(flag bool) {
  216. client.autoIncrementID = flag
  217. }
  218. // SetNextID can be used to manually set the next id / reset the id.
  219. func (client *RPCClient) SetNextID(id uint) {
  220. client.idMutex.Lock()
  221. client.nextID = id
  222. client.idMutex.Unlock()
  223. }
  224. // SetCustomHeader is used to set a custom header for each rpc request.
  225. // You could for example set the Authorization Bearer here.
  226. func (client *RPCClient) SetCustomHeader(key string, value string) {
  227. client.customHeaders[key] = value
  228. }
  229. // UnsetCustomHeader is used to removes a custom header that was added before.
  230. func (client *RPCClient) UnsetCustomHeader(key string) {
  231. delete(client.customHeaders, key)
  232. }
  233. // SetBasicAuth is a helper function that sets the header for the given basic authentication credentials.
  234. // To reset / disable authentication just set username or password to an empty string value.
  235. func (client *RPCClient) SetBasicAuth(username string, password string) {
  236. if username == "" || password == "" {
  237. delete(client.customHeaders, "Authorization")
  238. return
  239. }
  240. auth := username + ":" + password
  241. client.customHeaders["Authorization"] = "Basic " + base64.StdEncoding.EncodeToString([]byte(auth))
  242. }
  243. // SetHTTPClient can be used to set a custom http.Client.
  244. // This can be useful for example if you want to customize the http.Client behaviour (e.g. proxy settings)
  245. func (client *RPCClient) SetHTTPClient(httpClient *http.Client) {
  246. if httpClient == nil {
  247. panic("httpClient cannot be nil")
  248. }
  249. client.httpClient = httpClient
  250. }
  251. func (client *RPCClient) newRequest(notification bool, method string, params interface{}) (*http.Request, error) {
  252. // TODO: easier way to remove ID from RPCRequest without extra struct
  253. var rpcRequest interface{}
  254. if notification {
  255. rpcNotification := RPCNotification{
  256. JSONRPC: "2.0",
  257. Method: method,
  258. Params: params,
  259. }
  260. rpcRequest = rpcNotification
  261. } else {
  262. client.idMutex.Lock()
  263. request := RPCRequest{
  264. ID: client.nextID,
  265. JSONRPC: "2.0",
  266. Method: method,
  267. Params: params,
  268. }
  269. if client.autoIncrementID == true {
  270. client.nextID++
  271. }
  272. client.idMutex.Unlock()
  273. rpcRequest = request
  274. }
  275. body, err := json.Marshal(rpcRequest)
  276. if err != nil {
  277. return nil, err
  278. }
  279. request, err := http.NewRequest("POST", client.endpoint, bytes.NewReader(body))
  280. if err != nil {
  281. return nil, err
  282. }
  283. for k, v := range client.customHeaders {
  284. request.Header.Add(k, v)
  285. }
  286. request.Header.Add("Content-Type", "application/json")
  287. request.Header.Add("Accept", "application/json")
  288. return request, nil
  289. }
  290. func (client *RPCClient) newBatchRequest(requests ...interface{}) (*http.Request, error) {
  291. body, err := json.Marshal(requests)
  292. if err != nil {
  293. return nil, err
  294. }
  295. request, err := http.NewRequest("POST", client.endpoint, bytes.NewReader(body))
  296. if err != nil {
  297. return nil, err
  298. }
  299. for k, v := range client.customHeaders {
  300. request.Header.Add(k, v)
  301. }
  302. request.Header.Add("Content-Type", "application/json")
  303. request.Header.Add("Accept", "application/json")
  304. return request, nil
  305. }
  306. // UpdateRequestID updates the ID of an RPCRequest structure.
  307. //
  308. // This is used if a request is sent another time and the request should get an updated id.
  309. //
  310. // This does only make sense when used on with Batch() since Call() and Notififcation() do update the id automatically.
  311. func (client *RPCClient) UpdateRequestID(rpcRequest *RPCRequest) {
  312. if rpcRequest == nil {
  313. return
  314. }
  315. client.idMutex.Lock()
  316. defer client.idMutex.Unlock()
  317. rpcRequest.ID = client.nextID
  318. if client.autoIncrementID == true {
  319. client.nextID++
  320. }
  321. }
  322. // GetInt converts the rpc response to an int and returns it.
  323. //
  324. // This is a convenient function. Int could be 32 or 64 bit, depending on the architecture the code is running on.
  325. // For a deterministic result use GetInt64().
  326. //
  327. // If result was not an integer an error is returned.
  328. func (rpcResponse *RPCResponse) GetInt() (int, error) {
  329. i, err := rpcResponse.GetInt64()
  330. return int(i), err
  331. }
  332. // GetInt64 converts the rpc response to an int64 and returns it.
  333. //
  334. // If result was not an integer an error is returned.
  335. func (rpcResponse *RPCResponse) GetInt64() (int64, error) {
  336. val, ok := rpcResponse.Result.(json.Number)
  337. if !ok {
  338. return 0, fmt.Errorf("could not parse int64 from %s", rpcResponse.Result)
  339. }
  340. i, err := val.Int64()
  341. if err != nil {
  342. return 0, err
  343. }
  344. return i, nil
  345. }
  346. // GetFloat64 converts the rpc response to an float64 and returns it.
  347. //
  348. // If result was not an float64 an error is returned.
  349. func (rpcResponse *RPCResponse) GetFloat64() (float64, error) {
  350. val, ok := rpcResponse.Result.(json.Number)
  351. if !ok {
  352. return 0, fmt.Errorf("could not parse float64 from %s", rpcResponse.Result)
  353. }
  354. f, err := val.Float64()
  355. if err != nil {
  356. return 0, err
  357. }
  358. return f, nil
  359. }
  360. // GetBool converts the rpc response to a bool and returns it.
  361. //
  362. // If result was not a bool an error is returned.
  363. func (rpcResponse *RPCResponse) GetBool() (bool, error) {
  364. val, ok := rpcResponse.Result.(bool)
  365. if !ok {
  366. return false, fmt.Errorf("could not parse bool from %s", rpcResponse.Result)
  367. }
  368. return val, nil
  369. }
  370. // GetString converts the rpc response to a string and returns it.
  371. //
  372. // If result was not a string an error is returned.
  373. func (rpcResponse *RPCResponse) GetString() (string, error) {
  374. val, ok := rpcResponse.Result.(string)
  375. if !ok {
  376. return "", fmt.Errorf("could not parse string from %s", rpcResponse.Result)
  377. }
  378. return val, nil
  379. }
  380. // GetObject converts the rpc response to an object (e.g. a struct) and returns it.
  381. // The parameter should be a structure that can hold the data of the response object.
  382. //
  383. // For example if the following json return value is expected: {"name": "alex", age: 33, "country": "Germany"}
  384. // the struct should look like
  385. // type Person struct {
  386. // Name string
  387. // Age int
  388. // Country string
  389. // }
  390. func (rpcResponse *RPCResponse) GetObject(toType interface{}) error {
  391. js, err := json.Marshal(rpcResponse.Result)
  392. if err != nil {
  393. return err
  394. }
  395. err = json.Unmarshal(js, toType)
  396. if err != nil {
  397. return err
  398. }
  399. return nil
  400. }
  401. // GetResponseOf returns the rpc response of the corresponding request by matching the id.
  402. //
  403. // For this method to work, autoincrementID should be set to true (default).
  404. func (batchResponse *BatchResponse) GetResponseOf(request *RPCRequest) (*RPCResponse, error) {
  405. if request == nil {
  406. return nil, errors.New("parameter cannot be nil")
  407. }
  408. for _, elem := range batchResponse.rpcResponses {
  409. if elem.ID == request.ID {
  410. return &elem, nil
  411. }
  412. }
  413. return nil, fmt.Errorf("element with id %d not found", request.ID)
  414. }