-
Notifications
You must be signed in to change notification settings - Fork 0
/
client.go
531 lines (441 loc) · 13.7 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
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
package greq
import (
"context"
"io"
"net/http"
gourl "net/url"
"strings"
"github.com/gookit/goutil/netutil/httpctype"
"github.com/gookit/goutil/netutil/httpheader"
"github.com/gookit/goutil/netutil/httpreq"
"github.com/gookit/goutil/strutil"
)
// Client is an HTTP Request builder and sender.
type Client struct {
doer httpreq.Doer
// core handler.
handler HandleFunc
middles []Middleware
// Vars template vars for URL, Header, Query, Body
//
// eg: http://example.com/{name}
Vars map[string]string
// BeforeSend callback
BeforeSend func(r *http.Request)
// AfterSend callback
AfterSend AfterSendFn
//
// default options for all requests
//
// http method eg: GET,POST
method string
baseURL string
header http.Header
// Query params data. allow: map[string]string, url.Values
query gourl.Values
// content type
ContentType string
// response data decoder
respDecoder RespDecoder
}
// New create
func New(baseURL ...string) *Client {
h := &Client{
doer: &http.Client{},
method: http.MethodGet,
header: make(http.Header),
query: make(gourl.Values),
// default use JSON decoder
respDecoder: jsonDecoder{},
}
if len(baseURL) > 0 {
h.baseURL = baseURL[0]
}
return h
}
// Sub create an instance from current.
func (h *Client) Sub() *Client {
// copy HeaderM pairs into new Header map
headerCopy := make(http.Header)
for k, v := range h.header {
headerCopy[k] = v
}
return &Client{
doer: h.doer,
method: h.method,
baseURL: h.baseURL,
header: headerCopy,
// query: append([]any{}, s.query...),
respDecoder: h.respDecoder,
}
}
// ------------ Config ------------
// Doer custom set http request doer.
// If a nil cli is given, the DefaultDoer will be used.
func (h *Client) Doer(doer httpreq.Doer) *Client {
if doer != nil {
h.doer = doer
} else {
h.doer = DefaultDoer
}
return h
}
// Client custom set http request doer
func (h *Client) Client(doer httpreq.Doer) *Client {
return h.Doer(doer)
}
// HttpClient custom set http cli as request doer
func (h *Client) HttpClient(hClient *http.Client) *Client {
return h.Doer(hClient)
}
// Config custom config http request doer
func (h *Client) Config(fn func(doer httpreq.Doer)) *Client {
fn(h.doer)
return h
}
// ConfigHClient custom config http cli.
//
// Usage:
//
// h.ConfigHClient(func(hClient *http.Client) {
// hClient.Timeout = 30 * time.Second
// })
func (h *Client) ConfigHClient(fn func(hClient *http.Client)) *Client {
if hc, ok := h.doer.(*http.Client); ok {
fn(hc)
} else {
panic("the doer is not an *http.Client")
}
return h
}
// Use one or multi middlewares
func (h *Client) Use(middles ...Middleware) *Client {
return h.Middlewares(middles...)
}
// Uses one or multi middlewares
func (h *Client) Uses(middles ...Middleware) *Client {
return h.Middlewares(middles...)
}
// Middleware add one or multi middlewares
func (h *Client) Middleware(middles ...Middleware) *Client {
return h.Middlewares(middles...)
}
// Middlewares add one or multi middlewares
func (h *Client) Middlewares(middles ...Middleware) *Client {
h.middles = append(h.middles, middles...)
return h
}
// WithRespDecoder for cli
func (h *Client) WithRespDecoder(respDecoder RespDecoder) *Client {
h.respDecoder = respDecoder
return h
}
// OnBeforeSend for cli
func (h *Client) OnBeforeSend(fn func(r *http.Request)) *Client {
h.BeforeSend = fn
return h
}
// ----------- Header ------------
// DefaultHeader sets the http.Header value, it will be used for all requests.
func (h *Client) DefaultHeader(key, value string) *Client {
h.header.Set(key, value)
return h
}
// DefaultHeaders sets all the http.Header values, it will be used for all requests.
func (h *Client) DefaultHeaders(headers http.Header) *Client {
h.header = headers
return h
}
// DefaultContentType set default ContentType header, it will be used for all requests.
//
// Usage:
//
// // json type
// h.DefaultContentType(httpctype.JSON)
// // form type
// h.DefaultContentType(httpctype.Form)
func (h *Client) DefaultContentType(value string) *Client {
h.ContentType = value
return h
}
// DefaultUserAgent with User-Agent header setting for all requests.
func (h *Client) DefaultUserAgent(value string) *Client {
return h.DefaultHeader(httpheader.UserAgent, value)
}
// DefaultUserAuth with user auth header value for all requests.
func (h *Client) DefaultUserAuth(value string) *Client {
return h.DefaultHeader(httpheader.UserAuth, value)
}
// DefaultBasicAuth sets the Authorization header to use HTTP Basic Authentication
// with the provided username and password. With HTTP Basic Authentication
// the provided username and password are not encrypted.
func (h *Client) DefaultBasicAuth(username, password string) *Client {
return h.DefaultHeader(httpheader.UserAuth, httpreq.BuildBasicAuth(username, password))
}
// ------------ Method ------------
// BaseURL set default base URL for all request
func (h *Client) BaseURL(baseURL string) *Client {
h.baseURL = baseURL
return h
}
// DefaultMethod set default method name. it will be used when the Get()/Post() method is empty.
func (h *Client) DefaultMethod(method string) *Client {
h.method = method
return h
}
// Builder create a new builder with current client.
func (h *Client) Builder(optFns ...OptionFn) *Builder {
return BuilderWithClient(h, optFns...)
}
//
// ------------ REST requests ------------
//
// Head sets the method to HEAD and request the pathURL, then send request and return response.
func (h *Client) Head(pathURL string) *Builder {
return newBuilder(h, http.MethodHead, pathURL)
}
// HeadDo sets the method to HEAD and request the pathURL,
// then send request and return response.
func (h *Client) HeadDo(pathURL string, optFns ...OptionFn) (*Response, error) {
return h.SendWithOpt(pathURL, NewOpt2(optFns, http.MethodHead))
}
// Get sets the method to GET and sets the given pathURL
func (h *Client) Get(pathURL string) *Builder {
return newBuilder(h, http.MethodGet, pathURL)
}
// GetDo sets the method to GET and sets the given pathURL,
// then send request and return response.
func (h *Client) GetDo(pathURL string, optFns ...OptionFn) (*Response, error) {
return h.SendWithOpt(pathURL, NewOpt2(optFns, http.MethodGet))
}
// Post sets the method to POST and sets the given pathURL
func (h *Client) Post(pathURL string) *Builder {
return newBuilder(h, http.MethodPost, pathURL)
}
// PostDo sets the method to POST and sets the given pathURL,
// then send request and return http response.
func (h *Client) PostDo(pathURL string, optFns ...OptionFn) (*Response, error) {
return h.SendWithOpt(pathURL, NewOpt2(optFns, http.MethodPost))
}
// Put sets the method to PUT and sets the given pathURL
func (h *Client) Put(pathURL string) *Builder {
return newBuilder(h, http.MethodPut, pathURL)
}
// PutDo sets the method to PUT and sets the given pathURL,
// then send request and return http response.
func (h *Client) PutDo(pathURL string, optFns ...OptionFn) (*Response, error) {
return h.SendWithOpt(pathURL, NewOpt2(optFns, http.MethodPut))
}
// Patch sets the method to PATCH and sets the given pathURL
func (h *Client) Patch(pathURL string) *Builder {
return newBuilder(h, http.MethodPatch, pathURL)
}
// PatchDo sets the method to PATCH and sets the given pathURL,
// then send request and return http response.
func (h *Client) PatchDo(pathURL string, optFns ...OptionFn) (*Response, error) {
return h.SendWithOpt(pathURL, NewOpt2(optFns, http.MethodPatch))
}
// Delete sets the method to DELETE and sets the given pathURL
func (h *Client) Delete(pathURL string) *Builder {
return newBuilder(h, http.MethodDelete, pathURL)
}
// DeleteDo sets the method to DELETE and sets the given pathURL,
// then send request and return http response.
func (h *Client) DeleteDo(pathURL string, optFns ...OptionFn) (*Response, error) {
return h.SendWithOpt(pathURL, NewOpt2(optFns, http.MethodDelete))
}
// ----------- URL, Query params ------------
// WithContentType with custom Content-Type header
func (h *Client) WithContentType(value string) *Builder {
return BuilderWithClient(h).WithContentType(value)
}
// JSONType with json Content-Type header
func (h *Client) JSONType() *Builder {
return BuilderWithClient(h).JSONType()
}
// FormType with from Content-Type header
func (h *Client) FormType() *Builder {
return BuilderWithClient(h).FormType()
}
// UserAgent with User-Agent header setting for all requests.
func (h *Client) UserAgent(value string) *Builder {
return BuilderWithClient(h).UserAgent(value)
}
// UserAuth with user auth header value for all requests.
func (h *Client) UserAuth(value string) *Builder {
return BuilderWithClient(h).UserAuth(value)
}
// BasicAuth sets the Authorization header to use HTTP Basic Authentication
// with the provided username and password.
//
// With HTTP Basic Authentication the provided username and password are not encrypted.
func (h *Client) BasicAuth(username, password string) *Builder {
return BuilderWithClient(h).BasicAuth(username, password)
}
//
//
// ----------- URL, Query params ------------
//
//
// QueryParams appends url.Values/map[string]string to the Query string.
// The value will be encoded as url Query parameters on send requests (see Send()).
func (h *Client) QueryParams(ps any) *Builder {
return BuilderWithClient(h).QueryParams(ps)
}
// ----------- Request Body ------------
// Body with custom any type body
func (h *Client) Body(body any) *Builder {
return BuilderWithClient(h).AnyBody(body)
}
// AnyBody with custom any type body
func (h *Client) AnyBody(body any) *Builder {
return BuilderWithClient(h).AnyBody(body)
}
// BodyReader with custom io reader body
func (h *Client) BodyReader(r io.Reader) *Builder {
return BuilderWithClient(h).BodyReader(r)
}
// BodyProvider with custom body provider
func (h *Client) BodyProvider(bp BodyProvider) *Builder {
return BuilderWithClient(h).BodyProvider(bp)
}
// ----------- Do send request ------------
// Do send request and return response
func (h *Client) Do(method, url string, optFns ...OptionFn) (*Response, error) {
return h.SendWithOption(method, url, optFns...)
}
// Send request and return response, alias of Do()
func (h *Client) Send(method, url string, optFns ...OptionFn) (*Response, error) {
return h.SendWithOption(method, url, optFns...)
}
// MustSend send request and return response, will panic on error
func (h *Client) MustSend(method, url string, optFns ...OptionFn) *Response {
resp, err := h.SendWithOption(method, url, optFns...)
if err != nil {
panic(err)
}
return resp
}
// SendRaw http request text.
//
// Format:
//
// POST https://example.com/path?name=inhere
// Content-Type: application/json
// Accept: */*
//
// <content>
func (h *Client) SendRaw(raw string, varMp map[string]string) (*Response, error) {
method := "GET"
reqUrl := "TODO"
var body io.Reader
req, err := http.NewRequest(method, reqUrl, body)
if err != nil {
return nil, err
}
return h.SendRequest(req)
}
// DoWithOption request with options, then return response
func (h *Client) DoWithOption(method, url string, optFns ...OptionFn) (*Response, error) {
return h.SendWithOption(method, url, optFns...)
}
// SendWithOption request with options, then return response
func (h *Client) SendWithOption(method, url string, optFns ...OptionFn) (*Response, error) {
return h.SendWithOpt(url, NewOpt2(optFns, method))
}
// SendWithOpt send request with option, then return response
func (h *Client) SendWithOpt(pathURL string, opt *Options) (*Response, error) {
// build request
req, err := h.NewRequestWithOptions(pathURL, opt)
if err != nil {
return nil, err
}
// do send
return h.SendRequest(req)
}
// SendRequest send request
func (h *Client) SendRequest(req *http.Request) (*Response, error) {
// wrap middlewares
h.wrapMiddlewares()
// call before send.
if h.BeforeSend != nil {
h.BeforeSend(req)
}
// do send by core handler
resp, err := h.handler(req)
// call after send.
if h.AfterSend != nil {
h.AfterSend(resp, err)
}
return resp, err
}
// ----------- Build request ------------
// NewRequest build new request
func (h *Client) NewRequest(method, url string, optFns ...OptionFn) (*http.Request, error) {
return h.NewRequestWithOptions(url, NewOpt2(optFns, method))
}
// NewRequestWithOptions build new request with Options
func (h *Client) NewRequestWithOptions(url string, opt *Options) (*http.Request, error) {
fullURL := url
if len(h.baseURL) > 0 {
if !strings.HasPrefix(url, "http") {
fullURL = h.baseURL + url
} else if len(url) == 0 {
fullURL = h.baseURL
}
}
opt = orCreate(opt)
ctx := opt.Context
if ctx == nil {
ctx = context.Background()
}
// append Query params
qm := MergeURLValues(h.query, opt.Query)
if len(qm) > 0 {
fullURL = httpreq.AppendQueryToURLString(fullURL, qm)
}
// make body
var err error
var body io.Reader
if opt.Provider != nil {
body, err = opt.Provider.Body()
if err != nil {
return nil, err
}
if bpTyp := opt.Provider.ContentType(); bpTyp != "" {
opt.ContentType = bpTyp
}
}
cType := strutil.OrElse(opt.ContentType, h.ContentType)
method := strings.ToUpper(strutil.OrElse(opt.Method, h.method))
if opt.Data != nil {
if httpreq.IsNoBodyMethod(method) {
body = nil
fullURL = httpreq.AppendQueryToURLString(fullURL, httpreq.MakeQuery(opt.Data))
} else if body == nil {
cType := strutil.OrElse(opt.HeaderM[httpctype.Key], cType)
body = httpreq.MakeBody(opt.Data, cType)
}
}
req, err := http.NewRequestWithContext(ctx, method, fullURL, body)
if err != nil {
return nil, err
}
// copy and set headers
SetHeaders(req, h.header, opt.Header)
if len(opt.HeaderM) > 0 {
SetHeaderMap(req, opt.HeaderM)
}
if len(cType) > 0 {
req.Header.Set(httpheader.ContentType, cType)
}
return req, err
}
// String request to string.
func (h *Client) String() string {
r, err := h.NewRequest("", "")
if err != nil {
return ""
}
return httpreq.RequestToString(r)
}