forked from toorop/go-bitcoind
-
Notifications
You must be signed in to change notification settings - Fork 0
/
rpcClient.go
135 lines (120 loc) · 3.52 KB
/
rpcClient.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
package bitcoind
import (
"bytes"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"time"
)
// A rpcClient represents a JSON RPC client (over HTTP(s)).
type rpcClient struct {
serverAddr string
user string
passwd string
httpClient *http.Client
timeout int
}
// rpcRequest represent a RCP request
type rpcRequest struct {
Method string `json:"method"`
Params interface{} `json:"params"`
Id int64 `json:"id"`
JsonRpc string `json:"jsonrpc"`
}
// RPCErrorCode represents an error code to be used as a part of an RPCError
// which is in turn used in a JSON-RPC Response object.
//
// A specific type is used to help ensure the wrong errors aren't used.
type RPCErrorCode int
// RPCError represents an error that is used as a part of a JSON-RPC Response
// object.
type RPCError struct {
Code RPCErrorCode `json:"code,omitempty"`
Message string `json:"message,omitempty"`
}
// Guarantee RPCError satisfies the builtin error interface.
var _, _ error = RPCError{}, (*RPCError)(nil)
// Error returns a string describing the RPC error. This satisfies the
// builtin error interface.
func (e RPCError) Error() string {
return fmt.Sprintf("%d: %s", e.Code, e.Message)
}
type rpcResponse struct {
Id int64 `json:"id"`
Result json.RawMessage `json:"result"`
Err *RPCError `json:"error"`
}
func newClient(host string, port int, user, passwd string, useSSL bool, timeout int) (c *rpcClient, err error) {
if len(host) == 0 {
err = errors.New("Bad call missing argument host")
return
}
var serverAddr string
var httpClient *http.Client
if useSSL {
serverAddr = "https://"
t := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
httpClient = &http.Client{Transport: t}
} else {
serverAddr = "http://"
httpClient = &http.Client{}
}
c = &rpcClient{serverAddr: fmt.Sprintf("%s%s:%d", serverAddr, host, port), user: user, passwd: passwd, httpClient: httpClient, timeout: timeout}
return
}
// doTimeoutRequest process a HTTP request with timeout
func (c *rpcClient) doTimeoutRequest(timer *time.Timer, req *http.Request) (*http.Response, error) {
type result struct {
resp *http.Response
err error
}
done := make(chan result, 1)
go func() {
resp, err := c.httpClient.Do(req)
done <- result{resp, err}
}()
// Wait for the read or the timeout
select {
case r := <-done:
return r.resp, r.err
case <-timer.C:
return nil, errors.New("Timeout reading data from server")
}
}
// call prepare & exec the request
func (c *rpcClient) call(method string, params interface{}) (rr rpcResponse, err error) {
connectTimer := time.NewTimer(time.Duration(c.timeout) * time.Second)
rpcR := rpcRequest{method, params, time.Now().UnixNano(), "1.0"}
payloadBuffer := &bytes.Buffer{}
jsonEncoder := json.NewEncoder(payloadBuffer)
err = jsonEncoder.Encode(rpcR)
if err != nil {
return
}
req, err := http.NewRequest("POST", c.serverAddr, payloadBuffer)
if err != nil {
return
}
req.Header.Add("Content-Type", "application/json;charset=utf-8")
req.Header.Add("Accept", "application/json")
// Auth ?
if len(c.user) > 0 || len(c.passwd) > 0 {
req.SetBasicAuth(c.user, c.passwd)
}
resp, err := c.doTimeoutRequest(connectTimer, req)
if err != nil {
return
}
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return
}
err = json.Unmarshal(data, &rr)
return
}