Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Couple of enhancements #374

Merged
merged 3 commits into from
Sep 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
106 changes: 56 additions & 50 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

* v2.3.0 [released](https://github.com/go-resty/resty/releases/tag/v2.3.0) and tagged on May 20, 2020.
* v2.0.0 [released](https://github.com/go-resty/resty/releases/tag/v2.0.0) and tagged on Jul 16, 2019.
* v1.12.0 [released](https://github.com/go-resty/resty/releases/tag/v1.12.0) and tagged on Feb 27, 2019.
* v1.12.0 [released](https://github.com/go-resty/resty/releases/tag/v1.12.0) and tagged on Feb 27, 2019.
* v1.0 released and tagged on Sep 25, 2017. - Resty's first version was released on Sep 15, 2015 then it grew gradually as a very handy and helpful library. Its been a two years since first release. I'm very thankful to Resty users and its [contributors](https://github.com/go-resty/resty/graphs/contributors).

## Features
Expand Down Expand Up @@ -55,11 +55,12 @@
* Option to specify expected `Content-Type` when response `Content-Type` header missing. Refer to [#92](https://github.com/go-resty/resty/issues/92)
* Resty design
* Have client level settings & options and also override at Request level if you want to
* Request and Response middlewares
* Request and Response middleware
* Create Multiple clients if you want to `resty.New()`
* Supports `http.RoundTripper` implementation, see [SetTransport](https://godoc.org/github.com/go-resty/resty#Client.SetTransport)
* goroutine concurrent safe
* Resty Client trace, see [Client.EnableTrace](https://godoc.org/github.com/go-resty/resty#Client.EnableTrace) and [Request.EnableTrace](https://godoc.org/github.com/go-resty/resty#Request.EnableTrace)
* Since v2.4.0, `RequestAttempt` value supported in trace info also Request instance contains `Attempt` attribute
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rewording suggestion:

Since v2.4.0, trace info contains a RequestAttempt value, and the Request object contains an Attempt attribute

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @moorereason, I will update the readme with suggestion on the next round. I have to do a few other things.

* Debug mode - clean and informative logging presentation
* Gzip - Go does it automatically also resty has fallback handling too
* Works fine with `HTTP/2` and `HTTP/1.1`
Expand All @@ -83,7 +84,7 @@

#### Supported Go Versions

Initially Resty started supporting `go modules` since `v1.10.0` release.
Initially Resty started supporting `go modules` since `v1.10.0` release.

Starting Resty v2 and higher versions, it fully embraces [go modules](https://github.com/golang/go/wiki/Modules) package release. It requires a Go version capable of understanding `/vN` suffixed imports:

Expand Down Expand Up @@ -124,65 +125,70 @@ import "github.com/go-resty/resty/v2"
client := resty.New()

resp, err := client.R().
EnableTrace().
Get("https://httpbin.org/get")
EnableTrace().
Get("https://httpbin.org/get")

// Explore response object
fmt.Println("Response Info:")
fmt.Println("Error :", err)
fmt.Println("Status Code:", resp.StatusCode())
fmt.Println("Status :", resp.Status())
fmt.Println("Proto :", resp.Proto())
fmt.Println("Time :", resp.Time())
fmt.Println("Received At:", resp.ReceivedAt())
fmt.Println("Body :\n", resp)
fmt.Println(" Error :", err)
fmt.Println(" Status Code:", resp.StatusCode())
fmt.Println(" Status :", resp.Status())
fmt.Println(" Proto :", resp.Proto())
fmt.Println(" Time :", resp.Time())
fmt.Println(" Received At:", resp.ReceivedAt())
fmt.Println(" Body :\n", resp)
fmt.Println()

// Explore trace info
fmt.Println("Request Trace Info:")
ti := resp.Request.TraceInfo()
fmt.Println("DNSLookup :", ti.DNSLookup)
fmt.Println("ConnTime :", ti.ConnTime)
fmt.Println("TCPConnTime :", ti.TCPConnTime)
fmt.Println("TLSHandshake :", ti.TLSHandshake)
fmt.Println("ServerTime :", ti.ServerTime)
fmt.Println("ResponseTime :", ti.ResponseTime)
fmt.Println("TotalTime :", ti.TotalTime)
fmt.Println("IsConnReused :", ti.IsConnReused)
fmt.Println("IsConnWasIdle:", ti.IsConnWasIdle)
fmt.Println("ConnIdleTime :", ti.ConnIdleTime)
fmt.Println(" DNSLookup :", ti.DNSLookup)
fmt.Println(" ConnTime :", ti.ConnTime)
fmt.Println(" TCPConnTime :", ti.TCPConnTime)
fmt.Println(" TLSHandshake :", ti.TLSHandshake)
fmt.Println(" ServerTime :", ti.ServerTime)
fmt.Println(" ResponseTime :", ti.ResponseTime)
fmt.Println(" TotalTime :", ti.TotalTime)
fmt.Println(" IsConnReused :", ti.IsConnReused)
fmt.Println(" IsConnWasIdle :", ti.IsConnWasIdle)
fmt.Println(" ConnIdleTime :", ti.ConnIdleTime)
fmt.Println(" RequestAttempt:", ti.RequestAttempt)
fmt.Println(" RemoteAddr :", ti.RemoteAddr.String())

/* Output
Response Info:
Error : <nil>
Status Code: 200
Status : 200 OK
Proto : HTTP/2.0
Time : 475.611189ms
Received At: 2020-05-19 00:11:06.828188 -0700 PDT m=+0.476510773
Body :
{
"args": {},
"headers": {
"Accept-Encoding": "gzip",
"Host": "httpbin.org",
"User-Agent": "go-resty/2.3.0 (https://github.com/go-resty/resty)"
},
"origin": "0.0.0.0",
"url": "https://httpbin.org/get"
}
Error : <nil>
Status Code: 200
Status : 200 OK
Proto : HTTP/2.0
Time : 457.034718ms
Received At: 2020-09-14 15:35:29.784681 -0700 PDT m=+0.458137045
Body :
{
"args": {},
"headers": {
"Accept-Encoding": "gzip",
"Host": "httpbin.org",
"User-Agent": "go-resty/2.3.0-dev (https://github.com/go-resty/resty)",
"X-Amzn-Trace-Id": "Root=1-5f5ff031-000ff6292204aa6898e4de49"
},
"origin": "0.0.0.0",
"url": "https://httpbin.org/get"
}

Request Trace Info:
DNSLookup : 4.870246ms
ConnTime : 393.95373ms
TCPConnTime : 78.360432ms
TLSHandshake : 310.032859ms
ServerTime : 81.648284ms
ResponseTime : 124.266µs
TotalTime : 475.611189ms
IsConnReused : false
IsConnWasIdle: false
ConnIdleTime : 0s
DNSLookup : 4.074657ms
ConnTime : 381.709936ms
TCPConnTime : 77.428048ms
TLSHandshake : 299.623597ms
ServerTime : 75.414703ms
ResponseTime : 79.337µs
TotalTime : 457.034718ms
IsConnReused : false
IsConnWasIdle : false
ConnIdleTime : 0s
RequestAttempt: 1
RemoteAddr : 3.221.81.55:443
*/
```

Expand Down Expand Up @@ -831,7 +837,7 @@ More detailed example of mocking resty http requests using ginko could be found
Resty releases versions according to [Semantic Versioning](http://semver.org)

* Resty v2 does not use `gopkg.in` service for library versioning.
* Resty fully adapted to `go mod` capabilities since `v1.10.0` release.
* Resty fully adapted to `go mod` capabilities since `v1.10.0` release.
* Resty v1 series was using `gopkg.in` to provide versioning. `gopkg.in/resty.vX` points to appropriate tagged versions; `X` denotes version series number and it's a stable release for production use. For e.g. `gopkg.in/resty.v0`.
* Development takes place at the master branch. Although the code in master should always compile and test successfully, it might break API's. I aim to maintain backwards compatibility, but sometimes API's and behavior might be changed to fix a bug.

Expand Down
2 changes: 1 addition & 1 deletion middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ func responseLogger(c *Client, res *Response) error {
"HEADERS :\n" +
composeHeaders(c, res.Request, rl.Header) + "\n"
if res.Request.isSaveResponse {
debugLog += fmt.Sprintf("BODY :\n***** RESPONSE WRITTEN INTO FILE *****\n")
debugLog += "BODY :\n***** RESPONSE WRITTEN INTO FILE *****\n"
} else {
debugLog += fmt.Sprintf("BODY :\n%v\n", rl.Body)
}
Expand Down
34 changes: 24 additions & 10 deletions request.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ type Request struct {
UserInfo *User
Cookies []*http.Cookie

// Attempt is to represent the request attempt made during a Resty
// request execution flow, including retry count.
//
// Since v2.4.0
Attempt int

isMultiPart bool
isFormData bool
setContentLength bool
Expand Down Expand Up @@ -576,14 +582,17 @@ func (r *Request) TraceInfo() TraceInfo {
}

ti := TraceInfo{
DNSLookup: ct.dnsDone.Sub(ct.dnsStart),
TLSHandshake: ct.tlsHandshakeDone.Sub(ct.tlsHandshakeStart),
ServerTime: ct.gotFirstResponseByte.Sub(ct.gotConn),
IsConnReused: ct.gotConnInfo.Reused,
IsConnWasIdle: ct.gotConnInfo.WasIdle,
ConnIdleTime: ct.gotConnInfo.IdleTime,
DNSLookup: ct.dnsDone.Sub(ct.dnsStart),
TLSHandshake: ct.tlsHandshakeDone.Sub(ct.tlsHandshakeStart),
ServerTime: ct.gotFirstResponseByte.Sub(ct.gotConn),
IsConnReused: ct.gotConnInfo.Reused,
IsConnWasIdle: ct.gotConnInfo.WasIdle,
ConnIdleTime: ct.gotConnInfo.IdleTime,
RequestAttempt: r.Attempt,
}

// Calculate the total time accordingly,
// when connection is reused
if ct.gotConnInfo.Reused {
ti.TotalTime = ct.endTime.Sub(ct.getConn)
} else {
Expand All @@ -605,6 +614,11 @@ func (r *Request) TraceInfo() TraceInfo {
ti.ResponseTime = ct.endTime.Sub(ct.gotFirstResponseByte)
}

// Capture remote address info when connection is non-nil
if ct.gotConnInfo.Conn != nil {
ti.RemoteAddr = ct.gotConnInfo.Conn.RemoteAddr()
}

return ti
}

Expand Down Expand Up @@ -680,20 +694,20 @@ func (r *Request) Execute(method, url string) (*Response, error) {
r.URL = r.selectAddr(addrs, url, 0)

if r.client.RetryCount == 0 {
r.Attempt = 1
resp, err = r.client.execute(r)
return resp, unwrapNoRetryErr(err)
}

attempt := 0
err = Backoff(
func() (*Response, error) {
attempt++
r.Attempt++

r.URL = r.selectAddr(addrs, url, attempt)
r.URL = r.selectAddr(addrs, url, r.Attempt)

resp, err = r.client.execute(r)
if err != nil {
r.client.log.Errorf("%v, Attempt %v", err, attempt)
r.client.log.Errorf("%v, Attempt %v", err, r.Attempt)
}

return resp, err
Expand Down
4 changes: 4 additions & 0 deletions request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1644,6 +1644,8 @@ func TestTraceInfo(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()

serverAddr := ts.URL[strings.LastIndex(ts.URL, "/")+1:]

client := dc()
client.SetHostURL(ts.URL).EnableTrace()
for _, u := range []string{"/", "/json", "/long-text", "/long-json"} {
Expand All @@ -1660,6 +1662,7 @@ func TestTraceInfo(t *testing.T) {
assertEqual(t, true, tr.TotalTime >= 0)
assertEqual(t, true, tr.TotalTime < time.Hour)
assertEqual(t, true, tr.TotalTime == resp.Time())
assertEqual(t, tr.RemoteAddr.String(), serverAddr)
}

client.DisableTrace()
Expand All @@ -1677,6 +1680,7 @@ func TestTraceInfo(t *testing.T) {
assertEqual(t, true, tr.ResponseTime >= 0)
assertEqual(t, true, tr.TotalTime >= 0)
assertEqual(t, true, tr.TotalTime == resp.Time())
assertEqual(t, tr.RemoteAddr.String(), serverAddr)
}

// for sake of hook funcs
Expand Down
2 changes: 1 addition & 1 deletion resty.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
)

// Version # of resty
const Version = "2.3.0"
const Version = "2.3.0-dev"

// New method creates a new Resty client.
func New() *Client {
Expand Down
5 changes: 4 additions & 1 deletion retry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,7 @@ func TestClientRetryWaitCallbackSwitchToDefault(t *testing.T) {
}

c := dc().
EnableTrace().
SetRetryCount(retryCount).
SetRetryWaitTime(retryWaitTime).
SetRetryMaxWaitTime(retryMaxWaitTime).
Expand All @@ -456,10 +457,12 @@ func TestClientRetryWaitCallbackSwitchToDefault(t *testing.T) {
return true
},
)
_, _ = c.R().Get(ts.URL + "/set-retrywaittime-test")
resp, _ := c.R().Get(ts.URL + "/set-retrywaittime-test")

// 6 attempts were made
assertEqual(t, attempt, 6)
assertEqual(t, resp.Request.Attempt, 6)
assertEqual(t, resp.Request.TraceInfo().RequestAttempt, 6)

// Initial attempt has 0 time slept since last request
assertEqual(t, retryIntervals[0], uint64(0))
Expand Down
10 changes: 9 additions & 1 deletion trace.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package resty
import (
"context"
"crypto/tls"
"net"
"net/http/httptrace"
"time"
)
Expand Down Expand Up @@ -54,10 +55,17 @@ type TraceInfo struct {
// ConnIdleTime is a duration how long the connection was previously
// idle, if IsConnWasIdle is true.
ConnIdleTime time.Duration

// RequestAttempt is to represent the request attempt made during a Resty
// request execution flow, including retry count.
RequestAttempt int

// RemoteAddr returns the remote network address.
RemoteAddr net.Addr
}

//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// CientTrace struct and its methods
// ClientTrace struct and its methods
//_______________________________________________________________________

// tracer struct maps the `httptrace.ClientTrace` hooks into Fields
Expand Down