forked from stellar/go
-
Notifications
You must be signed in to change notification settings - Fork 1
/
client.go
141 lines (115 loc) · 3.06 KB
/
client.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
136
137
138
139
140
141
package stellarcore
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"path"
"strings"
"time"
"github.com/stellar/go/support/errors"
)
// Client represents a client that is capable of communicating with a
// stellar-core server using HTTP
type Client struct {
// HTTP is the client to use when communicating with stellar-core. If nil,
// http.DefaultClient will be used.
HTTP HTTP
// URL of Stellar Core server to connect.
URL string
}
// Info calls the `info` command on the connected stellar core and returns the
// provided response
func (c *Client) Info(ctx context.Context) (resp *InfoResponse, err error) {
req, err := c.simpleGet(ctx, "info", nil)
hresp, err := c.http().Do(req)
if err != nil {
err = errors.Wrap(err, "http request errored")
return
}
defer hresp.Body.Close()
if !(hresp.StatusCode >= 200 && hresp.StatusCode < 300) {
err = errors.New("http request failed with non-200 status code")
return
}
err = json.NewDecoder(hresp.Body).Decode(&resp)
if err != nil {
err = errors.Wrap(err, "json decode failed")
return
}
return
}
// SetCursor calls the `setcursor` command on the connected stellar core
func (c *Client) SetCursor(ctx context.Context, id string, cursor int32) error {
req, err := c.simpleGet(ctx, "setcursor", url.Values{
"id": []string{id},
"cursor": []string{fmt.Sprintf("%d", cursor)},
})
if err != nil {
return errors.Wrap(err, "failed to create request")
}
hresp, err := c.http().Do(req)
if err != nil {
return errors.Wrap(err, "http request errored")
}
defer hresp.Body.Close()
raw, err := ioutil.ReadAll(hresp.Body)
if err != nil {
return err
}
body := strings.TrimSpace(string(raw))
if body != SetCursorDone {
return errors.Errorf("failed to set cursor on stellar-core: %s", body)
}
return nil
}
// WaitForNetworkSync continually polls the connected stellar-core until it
// receives a response that indicated the node has synced with the network
func (c *Client) WaitForNetworkSync(ctx context.Context) error {
for {
info, err := c.Info(ctx)
if err != nil {
return errors.Wrap(err, "info request failed")
}
if info.IsSynced() {
return nil
}
// wait for next attempt or error if canceled while waiting
select {
case <-ctx.Done():
return errors.New("canceled")
case <-time.After(5 * time.Second):
continue
}
}
}
func (c *Client) http() HTTP {
if c.HTTP == nil {
return http.DefaultClient
}
return c.HTTP
}
// simpleGet returns a new GET request to the connected stellar-core using the
// provided path and query values to construct the result.
func (c *Client) simpleGet(
ctx context.Context,
newPath string,
query url.Values,
) (*http.Request, error) {
u, err := url.Parse(c.URL)
if err != nil {
return nil, errors.Wrap(err, "unparseable url")
}
u.Path = path.Join(u.Path, newPath)
if query != nil {
u.RawQuery = query.Encode()
}
newURL := u.String()
req, err := http.NewRequest(http.MethodGet, newURL, nil)
if err != nil {
return nil, errors.Wrap(err, "failed to create request")
}
return req.WithContext(ctx), nil
}