@@ -37,9 +37,13 @@ type Client struct {
3737
3838 ctx context.Context //nolint:containedctx // Context is configured separately.
3939
40+ req * http.Request
4041 resp * http.Response
4142 respBody []byte
4243
44+ attempt int
45+ retryDelays []time.Duration
46+
4347 reqHeaders map [string ]string
4448 reqCookies map [string ]string
4549 reqQueryParams url.Values
@@ -114,6 +118,38 @@ func NewClient(baseURL string) *Client {
114118 return c
115119}
116120
121+ // HTTPValue contains information about request and response.
122+ type HTTPValue struct {
123+ Req * http.Request
124+ ReqBody []byte
125+
126+ Resp * http.Response
127+ RespBody []byte
128+
129+ OtherResp * http.Response
130+ OtherRespBody []byte
131+
132+ Attempt int
133+ RetryDelays []time.Duration
134+ }
135+
136+ // Details returns HTTP request and response information of last run.
137+ func (c * Client ) Details () HTTPValue {
138+ return HTTPValue {
139+ Req : c .req ,
140+ ReqBody : c .reqBody ,
141+
142+ Resp : c .resp ,
143+ RespBody : c .respBody ,
144+
145+ OtherResp : c .otherResp ,
146+ OtherRespBody : c .otherRespBody ,
147+
148+ Attempt : c .attempt ,
149+ RetryDelays : c .retryDelays ,
150+ }
151+ }
152+
117153// SetBaseURL changes baseURL configured with constructor.
118154func (c * Client ) SetBaseURL (baseURL string ) {
119155 if ! strings .HasPrefix (baseURL , "http://" ) && ! strings .HasPrefix (baseURL , "https://" ) {
@@ -132,6 +168,8 @@ func (c *Client) Reset() *Client {
132168 c .reqQueryParams = map [string ][]string {}
133169 c .reqFormDataParams = map [string ][]string {}
134170
171+ c .req = nil
172+
135173 c .resp = nil
136174 c .respBody = nil
137175
@@ -147,6 +185,9 @@ func (c *Client) Reset() *Client {
147185 c .otherRespBody = nil
148186 c .otherRespExpected = false
149187
188+ c .attempt = 0
189+ c .retryDelays = nil
190+
150191 return c
151192}
152193
@@ -158,7 +199,7 @@ func (c *Client) Reset() *Client {
158199// This method enables context-driven concurrent access to shared base Client.
159200func (c * Client ) Fork (ctx context.Context ) (context.Context , * Client ) {
160201 // Pointer to current Client is used as context key
161- // to enable multiple different clients in sam context.
202+ // to enable multiple different clients in same context.
162203 if fc , ok := ctx .Value (c ).(* Client ); ok {
163204 return ctx , fc
164205 }
@@ -261,7 +302,9 @@ func (c *Client) WithURLEncodedFormDataParam(name, value string) *Client {
261302 return c
262303}
263304
264- func (c * Client ) do () (err error ) {
305+ func (c * Client ) do () (err error ) { //nolint:funlen
306+ c .attempt ++
307+
265308 if c .reqConcurrency < 1 {
266309 c .reqConcurrency = 1
267310 }
@@ -289,7 +332,7 @@ func (c *Client) do() (err error) {
289332 wg .Done ()
290333 }()
291334
292- resp , er := c .doOnce ()
335+ req , resp , er := c .doOnce ()
293336 if er != nil {
294337 return
295338 }
@@ -305,6 +348,10 @@ func (c *Client) do() (err error) {
305348 }
306349
307350 mu .Lock ()
351+ if c .req == nil {
352+ c .req = req
353+ }
354+
308355 if _ , ok := statusCodeCount [resp .StatusCode ]; ! ok {
309356 resps [resp .StatusCode ] = resp
310357 bodies [resp .StatusCode ] = body
@@ -315,6 +362,7 @@ func (c *Client) do() (err error) {
315362 mu .Unlock ()
316363 }()
317364 }
365+
318366 wg .Wait ()
319367
320368 if err != nil {
@@ -329,6 +377,14 @@ func (c *Client) expectResp(check func() error) (err error) {
329377 return check ()
330378 }
331379
380+ if len (c .reqBody ) == 0 && len (c .reqFormDataParams ) > 0 {
381+ c .reqBody = []byte (c .reqFormDataParams .Encode ())
382+
383+ if c .reqMethod == "" {
384+ c .reqMethod = http .MethodPost
385+ }
386+ }
387+
332388 if c .retryBackOff != nil {
333389 for {
334390 if err = c .do (); err == nil {
@@ -343,6 +399,8 @@ func (c *Client) expectResp(check func() error) (err error) {
343399 return err
344400 }
345401
402+ c .retryDelays = append (c .retryDelays , dur )
403+
346404 time .Sleep (dur )
347405 }
348406 }
@@ -433,15 +491,17 @@ func (c *Client) buildURI() (string, error) {
433491 return uri , nil
434492}
435493
494+ type readSeekNopCloser struct {
495+ io.ReadSeeker
496+ }
497+
498+ func (r * readSeekNopCloser ) Close () error {
499+ return nil
500+ }
501+
436502func (c * Client ) buildBody () io.Reader {
437503 if len (c .reqBody ) > 0 {
438- return bytes .NewBuffer (c .reqBody )
439- } else if len (c .reqFormDataParams ) > 0 {
440- if c .reqMethod == "" {
441- c .reqMethod = http .MethodPost
442- }
443-
444- return strings .NewReader (c .reqFormDataParams .Encode ())
504+ return & readSeekNopCloser {ReadSeeker : bytes .NewReader (c .reqBody )}
445505 }
446506
447507 return nil
@@ -486,17 +546,17 @@ func (c *Client) applyCookies(req *http.Request) {
486546 }
487547}
488548
489- func (c * Client ) doOnce () (* http.Response , error ) {
549+ func (c * Client ) doOnce () (* http.Request , * http. Response , error ) {
490550 uri , err := c .buildURI ()
491551 if err != nil {
492- return nil , err
552+ return nil , nil , err
493553 }
494554
495555 body := c .buildBody ()
496556
497557 req , err := http .NewRequestWithContext (c .ctx , c .reqMethod , uri , body )
498558 if err != nil {
499- return nil , err
559+ return nil , nil , err
500560 }
501561
502562 c .applyHeaders (req )
@@ -513,10 +573,14 @@ func (c *Client) doOnce() (*http.Response, error) {
513573 cl .Transport = tr
514574 cl .Jar = j
515575
516- return cl .Do (req )
576+ resp , err := cl .Do (req )
577+
578+ return req , resp , err
517579 }
518580
519- return tr .RoundTrip (req )
581+ resp , err := tr .RoundTrip (req )
582+
583+ return req , resp , err
520584}
521585
522586// ExpectResponseStatus sets expected response status code.
0 commit comments