@@ -12,22 +12,26 @@ import (
12
12
"math/rand"
13
13
"net/http"
14
14
"net/url"
15
+ "strings"
15
16
"sync"
16
17
"time"
17
18
)
18
19
19
20
const (
20
- methodDo = "Do"
21
- methodGet = "Get"
22
- methodHead = "Head"
23
- methodPost = "Post"
24
- methodPostForm = "PostForm"
21
+ methodDo = "Do"
22
+ methodGet = "Get"
23
+ methodHead = "Head"
24
+ methodPost = "Post"
25
+ methodPostForm = "PostForm"
26
+ headerKeyContentType = "Content-Type"
27
+ contentTypeFormURLEncoded = "application/x-www-form-urlencoded"
25
28
)
26
29
27
30
//ErrUnexpectedMethod occurs when an http.Client method is unable to be mapped from a calling method in the pester client
28
31
var ErrUnexpectedMethod = errors .New ("unexpected client method, must be one of Do, Get, Head, Post, or PostFrom" )
29
32
30
33
// ErrReadingBody happens when we cannot read the body bytes
34
+ // Deprecated: use ErrReadingRequestBody
31
35
var ErrReadingBody = errors .New ("error reading body" )
32
36
33
37
// ErrReadingRequestBody happens when we cannot read the request body bytes
@@ -91,7 +95,7 @@ type params struct {
91
95
req * http.Request
92
96
url string
93
97
bodyType string
94
- body io.Reader
98
+ body io.ReadCloser
95
99
data url.Values
96
100
}
97
101
@@ -184,6 +188,16 @@ func (c *Client) Wait() {
184
188
c .wg .Wait ()
185
189
}
186
190
191
+ func (c * Client ) copyBody (src io.ReadCloser ) ([]byte , error ) {
192
+ b , err := ioutil .ReadAll (src )
193
+ if err != nil {
194
+ return nil , ErrReadingRequestBody
195
+ }
196
+ src .Close ()
197
+
198
+ return b , nil
199
+ }
200
+
187
201
// pester provides all the logic of retries, concurrency, backoff, and logging
188
202
func (c * Client ) pester (p params ) (* http.Response , error ) {
189
203
resultCh := make (chan result )
@@ -227,95 +241,80 @@ func (c *Client) pester(p params) (*http.Response, error) {
227
241
}
228
242
229
243
// if we have a request body, we need to save it for later
230
- var originalRequestBody []byte
231
- var originalBody []byte
232
- var err error
233
- if p .req != nil && p .req .Body != nil {
234
- originalRequestBody , err = ioutil .ReadAll (p .req .Body )
235
- if err != nil {
236
- return nil , ErrReadingRequestBody
237
- }
238
- p .req .Body .Close ()
244
+ var (
245
+ request * http.Request
246
+ originalBody []byte
247
+ err error
248
+ )
249
+
250
+ if p .req != nil && p .req .Body != nil && p .body == nil {
251
+ originalBody , err = c .copyBody (p .req .Body )
252
+ } else if p .body != nil {
253
+ originalBody , err = c .copyBody (p .body )
239
254
}
240
- if p .body != nil {
241
- originalBody , err = ioutil .ReadAll (p .body )
242
- if err != nil {
243
- return nil , ErrReadingBody
244
- }
255
+
256
+ switch p .method {
257
+ case methodDo :
258
+ request = p .req
259
+ case methodGet , methodHead :
260
+ request , err = http .NewRequest (p .verb , p .url , nil )
261
+ case methodPostForm , methodPost :
262
+ request , err = http .NewRequest (http .MethodPost , p .url , ioutil .NopCloser (bytes .NewBuffer (originalBody )))
263
+ default :
264
+ err = ErrUnexpectedMethod
265
+ }
266
+ if err != nil {
267
+ return nil , err
268
+ }
269
+
270
+ if len (p .bodyType ) > 0 {
271
+ request .Header .Set (headerKeyContentType , p .bodyType )
245
272
}
246
273
247
274
AttemptLimit := c .MaxRetries
248
275
if AttemptLimit <= 0 {
249
276
AttemptLimit = 1
250
277
}
251
278
252
- for req := 0 ; req < concurrency ; req ++ {
279
+ for n := 0 ; n < concurrency ; n ++ {
253
280
c .wg .Add (1 )
254
281
totalSentRequests .Add (1 )
255
- go func (n int , p params ) {
282
+ go func (n int , req * http. Request ) {
256
283
defer c .wg .Done ()
257
284
defer totalSentRequests .Done ()
258
285
259
- var err error
260
286
for i := 1 ; i <= AttemptLimit ; i ++ {
261
287
c .wg .Add (1 )
262
288
defer c .wg .Done ()
289
+
263
290
select {
264
291
case <- finishCh :
265
292
return
266
293
default :
267
294
}
268
295
269
- // rehydrate the body (it is drained each read)
270
- if len (originalRequestBody ) > 0 {
271
- p .req .Body = ioutil .NopCloser (bytes .NewBuffer (originalRequestBody ))
272
- }
273
- if len (originalBody ) > 0 {
274
- p .body = bytes .NewBuffer (originalBody )
275
- }
276
-
277
- var resp * http.Response
278
- // route the calls
279
- switch p .method {
280
- case methodDo :
281
- resp , err = httpClient .Do (p .req )
282
- case methodGet :
283
- resp , err = httpClient .Get (p .url )
284
- case methodHead :
285
- resp , err = httpClient .Head (p .url )
286
- case methodPost :
287
- resp , err = httpClient .Post (p .url , p .bodyType , p .body )
288
- case methodPostForm :
289
- resp , err = httpClient .PostForm (p .url , p .data )
290
- default :
291
- err = ErrUnexpectedMethod
292
- }
293
-
296
+ resp , err := httpClient .Do (req )
294
297
// Early return if we have a valid result
295
298
// Only retry (ie, continue the loop) on 5xx status codes and 429
296
-
297
299
if err == nil && resp .StatusCode < http .StatusInternalServerError && (resp .StatusCode != http .StatusTooManyRequests || (resp .StatusCode == http .StatusTooManyRequests && ! c .RetryOnHTTP429 )) {
298
300
multiplexCh <- result {resp : resp , err : err , req : n , retry : i }
299
301
return
300
302
}
301
303
302
- loggingContext := context .Background ()
303
- if p .req != nil {
304
- loggingContext = p .req .Context ()
305
- }
306
-
304
+ loggingContext := req .Context ()
307
305
c .log (
308
306
loggingContext ,
309
307
ErrEntry {
310
308
Time : time .Now (),
311
309
Method : p .method ,
312
- Verb : p . verb ,
313
- URL : p . url ,
310
+ Verb : req . Method ,
311
+ URL : req . URL . String () ,
314
312
Request : n ,
315
313
Retry : i + 1 , // would remove, but would break backward compatibility
316
314
Attempt : i ,
317
315
Err : err ,
318
- })
316
+ },
317
+ )
319
318
320
319
// if it is the last iteration, grab the result (which is an error at this point)
321
320
if i == AttemptLimit {
@@ -324,14 +323,11 @@ func (c *Client) pester(p params) (*http.Response, error) {
324
323
}
325
324
326
325
//If the request has been cancelled, skip retries
327
- if p .req != nil {
328
- ctx := p .req .Context ()
329
- select {
330
- case <- ctx .Done ():
331
- multiplexCh <- result {resp : resp , err : ctx .Err ()}
332
- return
333
- default :
334
- }
326
+ select {
327
+ case <- req .Context ().Done ():
328
+ multiplexCh <- result {resp : resp , err : req .Context ().Err ()}
329
+ return
330
+ default :
335
331
}
336
332
337
333
// if we are retrying, we should close this response body to free the fd
@@ -342,7 +338,12 @@ func (c *Client) pester(p params) (*http.Response, error) {
342
338
// prevent a 0 from causing the tick to block, pass additional microsecond
343
339
<- time .After (c .Backoff (i ) + 1 * time .Microsecond )
344
340
}
345
- }(req , p )
341
+ }(n , request )
342
+
343
+ // rehydrate the body (it is drained each read)
344
+ if request .Body != nil {
345
+ request .Body = ioutil .NopCloser (bytes .NewBuffer (originalBody ))
346
+ }
346
347
}
347
348
348
349
// spin off the go routine so it can continually listen in on late results and close the response bodies
@@ -373,8 +374,8 @@ func (c *Client) pester(p params) (*http.Response, error) {
373
374
defer c .Unlock ()
374
375
c .SuccessReqNum = res .req
375
376
c .SuccessRetryNum = res .retry
376
- return res .resp , res .err
377
377
378
+ return res .resp , res .err
378
379
}
379
380
380
381
// LogString provides a string representation of the errors the client has seen
@@ -440,12 +441,12 @@ func (c *Client) Head(url string) (resp *http.Response, err error) {
440
441
441
442
// Post provides the same functionality as http.Client.Post
442
443
func (c * Client ) Post (url string , bodyType string , body io.Reader ) (resp * http.Response , err error ) {
443
- return c .pester (params {method : methodPost , url : url , bodyType : bodyType , body : body , verb : http .MethodPost })
444
+ return c .pester (params {method : methodPost , url : url , bodyType : bodyType , body : ioutil . NopCloser ( body ) , verb : http .MethodPost })
444
445
}
445
446
446
447
// PostForm provides the same functionality as http.Client.PostForm
447
448
func (c * Client ) PostForm (url string , data url.Values ) (resp * http.Response , err error ) {
448
- return c .pester (params {method : methodPostForm , url : url , data : data , verb : http .MethodPost })
449
+ return c .pester (params {method : methodPostForm , url : url , bodyType : contentTypeFormURLEncoded , body : ioutil . NopCloser ( strings . NewReader ( data . Encode ())) , verb : http .MethodPost })
449
450
}
450
451
451
452
// set RetryOnHTTP429 for clients,
0 commit comments