-
Notifications
You must be signed in to change notification settings - Fork 0
/
client.go
220 lines (181 loc) · 4.65 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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
package apik
import (
"bytes"
"encoding/json"
"io"
"net/http"
"net/http/cookiejar"
"net/url"
"time"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"golang.org/x/net/publicsuffix"
"github.com/niklak/apik/reqopt"
"github.com/niklak/apik/request"
)
var defaultTimeout = time.Minute
// Request is an alias for request.Request
type Request = request.Request
// NewRequest is an alias for request.NewRequest
var NewRequest = request.NewRequest
// Response represents a wrapper around http.Response with the result of the request
type Response struct {
Raw *http.Response
Result any
Request *Request
}
type Client struct {
c *http.Client
timeout time.Duration
trace bool
logger zerolog.Logger
cookies []*http.Cookie
header http.Header
baseURL *url.URL
jar http.CookieJar
}
// Do sends an http.Request built from Request and returns an http.Response
func (c *Client) Do(req *Request) (resp *http.Response, err error) {
if c.baseURL != nil {
req.URL = c.baseURL.ResolveReference(req.URL)
}
if c.trace {
reqopt.Trace()(req)
}
for key, values := range c.header {
if _, ok := req.Header[key]; !ok {
req.Header[key] = values
}
}
var rawReq *http.Request
if rawReq, err = req.IntoHttpRequest(); err != nil {
return
}
return c.c.Do(rawReq)
}
// Fetch sends an http.Request built from Request and returns a Response,
// containing the http.Response and the result of the request.
// The result can be a *string, a *[]byte or an io.Writer.
// If the result is nil, then result will be set as a *bytes.Buffer.
func (c *Client) Fetch(req *request.Request, result any) (resp *Response, err error) {
var rawResp *http.Response
if rawResp, err = c.Do(req); err != nil {
return
}
defer rawResp.Body.Close()
resp = &Response{Raw: rawResp}
if result == nil {
result = new(bytes.Buffer)
}
switch v := result.(type) {
case io.Writer:
_, err = io.Copy(v, rawResp.Body)
case *[]byte:
*v, err = io.ReadAll(rawResp.Body)
case *string:
var b []byte
b, err = io.ReadAll(rawResp.Body)
*v = string(b)
}
resp.Result = result
return
}
// JSON sends an http.Request built from Request and returns a Response,
// containing the http.Response and the result of the request.
// The result must be a pointer to entity that can be decoded from json body.
func (c *Client) JSON(req *request.Request, result any) (resp *Response, err error) {
var rawResp *http.Response
if rawResp, err = c.Do(req); err != nil {
return
}
defer rawResp.Body.Close()
resp = &Response{Raw: rawResp}
if result == nil {
return
}
err = json.NewDecoder(rawResp.Body).Decode(result)
if err != nil {
return
}
resp.Result = result
return
}
// New creates a new Client with the given options
func New(opts ...ClientOption) *Client {
c := &Client{
header: make(http.Header),
}
for _, opt := range opts {
opt(c)
}
if c.c == nil {
c.c = &http.Client{}
}
if c.timeout == 0 {
c.timeout = defaultTimeout
c.c.Timeout = c.timeout
}
if c.jar != nil {
c.c.Jar = c.jar
} else if c.c.Jar == nil {
cookieJar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
c.c.Jar = cookieJar
}
if c.cookies != nil {
c.c.Jar.SetCookies(c.baseURL, c.cookies)
}
c.logger = log.With().Str("module", "apik").Str("component", "Client").Logger()
return c
}
// ClientOption is a function that modifies a Client
type ClientOption func(*Client)
// WithHttpClient sets the http.Client to use
func WithHttpClient(hc *http.Client) ClientOption {
return func(c *Client) {
c.c = hc
}
}
// WithTimeout sets the timeout for the http.Client
func WithTimeout(t time.Duration) ClientOption {
return func(c *Client) {
c.timeout = t
}
}
// WithTrace enables tracing for the http.Request
func WithTrace() ClientOption {
return func(c *Client) {
c.trace = true
}
}
// WithCookies sets the cookies for the http.Client
func WithCookies(cookies []*http.Cookie) ClientOption {
return func(c *Client) {
c.cookies = cookies
}
}
// WithCookieJar sets the cookie jar for the http.Client
// Also CookieJar can be set with `WithHttpClient` option.
func WithCookieJar(jar http.CookieJar) ClientOption {
return func(c *Client) {
c.jar = jar
}
}
// WithHeaders sets an http.Header for the http.Client
func WithHeaders(header http.Header) ClientOption {
return func(c *Client) {
c.header = header
}
}
// WithHeader adds a key-value pair in the http.Header for the http.Client
func WithHeader(key, value string) ClientOption {
return func(c *Client) {
c.header.Add(key, value)
}
}
// WithBaseUrl sets the base url for the http.Client
func WithBaseUrl(baseURL string) ClientOption {
return func(c *Client) {
u, _ := url.Parse(baseURL)
c.baseURL = u
}
}