...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package api
16
17 import (
18 "bytes"
19 "context"
20 "errors"
21 "net"
22 "net/http"
23 "net/url"
24 "path"
25 "strings"
26 "time"
27 )
28
29
30 var DefaultRoundTripper http.RoundTripper = &http.Transport{
31 Proxy: http.ProxyFromEnvironment,
32 DialContext: (&net.Dialer{
33 Timeout: 30 * time.Second,
34 KeepAlive: 30 * time.Second,
35 }).DialContext,
36 TLSHandshakeTimeout: 10 * time.Second,
37 }
38
39
40 type Config struct {
41
42 Address string
43
44
45
46 Client *http.Client
47
48
49
50 RoundTripper http.RoundTripper
51 }
52
53 func (cfg *Config) roundTripper() http.RoundTripper {
54 if cfg.RoundTripper == nil {
55 return DefaultRoundTripper
56 }
57 return cfg.RoundTripper
58 }
59
60 func (cfg *Config) client() http.Client {
61 if cfg.Client == nil {
62 return http.Client{
63 Transport: cfg.roundTripper(),
64 }
65 }
66 return *cfg.Client
67 }
68
69 func (cfg *Config) validate() error {
70 if cfg.Client != nil && cfg.RoundTripper != nil {
71 return errors.New("api.Config.RoundTripper and api.Config.Client are mutually exclusive")
72 }
73 return nil
74 }
75
76
77 type Client interface {
78 URL(ep string, args map[string]string) *url.URL
79 Do(context.Context, *http.Request) (*http.Response, []byte, error)
80 }
81
82
83
84
85 func NewClient(cfg Config) (Client, error) {
86 u, err := url.Parse(cfg.Address)
87 if err != nil {
88 return nil, err
89 }
90 u.Path = strings.TrimRight(u.Path, "/")
91
92 if err := cfg.validate(); err != nil {
93 return nil, err
94 }
95
96 return &httpClient{
97 endpoint: u,
98 client: cfg.client(),
99 }, nil
100 }
101
102 type httpClient struct {
103 endpoint *url.URL
104 client http.Client
105 }
106
107 func (c *httpClient) URL(ep string, args map[string]string) *url.URL {
108 p := path.Join(c.endpoint.Path, ep)
109
110 for arg, val := range args {
111 arg = ":" + arg
112 p = strings.ReplaceAll(p, arg, val)
113 }
114
115 u := *c.endpoint
116 u.Path = p
117
118 return &u
119 }
120
121 func (c *httpClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) {
122 if ctx != nil {
123 req = req.WithContext(ctx)
124 }
125 resp, err := c.client.Do(req)
126 defer func() {
127 if resp != nil {
128 resp.Body.Close()
129 }
130 }()
131
132 if err != nil {
133 return nil, nil, err
134 }
135
136 var body []byte
137 done := make(chan struct{})
138 go func() {
139 var buf bytes.Buffer
140 _, err = buf.ReadFrom(resp.Body)
141 body = buf.Bytes()
142 close(done)
143 }()
144
145 select {
146 case <-ctx.Done():
147 <-done
148 err = resp.Body.Close()
149 if err == nil {
150 err = ctx.Err()
151 }
152 case <-done:
153 }
154
155 return resp, body, err
156 }
157
View as plain text