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.

347 lines
11 KiB

  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. # JSON-RPC 2.0 Client for golang
  5. A go implementation of an rpc client using json as data format over http.
  6. The implementation is based on the JSON-RPC 2.0 specification: http://www.jsonrpc.org/specification
  7. Supports:
  8. - requests with arbitrary parameters
  9. - requests with named parameters
  10. - notifications
  11. - batch requests
  12. - convenient response retrieval
  13. - basic authentication
  14. - custom headers
  15. - custom http client
  16. ## Installation
  17. ```sh
  18. go get -u github.com/ybbus/jsonrpc
  19. ```
  20. ## Getting started
  21. Let's say we want to retrieve a person with a specific id using rpc-json over http.
  22. Then we want to save this person with new properties.
  23. We have to provide basic authentication credentials.
  24. (Error handling is omitted here)
  25. ```go
  26. type Person struct {
  27. Id int `json:"id"`
  28. Name string `json:"name"`
  29. Age int `json:"age"`
  30. }
  31. func main() {
  32. rpcClient := jsonrpc.NewRPCClient("http://my-rpc-service:8080/rpc")
  33. rpcClient.SetBasicAuth("alex", "secret")
  34. response, _ := rpcClient.Call("getPersonById", 123)
  35. person := Person{}
  36. response.GetObject(&person)
  37. person.Age = 33
  38. rpcClient.Call("updatePerson", person)
  39. }
  40. ```
  41. ## In detail
  42. ### Generating rpc-json requests
  43. Let's start by executing a simple json-rpc http call:
  44. In production code: Always make sure to check err != nil first!
  45. This calls generate and send a valid rpc-json object. (see: http://www.jsonrpc.org/specification#request_object)
  46. ```go
  47. func main() {
  48. rpcClient := jsonrpc.NewRPCClient("http://my-rpc-service:8080/rpc")
  49. response, _ := rpcClient.Call("getDate")
  50. // generates body: {"jsonrpc":"2.0","method":"getDate","id":0}
  51. }
  52. ```
  53. Call a function with parameter:
  54. ```go
  55. func main() {
  56. rpcClient := jsonrpc.NewRPCClient("http://my-rpc-service:8080/rpc")
  57. response, _ := rpcClient.Call("addNumbers", 1, 2)
  58. // generates body: {"jsonrpc":"2.0","method":"addNumbers","params":[1,2],"id":0}
  59. }
  60. ```
  61. Call a function with arbitrary parameters:
  62. ```go
  63. func main() {
  64. rpcClient := jsonrpc.NewRPCClient("http://my-rpc-service:8080/rpc")
  65. response, _ := rpcClient.Call("createPerson", "Alex", 33, "Germany")
  66. // generates body: {"jsonrpc":"2.0","method":"createPerson","params":["Alex",33,"Germany"],"id":0}
  67. }
  68. ```
  69. Call a function with named parameters:
  70. ```go
  71. func main() {
  72. rpcClient := jsonrpc.NewRPCClient("http://my-rpc-service:8080/rpc")
  73. rpcClient.CallNamed("createPerson", map[string]interface{}{
  74. "name": "Bartholomew Allen",
  75. "nicknames": []string{"Barry", "Flash",},
  76. "male": true,
  77. "age": 28,
  78. "address": map[string]interface{}{"street": "Main Street", "city": "Central City"},
  79. })
  80. // generates body: {"jsonrpc":"2.0","method":"createPerson","params":
  81. // {"name": "Bartholomew Allen", "nicknames": ["Barry", "Flash"], "male": true, "age": 28,
  82. // "address": {"street": "Main Street", "city": "Central City"}}
  83. // ,"id":0}
  84. }
  85. ```
  86. Call a function providing custom data structures as parameters:
  87. ```go
  88. type Person struct {
  89. Name string `json:"name"`
  90. Age int `json:"age"`
  91. Country string `json:"country"`
  92. }
  93. func main() {
  94. rpcClient := jsonrpc.NewRPCClient("http://my-rpc-service:8080/rpc")
  95. response, _ := rpcClient.Call("createPerson", Person{"Alex", 33, "Germany"})
  96. // generates body: {"jsonrpc":"2.0","method":"createPerson","params":[{"name":"Alex","age":33,"country":"Germany"}],"id":0}
  97. }
  98. ```
  99. Complex example:
  100. ```go
  101. type Person struct {
  102. Name string `json:"name"`
  103. Age int `json:"age"`
  104. Country string `json:"country"`
  105. }
  106. func main() {
  107. rpcClient := jsonrpc.NewRPCClient("http://my-rpc-service:8080/rpc")
  108. response, _ := rpcClient.Call("createPersonsWithRole", []Person{{"Alex", 33, "Germany"}, {"Barney", 38, "Germany"}}, []string{"Admin", "User"})
  109. // generates body: {"jsonrpc":"2.0","method":"createPersonsWithRole","params":[[{"name":"Alex","age":33,"country":"Germany"},{"name":"Barney","age":38,"country":"Germany"}],["Admin","User"]],"id":0}
  110. }
  111. ```
  112. ### Notification
  113. A jsonrpc notification is a rpc call to the server without expecting a response.
  114. Only an error object is returned in case of networkt / http error.
  115. No id field is set in the request json object. (see: http://www.jsonrpc.org/specification#notification)
  116. Execute an simple notification:
  117. ```go
  118. func main() {
  119. rpcClient := jsonrpc.NewRPCClient("http://my-rpc-service:8080/rpc")
  120. err := rpcClient.Notification("disconnectClient", 123)
  121. if err != nil {
  122. //error handling goes here
  123. }
  124. }
  125. ```
  126. ### Batch rpcjson calls
  127. A jsonrpc batch call encapsulates multiple json-rpc requests in a single rpc-service call.
  128. It returns an array of results (for all non-notification requests).
  129. (see: http://www.jsonrpc.org/specification#batch)
  130. Execute two jsonrpc calls and a single notification as batch:
  131. ```go
  132. func main() {
  133. rpcClient := jsonrpc.NewRPCClient(httpServer.URL)
  134. req1 := rpcClient.NewRPCRequestObject("addNumbers", 1, 2)
  135. req2 := rpcClient.NewRPCRequestObject("getPersonByName", "alex")
  136. notify1 := rpcClient.NewRPCNotificationObject("disconnect", true)
  137. responses, _ := rpcClient.Batch(req1, req2, notify1)
  138. person := Person{}
  139. response2, _ := responses.GetResponseOf(req2)
  140. response2.GetObject(&person)
  141. }
  142. ```
  143. To update the ID of an existing rpcRequest object:
  144. ```go
  145. func main() {
  146. rpcClient := jsonrpc.NewRPCClient(httpServer.URL)
  147. req1 := rpcClient.NewRPCRequestObject("addNumbers", 1, 2)
  148. req2 := rpcClient.NewRPCRequestObject("getPersonByName", "alex")
  149. notify1 := rpcClient.NewRPCNotifyObject("disconnect", true)
  150. responses, _ := rpcClient.Batch(req1, req2, notify1)
  151. rpcClient.UpdateRequestID(req1) // updates id to the next valid id if autoincrement is enabled
  152. }
  153. ```
  154. ### Working with rpc-json responses
  155. Before working with the response object, make sure to check err != nil first.
  156. This error indicates problems on the network / http level of an error when parsing the json response.
  157. ```go
  158. func main() {
  159. rpcClient := jsonrpc.NewRPCClient("http://my-rpc-service:8080/rpc")
  160. response, err := rpcClient.Call("addNumbers", 1, 2)
  161. if err != nil {
  162. //error handling goes here
  163. }
  164. }
  165. ```
  166. The next thing you have to check is if an rpc-json protocol error occoured. This is done by checking if the Error field in the rpc-response != nil:
  167. (see: http://www.jsonrpc.org/specification#error_object)
  168. ```go
  169. func main() {
  170. rpcClient := jsonrpc.NewRPCClient("http://my-rpc-service:8080/rpc")
  171. response, err := rpcClient.Call("addNumbers", 1, 2)
  172. if err != nil {
  173. //error handling goes here
  174. }
  175. if response.Error != nil {
  176. // check response.Error.Code, response.Error.Message, response.Error.Data here
  177. }
  178. }
  179. ```
  180. After making sure that no errors occoured you can now examine the RPCResponse object.
  181. 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.
  182. (see: http://www.jsonrpc.org/specification#response_object)
  183. The library provides some helper functions to retrieve the result in the data format you are interested in.
  184. Again: check for err != nil here to be sure the expected type was provided in the response and could be parsed.
  185. ```go
  186. func main() {
  187. rpcClient := jsonrpc.NewRPCClient("http://my-rpc-service:8080/rpc")
  188. response, _ := rpcClient.Call("addNumbers", 1, 2)
  189. result, err := response.GetInt()
  190. if err != nil {
  191. // result seems not to be an integer value
  192. }
  193. // helpers provided for all primitive types:
  194. response.GetInt() // int32 or int64 depends on architecture / implementation
  195. response.GetInt64()
  196. response.GetFloat64()
  197. response.GetString()
  198. response.GetBool()
  199. }
  200. ```
  201. Retrieving arrays and objects is also very simple:
  202. ```go
  203. // json annotations are only required to transform the structure back to json
  204. type Person struct {
  205. Id int `json:"id"`
  206. Name string `json:"name"`
  207. Age int `json:"age"`
  208. }
  209. func main() {
  210. rpcClient := jsonrpc.NewRPCClient("http://my-rpc-service:8080/rpc")
  211. response, _ := rpcClient.Call("getPersonById", 123)
  212. person := Person{}
  213. err := response.GetObject(&Person) // expects a rpc-object result value like: {"id": 123, "name": "alex", "age": 33}
  214. fmt.Println(person.Name)
  215. }
  216. ```
  217. Retrieving arrays e.g. of ints:
  218. ```go
  219. func main() {
  220. rpcClient := jsonrpc.NewRPCClient("http://my-rpc-service:8080/rpc")
  221. response, _ := rpcClient.Call("getRandomNumbers", 10)
  222. rndNumbers := []int{}
  223. err := response.getObject(&rndNumbers) // expects a rpc-object result value like: [10, 188, 14, 3]
  224. fmt.Println(rndNumbers[0])
  225. }
  226. ```
  227. ### Basic authentication
  228. If the rpc-service is running behind a basic authentication you can easily set the credentials:
  229. ```go
  230. func main() {
  231. rpcClient := jsonrpc.NewRPCClient("http://my-rpc-service:8080/rpc")
  232. rpcClient.SetBasicAuth("alex", "secret")
  233. response, _ := rpcClient.Call("addNumbers", 1, 2) // send with Authorization-Header
  234. }
  235. ```
  236. ### Set custom headers
  237. Setting some custom headers (e.g. when using another authentication) is simple:
  238. ```go
  239. func main() {
  240. rpcClient := jsonrpc.NewRPCClient("http://my-rpc-service:8080/rpc")
  241. rpcClient.SetCustomHeader("Authorization", "Bearer abcd1234")
  242. response, _ := rpcClient.Call("addNumbers", 1, 2) // send with a custom Auth-Header
  243. }
  244. ```
  245. ### ID auto-increment
  246. Per default the ID of the json-rpc request increments automatically for each request.
  247. You can change this behaviour:
  248. ```go
  249. func main() {
  250. rpcClient := jsonrpc.NewRPCClient("http://my-rpc-service:8080/rpc")
  251. response, _ := rpcClient.Call("addNumbers", 1, 2) // send with ID == 0
  252. response, _ = rpcClient.Call("addNumbers", 1, 2) // send with ID == 1
  253. rpcClient.SetNextID(10)
  254. response, _ = rpcClient.Call("addNumbers", 1, 2) // send with ID == 10
  255. rpcClient.SetAutoIncrementID(false)
  256. response, _ = rpcClient.Call("addNumbers", 1, 2) // send with ID == 11
  257. response, _ = rpcClient.Call("addNumbers", 1, 2) // send with ID == 11
  258. }
  259. ```
  260. ### Set a custom httpClient
  261. If you have some special needs on the http.Client of the standard go library, just provide your own one.
  262. For example to use a proxy when executing json-rpc calls:
  263. ```go
  264. func main() {
  265. rpcClient := jsonrpc.NewRPCClient("http://my-rpc-service:8080/rpc")
  266. proxyURL, _ := url.Parse("http://proxy:8080")
  267. transport := &http.Transport{Proxy: http.ProxyURL(proxyURL)}
  268. httpClient := &http.Client{
  269. Transport: transport,
  270. }
  271. rpcClient.SetHTTPClient(httpClient)
  272. }
  273. ```