From 4c7ee2152f1fe31966d40bf5a968dff22a9cf9fd Mon Sep 17 00:00:00 2001
From: Jeevanandam M
Date: Mon, 4 Feb 2019 21:14:04 -0800
Subject: [PATCH 01/28] #217 updated the pre request hook signature
---
client.go | 6 +++---
client_test.go | 6 +++---
default.go | 2 +-
3 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/client.go b/client.go
index 68d447fa..cd5af53b 100644
--- a/client.go
+++ b/client.go
@@ -101,7 +101,7 @@ type Client struct {
pathParams map[string]string
beforeRequest []func(*Client, *Request) error
udBeforeRequest []func(*Client, *Request) error
- preReqHook func(*Client, *Request) error
+ preReqHook func(*Client, *http.Request) error
afterResponse []func(*Client, *Response) error
requestLog func(*RequestLog) error
responseLog func(*ResponseLog) error
@@ -359,7 +359,7 @@ func (c *Client) OnAfterResponse(m func(*Client, *Response) error) *Client {
// It is called right before the request is fired.
//
// Note: Only one pre-request hook can be registered. Use `resty.OnBeforeRequest` for mutilple.
-func (c *Client) SetPreRequestHook(h func(*Client, *Request) error) *Client {
+func (c *Client) SetPreRequestHook(h func(*Client, *http.Request) error) *Client {
if c.preReqHook != nil {
c.Log.Printf("Overwriting an existing pre-request hook: %s", functionName(h))
}
@@ -806,7 +806,7 @@ func (c *Client) execute(req *Request) (*Response, error) {
// call pre-request if defined
if c.preReqHook != nil {
- if err = c.preReqHook(c, req); err != nil {
+ if err = c.preReqHook(c, req.RawRequest); err != nil {
return nil, err
}
}
diff --git a/client_test.go b/client_test.go
index 4d57c632..dfc526cc 100644
--- a/client_test.go
+++ b/client_test.go
@@ -359,12 +359,12 @@ func TestClientOptions(t *testing.T) {
}
func TestClientPreRequestHook(t *testing.T) {
- SetPreRequestHook(func(c *Client, r *Request) error {
+ SetPreRequestHook(func(c *Client, r *http.Request) error {
c.Log.Println("I'm in Pre-Request Hook")
return nil
})
- SetPreRequestHook(func(c *Client, r *Request) error {
+ SetPreRequestHook(func(c *Client, r *http.Request) error {
c.Log.Println("I'm Overwriting existing Pre-Request Hook")
return nil
})
@@ -376,7 +376,7 @@ func TestClientAllowsGetMethodPayload(t *testing.T) {
c := dc()
c.SetAllowGetMethodPayload(true)
- c.SetPreRequestHook(func(*Client, *Request) error { return nil }) // for coverage
+ c.SetPreRequestHook(func(*Client, *http.Request) error { return nil }) // for coverage
payload := "test-payload"
resp, err := c.R().SetBody(payload).Get(ts.URL + "/get-method-payload-test")
diff --git a/default.go b/default.go
index cc6ae478..52b6523d 100644
--- a/default.go
+++ b/default.go
@@ -110,7 +110,7 @@ func OnAfterResponse(m func(*Client, *Response) error) *Client {
}
// SetPreRequestHook method sets the pre-request hook. See `Client.SetPreRequestHook` for more information.
-func SetPreRequestHook(h func(*Client, *Request) error) *Client {
+func SetPreRequestHook(h func(*Client, *http.Request) error) *Client {
return DefaultClient.SetPreRequestHook(h)
}
From 432457a4e6e3f8de64eb35f95e48ceff0b065412 Mon Sep 17 00:00:00 2001
From: Jeevanandam M
Date: Mon, 4 Feb 2019 21:32:56 -0800
Subject: [PATCH 02/28] version bump to v2.0.0-edge
---
resty.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/resty.go b/resty.go
index 959c6fa3..874bde2d 100644
--- a/resty.go
+++ b/resty.go
@@ -6,4 +6,4 @@
package resty
// Version # of resty
-const Version = "1.11.0"
+const Version = "2.0.0-edge"
From 8e4f35c246e0d9dac6207acbf23a5bd65443557d Mon Sep 17 00:00:00 2001
From: Jeevanandam M
Date: Mon, 4 Feb 2019 21:34:44 -0800
Subject: [PATCH 03/28] #218 added improved debug log compose and print
---
middleware.go | 21 ++++++++++++---------
request.go | 6 ++++++
request16.go | 1 +
request17.go | 1 +
4 files changed, 20 insertions(+), 9 deletions(-)
diff --git a/middleware.go b/middleware.go
index 9b6f102e..49d82eba 100644
--- a/middleware.go
+++ b/middleware.go
@@ -20,6 +20,8 @@ import (
"time"
)
+const debugRequestLogKey = "__restyDebugRequestLog"
+
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Request Middleware(s)
//___________________________________
@@ -230,15 +232,16 @@ func requestLogger(c *Client, r *Request) error {
}
}
- reqLog := "\n---------------------- REQUEST LOG -----------------------\n" +
+ reqLog := "\n==============================================================================\n" +
fmt.Sprintf("%s %s %s\n", r.Method, rr.URL.RequestURI(), rr.Proto) +
fmt.Sprintf("HOST : %s\n", rr.URL.Host) +
fmt.Sprintf("HEADERS:\n") +
composeHeaders(rl.Header) + "\n" +
fmt.Sprintf("BODY :\n%v\n", rl.Body) +
- "----------------------------------------------------------\n"
+ "------------------------------------------------------------------------------\n"
- c.Log.Print(reqLog)
+ r.initValuesMap()
+ r.values[debugRequestLogKey] = reqLog
}
return nil
@@ -257,20 +260,20 @@ func responseLogger(c *Client, res *Response) error {
}
}
- resLog := "\n---------------------- RESPONSE LOG -----------------------\n" +
- fmt.Sprintf("STATUS : %s\n", res.Status()) +
+ debugLog := res.Request.values[debugRequestLogKey].(string)
+ debugLog += fmt.Sprintf("STATUS : %s\n", res.Status()) +
fmt.Sprintf("RECEIVED AT : %v\n", res.ReceivedAt().Format(time.RFC3339Nano)) +
fmt.Sprintf("RESPONSE TIME : %v\n", res.Time()) +
"HEADERS:\n" +
composeHeaders(rl.Header) + "\n"
if res.Request.isSaveResponse {
- resLog += fmt.Sprintf("BODY :\n***** RESPONSE WRITTEN INTO FILE *****\n")
+ debugLog += fmt.Sprintf("BODY :\n***** RESPONSE WRITTEN INTO FILE *****\n")
} else {
- resLog += fmt.Sprintf("BODY :\n%v\n", rl.Body)
+ debugLog += fmt.Sprintf("BODY :\n%v\n", rl.Body)
}
- resLog += "----------------------------------------------------------\n"
+ debugLog += "==============================================================================\n"
- c.Log.Print(resLog)
+ c.Log.Print(debugLog)
}
return nil
diff --git a/request.go b/request.go
index c6adff3d..0231dae8 100644
--- a/request.go
+++ b/request.go
@@ -584,3 +584,9 @@ func (r *Request) selectAddr(addrs []*net.SRV, path string, attempt int) string
return fmt.Sprintf("%s://%s:%d/%s", r.client.scheme, domain, addrs[idx].Port, path)
}
+
+func (r *Request) initValuesMap() {
+ if r.values == nil {
+ r.values = make(map[string]interface{})
+ }
+}
diff --git a/request16.go b/request16.go
index 079ecfca..c578af0b 100644
--- a/request16.go
+++ b/request16.go
@@ -48,6 +48,7 @@ type Request struct {
bodyBuf *bytes.Buffer
multipartFiles []*File
multipartFields []*MultipartField
+ values map[string]interface{}
}
func (r *Request) addContextIfAvailable() {
diff --git a/request17.go b/request17.go
index 0629a114..8af35598 100644
--- a/request17.go
+++ b/request17.go
@@ -50,6 +50,7 @@ type Request struct {
bodyBuf *bytes.Buffer
multipartFiles []*File
multipartFields []*MultipartField
+ values map[string]interface{}
}
// Context method returns the Context if its already set in request
From 7bc61b57f701233c7899000753b3f5f0de618b9f Mon Sep 17 00:00:00 2001
From: Jeevanandam M
Date: Mon, 4 Feb 2019 21:48:24 -0800
Subject: [PATCH 04/28] #215 update go mod import path to
github.com/go-resty/resty
---
README.md | 26 +++++++++++++-------------
go.mod | 4 ++--
2 files changed, 15 insertions(+), 15 deletions(-)
diff --git a/README.md b/README.md
index 6583fac8..186d7ee4 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
Features section describes in detail about Resty capabilities
-
![License](https://img.shields.io/github/license/go-resty/resty.svg)
+
![License](https://img.shields.io/github/license/go-resty/resty.svg)
Resty Communication Channels
@@ -26,32 +26,32 @@
* Request Body can be `string`, `[]byte`, `struct`, `map`, `slice` and `io.Reader` too
* Auto detects `Content-Type`
* Buffer less processing for `io.Reader`
- * [Response](https://godoc.org/gopkg.in/resty.v1#Response) object gives you more possibility
+ * [Response](https://godoc.org/github.com/go-resty/resty#Response) object gives you more possibility
* Access as `[]byte` array - `response.Body()` OR Access as `string` - `response.String()`
* Know your `response.Time()` and when we `response.ReceivedAt()`
* Automatic marshal and unmarshal for `JSON` and `XML` content type
* Default is `JSON`, if you supply `struct/map` without header `Content-Type`
* For auto-unmarshal, refer to -
- - Success scenario [Request.SetResult()](https://godoc.org/gopkg.in/resty.v1#Request.SetResult) and [Response.Result()](https://godoc.org/gopkg.in/resty.v1#Response.Result).
- - Error scenario [Request.SetError()](https://godoc.org/gopkg.in/resty.v1#Request.SetError) and [Response.Error()](https://godoc.org/gopkg.in/resty.v1#Response.Error).
+ - Success scenario [Request.SetResult()](https://godoc.org/github.com/go-resty/resty#Request.SetResult) and [Response.Result()](https://godoc.org/github.com/go-resty/resty#Response.Result).
+ - Error scenario [Request.SetError()](https://godoc.org/github.com/go-resty/resty#Request.SetError) and [Response.Error()](https://godoc.org/github.com/go-resty/resty#Response.Error).
- Supports [RFC7807](https://tools.ietf.org/html/rfc7807) - `application/problem+json` & `application/problem+xml`
* Easy to upload one or more file(s) via `multipart/form-data`
* Auto detects file content type
- * Request URL [Path Params (aka URI Params)](https://godoc.org/gopkg.in/resty.v1#Request.SetPathParams)
+ * Request URL [Path Params (aka URI Params)](https://godoc.org/github.com/go-resty/resty#Request.SetPathParams)
* Backoff Retry Mechanism with retry condition function [reference](retry_test.go)
- * resty client HTTP & REST [Request](https://godoc.org/gopkg.in/resty.v1#Client.OnBeforeRequest) and [Response](https://godoc.org/gopkg.in/resty.v1#Client.OnAfterResponse) middlewares
+ * resty client HTTP & REST [Request](https://godoc.org/github.com/go-resty/resty#Client.OnBeforeRequest) and [Response](https://godoc.org/github.com/go-resty/resty#Client.OnAfterResponse) middlewares
* `Request.SetContext` supported `go1.7` and above
* Authorization option of `BasicAuth` and `Bearer` token
* Set request `ContentLength` value for all request or particular request
* Choose between HTTP and REST mode. Default is `REST`
* `HTTP` - default up to 10 redirects and no automatic response unmarshal
* `REST` - defaults to no redirects and automatic response marshal/unmarshal for `JSON` & `XML`
- * Custom [Root Certificates](https://godoc.org/gopkg.in/resty.v1#Client.SetRootCertificate) and Client [Certificates](https://godoc.org/gopkg.in/resty.v1#Client.SetCertificates)
- * Download/Save HTTP response directly into File, like `curl -o` flag. See [SetOutputDirectory](https://godoc.org/gopkg.in/resty.v1#Client.SetOutputDirectory) & [SetOutput](https://godoc.org/gopkg.in/resty.v1#Request.SetOutput).
+ * Custom [Root Certificates](https://godoc.org/github.com/go-resty/resty#Client.SetRootCertificate) and Client [Certificates](https://godoc.org/github.com/go-resty/resty#Client.SetCertificates)
+ * Download/Save HTTP response directly into File, like `curl -o` flag. See [SetOutputDirectory](https://godoc.org/github.com/go-resty/resty#Client.SetOutputDirectory) & [SetOutput](https://godoc.org/github.com/go-resty/resty#Request.SetOutput).
* Cookies for your request and CookieJar support
* SRV Record based request instead of Host URL
* Client settings like `Timeout`, `RedirectPolicy`, `Proxy`, `TLSClientConfig`, `Transport`, etc.
- * Optionally allows GET request with payload, see [SetAllowGetMethodPayload](https://godoc.org/gopkg.in/resty.v1#Client.SetAllowGetMethodPayload)
+ * Optionally allows GET request with payload, see [SetAllowGetMethodPayload](https://godoc.org/github.com/go-resty/resty#Client.SetAllowGetMethodPayload)
* Supports registering external JSON library into resty, see [how to use](https://github.com/go-resty/resty/issues/76#issuecomment-314015250)
* Exposes Response reader without reading response (no auto-unmarshaling) if need be, see [how to use](https://github.com/go-resty/resty/issues/87#issuecomment-322100604)
* Option to specify expected `Content-Type` when response `Content-Type` header missing. Refer to [#92](https://github.com/go-resty/resty/issues/92)
@@ -59,7 +59,7 @@
* Have client level settings & options and also override at Request level if you want to
* Request and Response middlewares
* Create Multiple clients if you want to `resty.New()`
- * Supports `http.RoundTripper` implementation, see [SetTransport](https://godoc.org/gopkg.in/resty.v1#Client.SetTransport)
+ * Supports `http.RoundTripper` implementation, see [SetTransport](https://godoc.org/github.com/go-resty/resty#Client.SetTransport)
* goroutine concurrent safe
* REST and HTTP modes
* Debug mode - clean and informative logging presentation
@@ -93,12 +93,12 @@ Please refer section [Versioning](#versioning) for detailed info.
##### go.mod
```bash
-require gopkg.in/resty.v1 v1.11.0
+require github.com/go-resty/resty v2.0.0
```
##### go get
```bash
-go get -u gopkg.in/resty.v1
+go get -u github.com/go-resty/resty
```
#### Heads up for upcoming Resty v2
@@ -121,7 +121,7 @@ The following samples will assist you to become as comfortable as possible with
Import resty into your code and refer it as `resty`.
```go
-import "gopkg.in/resty.v1"
+import "github.com/go-resty/resty"
```
#### Simple GET
diff --git a/go.mod b/go.mod
index 61341be8..f394daea 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,3 @@
-module gopkg.in/resty.v1
+module github.com/go-resty/resty/v2
-require golang.org/x/net v0.0.0-20181220203305-927f97764cc3
+require golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3
From 23bcb1ab79ba9d4f772a658f1ed7cbb7218c4012 Mon Sep 17 00:00:00 2001
From: Jeevanandam M
Date: Mon, 4 Feb 2019 22:12:12 -0800
Subject: [PATCH 05/28] version updated to v2.0.0-alpha.1
---
resty.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/resty.go b/resty.go
index 874bde2d..b886e9e0 100644
--- a/resty.go
+++ b/resty.go
@@ -6,4 +6,4 @@
package resty
// Version # of resty
-const Version = "2.0.0-edge"
+const Version = "2.0.0-alpha.1"
From a29aab7200d711f031e9c0b461014f7f4c28b6b7 Mon Sep 17 00:00:00 2001
From: Jeevanandam M
Date: Mon, 4 Feb 2019 22:35:52 -0800
Subject: [PATCH 06/28] try v2 alpha [ci skip]
---
go.mod | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/go.mod b/go.mod
index f394daea..32bd9b87 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,3 @@
-module github.com/go-resty/resty/v2
+module github.com/go-resty/resty
require golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3
From 1fddcd1acea3ae0ac13a8273c85cfd4665a37b34 Mon Sep 17 00:00:00 2001
From: Cameron Moore
Date: Thu, 7 Feb 2019 00:03:47 -0600
Subject: [PATCH 07/28] Build User-Agent string only once (#221)
---
client.go | 2 +-
middleware.go | 2 +-
redirect.go | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/client.go b/client.go
index cd5af53b..266d1bef 100644
--- a/client.go
+++ b/client.go
@@ -61,7 +61,7 @@ var (
jsonCheck = regexp.MustCompile(`(?i:(application|text)/(json|.*\+json|json\-.*)(;|$))`)
xmlCheck = regexp.MustCompile(`(?i:(application|text)/(xml|.*\+xml)(;|$))`)
- hdrUserAgentValue = "go-resty/%s (https://github.com/go-resty/resty)"
+ hdrUserAgentValue = "go-resty/" + Version + " (https://github.com/go-resty/resty)"
bufPool = &sync.Pool{New: func() interface{} { return &bytes.Buffer{} }}
)
diff --git a/middleware.go b/middleware.go
index 49d82eba..88014b50 100644
--- a/middleware.go
+++ b/middleware.go
@@ -106,7 +106,7 @@ func parseRequestHeader(c *Client, r *Request) error {
}
if IsStringEmpty(hdr.Get(hdrUserAgentKey)) {
- hdr.Set(hdrUserAgentKey, fmt.Sprintf(hdrUserAgentValue, Version))
+ hdr.Set(hdrUserAgentKey, hdrUserAgentValue)
}
ct := hdr.Get(hdrContentTypeKey)
diff --git a/redirect.go b/redirect.go
index b426134a..b9b7894b 100644
--- a/redirect.go
+++ b/redirect.go
@@ -94,6 +94,6 @@ func checkHostAndAddHeaders(cur *http.Request, pre *http.Request) {
cur.Header[key] = val
}
} else { // only library User-Agent header is added
- cur.Header.Set(hdrUserAgentKey, fmt.Sprintf(hdrUserAgentValue, Version))
+ cur.Header.Set(hdrUserAgentKey, hdrUserAgentValue)
}
}
From b5f5d8e86f8cb78132f6120fbfa0406f28923f02 Mon Sep 17 00:00:00 2001
From: Jeevanandam M
Date: Thu, 7 Mar 2019 22:11:48 -0800
Subject: [PATCH 08/28] #215 update import path to github.com/go-resty/resty
---
BUILD.bazel | 6 +++---
example_test.go | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/BUILD.bazel b/BUILD.bazel
index 698c326c..3d8681b7 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -6,7 +6,7 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
gazelle(
name = "gazelle",
command = "fix",
- prefix = "gopkg.in/resty.v1",
+ prefix = "github.com/go-resty/resty",
)
go_library(
@@ -15,7 +15,7 @@ go_library(
["*.go"],
exclude = ["*_test.go"],
),
- importpath = "gopkg.in/resty.v1",
+ importpath = "github.com/go-resty/resty",
visibility = ["//visibility:public"],
deps = ["@org_golang_x_net//publicsuffix:go_default_library"],
)
@@ -29,7 +29,7 @@ go_test(
),
data = glob([".testdata/*"]),
embed = [":go_default_library"],
- importpath = "gopkg.in/resty.v1",
+ importpath = "github.com/go-resty/resty",
deps = [
"@org_golang_x_net//proxy:go_default_library",
],
diff --git a/example_test.go b/example_test.go
index 9272fd07..35da1031 100644
--- a/example_test.go
+++ b/example_test.go
@@ -16,7 +16,7 @@ import (
"golang.org/x/net/proxy"
- "gopkg.in/resty.v1"
+ "github.com/go-resty/resty"
)
type DropboxError struct {
From bb5d3f41ca51967d330ba3ed161119fab7a97387 Mon Sep 17 00:00:00 2001
From: Jeevanandam M
Date: Thu, 7 Mar 2019 22:22:23 -0800
Subject: [PATCH 09/28] #212 added default values when creating Transport
instance
---
client.go | 36 +++++++++++++++++++++++++++---------
client_test.go | 2 +-
request_test.go | 6 +++---
3 files changed, 31 insertions(+), 13 deletions(-)
diff --git a/client.go b/client.go
index 266d1bef..824e8df5 100644
--- a/client.go
+++ b/client.go
@@ -14,10 +14,12 @@ import (
"io"
"io/ioutil"
"log"
+ "net"
"net/http"
"net/url"
"reflect"
"regexp"
+ "runtime"
"strings"
"sync"
"time"
@@ -600,8 +602,8 @@ func (c *Client) SetTLSClientConfig(config *tls.Config) *Client {
// SetProxy method sets the Proxy URL and Port for resty client.
// resty.SetProxy("http://proxyserver:8888")
//
-// Alternatives: At request level proxy, see `Request.SetProxy`. OR Without this `SetProxy` method,
-// you can also set Proxy via environment variable. By default `Go` uses setting from `HTTP_PROXY`.
+// OR Without this `SetProxy` method, you could also set Proxy via environment variable.
+// Refer to godoc `http.ProxyFromEnvironment`.
//
func (c *Client) SetProxy(proxyURL string) *Client {
transport, err := c.getTransport()
@@ -610,13 +612,14 @@ func (c *Client) SetProxy(proxyURL string) *Client {
return c
}
- if pURL, err := url.Parse(proxyURL); err == nil {
- c.proxyURL = pURL
- transport.Proxy = http.ProxyURL(c.proxyURL)
- } else {
+ pURL, err := url.Parse(proxyURL)
+ if err != nil {
c.Log.Printf("ERROR %v", err)
- c.RemoveProxy()
+ return c
}
+
+ c.proxyURL = pURL
+ transport.Proxy = http.ProxyURL(c.proxyURL)
return c
}
@@ -769,7 +772,9 @@ func (c *Client) SetJSONEscapeHTML(b bool) *Client {
return c
}
-// IsProxySet method returns the true if proxy is set on client otherwise false.
+// IsProxySet method returns the true is proxy is set from resty client otherwise
+// false. By default proxy is set from environment, refer to `http.ProxyFromEnvironment`.
+//
func (c *Client) IsProxySet() bool {
return c.proxyURL != nil
}
@@ -892,7 +897,20 @@ func (c *Client) getTLSConfig() (*tls.Config, error) {
// in case currently used `transport` is not an `*http.Transport`
func (c *Client) getTransport() (*http.Transport, error) {
if c.httpClient.Transport == nil {
- c.SetTransport(new(http.Transport))
+ transport := &http.Transport{
+ Proxy: http.ProxyFromEnvironment,
+ DialContext: (&net.Dialer{
+ Timeout: 30 * time.Second,
+ KeepAlive: 30 * time.Second,
+ DualStack: true,
+ }).DialContext,
+ MaxIdleConns: 100,
+ IdleConnTimeout: 90 * time.Second,
+ TLSHandshakeTimeout: 10 * time.Second,
+ ExpectContinueTimeout: 1 * time.Second,
+ MaxIdleConnsPerHost: runtime.GOMAXPROCS(0) + 1,
+ }
+ c.SetTransport(transport)
}
if transport, ok := c.httpClient.Transport.(*http.Transport); ok {
diff --git a/client_test.go b/client_test.go
index dfc526cc..5300419a 100644
--- a/client_test.go
+++ b/client_test.go
@@ -146,7 +146,7 @@ func TestClientProxy(t *testing.T) {
resp, err = c.R().
Get(ts.URL)
- assertNil(t, err)
+ assertNotNil(t, err)
assertNotNil(t, resp)
}
diff --git a/request_test.go b/request_test.go
index d94c4dbe..9d6c9e9f 100644
--- a/request_test.go
+++ b/request_test.go
@@ -957,15 +957,15 @@ func TestProxySetting(t *testing.T) {
assertNil(t, err)
assertEqual(t, false, c.IsProxySet())
- assertNil(t, transport.Proxy)
+ assertNotNil(t, transport.Proxy)
c.SetProxy("http://sampleproxy:8888")
assertEqual(t, true, c.IsProxySet())
assertNotNil(t, transport.Proxy)
c.SetProxy("//not.a.user@%66%6f%6f.com:8888")
- assertEqual(t, false, c.IsProxySet())
- assertNil(t, transport.Proxy)
+ assertEqual(t, true, c.IsProxySet())
+ assertNotNil(t, transport.Proxy)
SetProxy("http://sampleproxy:8888")
assertEqual(t, true, IsProxySet())
From 19e182266c6f0536b3eb1a972d7338dcc2e93fc7 Mon Sep 17 00:00:00 2001
From: Jeevanandam M
Date: Thu, 7 Mar 2019 22:24:44 -0800
Subject: [PATCH 10/28] #213 refactored the method name for easy understanding
---
README.md | 2 +-
request.go | 20 +++++++++++---------
request_test.go | 4 ++--
3 files changed, 14 insertions(+), 12 deletions(-)
diff --git a/README.md b/README.md
index 186d7ee4..993e3e66 100644
--- a/README.md
+++ b/README.md
@@ -380,7 +380,7 @@ criteria := url.Values{
"search_criteria": []string{"book", "glass", "pencil"},
}
resp, err := resty.R().
- SetMultiValueFormData(criteria).
+ SetFormDataFromValues(criteria).
Post("http://myapp.com/search")
```
diff --git a/request.go b/request.go
index 0231dae8..f5aa4b9c 100644
--- a/request.go
+++ b/request.go
@@ -84,16 +84,18 @@ func (r *Request) SetQueryParams(params map[string]string) *Request {
return r
}
-// SetMultiValueQueryParams method appends multiple parameters with multi-value
-// at one go in the current request. It will be formed as query string for the request.
+// SetQueryParamsFromValues method appends multiple parameters with multi-value
+// (`url.Values`) at one go in the current request. It will be formed as
+// query string for the request.
+//
// Example: `status=pending&status=approved&status=open` in the URL after `?` mark.
// resty.R().
-// SetMultiValueQueryParams(url.Values{
+// SetQueryParamsFromValues(url.Values{
// "status": []string{"pending", "approved", "open"},
// })
// Also you can override query params value, which was set at client instance level
//
-func (r *Request) SetMultiValueQueryParams(params url.Values) *Request {
+func (r *Request) SetQueryParamsFromValues(params url.Values) *Request {
for p, v := range params {
for _, pv := range v {
r.QueryParam.Add(p, pv)
@@ -141,16 +143,16 @@ func (r *Request) SetFormData(data map[string]string) *Request {
return r
}
-// SetMultiValueFormData method appends multiple form parameters with multi-value
-// at one go in the current request.
+// SetFormDataFromValues method appends multiple form parameters with multi-value
+// (`url.Values`) at one go in the current request.
// resty.R().
-// SetMultiValueFormData(url.Values{
+// SetFormDataFromValues(url.Values{
// "search_criteria": []string{"book", "glass", "pencil"},
// })
// Also you can override form data value, which was set at client instance level
//
-func (r *Request) SetMultiValueFormData(params url.Values) *Request {
- for k, v := range params {
+func (r *Request) SetFormDataFromValues(data url.Values) *Request {
+ for k, v := range data {
for _, kv := range v {
r.FormData.Add(k, kv)
}
diff --git a/request_test.go b/request_test.go
index 9d6c9e9f..bcf029db 100644
--- a/request_test.go
+++ b/request_test.go
@@ -525,7 +525,7 @@ func TestMultiValueFormData(t *testing.T) {
SetLogger(ioutil.Discard)
resp, err := c.R().
- SetMultiValueFormData(v).
+ SetQueryParamsFromValues(v).
Post(ts.URL + "/search")
assertError(t, err)
@@ -1125,7 +1125,7 @@ func TestMultiParamsQueryString(t *testing.T) {
"status": []string{"pending", "approved", "reject"},
}
- _, _ = req2.SetMultiValueQueryParams(v).Get(ts2.URL)
+ _, _ = req2.SetQueryParamsFromValues(v).Get(ts2.URL)
assertEqual(t, true, strings.Contains(req2.URL, "status=pending"))
assertEqual(t, true, strings.Contains(req2.URL, "status=approved"))
From 9baf69e1e305f9ef643e691b6663142a741fc5bb Mon Sep 17 00:00:00 2001
From: Jeevanandam M
Date: Thu, 7 Mar 2019 22:26:07 -0800
Subject: [PATCH 11/28] #214 added error parameter into RetryConditionFunc
---
retry.go | 4 ++--
retry_test.go | 14 +++++++-------
2 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/retry.go b/retry.go
index 4ed9b6d6..fb35f49b 100644
--- a/retry.go
+++ b/retry.go
@@ -21,7 +21,7 @@ type (
Option func(*Options)
// RetryConditionFunc type is for retry condition function
- RetryConditionFunc func(*Response) (bool, error)
+ RetryConditionFunc func(*Response, error) (bool, error)
// Options to hold go-resty retry values
Options struct {
@@ -87,7 +87,7 @@ func Backoff(operation func() (*Response, error), options ...Option) error {
var needsRetry bool
var conditionErr error
for _, condition := range opts.retryConditions {
- needsRetry, conditionErr = condition(resp)
+ needsRetry, conditionErr = condition(resp, err)
if needsRetry || conditionErr != nil {
break
}
diff --git a/retry_test.go b/retry_test.go
index ccb07a9c..89eaeece 100644
--- a/retry_test.go
+++ b/retry_test.go
@@ -50,7 +50,7 @@ func TestBackoffTenAttemptsSuccess(t *testing.T) {
func TestConditionalBackoffCondition(t *testing.T) {
attempts := 3
counter := 0
- check := RetryConditionFunc(func(*Response) (bool, error) {
+ check := RetryConditionFunc(func(*Response, error) (bool, error) {
return attempts != counter, nil
})
retryErr := Backoff(func() (*Response, error) {
@@ -66,7 +66,7 @@ func TestConditionalBackoffCondition(t *testing.T) {
func TestConditionalBackoffConditionError(t *testing.T) {
attempts := 3
counter := 0
- check := RetryConditionFunc(func(*Response) (bool, error) {
+ check := RetryConditionFunc(func(*Response, error) (bool, error) {
if attempts != counter {
return false, errors.New("attempts not equal Counter")
}
@@ -104,7 +104,7 @@ func TestConditionalGet(t *testing.T) {
externalCounter := 0
// This check should pass on first run, and let the response through
- check := RetryConditionFunc(func(*Response) (bool, error) {
+ check := RetryConditionFunc(func(*Response, error) (bool, error) {
externalCounter++
if attemptCount != externalCounter {
return false, errors.New("attempts not equal Counter")
@@ -135,7 +135,7 @@ func TestConditionalGetDefaultClient(t *testing.T) {
externalCounter := 0
// This check should pass on first run, and let the response through
- check := RetryConditionFunc(func(*Response) (bool, error) {
+ check := RetryConditionFunc(func(*Response, error) (bool, error) {
externalCounter++
if attemptCount != externalCounter {
return false, errors.New("attempts not equal Counter")
@@ -199,7 +199,7 @@ func TestClientRetryWait(t *testing.T) {
SetRetryWaitTime(retryWaitTime).
SetRetryMaxWaitTime(retryMaxWaitTime).
AddRetryCondition(
- func(r *Response) (bool, error) {
+ func(r *Response, err error) (bool, error) {
timeSlept, _ := strconv.ParseUint(string(r.Body()), 10, 64)
retryIntervals[attempt] = timeSlept
attempt++
@@ -237,7 +237,7 @@ func TestClientRetryPost(t *testing.T) {
c := dc()
c.SetRetryCount(3)
- c.AddRetryCondition(RetryConditionFunc(func(r *Response) (bool, error) {
+ c.AddRetryCondition(RetryConditionFunc(func(r *Response, err error) (bool, error) {
if r.StatusCode() >= http.StatusInternalServerError {
return false, errors.New("error")
}
@@ -265,6 +265,6 @@ func TestClientRetryPost(t *testing.T) {
}
}
-func filler(*Response) (bool, error) {
+func filler(*Response, error) (bool, error) {
return false, nil
}
From 1b476a1397de74e80f4f788789659a73b3013b70 Mon Sep 17 00:00:00 2001
From: Jeevanandam M
Date: Fri, 8 Mar 2019 22:18:05 -0800
Subject: [PATCH 12/28] add go1.12 into build and add -race flag into test
---
.travis.yml | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index d1100290..07b84371 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,7 +2,7 @@ language: go
sudo: false
-go:
+go: # use travis ci resource effectively
# - 1.3
# - 1.4
# - 1.5
@@ -12,13 +12,14 @@ go:
# - 1.9.x
- 1.10.x
- 1.11.x
+ - 1.12.x
- tip
install:
- go get -v -t ./...
script:
- - go test ./... -coverprofile=coverage.txt -covermode=atomic
+ - go test ./... -race -coverprofile=coverage.txt -covermode=atomic
after_success:
- bash <(curl -s https://codecov.io/bash)
From 16c19211a1051af112494640a61257292ca31031 Mon Sep 17 00:00:00 2001
From: Jeevanandam M
Date: Sat, 9 Mar 2019 11:00:26 -0800
Subject: [PATCH 13/28] #232, #233 removed default client approach and made
resty support starts from go1.8
---
README.md | 230 +++++++++++++++++++-------------
client.go | 134 ++++++++-----------
client_test.go | 199 ++++++++++++----------------
context17_test.go | 15 ---
context18_test.go | 31 -----
context_test.go | 32 +++--
default.go | 327 ----------------------------------------------
example_test.go | 52 ++++++--
middleware.go | 12 +-
redirect.go | 6 +-
request.go | 95 ++++++++++++--
request16.go | 64 ---------
request17.go | 97 --------------
request_test.go | 122 ++++++-----------
response.go | 13 +-
resty.go | 18 +++
resty_test.go | 6 +-
retry_test.go | 10 +-
util.go | 25 +---
19 files changed, 517 insertions(+), 971 deletions(-)
delete mode 100644 context17_test.go
delete mode 100644 context18_test.go
delete mode 100644 default.go
delete mode 100644 request16.go
delete mode 100644 request17.go
diff --git a/README.md b/README.md
index 993e3e66..3257c48e 100644
--- a/README.md
+++ b/README.md
@@ -4,19 +4,17 @@
Features section describes in detail about Resty capabilities
-
![License](https://img.shields.io/github/license/go-resty/resty.svg)
+
![License](https://img.shields.io/github/license/go-resty/resty.svg)
Resty Communication Channels
-
![Twitter @go_resty](https://img.shields.io/badge/twitter-@go_resty-55acee.svg)
+
![Twitter @go__resty](https://img.shields.io/badge/twitter-@go_resty-55acee.svg)
## News
- * [Collecting Inputs for Resty v2.0.0](https://github.com/go-resty/resty/issues/166)
- * v1.11.0 [released](https://github.com/go-resty/resty/releases/tag/v1.11.0) and tagged on Jan 06, 2019.
- * v1.10.3 [released](https://github.com/go-resty/resty/releases/tag/v1.10.3) and tagged on Dec 04, 2018.
- * v1.10.0 [released](https://github.com/go-resty/resty/releases/tag/v1.10.0) and tagged on Oct 22, 2018.
+ * v2.0.0 [released](https://github.com/go-resty/resty/releases/tag/v2.0.0) and tagged on TBD
+ * 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
@@ -40,12 +38,9 @@
* Request URL [Path Params (aka URI Params)](https://godoc.org/github.com/go-resty/resty#Request.SetPathParams)
* Backoff Retry Mechanism with retry condition function [reference](retry_test.go)
* resty client HTTP & REST [Request](https://godoc.org/github.com/go-resty/resty#Client.OnBeforeRequest) and [Response](https://godoc.org/github.com/go-resty/resty#Client.OnAfterResponse) middlewares
- * `Request.SetContext` supported `go1.7` and above
+ * `Request.SetContext` supported
* Authorization option of `BasicAuth` and `Bearer` token
* Set request `ContentLength` value for all request or particular request
- * Choose between HTTP and REST mode. Default is `REST`
- * `HTTP` - default up to 10 redirects and no automatic response unmarshal
- * `REST` - defaults to no redirects and automatic response marshal/unmarshal for `JSON` & `XML`
* Custom [Root Certificates](https://godoc.org/github.com/go-resty/resty#Client.SetRootCertificate) and Client [Certificates](https://godoc.org/github.com/go-resty/resty#Client.SetCertificates)
* Download/Save HTTP response directly into File, like `curl -o` flag. See [SetOutputDirectory](https://godoc.org/github.com/go-resty/resty#Client.SetOutputDirectory) & [SetOutput](https://godoc.org/github.com/go-resty/resty#Request.SetOutput).
* Cookies for your request and CookieJar support
@@ -66,10 +61,10 @@
* Gzip - Go does it automatically also resty has fallback handling too
* Works fine with `HTTP/2` and `HTTP/1.1`
* [Bazel support](#bazel-support)
- * Easily mock resty for testing, [for e.g.](#mocking-http-requests-using-httpmock-library)
+ * Easily mock Resty for testing, [for e.g.](#mocking-http-requests-using-httpmock-library)
* Well tested client library
-Resty works with `go1.3` and above.
+Resty supports with `go1.8` and above.
### Included Batteries
@@ -101,17 +96,12 @@ require github.com/go-resty/resty v2.0.0
go get -u github.com/go-resty/resty
```
-#### Heads up for upcoming Resty v2
-
-Resty v2 release will be moving away from `gopkg.in` proxy versioning. It will completely follow and adpating Go Mod versioning recommendation. For e.g.: module definition would be `module github.com/go-resty/resty/v2`.
-
-
## It might be beneficial for your project :smile:
Resty author also published following projects for Go Community.
* [aah framework](https://aahframework.org) - A secure, flexible, rapid Go web framework.
- * [THUMBAI](https://thumbai.app), [Source Code](https://github.com/thumbai/thumbai) - Go Mod Repository, Go Vanity Service and Simple Proxy Server.
+ * [THUMBAI](https://thumbai.app) - Go Mod Repository, Go Vanity Service and Simple Proxy Server.
* [go-model](https://github.com/jeevatkm/go-model) - Robust & Easy to use model mapper and utility methods for Go `struct`.
## Usage
@@ -127,8 +117,11 @@ import "github.com/go-resty/resty"
#### Simple GET
```go
+// Create a Resty Client
+client := resty.New()
+
// GET request
-resp, err := resty.R().Get("http://httpbin.org/get")
+resp, err := client.R().Get("http://httpbin.org/get")
// explore response object
fmt.Printf("\nError: %v", err)
@@ -162,7 +155,10 @@ Response Body: {
#### Enhanced GET
```go
-resp, err := resty.R().
+// Create a Resty Client
+client := resty.New()
+
+resp, err := client.R().
SetQueryParams(map[string]string{
"page_no": "1",
"limit": "20",
@@ -176,7 +172,7 @@ resp, err := resty.R().
// Sample of using Request.SetQueryString method
-resp, err := resty.R().
+resp, err := client.R().
SetQueryString("productId=232&template=fresh-sample&cat=resty&source=google&kw=buy a lot more").
SetHeader("Accept", "application/json").
SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F").
@@ -186,9 +182,12 @@ resp, err := resty.R().
#### Various POST method combinations
```go
+// Create a Resty Client
+client := resty.New()
+
// POST JSON string
// No need to set content type, if you have client level setting
-resp, err := resty.R().
+resp, err := client.R().
SetHeader("Content-Type", "application/json").
SetBody(`{"username":"testuser", "password":"testpass"}`).
SetResult(&AuthSuccess{}). // or SetResult(AuthSuccess{}).
@@ -196,21 +195,21 @@ resp, err := resty.R().
// POST []byte array
// No need to set content type, if you have client level setting
-resp, err := resty.R().
+resp, err := client.R().
SetHeader("Content-Type", "application/json").
SetBody([]byte(`{"username":"testuser", "password":"testpass"}`)).
SetResult(&AuthSuccess{}). // or SetResult(AuthSuccess{}).
Post("https://myapp.com/login")
// POST Struct, default is JSON content type. No need to set one
-resp, err := resty.R().
+resp, err := client.R().
SetBody(User{Username: "testuser", Password: "testpass"}).
SetResult(&AuthSuccess{}). // or SetResult(AuthSuccess{}).
SetError(&AuthError{}). // or SetError(AuthError{}).
Post("https://myapp.com/login")
// POST Map, default is JSON content type. No need to set one
-resp, err := resty.R().
+resp, err := client.R().
SetBody(map[string]interface{}{"username": "testuser", "password": "testpass"}).
SetResult(&AuthSuccess{}). // or SetResult(AuthSuccess{}).
SetError(&AuthError{}). // or SetError(AuthError{}).
@@ -220,7 +219,7 @@ resp, err := resty.R().
fileBytes, _ := ioutil.ReadFile("/Users/jeeva/mydocument.pdf")
// See we are not setting content-type header, since go-resty automatically detects Content-Type for you
-resp, err := resty.R().
+resp, err := client.R().
SetBody(fileBytes).
SetContentLength(true). // Dropbox expects this value
SetAuthToken("").
@@ -239,9 +238,12 @@ You can use various combinations of `PUT` method call like demonstrated for `POS
```go
// Note: This is one sample of PUT method usage, refer POST for more combination
+// Create a Resty Client
+client := resty.New()
+
// Request goes as JSON content type
// No need to set auth token, error, if you have client level settings
-resp, err := resty.R().
+resp, err := client.R().
SetBody(Article{
Title: "go-resty",
Content: "This is my article content, oh ya!",
@@ -260,9 +262,12 @@ You can use various combinations of `PATCH` method call like demonstrated for `P
```go
// Note: This is one sample of PUT method usage, refer POST for more combination
+// Create a Resty Client
+client := resty.New()
+
// Request goes as JSON content type
// No need to set auth token, error, if you have client level settings
-resp, err := resty.R().
+resp, err := client.R().
SetBody(Article{
Tags: []string{"new tag1", "new tag2"},
}).
@@ -274,16 +279,19 @@ resp, err := resty.R().
#### Sample DELETE, HEAD, OPTIONS
```go
+// Create a Resty Client
+client := resty.New()
+
// DELETE a article
// No need to set auth token, error, if you have client level settings
-resp, err := resty.R().
+resp, err := client.R().
SetAuthToken("C6A79608-782F-4ED0-A11D-BD82FAD829CD").
SetError(&Error{}). // or SetError(Error{}).
Delete("https://myapp.com/articles/1234")
// DELETE a articles with payload/body as a JSON string
// No need to set auth token, error, if you have client level settings
-resp, err := resty.R().
+resp, err := client.R().
SetAuthToken("C6A79608-782F-4ED0-A11D-BD82FAD829CD").
SetError(&Error{}). // or SetError(Error{}).
SetHeader("Content-Type", "application/json").
@@ -292,13 +300,13 @@ resp, err := resty.R().
// HEAD of resource
// No need to set auth token, if you have client level settings
-resp, err := resty.R().
+resp, err := client.R().
SetAuthToken("C6A79608-782F-4ED0-A11D-BD82FAD829CD").
Head("https://myapp.com/videos/hi-res-video")
// OPTIONS of resource
// No need to set auth token, if you have client level settings
-resp, err := resty.R().
+resp, err := client.R().
SetAuthToken("C6A79608-782F-4ED0-A11D-BD82FAD829CD").
Options("https://myapp.com/servers/nyc-dc-01")
```
@@ -311,7 +319,10 @@ resp, err := resty.R().
profileImgBytes, _ := ioutil.ReadFile("/Users/jeeva/test-img.png")
notesBytes, _ := ioutil.ReadFile("/Users/jeeva/text-file.txt")
-resp, err := resty.R().
+// Create a Resty Client
+client := resty.New()
+
+resp, err := client.R().
SetFileReader("profile_img", "test-img.png", bytes.NewReader(profileImgBytes)).
SetFileReader("notes", "text-file.txt", bytes.NewReader(notesBytes)).
SetFormData(map[string]string{
@@ -324,13 +335,16 @@ resp, err := resty.R().
#### Using File directly from Path
```go
+// Create a Resty Client
+client := resty.New()
+
// Single file scenario
-resp, err := resty.R().
+resp, err := client.R().
SetFile("profile_img", "/Users/jeeva/test-img.png").
Post("http://myapp.com/upload")
// Multiple files scenario
-resp, err := resty.R().
+resp, err := client.R().
SetFiles(map[string]string{
"profile_img": "/Users/jeeva/test-img.png",
"notes": "/Users/jeeva/text-file.txt",
@@ -338,7 +352,7 @@ resp, err := resty.R().
Post("http://myapp.com/upload")
// Multipart of form fields and files
-resp, err := resty.R().
+resp, err := client.R().
SetFiles(map[string]string{
"profile_img": "/Users/jeeva/test-img.png",
"notes": "/Users/jeeva/text-file.txt",
@@ -356,9 +370,12 @@ resp, err := resty.R().
#### Sample Form submission
```go
+// Create a Resty Client
+client := resty.New()
+
// just mentioning about POST as an example with simple flow
// User Login
-resp, err := resty.R().
+resp, err := client.R().
SetFormData(map[string]string{
"username": "jeeva",
"password": "mypass",
@@ -366,7 +383,7 @@ resp, err := resty.R().
Post("http://myapp.com/login")
// Followed by profile update
-resp, err := resty.R().
+resp, err := client.R().
SetFormData(map[string]string{
"first_name": "Jeevanandam",
"last_name": "M",
@@ -379,7 +396,7 @@ resp, err := resty.R().
criteria := url.Values{
"search_criteria": []string{"book", "glass", "pencil"},
}
-resp, err := resty.R().
+resp, err := client.R().
SetFormDataFromValues(criteria).
Post("http://myapp.com/search")
```
@@ -387,19 +404,22 @@ resp, err := resty.R().
#### Save HTTP Response into File
```go
+// Create a Resty Client
+client := resty.New()
+
// Setting output directory path, If directory not exists then resty creates one!
// This is optional one, if you're planning using absoule path in
// `Request.SetOutput` and can used together.
-resty.SetOutputDirectory("/Users/jeeva/Downloads")
+client.SetOutputDirectory("/Users/jeeva/Downloads")
// HTTP response gets saved into file, similar to curl -o flag
-_, err := resty.R().
+_, err := client.R().
SetOutput("plugin/ReplyWithHeader-v5.1-beta.zip").
Get("http://bit.ly/1LouEKr")
// OR using absolute path
// Note: output directory path is not used for absoulte path
-_, err := resty.R().
+_, err := client.R().
SetOutput("/MyDownloads/plugin/ReplyWithHeader-v5.1-beta.zip").
Get("http://bit.ly/1LouEKr")
```
@@ -409,7 +429,10 @@ _, err := resty.R().
Resty provides easy to use dynamic request URL path params. Params can be set at client and request level. Client level params value can be overridden at request level.
```go
-resty.R().SetPathParams(map[string]string{
+// Create a Resty Client
+client := resty.New()
+
+client.R().SetPathParams(map[string]string{
"userId": "sample@sample.com",
"subAccountId": "100002",
}).
@@ -424,8 +447,11 @@ Get("/v1/users/{userId}/{subAccountId}/details")
Resty provides middleware ability to manipulate for Request and Response. It is more flexible than callback approach.
```go
+// Create a Resty Client
+client := resty.New()
+
// Registering Request Middleware
-resty.OnBeforeRequest(func(c *resty.Client, req *resty.Request) error {
+client.OnBeforeRequest(func(c *resty.Client, req *resty.Request) error {
// Now you have access to Client and current Request object
// manipulate it as per your need
@@ -433,7 +459,7 @@ resty.OnBeforeRequest(func(c *resty.Client, req *resty.Request) error {
})
// Registering Response Middleware
-resty.OnAfterResponse(func(c *resty.Client, resp *resty.Response) error {
+client.OnAfterResponse(func(c *resty.Client, resp *resty.Response) error {
// Now you have access to Client and current Response object
// manipulate it as per your need
@@ -446,11 +472,14 @@ resty.OnAfterResponse(func(c *resty.Client, resp *resty.Response) error {
Resty provides few ready to use redirect policy(s) also it supports multiple policies together.
```go
+// Create a Resty Client
+client := resty.New()
+
// Assign Client Redirect Policy. Create one as per you need
-resty.SetRedirectPolicy(resty.FlexibleRedirectPolicy(15))
+client.SetRedirectPolicy(resty.FlexibleRedirectPolicy(15))
// Wanna multiple policies such as redirect count, domain name check, etc
-resty.SetRedirectPolicy(resty.FlexibleRedirectPolicy(20),
+client.SetRedirectPolicy(resty.FlexibleRedirectPolicy(20),
resty.DomainCheckRedirectPolicy("host1.com", "host2.org", "host3.net"))
```
@@ -459,8 +488,11 @@ resty.SetRedirectPolicy(resty.FlexibleRedirectPolicy(20),
Implement [RedirectPolicy](redirect.go#L20) interface and register it with resty client. Have a look [redirect.go](redirect.go) for more information.
```go
+// Create a Resty Client
+client := resty.New()
+
// Using raw func into resty.SetRedirectPolicy
-resty.SetRedirectPolicy(resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error {
+client.SetRedirectPolicy(resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error {
// Implement your logic here
// return nil for continue redirect otherwise return error to stop/prevent redirect
@@ -482,16 +514,19 @@ func (c *CustomRedirectPolicy) Apply(req *http.Request, via []*http.Request) err
}
// Registering in resty
-resty.SetRedirectPolicy(CustomRedirectPolicy{/* initialize variables */})
+client.SetRedirectPolicy(CustomRedirectPolicy{/* initialize variables */})
```
#### Custom Root Certificates and Client Certificates
```go
+// Create a Resty Client
+client := resty.New()
+
// Custom Root certificates, just supply .pem file.
// you can add one or more root certificates, its get appended
-resty.SetRootCertificate("/path/to/root/pemFile1.pem")
-resty.SetRootCertificate("/path/to/root/pemFile2.pem")
+client.SetRootCertificate("/path/to/root/pemFile1.pem")
+client.SetRootCertificate("/path/to/root/pemFile2.pem")
// ... and so on!
// Adding Client Certificates, you add one or more certificates
@@ -504,7 +539,7 @@ if err != nil {
// ...
// You add one or more certificates
-resty.SetCertificates(cert1, cert2, cert3)
+client.SetCertificates(cert1, cert2, cert3)
```
#### Proxy Settings - Client as well as at Request Level
@@ -515,11 +550,14 @@ Choose as per your need.
**Client Level Proxy** settings applied to all the request
```go
+// Create a Resty Client
+client := resty.New()
+
// Setting a Proxy URL and Port
-resty.SetProxy("http://proxyserver:8888")
+client.SetProxy("http://proxyserver:8888")
// Want to remove proxy setting
-resty.RemoveProxy()
+client.RemoveProxy()
```
#### Retries
@@ -530,8 +568,11 @@ to increase retry intervals after each attempt.
Usage example:
```go
+// Create a Resty Client
+client := resty.New()
+
// Retries are configured per client
-resty.
+client.
// Set retry count to non zero to enable retries
SetRetryCount(3).
// You can override initial retry wait time.
@@ -548,11 +589,14 @@ Above setup will result in resty retrying requests returned non nil error up to
You can optionally provide client with custom retry conditions:
```go
-resty.AddRetryCondition(
+// Create a Resty Client
+client := resty.New()
+
+client.AddRetryCondition(
// Condition function will be provided with *resty.Response as a
// parameter. It is expected to return (bool, error) pair. Resty will retry
// in case condition returns true or non nil error.
- func(r *resty.Response) (bool, error) {
+ func(r *resty.Response, err error) (bool, error) {
return r.StatusCode() == http.StatusTooManyRequests, nil
},
)
@@ -566,21 +610,14 @@ Multiple retry conditions can be added.
It is also possible to use `resty.Backoff(...)` to get arbitrary retry scenarios
implemented. [Reference](retry_test.go).
-#### Choose REST or HTTP mode
-
-```go
-// REST mode. This is Default.
-resty.SetRESTMode()
-
-// HTTP mode
-resty.SetHTTPMode()
-```
-
#### Allow GET request with Payload
```go
+// Create a Resty Client
+client := resty.New()
+
// Allow GET request with Payload. This is disabled by default.
-resty.SetAllowGetMethodPayload(true)
+client.SetAllowGetMethodPayload(true)
```
#### Wanna Multiple Clients
@@ -603,40 +640,43 @@ client2.R().Head("http://httpbin.org")
#### Remaining Client Settings & its Options
```go
+// Create a Resty Client
+client := resty.New()
+
// Unique settings at Client level
//--------------------------------
// Enable debug mode
-resty.SetDebug(true)
+client.SetDebug(true)
// Using you custom log writer
logFile, _ := os.OpenFile("/Users/jeeva/go-resty.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
-resty.SetLogger(logFile)
+client.SetLogger(logFile)
// Assign Client TLSClientConfig
// One can set custom root-certificate. Refer: http://golang.org/pkg/crypto/tls/#example_Dial
-resty.SetTLSClientConfig(&tls.Config{ RootCAs: roots })
+client.SetTLSClientConfig(&tls.Config{ RootCAs: roots })
// or One can disable security check (https)
-resty.SetTLSClientConfig(&tls.Config{ InsecureSkipVerify: true })
+client.SetTLSClientConfig(&tls.Config{ InsecureSkipVerify: true })
// Set client timeout as per your need
-resty.SetTimeout(1 * time.Minute)
+client.SetTimeout(1 * time.Minute)
// You can override all below settings and options at request level if you want to
//--------------------------------------------------------------------------------
// Host URL for all request. So you can use relative URL in the request
-resty.SetHostURL("http://httpbin.org")
+client.SetHostURL("http://httpbin.org")
// Headers for all request
-resty.SetHeader("Accept", "application/json")
-resty.SetHeaders(map[string]string{
+client.SetHeader("Accept", "application/json")
+client.SetHeaders(map[string]string{
"Content-Type": "application/json",
"User-Agent": "My custom User Agent String",
})
// Cookies for all request
-resty.SetCookie(&http.Cookie{
+client.SetCookie(&http.Cookie{
Name:"go-resty",
Value:"This is cookie value",
Path: "/",
@@ -645,32 +685,32 @@ resty.SetCookie(&http.Cookie{
HttpOnly: true,
Secure: false,
})
-resty.SetCookies(cookies)
+client.SetCookies(cookies)
// URL query parameters for all request
-resty.SetQueryParam("user_id", "00001")
-resty.SetQueryParams(map[string]string{ // sample of those who use this manner
+client.SetQueryParam("user_id", "00001")
+client.SetQueryParams(map[string]string{ // sample of those who use this manner
"api_key": "api-key-here",
"api_secert": "api-secert",
})
-resty.R().SetQueryString("productId=232&template=fresh-sample&cat=resty&source=google&kw=buy a lot more")
+client.R().SetQueryString("productId=232&template=fresh-sample&cat=resty&source=google&kw=buy a lot more")
// Form data for all request. Typically used with POST and PUT
-resty.SetFormData(map[string]string{
+client.SetFormData(map[string]string{
"access_token": "BC594900-518B-4F7E-AC75-BD37F019E08F",
})
// Basic Auth for all request
-resty.SetBasicAuth("myuser", "mypass")
+client.SetBasicAuth("myuser", "mypass")
// Bearer Auth Token for all request
-resty.SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F")
+client.SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F")
// Enabling Content length value for all request
-resty.SetContentLength(true)
+client.SetContentLength(true)
// Registering global Error object structure for JSON/XML request
-resty.SetError(&Error{}) // or resty.SetError(Error{})
+client.SetError(&Error{}) // or resty.SetError(Error{})
```
#### Unix Socket
@@ -685,12 +725,15 @@ transport := http.Transport{
},
}
+// Create a Resty Client
+client := resty.New()
+
// Set the previous transport that we created, set the scheme of the communication to the
// socket and set the unixSocket as the HostURL.
-r := resty.New().SetTransport(&transport).SetScheme("http").SetHostURL(unixSocket)
+client.SetTransport(&transport).SetScheme("http").SetHostURL(unixSocket)
// No need to write the host's URL on the request, just the path.
-r.R().Get("/index.html")
+client.R().Get("/index.html")
```
#### Bazel support
@@ -710,16 +753,21 @@ could use the `httpmock` library.
When using the default resty client, you should pass the client to the library as follow:
```go
-httpmock.ActivateNonDefault(resty.DefaultClient.GetClient())
+// Create a Resty Client
+client := resty.New()
+
+// Get the underlying HTTP Client and set it to Mock
+httpmock.ActivateNonDefault(client.GetClient())
```
More detailed example of mocking resty http requests using ginko could be found [here](https://github.com/jarcoal/httpmock#ginkgo--resty-example).
## Versioning
-resty releases versions according to [Semantic Versioning](http://semver.org)
+Resty releases versions according to [Semantic Versioning](http://semver.org)
- * `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`.
+ * 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.
## Contribution
diff --git a/client.go b/client.go
index 824e8df5..2f1c61ca 100644
--- a/client.go
+++ b/client.go
@@ -9,14 +9,17 @@ import (
"compress/gzip"
"crypto/tls"
"crypto/x509"
+ "encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
+ "math"
"net"
"net/http"
"net/url"
+ "os"
"reflect"
"regexp"
"runtime"
@@ -162,7 +165,6 @@ func (c *Client) SetHeaders(headers map[string]string) *Client {
for h, v := range headers {
c.Header.Set(h, v)
}
-
return c
}
@@ -254,7 +256,6 @@ func (c *Client) SetQueryParams(params map[string]string) *Client {
for p, v := range params {
c.SetQueryParam(p, v)
}
-
return c
}
@@ -271,7 +272,6 @@ func (c *Client) SetFormData(data map[string]string) *Client {
for k, v := range data {
c.FormData.Set(k, v)
}
-
return c
}
@@ -317,7 +317,6 @@ func (c *Client) R() *Request {
pathParams: map[string]string{},
jsonEscapeHTML: true,
}
-
return r
}
@@ -526,59 +525,6 @@ func (c *Client) AddRetryCondition(condition RetryConditionFunc) *Client {
return c
}
-// SetHTTPMode method sets go-resty mode to 'http'
-func (c *Client) SetHTTPMode() *Client {
- return c.SetMode("http")
-}
-
-// SetRESTMode method sets go-resty mode to 'rest'
-func (c *Client) SetRESTMode() *Client {
- return c.SetMode("rest")
-}
-
-// SetMode method sets go-resty client mode to given value such as 'http' & 'rest'.
-// 'rest':
-// - No Redirect
-// - Automatic response unmarshal if it is JSON or XML
-// 'http':
-// - Up to 10 Redirects
-// - No automatic unmarshall. Response will be treated as `response.String()`
-//
-// If you want more redirects, use FlexibleRedirectPolicy
-// resty.SetRedirectPolicy(FlexibleRedirectPolicy(20))
-//
-func (c *Client) SetMode(mode string) *Client {
- // HTTP
- if mode == "http" {
- c.isHTTPMode = true
- c.SetRedirectPolicy(FlexibleRedirectPolicy(10))
- c.afterResponse = []func(*Client, *Response) error{
- responseLogger,
- saveResponseIntoFile,
- }
- return c
- }
-
- // RESTful
- c.isHTTPMode = false
- c.SetRedirectPolicy(NoRedirectPolicy())
- c.afterResponse = []func(*Client, *Response) error{
- responseLogger,
- parseResponseBody,
- saveResponseIntoFile,
- }
- return c
-}
-
-// Mode method returns the current client mode. Typically its a "http" or "rest".
-// Default is "rest"
-func (c *Client) Mode() string {
- if c.isHTTPMode {
- return "http"
- }
- return "rest"
-}
-
// SetTLSClientConfig method sets TLSClientConfig for underling client Transport.
//
// Example:
@@ -669,7 +615,6 @@ func (c *Client) SetRootCertificate(pemFilePath string) *Client {
}
config.RootCAs.AppendCertsFromPEM(rootPemData)
-
return c
}
@@ -716,7 +661,6 @@ func (c *Client) SetScheme(scheme string) *Client {
if !IsStringEmpty(scheme) {
c.scheme = scheme
}
-
return c
}
@@ -784,9 +728,9 @@ func (c *Client) GetClient() *http.Client {
return c.httpClient
}
-//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Client Unexported methods
-//___________________________________
+//_______________________________________________________________________
// executes the given `Request` object and returns response
func (c *Client) execute(req *Request) (*Response, error) {
@@ -869,18 +813,6 @@ func (c *Client) execute(req *Request) (*Response, error) {
return response, err
}
-// enables a log prefix
-func (c *Client) enableLogPrefix() {
- c.Log.SetFlags(log.LstdFlags)
- c.Log.SetPrefix(c.logPrefix)
-}
-
-// disables a log prefix
-func (c *Client) disableLogPrefix() {
- c.Log.SetFlags(0)
- c.Log.SetPrefix("")
-}
-
// getting TLS client config if not exists then create one
func (c *Client) getTLSConfig() (*tls.Config, error) {
transport, err := c.getTransport()
@@ -919,9 +851,9 @@ func (c *Client) getTransport() (*http.Transport, error) {
return nil, errors.New("current transport is not an *http.Transport instance")
}
-//
-// File
-//
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+// File struct and its methods
+//_______________________________________________________________________
// File represent file information for multipart request
type File struct {
@@ -935,6 +867,10 @@ func (f *File) String() string {
return fmt.Sprintf("ParamName: %v; FileName: %v", f.ParamName, f.Name)
}
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+// MultipartField struct
+//_______________________________________________________________________
+
// MultipartField represent custom data part for multipart request
type MultipartField struct {
Param string
@@ -942,3 +878,49 @@ type MultipartField struct {
ContentType string
io.Reader
}
+
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+// Unexported package methods
+//_______________________________________________________________________
+
+func createClient(hc *http.Client) *Client {
+ c := &Client{ // not setting default values
+ QueryParam: url.Values{},
+ FormData: url.Values{},
+ Header: http.Header{},
+ Cookies: make([]*http.Cookie, 0),
+ Log: getLogger(os.Stderr),
+ RetryWaitTime: defaultWaitTime,
+ RetryMaxWaitTime: defaultMaxWaitTime,
+ JSONMarshal: json.Marshal,
+ JSONUnmarshal: json.Unmarshal,
+ jsonEscapeHTML: true,
+ httpClient: hc,
+ debugBodySizeLimit: math.MaxInt32,
+ pathParams: make(map[string]string),
+ }
+
+ // Log Prefix
+ c.SetLogPrefix("RESTY ")
+
+ // default before request middlewares
+ c.beforeRequest = []func(*Client, *Request) error{
+ parseRequestURL,
+ parseRequestHeader,
+ parseRequestBody,
+ createHTTPRequest,
+ addCredentials,
+ }
+
+ // user defined request middlewares
+ c.udBeforeRequest = []func(*Client, *Request) error{}
+
+ // default after response middlewares
+ c.afterResponse = []func(*Client, *Response) error{
+ responseLogger,
+ parseResponseBody,
+ saveResponseIntoFile,
+ }
+
+ return c
+}
diff --git a/client_test.go b/client_test.go
index 5300419a..3fa5aab9 100644
--- a/client_test.go
+++ b/client_test.go
@@ -79,24 +79,23 @@ func TestClientRedirectPolicy(t *testing.T) {
ts := createRedirectServer(t)
defer ts.Close()
- c := dc()
- c.SetHTTPMode().
- SetRedirectPolicy(FlexibleRedirectPolicy(20))
-
+ c := dc().SetRedirectPolicy(FlexibleRedirectPolicy(20))
_, err := c.R().Get(ts.URL + "/redirect-1")
assertEqual(t, "Get /redirect-21: stopped after 20 redirects", err.Error())
+
+ c.SetRedirectPolicy(NoRedirectPolicy())
+ _, err = c.R().Get(ts.URL + "/redirect-1")
+ assertEqual(t, "Get /redirect-2: auto redirect is disabled", err.Error())
}
func TestClientTimeout(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- c := dc()
- c.SetHTTPMode().
- SetTimeout(time.Second * 3)
-
+ c := dc().SetTimeout(time.Second * 3)
_, err := c.R().Get(ts.URL + "/set-timeout-test")
+
assertEqual(t, true, strings.Contains(strings.ToLower(err.Error()), "timeout"))
}
@@ -104,11 +103,9 @@ func TestClientTimeoutWithinThreshold(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- c := dc()
- c.SetHTTPMode().
- SetTimeout(time.Second * 3)
-
+ c := dc().SetTimeout(time.Second * 3)
resp, err := c.R().Get(ts.URL + "/set-timeout-test-with-sequence")
+
assertError(t, err)
seq1, _ := strconv.ParseInt(resp.String(), 10, 32)
@@ -122,10 +119,7 @@ func TestClientTimeoutWithinThreshold(t *testing.T) {
}
func TestClientTimeoutInternalError(t *testing.T) {
- c := dc()
- c.SetHTTPMode()
- c.SetTimeout(time.Second * 1)
-
+ c := dc().SetTimeout(time.Second * 1)
_, _ = c.R().Get("http://localhost:9000/set-timeout-test")
}
@@ -151,37 +145,37 @@ func TestClientProxy(t *testing.T) {
}
func TestClientSetCertificates(t *testing.T) {
- DefaultClient = dc()
- SetCertificates(tls.Certificate{})
+ client := dc()
+ client.SetCertificates(tls.Certificate{})
- transport, err := DefaultClient.getTransport()
+ transport, err := client.getTransport()
assertNil(t, err)
assertEqual(t, 1, len(transport.TLSClientConfig.Certificates))
}
func TestClientSetRootCertificate(t *testing.T) {
- DefaultClient = dc()
- SetRootCertificate(filepath.Join(getTestDataPath(), "sample-root.pem"))
+ client := dc()
+ client.SetRootCertificate(filepath.Join(getTestDataPath(), "sample-root.pem"))
- transport, err := DefaultClient.getTransport()
+ transport, err := client.getTransport()
assertNil(t, err)
assertNotNil(t, transport.TLSClientConfig.RootCAs)
}
func TestClientSetRootCertificateNotExists(t *testing.T) {
- DefaultClient = dc()
- SetRootCertificate(filepath.Join(getTestDataPath(), "not-exists-sample-root.pem"))
+ client := dc()
+ client.SetRootCertificate(filepath.Join(getTestDataPath(), "not-exists-sample-root.pem"))
- transport, err := DefaultClient.getTransport()
+ transport, err := client.getTransport()
assertNil(t, err)
assertNil(t, transport.TLSClientConfig)
}
func TestClientOnBeforeRequestModification(t *testing.T) {
- tc := New()
+ tc := dc()
tc.OnBeforeRequest(func(c *Client, r *Request) error {
r.SetAuthToken("This is test auth token")
return nil
@@ -204,7 +198,7 @@ func TestClientOnBeforeRequestModification(t *testing.T) {
func TestClientSetTransport(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- DefaultClient = dc()
+ client := dc()
transport := &http.Transport{
// something like Proxying to httptest.Server, etc...
@@ -212,53 +206,48 @@ func TestClientSetTransport(t *testing.T) {
return url.Parse(ts.URL)
},
}
- SetTransport(transport)
-
- transportInUse, err := DefaultClient.getTransport()
+ client.SetTransport(transport)
+ transportInUse, err := client.getTransport()
assertNil(t, err)
-
assertEqual(t, true, transport == transportInUse)
}
func TestClientSetScheme(t *testing.T) {
- DefaultClient = dc()
+ client := dc()
- SetScheme("http")
+ client.SetScheme("http")
- assertEqual(t, true, DefaultClient.scheme == "http")
+ assertEqual(t, true, client.scheme == "http")
}
func TestClientSetCookieJar(t *testing.T) {
- DefaultClient = dc()
- backupJar := DefaultClient.httpClient.Jar
+ client := dc()
+ backupJar := client.httpClient.Jar
- SetCookieJar(nil)
- assertNil(t, DefaultClient.httpClient.Jar)
+ client.SetCookieJar(nil)
+ assertNil(t, client.httpClient.Jar)
- SetCookieJar(backupJar)
- assertEqual(t, true, DefaultClient.httpClient.Jar == backupJar)
+ client.SetCookieJar(backupJar)
+ assertEqual(t, true, client.httpClient.Jar == backupJar)
}
func TestClientOptions(t *testing.T) {
- SetHTTPMode().SetContentLength(true)
- assertEqual(t, Mode(), "http")
- assertEqual(t, DefaultClient.setContentLength, true)
-
- SetRESTMode()
- assertEqual(t, Mode(), "rest")
+ client := dc()
+ client.SetContentLength(true)
+ assertEqual(t, client.setContentLength, true)
- SetHostURL("http://httpbin.org")
- assertEqual(t, "http://httpbin.org", DefaultClient.HostURL)
+ client.SetHostURL("http://httpbin.org")
+ assertEqual(t, "http://httpbin.org", client.HostURL)
- SetHeader(hdrContentTypeKey, jsonContentType)
- SetHeaders(map[string]string{
+ client.SetHeader(hdrContentTypeKey, jsonContentType)
+ client.SetHeaders(map[string]string{
hdrUserAgentKey: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) go-resty v0.1",
"X-Request-Id": strconv.FormatInt(time.Now().UnixNano(), 10),
})
- assertEqual(t, jsonContentType, DefaultClient.Header.Get(hdrContentTypeKey))
+ assertEqual(t, jsonContentType, client.Header.Get(hdrContentTypeKey))
- SetCookie(&http.Cookie{
+ client.SetCookie(&http.Cookie{
Name: "default-cookie",
Value: "This is cookie default-cookie value",
Path: "/",
@@ -267,7 +256,7 @@ func TestClientOptions(t *testing.T) {
HttpOnly: true,
Secure: false,
})
- assertEqual(t, "default-cookie", DefaultClient.Cookies[0].Name)
+ assertEqual(t, "default-cookie", client.Cookies[0].Name)
var cookies []*http.Cookie
cookies = append(cookies, &http.Cookie{
@@ -280,91 +269,92 @@ func TestClientOptions(t *testing.T) {
Value: "This is default-cookie 2 value",
Path: "/",
})
- SetCookies(cookies)
- assertEqual(t, "default-cookie-1", DefaultClient.Cookies[1].Name)
- assertEqual(t, "default-cookie-2", DefaultClient.Cookies[2].Name)
+ client.SetCookies(cookies)
+ assertEqual(t, "default-cookie-1", client.Cookies[1].Name)
+ assertEqual(t, "default-cookie-2", client.Cookies[2].Name)
- SetQueryParam("test_param_1", "Param_1")
- SetQueryParams(map[string]string{"test_param_2": "Param_2", "test_param_3": "Param_3"})
- assertEqual(t, "Param_3", DefaultClient.QueryParam.Get("test_param_3"))
+ client.SetQueryParam("test_param_1", "Param_1")
+ client.SetQueryParams(map[string]string{"test_param_2": "Param_2", "test_param_3": "Param_3"})
+ assertEqual(t, "Param_3", client.QueryParam.Get("test_param_3"))
rTime := strconv.FormatInt(time.Now().UnixNano(), 10)
- SetFormData(map[string]string{"r_time": rTime})
- assertEqual(t, rTime, DefaultClient.FormData.Get("r_time"))
+ client.SetFormData(map[string]string{"r_time": rTime})
+ assertEqual(t, rTime, client.FormData.Get("r_time"))
- SetBasicAuth("myuser", "mypass")
- assertEqual(t, "myuser", DefaultClient.UserInfo.Username)
+ client.SetBasicAuth("myuser", "mypass")
+ assertEqual(t, "myuser", client.UserInfo.Username)
- SetAuthToken("AC75BD37F019E08FBC594900518B4F7E")
- assertEqual(t, "AC75BD37F019E08FBC594900518B4F7E", DefaultClient.Token)
+ client.SetAuthToken("AC75BD37F019E08FBC594900518B4F7E")
+ assertEqual(t, "AC75BD37F019E08FBC594900518B4F7E", client.Token)
- SetDisableWarn(true)
- assertEqual(t, DefaultClient.DisableWarn, true)
+ client.SetDisableWarn(true)
+ assertEqual(t, client.DisableWarn, true)
- SetRetryCount(3)
- assertEqual(t, 3, DefaultClient.RetryCount)
+ client.SetRetryCount(3)
+ assertEqual(t, 3, client.RetryCount)
rwt := time.Duration(1000) * time.Millisecond
- SetRetryWaitTime(rwt)
- assertEqual(t, rwt, DefaultClient.RetryWaitTime)
+ client.SetRetryWaitTime(rwt)
+ assertEqual(t, rwt, client.RetryWaitTime)
mrwt := time.Duration(2) * time.Second
- SetRetryMaxWaitTime(mrwt)
- assertEqual(t, mrwt, DefaultClient.RetryMaxWaitTime)
+ client.SetRetryMaxWaitTime(mrwt)
+ assertEqual(t, mrwt, client.RetryMaxWaitTime)
err := &AuthError{}
- SetError(err)
- if reflect.TypeOf(err) == DefaultClient.Error {
+ client.SetError(err)
+ if reflect.TypeOf(err) == client.Error {
t.Error("SetError failed")
}
- SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
- transport, transportErr := DefaultClient.getTransport()
+ client.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
+ transport, transportErr := client.getTransport()
assertNil(t, transportErr)
assertEqual(t, true, transport.TLSClientConfig.InsecureSkipVerify)
- OnBeforeRequest(func(c *Client, r *Request) error {
+ client.OnBeforeRequest(func(c *Client, r *Request) error {
c.Log.Println("I'm in Request middleware")
return nil // if it success
})
- OnAfterResponse(func(c *Client, r *Response) error {
+ client.OnAfterResponse(func(c *Client, r *Response) error {
c.Log.Println("I'm in Response middleware")
return nil // if it success
})
- SetTimeout(5 * time.Second)
- SetRedirectPolicy(FlexibleRedirectPolicy(10), func(req *http.Request, via []*http.Request) error {
+ client.SetTimeout(5 * time.Second)
+ client.SetRedirectPolicy(FlexibleRedirectPolicy(10), func(req *http.Request, via []*http.Request) error {
return errors.New("sample test redirect")
})
- SetContentLength(true)
+ client.SetContentLength(true)
- SetDebug(true)
- assertEqual(t, DefaultClient.Debug, true)
+ client.SetDebug(true)
+ assertEqual(t, client.Debug, true)
var sl int64 = 1000000
- SetDebugBodyLimit(sl)
- assertEqual(t, DefaultClient.debugBodySizeLimit, sl)
+ client.SetDebugBodyLimit(sl)
+ assertEqual(t, client.debugBodySizeLimit, sl)
- SetAllowGetMethodPayload(true)
- assertEqual(t, DefaultClient.AllowGetMethodPayload, true)
+ client.SetAllowGetMethodPayload(true)
+ assertEqual(t, client.AllowGetMethodPayload, true)
- SetScheme("http")
- assertEqual(t, DefaultClient.scheme, "http")
+ client.SetScheme("http")
+ assertEqual(t, client.scheme, "http")
- SetCloseConnection(true)
- assertEqual(t, DefaultClient.closeConnection, true)
+ client.SetCloseConnection(true)
+ assertEqual(t, client.closeConnection, true)
- SetLogger(ioutil.Discard)
+ client.SetLogger(ioutil.Discard)
}
func TestClientPreRequestHook(t *testing.T) {
- SetPreRequestHook(func(c *Client, r *http.Request) error {
+ client := dc()
+ client.SetPreRequestHook(func(c *Client, r *http.Request) error {
c.Log.Println("I'm in Pre-Request Hook")
return nil
})
- SetPreRequestHook(func(c *Client, r *http.Request) error {
+ client.SetPreRequestHook(func(c *Client, r *http.Request) error {
c.Log.Println("I'm Overwriting existing Pre-Request Hook")
return nil
})
@@ -408,13 +398,6 @@ func TestClientRoundTripper(t *testing.T) {
func TestClientNewRequest(t *testing.T) {
c := New()
request := c.NewRequest()
-
- assertNotNil(t, request)
-}
-
-func TestNewRequest(t *testing.T) {
- request := NewRequest()
-
assertNotNil(t, request)
}
@@ -463,18 +446,6 @@ func (rt *CustomRoundTripper) RoundTrip(_ *http.Request) (*http.Response, error)
return &http.Response{}, nil
}
-func TestSetLogPrefix(t *testing.T) {
- c := New()
- c.SetLogPrefix("CUSTOM ")
- assertEqual(t, "CUSTOM ", c.logPrefix)
- assertEqual(t, "CUSTOM ", c.Log.Prefix())
-
- c.disableLogPrefix()
- c.enableLogPrefix()
- assertEqual(t, "CUSTOM ", c.logPrefix)
- assertEqual(t, "CUSTOM ", c.Log.Prefix())
-}
-
func TestAutoGzip(t *testing.T) {
ts := createGenServer(t)
defer ts.Close()
diff --git a/context17_test.go b/context17_test.go
deleted file mode 100644
index 5ca51a4a..00000000
--- a/context17_test.go
+++ /dev/null
@@ -1,15 +0,0 @@
-// +build !go1.8
-
-// Copyright (c) 2015-2019 Jeevanandam M (jeeva@myjeeva.com)
-// 2016 Andrew Grigorev (https://github.com/ei-grad)
-// All rights reserved.
-// resty source code and usage is governed by a MIT style
-// license that can be found in the LICENSE file.
-
-package resty
-
-import "strings"
-
-func errIsContextCanceled(err error) bool {
- return strings.Contains(err.Error(), "request canceled")
-}
diff --git a/context18_test.go b/context18_test.go
deleted file mode 100644
index 93159b75..00000000
--- a/context18_test.go
+++ /dev/null
@@ -1,31 +0,0 @@
-// +build go1.8
-
-// Copyright (c) 2015-2019 Jeevanandam M (jeeva@myjeeva.com)
-// 2016 Andrew Grigorev (https://github.com/ei-grad)
-// All rights reserved.
-// resty source code and usage is governed by a MIT style
-// license that can be found in the LICENSE file.
-
-package resty
-
-import (
- "context"
- "net/url"
- "testing"
-)
-
-func TestRequestContext(t *testing.T) {
- r := NewRequest()
- assertNotNil(t, r.Context())
-
- r.SetContext(context.Background())
- assertNotNil(t, r.Context())
-}
-
-func errIsContextCanceled(err error) bool {
- ue, ok := err.(*url.Error)
- if !ok {
- return false
- }
- return ue.Err == context.Canceled
-}
diff --git a/context_test.go b/context_test.go
index d0b9d1c7..5b69506d 100644
--- a/context_test.go
+++ b/context_test.go
@@ -1,5 +1,3 @@
-// +build go1.7
-
// Copyright (c) 2015-2019 Jeevanandam M (jeeva@myjeeva.com)
// 2016 Andrew Grigorev (https://github.com/ei-grad)
// All rights reserved.
@@ -11,6 +9,7 @@ package resty
import (
"context"
"net/http"
+ "net/url"
"strings"
"sync/atomic"
"testing"
@@ -21,7 +20,7 @@ func TestSetContext(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- resp, err := R().
+ resp, err := dc().R().
SetContext(context.Background()).
Get(ts.URL + "/")
@@ -72,7 +71,7 @@ func TestSetContextCancel(t *testing.T) {
cancel()
}()
- _, err := R().
+ _, err := dc().R().
SetContext(ctx).
Get(ts.URL + "/")
@@ -111,8 +110,7 @@ func TestSetContextCancelRetry(t *testing.T) {
cancel()
}()
- c := dc()
- c.SetHTTPMode().
+ c := dc().
SetTimeout(time.Second * 3).
SetRetryCount(3)
@@ -159,7 +157,7 @@ func TestSetContextCancelWithError(t *testing.T) {
cancel()
}()
- _, err := R().
+ _, err := dc().R().
SetContext(ctx).
Get(ts.URL + "/")
@@ -186,8 +184,7 @@ func TestClientRetryWithSetContext(t *testing.T) {
})
defer ts.Close()
- c := dc()
- c.SetHTTPMode().
+ c := dc().
SetTimeout(time.Second * 1).
SetRetryCount(3)
@@ -197,3 +194,20 @@ func TestClientRetryWithSetContext(t *testing.T) {
assertEqual(t, true, strings.HasPrefix(err.Error(), "Get "+ts.URL+"/"))
}
+
+func TestRequestContext(t *testing.T) {
+ client := dc()
+ r := client.NewRequest()
+ assertNotNil(t, r.Context())
+
+ r.SetContext(context.Background())
+ assertNotNil(t, r.Context())
+}
+
+func errIsContextCanceled(err error) bool {
+ ue, ok := err.(*url.Error)
+ if !ok {
+ return false
+ }
+ return ue.Err == context.Canceled
+}
diff --git a/default.go b/default.go
deleted file mode 100644
index 52b6523d..00000000
--- a/default.go
+++ /dev/null
@@ -1,327 +0,0 @@
-// Copyright (c) 2015-2019 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
-// resty source code and usage is governed by a MIT style
-// license that can be found in the LICENSE file.
-
-package resty
-
-import (
- "crypto/tls"
- "encoding/json"
- "io"
- "math"
- "net/http"
- "net/http/cookiejar"
- "net/url"
- "os"
- "time"
-
- "golang.org/x/net/publicsuffix"
-)
-
-// DefaultClient of resty
-var DefaultClient *Client
-
-// New method creates a new go-resty client.
-func New() *Client {
- cookieJar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
- return createClient(&http.Client{Jar: cookieJar})
-}
-
-// NewWithClient method create a new go-resty client with given `http.Client`.
-func NewWithClient(hc *http.Client) *Client {
- return createClient(hc)
-}
-
-// R creates a new resty request object, it is used form a HTTP/RESTful request
-// such as GET, POST, PUT, DELETE, HEAD, PATCH and OPTIONS.
-func R() *Request {
- return DefaultClient.R()
-}
-
-// NewRequest is an alias for R(). Creates a new resty request object, it is used form a HTTP/RESTful request
-// such as GET, POST, PUT, DELETE, HEAD, PATCH and OPTIONS.
-func NewRequest() *Request {
- return R()
-}
-
-// SetHostURL sets Host URL. See `Client.SetHostURL for more information.
-func SetHostURL(url string) *Client {
- return DefaultClient.SetHostURL(url)
-}
-
-// SetHeader sets single header. See `Client.SetHeader` for more information.
-func SetHeader(header, value string) *Client {
- return DefaultClient.SetHeader(header, value)
-}
-
-// SetHeaders sets multiple headers. See `Client.SetHeaders` for more information.
-func SetHeaders(headers map[string]string) *Client {
- return DefaultClient.SetHeaders(headers)
-}
-
-// SetCookieJar sets custom http.CookieJar. See `Client.SetCookieJar` for more information.
-func SetCookieJar(jar http.CookieJar) *Client {
- return DefaultClient.SetCookieJar(jar)
-}
-
-// SetCookie sets single cookie object. See `Client.SetCookie` for more information.
-func SetCookie(hc *http.Cookie) *Client {
- return DefaultClient.SetCookie(hc)
-}
-
-// SetCookies sets multiple cookie object. See `Client.SetCookies` for more information.
-func SetCookies(cs []*http.Cookie) *Client {
- return DefaultClient.SetCookies(cs)
-}
-
-// SetQueryParam method sets single parameter and its value. See `Client.SetQueryParam` for more information.
-func SetQueryParam(param, value string) *Client {
- return DefaultClient.SetQueryParam(param, value)
-}
-
-// SetQueryParams method sets multiple parameters and its value. See `Client.SetQueryParams` for more information.
-func SetQueryParams(params map[string]string) *Client {
- return DefaultClient.SetQueryParams(params)
-}
-
-// SetFormData method sets Form parameters and its values. See `Client.SetFormData` for more information.
-func SetFormData(data map[string]string) *Client {
- return DefaultClient.SetFormData(data)
-}
-
-// SetBasicAuth method sets the basic authentication header. See `Client.SetBasicAuth` for more information.
-func SetBasicAuth(username, password string) *Client {
- return DefaultClient.SetBasicAuth(username, password)
-}
-
-// SetAuthToken method sets bearer auth token header. See `Client.SetAuthToken` for more information.
-func SetAuthToken(token string) *Client {
- return DefaultClient.SetAuthToken(token)
-}
-
-// OnBeforeRequest method sets request middleware. See `Client.OnBeforeRequest` for more information.
-func OnBeforeRequest(m func(*Client, *Request) error) *Client {
- return DefaultClient.OnBeforeRequest(m)
-}
-
-// OnAfterResponse method sets response middleware. See `Client.OnAfterResponse` for more information.
-func OnAfterResponse(m func(*Client, *Response) error) *Client {
- return DefaultClient.OnAfterResponse(m)
-}
-
-// SetPreRequestHook method sets the pre-request hook. See `Client.SetPreRequestHook` for more information.
-func SetPreRequestHook(h func(*Client, *http.Request) error) *Client {
- return DefaultClient.SetPreRequestHook(h)
-}
-
-// SetDebug method enables the debug mode. See `Client.SetDebug` for more information.
-func SetDebug(d bool) *Client {
- return DefaultClient.SetDebug(d)
-}
-
-// SetDebugBodyLimit method sets the response body limit for debug mode. See `Client.SetDebugBodyLimit` for more information.
-func SetDebugBodyLimit(sl int64) *Client {
- return DefaultClient.SetDebugBodyLimit(sl)
-}
-
-// SetAllowGetMethodPayload method allows the GET method with payload. See `Client.SetAllowGetMethodPayload` for more information.
-func SetAllowGetMethodPayload(a bool) *Client {
- return DefaultClient.SetAllowGetMethodPayload(a)
-}
-
-// SetRetryCount method sets the retry count. See `Client.SetRetryCount` for more information.
-func SetRetryCount(count int) *Client {
- return DefaultClient.SetRetryCount(count)
-}
-
-// SetRetryWaitTime method sets the retry wait time. See `Client.SetRetryWaitTime` for more information.
-func SetRetryWaitTime(waitTime time.Duration) *Client {
- return DefaultClient.SetRetryWaitTime(waitTime)
-}
-
-// SetRetryMaxWaitTime method sets the retry max wait time. See `Client.SetRetryMaxWaitTime` for more information.
-func SetRetryMaxWaitTime(maxWaitTime time.Duration) *Client {
- return DefaultClient.SetRetryMaxWaitTime(maxWaitTime)
-}
-
-// AddRetryCondition method appends check function for retry. See `Client.AddRetryCondition` for more information.
-func AddRetryCondition(condition RetryConditionFunc) *Client {
- return DefaultClient.AddRetryCondition(condition)
-}
-
-// SetDisableWarn method disables warning comes from `go-resty` client. See `Client.SetDisableWarn` for more information.
-func SetDisableWarn(d bool) *Client {
- return DefaultClient.SetDisableWarn(d)
-}
-
-// SetLogger method sets given writer for logging. See `Client.SetLogger` for more information.
-func SetLogger(w io.Writer) *Client {
- return DefaultClient.SetLogger(w)
-}
-
-// SetContentLength method enables `Content-Length` value. See `Client.SetContentLength` for more information.
-func SetContentLength(l bool) *Client {
- return DefaultClient.SetContentLength(l)
-}
-
-// SetError method is to register the global or client common `Error` object. See `Client.SetError` for more information.
-func SetError(err interface{}) *Client {
- return DefaultClient.SetError(err)
-}
-
-// SetRedirectPolicy method sets the client redirect poilicy. See `Client.SetRedirectPolicy` for more information.
-func SetRedirectPolicy(policies ...interface{}) *Client {
- return DefaultClient.SetRedirectPolicy(policies...)
-}
-
-// SetHTTPMode method sets go-resty mode into HTTP. See `Client.SetMode` for more information.
-func SetHTTPMode() *Client {
- return DefaultClient.SetHTTPMode()
-}
-
-// SetRESTMode method sets go-resty mode into RESTful. See `Client.SetMode` for more information.
-func SetRESTMode() *Client {
- return DefaultClient.SetRESTMode()
-}
-
-// Mode method returns the current client mode. See `Client.Mode` for more information.
-func Mode() string {
- return DefaultClient.Mode()
-}
-
-// SetTLSClientConfig method sets TLSClientConfig for underling client Transport. See `Client.SetTLSClientConfig` for more information.
-func SetTLSClientConfig(config *tls.Config) *Client {
- return DefaultClient.SetTLSClientConfig(config)
-}
-
-// SetTimeout method sets timeout for request. See `Client.SetTimeout` for more information.
-func SetTimeout(timeout time.Duration) *Client {
- return DefaultClient.SetTimeout(timeout)
-}
-
-// SetProxy method sets Proxy for request. See `Client.SetProxy` for more information.
-func SetProxy(proxyURL string) *Client {
- return DefaultClient.SetProxy(proxyURL)
-}
-
-// RemoveProxy method removes the proxy configuration. See `Client.RemoveProxy` for more information.
-func RemoveProxy() *Client {
- return DefaultClient.RemoveProxy()
-}
-
-// SetCertificates method helps to set client certificates into resty conveniently.
-// See `Client.SetCertificates` for more information and example.
-func SetCertificates(certs ...tls.Certificate) *Client {
- return DefaultClient.SetCertificates(certs...)
-}
-
-// SetRootCertificate method helps to add one or more root certificates into resty client.
-// See `Client.SetRootCertificate` for more information.
-func SetRootCertificate(pemFilePath string) *Client {
- return DefaultClient.SetRootCertificate(pemFilePath)
-}
-
-// SetOutputDirectory method sets output directory. See `Client.SetOutputDirectory` for more information.
-func SetOutputDirectory(dirPath string) *Client {
- return DefaultClient.SetOutputDirectory(dirPath)
-}
-
-// SetTransport method sets custom `*http.Transport` or any `http.RoundTripper`
-// compatible interface implementation in the resty client.
-// See `Client.SetTransport` for more information.
-func SetTransport(transport http.RoundTripper) *Client {
- return DefaultClient.SetTransport(transport)
-}
-
-// SetScheme method sets custom scheme in the resty client.
-// See `Client.SetScheme` for more information.
-func SetScheme(scheme string) *Client {
- return DefaultClient.SetScheme(scheme)
-}
-
-// SetCloseConnection method sets close connection value in the resty client.
-// See `Client.SetCloseConnection` for more information.
-func SetCloseConnection(close bool) *Client {
- return DefaultClient.SetCloseConnection(close)
-}
-
-// SetDoNotParseResponse method instructs `Resty` not to parse the response body automatically.
-// See `Client.SetDoNotParseResponse` for more information.
-func SetDoNotParseResponse(parse bool) *Client {
- return DefaultClient.SetDoNotParseResponse(parse)
-}
-
-// SetPathParams method sets the Request path parameter key-value pairs. See
-// `Client.SetPathParams` for more information.
-func SetPathParams(params map[string]string) *Client {
- return DefaultClient.SetPathParams(params)
-}
-
-// IsProxySet method returns the true if proxy is set on client otherwise false.
-// See `Client.IsProxySet` for more information.
-func IsProxySet() bool {
- return DefaultClient.IsProxySet()
-}
-
-// GetClient method returns the current `http.Client` used by the default resty client.
-func GetClient() *http.Client {
- return DefaultClient.httpClient
-}
-
-//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
-// Unexported methods
-//___________________________________
-
-func createClient(hc *http.Client) *Client {
- c := &Client{
- HostURL: "",
- QueryParam: url.Values{},
- FormData: url.Values{},
- Header: http.Header{},
- UserInfo: nil,
- Token: "",
- Cookies: make([]*http.Cookie, 0),
- Debug: false,
- Log: getLogger(os.Stderr),
- RetryCount: 0,
- RetryWaitTime: defaultWaitTime,
- RetryMaxWaitTime: defaultMaxWaitTime,
- JSONMarshal: json.Marshal,
- JSONUnmarshal: json.Unmarshal,
- jsonEscapeHTML: true,
- httpClient: hc,
- debugBodySizeLimit: math.MaxInt32,
- pathParams: make(map[string]string),
- }
-
- // Log Prefix
- c.SetLogPrefix("RESTY ")
-
- // Default redirect policy
- c.SetRedirectPolicy(NoRedirectPolicy())
-
- // default before request middlewares
- c.beforeRequest = []func(*Client, *Request) error{
- parseRequestURL,
- parseRequestHeader,
- parseRequestBody,
- createHTTPRequest,
- addCredentials,
- }
-
- // user defined request middlewares
- c.udBeforeRequest = []func(*Client, *Request) error{}
-
- // default after response middlewares
- c.afterResponse = []func(*Client, *Response) error{
- responseLogger,
- parseResponseBody,
- saveResponseIntoFile,
- }
-
- return c
-}
-
-func init() {
- DefaultClient = New()
-}
diff --git a/example_test.go b/example_test.go
index 35da1031..b1491dd1 100644
--- a/example_test.go
+++ b/example_test.go
@@ -43,7 +43,10 @@ type Error struct {
//
func Example_get() {
- resp, err := resty.R().Get("http://httpbin.org/get")
+ // Create a resty client
+ client := resty.New()
+
+ resp, err := client.R().Get("http://httpbin.org/get")
fmt.Printf("\nError: %v", err)
fmt.Printf("\nResponse Status Code: %v", resp.StatusCode())
@@ -54,7 +57,10 @@ func Example_get() {
}
func Example_enhancedGet() {
- resp, err := resty.R().
+ // Create a resty client
+ client := resty.New()
+
+ resp, err := client.R().
SetQueryParams(map[string]string{
"page_no": "1",
"limit": "20",
@@ -70,9 +76,12 @@ func Example_enhancedGet() {
}
func Example_post() {
+ // Create a resty client
+ client := resty.New()
+
// POST JSON string
// No need to set content type, if you have client level setting
- resp, err := resty.R().
+ resp, err := client.R().
SetHeader("Content-Type", "application/json").
SetBody(`{"username":"testuser", "password":"testpass"}`).
SetResult(AuthSuccess{}). // or SetResult(&AuthSuccess{}).
@@ -82,7 +91,7 @@ func Example_post() {
// POST []byte array
// No need to set content type, if you have client level setting
- resp1, err1 := resty.R().
+ resp1, err1 := client.R().
SetHeader("Content-Type", "application/json").
SetBody([]byte(`{"username":"testuser", "password":"testpass"}`)).
SetResult(AuthSuccess{}). // or SetResult(&AuthSuccess{}).
@@ -91,7 +100,7 @@ func Example_post() {
printOutput(resp1, err1)
// POST Struct, default is JSON content type. No need to set one
- resp2, err2 := resty.R().
+ resp2, err2 := client.R().
SetBody(resty.User{Username: "testuser", Password: "testpass"}).
SetResult(&AuthSuccess{}). // or SetResult(AuthSuccess{}).
SetError(&AuthError{}). // or SetError(AuthError{}).
@@ -100,7 +109,7 @@ func Example_post() {
printOutput(resp2, err2)
// POST Map, default is JSON content type. No need to set one
- resp3, err3 := resty.R().
+ resp3, err3 := client.R().
SetBody(map[string]interface{}{"username": "testuser", "password": "testpass"}).
SetResult(&AuthSuccess{}). // or SetResult(AuthSuccess{}).
SetError(&AuthError{}). // or SetError(AuthError{}).
@@ -115,8 +124,11 @@ func Example_dropboxUpload() {
file, _ := os.Open("/Users/jeeva/mydocument.pdf")
fileBytes, _ := ioutil.ReadAll(file)
+ // Create a resty client
+ client := resty.New()
+
// See we are not setting content-type header, since go-resty automatically detects Content-Type for you
- resp, err := resty.R().
+ resp, err := client.R().
SetBody(fileBytes). // resty autodetects content type
SetContentLength(true). // Dropbox expects this value
SetAuthToken("").
@@ -130,10 +142,13 @@ func Example_dropboxUpload() {
}
func Example_put() {
+ // Create a resty client
+ client := resty.New()
+
// Just one sample of PUT, refer POST for more combination
// request goes as JSON content type
// No need to set auth token, error, if you have client level settings
- resp, err := resty.R().
+ resp, err := client.R().
SetBody(Article{
Title: "go-resty",
Content: "This is my article content, oh ya!",
@@ -154,11 +169,16 @@ func Example_clientCertificates() {
log.Fatalf("ERROR client certificate: %s", err)
}
- resty.SetCertificates(cert)
+ // Create a resty client
+ client := resty.New()
+
+ client.SetCertificates(cert)
}
func Example_customRootCertificate() {
- resty.SetRootCertificate("/path/to/root/pemFile.pem")
+ // Create a resty client
+ client := resty.New()
+ client.SetRootCertificate("/path/to/root/pemFile.pem")
}
//
@@ -188,7 +208,10 @@ func ExampleClient_SetCertificates() {
log.Fatalf("ERROR client certificate: %s", err)
}
- resty.SetCertificates(cert)
+ // Create a resty client
+ client := resty.New()
+
+ client.SetCertificates(cert)
}
//
@@ -205,10 +228,13 @@ func Example_socks5Proxy() {
// create a transport
ptransport := &http.Transport{Dial: dialer.Dial}
+ // Create a resty client
+ client := resty.New()
+
// set transport into resty
- resty.SetTransport(ptransport)
+ client.SetTransport(ptransport)
- resp, err := resty.R().Get("http://check.torproject.org")
+ resp, err := client.R().Get("http://check.torproject.org")
fmt.Println(err, resp)
}
diff --git a/middleware.go b/middleware.go
index 88014b50..3524b9a6 100644
--- a/middleware.go
+++ b/middleware.go
@@ -22,9 +22,9 @@ import (
const debugRequestLogKey = "__restyDebugRequestLog"
-//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Request Middleware(s)
-//___________________________________
+//_______________________________________________________________________
func parseRequestURL(c *Client, r *Request) error {
// GitHub #103 Path Params
@@ -190,7 +190,9 @@ func createHTTPRequest(c *Client, r *Request) (err error) {
}
// Use context if it was specified
- r.addContextIfAvailable()
+ if r.ctx != nil {
+ r.RawRequest = r.RawRequest.WithContext(r.ctx)
+ }
return
}
@@ -247,9 +249,9 @@ func requestLogger(c *Client, r *Request) error {
return nil
}
-//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Response Middleware(s)
-//___________________________________
+//_______________________________________________________________________
func responseLogger(c *Client, res *Response) error {
if c.Debug {
diff --git a/redirect.go b/redirect.go
index b9b7894b..49960c76 100644
--- a/redirect.go
+++ b/redirect.go
@@ -47,9 +47,7 @@ func FlexibleRedirectPolicy(noOfRedirect int) RedirectPolicy {
if len(via) >= noOfRedirect {
return fmt.Errorf("stopped after %d redirects", noOfRedirect)
}
-
checkHostAndAddHeaders(req, via[0])
-
return nil
})
}
@@ -74,6 +72,10 @@ func DomainCheckRedirectPolicy(hostnames ...string) RedirectPolicy {
return fn
}
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+// Package Unexported methods
+//_______________________________________________________________________
+
func getHostname(host string) (hostname string) {
if strings.Index(host, ":") > 0 {
host, _, _ = net.SplitHostPort(host)
diff --git a/request.go b/request.go
index f5aa4b9c..07cf4d72 100644
--- a/request.go
+++ b/request.go
@@ -5,21 +5,76 @@
package resty
import (
+ "bytes"
+ "context"
"encoding/base64"
"encoding/json"
"encoding/xml"
"fmt"
"io"
"net"
+ "net/http"
"net/url"
"reflect"
"strings"
+ "time"
)
-// SRVRecord holds the data to query the SRV record for the following service
-type SRVRecord struct {
- Service string
- Domain string
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+// Request struct and methods
+//_______________________________________________________________________
+
+// Request struct is used to compose and fire individual request from
+// resty client. Request provides an options to override client level
+// settings and also an options for the request composition.
+type Request struct {
+ URL string
+ Method string
+ Token string
+ QueryParam url.Values
+ FormData url.Values
+ Header http.Header
+ Time time.Time
+ Body interface{}
+ Result interface{}
+ Error interface{}
+ RawRequest *http.Request
+ SRV *SRVRecord
+ UserInfo *User
+
+ isMultiPart bool
+ isFormData bool
+ setContentLength bool
+ isSaveResponse bool
+ notParseResponse bool
+ jsonEscapeHTML bool
+ outputFile string
+ fallbackContentType string
+ ctx context.Context
+ pathParams map[string]string
+ client *Client
+ bodyBuf *bytes.Buffer
+ multipartFiles []*File
+ multipartFields []*MultipartField
+ values map[string]interface{}
+}
+
+// Context method returns the Context if its already set in request
+// otherwise it creates new one using `context.Background()`.
+func (r *Request) Context() context.Context {
+ if r.ctx == nil {
+ return context.Background()
+ }
+ return r.ctx
+}
+
+// SetContext method sets the context.Context for current Request. It allows
+// to interrupt the request execution if ctx.Done() channel is closed.
+// See https://blog.golang.org/context article and the "context" package
+// documentation.
+func (r *Request) SetContext(ctx context.Context) *Request {
+ r.ctx = ctx
+ return r
}
// SetHeader method is to set a single header field and its value in the current request.
@@ -49,7 +104,6 @@ func (r *Request) SetHeaders(headers map[string]string) *Request {
for h, v := range headers {
r.SetHeader(h, v)
}
-
return r
}
@@ -80,7 +134,6 @@ func (r *Request) SetQueryParams(params map[string]string) *Request {
for p, v := range params {
r.SetQueryParam(p, v)
}
-
return r
}
@@ -101,7 +154,6 @@ func (r *Request) SetQueryParamsFromValues(params url.Values) *Request {
r.QueryParam.Add(p, pv)
}
}
-
return r
}
@@ -139,7 +191,6 @@ func (r *Request) SetFormData(data map[string]string) *Request {
for k, v := range data {
r.FormData.Set(k, v)
}
-
return r
}
@@ -157,7 +208,6 @@ func (r *Request) SetFormDataFromValues(data url.Values) *Request {
r.FormData.Add(k, kv)
}
}
-
return r
}
@@ -259,11 +309,9 @@ func (r *Request) SetFile(param, filePath string) *Request {
//
func (r *Request) SetFiles(files map[string]string) *Request {
r.isMultiPart = true
-
for f, fp := range files {
r.FormData.Set("@"+f, fp)
}
-
return r
}
@@ -502,7 +550,7 @@ func (r *Request) Execute(method, url string) (*Response, error) {
resp, err = r.client.execute(r)
if err != nil {
r.client.Log.Printf("ERROR %v, Attempt %v", err, attempt)
- if r.isContextCancelledIfAvailable() {
+ if r.ctx != nil && r.ctx.Err() != nil {
// stop Backoff from retrying request if request has been
// canceled by context
return resp, nil
@@ -520,9 +568,19 @@ func (r *Request) Execute(method, url string) (*Response, error) {
return resp, err
}
-//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+// SRVRecord struct
+//_______________________________________________________________________
+
+// SRVRecord holds the data to query the SRV record for the following service
+type SRVRecord struct {
+ Service string
+ Domain string
+}
+
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Request Unexported methods
-//___________________________________
+//_______________________________________________________________________
func (r *Request) fmtBodyString() (body string) {
body = "***** NO CONTENT *****"
@@ -592,3 +650,12 @@ func (r *Request) initValuesMap() {
r.values = make(map[string]interface{})
}
}
+
+var noescapeJSONMarshal = func(v interface{}) ([]byte, error) {
+ buf := acquireBuffer()
+ defer releaseBuffer(buf)
+ encoder := json.NewEncoder(buf)
+ encoder.SetEscapeHTML(false)
+ err := encoder.Encode(v)
+ return buf.Bytes(), err
+}
diff --git a/request16.go b/request16.go
deleted file mode 100644
index c578af0b..00000000
--- a/request16.go
+++ /dev/null
@@ -1,64 +0,0 @@
-// +build !go1.7
-
-// Copyright (c) 2015-2019 Jeevanandam M (jeeva@myjeeva.com)
-// 2016 Andrew Grigorev (https://github.com/ei-grad)
-// All rights reserved.
-// resty source code and usage is governed by a MIT style
-// license that can be found in the LICENSE file.
-
-package resty
-
-import (
- "bytes"
- "encoding/json"
- "net/http"
- "net/url"
- "time"
-)
-
-// Request type is used to compose and send individual request from client
-// go-resty is provide option override client level settings such as
-// Auth Token, Basic Auth credentials, Header, Query Param, Form Data, Error object
-// and also you can add more options for that particular request
-type Request struct {
- URL string
- Method string
- Token string
- QueryParam url.Values
- FormData url.Values
- Header http.Header
- Time time.Time
- Body interface{}
- Result interface{}
- Error interface{}
- RawRequest *http.Request
- SRV *SRVRecord
- UserInfo *User
-
- isMultiPart bool
- isFormData bool
- setContentLength bool
- isSaveResponse bool
- notParseResponse bool
- jsonEscapeHTML bool
- outputFile string
- fallbackContentType string
- pathParams map[string]string
- client *Client
- bodyBuf *bytes.Buffer
- multipartFiles []*File
- multipartFields []*MultipartField
- values map[string]interface{}
-}
-
-func (r *Request) addContextIfAvailable() {
- // nothing to do for golang<1.7
-}
-
-func (r *Request) isContextCancelledIfAvailable() bool {
- // just always return false golang<1.7
- return false
-}
-
-// for !go1.7
-var noescapeJSONMarshal = json.Marshal
diff --git a/request17.go b/request17.go
deleted file mode 100644
index 8af35598..00000000
--- a/request17.go
+++ /dev/null
@@ -1,97 +0,0 @@
-// +build go1.7 go1.8
-
-// Copyright (c) 2015-2019 Jeevanandam M (jeeva@myjeeva.com)
-// 2016 Andrew Grigorev (https://github.com/ei-grad)
-// All rights reserved.
-// resty source code and usage is governed by a MIT style
-// license that can be found in the LICENSE file.
-
-package resty
-
-import (
- "bytes"
- "context"
- "encoding/json"
- "net/http"
- "net/url"
- "time"
-)
-
-// Request type is used to compose and send individual request from client
-// go-resty is provide option override client level settings such as
-// Auth Token, Basic Auth credentials, Header, Query Param, Form Data, Error object
-// and also you can add more options for that particular request
-type Request struct {
- URL string
- Method string
- Token string
- QueryParam url.Values
- FormData url.Values
- Header http.Header
- Time time.Time
- Body interface{}
- Result interface{}
- Error interface{}
- RawRequest *http.Request
- SRV *SRVRecord
- UserInfo *User
-
- isMultiPart bool
- isFormData bool
- setContentLength bool
- isSaveResponse bool
- notParseResponse bool
- jsonEscapeHTML bool
- outputFile string
- fallbackContentType string
- ctx context.Context
- pathParams map[string]string
- client *Client
- bodyBuf *bytes.Buffer
- multipartFiles []*File
- multipartFields []*MultipartField
- values map[string]interface{}
-}
-
-// Context method returns the Context if its already set in request
-// otherwise it creates new one using `context.Background()`.
-func (r *Request) Context() context.Context {
- if r.ctx == nil {
- return context.Background()
- }
- return r.ctx
-}
-
-// SetContext method sets the context.Context for current Request. It allows
-// to interrupt the request execution if ctx.Done() channel is closed.
-// See https://blog.golang.org/context article and the "context" package
-// documentation.
-func (r *Request) SetContext(ctx context.Context) *Request {
- r.ctx = ctx
- return r
-}
-
-func (r *Request) addContextIfAvailable() {
- if r.ctx != nil {
- r.RawRequest = r.RawRequest.WithContext(r.ctx)
- }
-}
-
-func (r *Request) isContextCancelledIfAvailable() bool {
- if r.ctx != nil {
- if r.ctx.Err() != nil {
- return true
- }
- }
- return false
-}
-
-// for go1.7+
-var noescapeJSONMarshal = func(v interface{}) ([]byte, error) {
- buf := acquireBuffer()
- defer releaseBuffer(buf)
- encoder := json.NewEncoder(buf)
- encoder.SetEscapeHTML(false)
- err := encoder.Encode(v)
- return buf.Bytes(), err
-}
diff --git a/request_test.go b/request_test.go
index bcf029db..0b862622 100644
--- a/request_test.go
+++ b/request_test.go
@@ -32,7 +32,7 @@ func TestGet(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- resp, err := R().
+ resp, err := dc().R().
SetQueryParam("request_no", strconv.FormatInt(time.Now().Unix(), 10)).
Get(ts.URL + "/")
@@ -603,7 +603,10 @@ func TestMultiPartUploadFiles(t *testing.T) {
basePath := getTestDataPath()
resp, err := dclr().
- SetFormData(map[string]string{"first_name": "Jeevanandam", "last_name": "M"}).
+ SetFormDataFromValues(url.Values{
+ "first_name": []string{"Jeevanandam"},
+ "last_name": []string{"M"},
+ }).
SetFiles(map[string]string{"profile_img": filepath.Join(basePath, "test-img.png"), "notes": filepath.Join(basePath, "text-file.txt")}).
Post(ts.URL + "/upload")
@@ -674,7 +677,10 @@ func TestMultiPartMultipartField(t *testing.T) {
jsonBytes := []byte(`{"input": {"name": "Uploaded document", "_filename" : ["file.txt"]}}`)
resp, err := dclr().
- SetFormData(map[string]string{"first_name": "Jeevanandam", "last_name": "M"}).
+ SetFormDataFromValues(url.Values{
+ "first_name": []string{"Jeevanandam"},
+ "last_name": []string{"M"},
+ }).
SetMultipartField("uploadManifest", "upload-file.json", "application/json", bytes.NewReader(jsonBytes)).
Post(ts.URL + "/upload")
@@ -789,7 +795,7 @@ func TestPutPlainString(t *testing.T) {
ts := createGenServer(t)
defer ts.Close()
- resp, err := R().
+ resp, err := dc().R().
SetBody("This is plain text body to server").
Put(ts.URL + "/plaintext")
@@ -802,19 +808,21 @@ func TestPutJSONString(t *testing.T) {
ts := createGenServer(t)
defer ts.Close()
- DefaultClient.OnBeforeRequest(func(c *Client, r *Request) error {
+ client := dc()
+
+ client.OnBeforeRequest(func(c *Client, r *Request) error {
r.SetHeader("X-Custom-Request-Middleware", "OnBeforeRequest middleware")
return nil
})
- DefaultClient.OnBeforeRequest(func(c *Client, r *Request) error {
+ client.OnBeforeRequest(func(c *Client, r *Request) error {
c.SetContentLength(true)
r.SetHeader("X-ContentLength", "OnBeforeRequest ContentLength set")
return nil
})
- DefaultClient.SetDebug(true).SetLogger(ioutil.Discard)
+ client.SetDebug(true).SetLogger(ioutil.Discard)
- resp, err := R().
+ resp, err := client.R().
SetHeaders(map[string]string{hdrContentTypeKey: jsonContentType, hdrAcceptKey: jsonContentType}).
SetBody(`{"content":"json content sending to server"}`).
Put(ts.URL + "/json")
@@ -828,7 +836,7 @@ func TestPutXMLString(t *testing.T) {
ts := createGenServer(t)
defer ts.Close()
- resp, err := R().
+ resp, err := dc().R().
SetHeaders(map[string]string{hdrContentTypeKey: "application/xml", hdrAcceptKey: "application/xml"}).
SetBody(`XML Content sending to server`).
Put(ts.URL + "/xml")
@@ -862,22 +870,11 @@ func TestOnBeforeMiddleware(t *testing.T) {
assertEqual(t, "TestPut: plain text response", resp.String())
}
-func TestNoAutoRedirect(t *testing.T) {
- ts := createRedirectServer(t)
- defer ts.Close()
-
- _, err := R().Get(ts.URL + "/redirect-1")
-
- assertEqual(t, "Get /redirect-2: auto redirect is disabled", err.Error())
-}
-
func TestHTTPAutoRedirectUpTo10(t *testing.T) {
ts := createRedirectServer(t)
defer ts.Close()
- c := dc()
- c.SetHTTPMode()
- _, err := c.R().Get(ts.URL + "/redirect-1")
+ _, err := dc().R().Get(ts.URL + "/redirect-1")
assertEqual(t, "Get /redirect-11: stopped after 10 redirects", err.Error())
}
@@ -967,33 +964,29 @@ func TestProxySetting(t *testing.T) {
assertEqual(t, true, c.IsProxySet())
assertNotNil(t, transport.Proxy)
- SetProxy("http://sampleproxy:8888")
- assertEqual(t, true, IsProxySet())
- RemoveProxy()
- assertNil(t, DefaultClient.proxyURL)
+ c.SetProxy("http://sampleproxy:8888")
+ assertEqual(t, true, c.IsProxySet())
+ c.RemoveProxy()
+ assertNil(t, c.proxyURL)
assertNil(t, transport.Proxy)
}
func TestGetClient(t *testing.T) {
- client := GetClient()
+ client := New()
custom := New()
customClient := custom.GetClient()
- assertNotNil(t, client)
assertNotNil(t, customClient)
-
assertNotEqual(t, client, http.DefaultClient)
assertNotEqual(t, customClient, http.DefaultClient)
assertNotEqual(t, client, customClient)
-
- assertEqual(t, DefaultClient.httpClient, client)
}
func TestIncorrectURL(t *testing.T) {
- _, err := R().Get("//not.a.user@%66%6f%6f.com/just/a/path/also")
+ c := dc()
+ _, err := c.R().Get("//not.a.user@%66%6f%6f.com/just/a/path/also")
assertEqual(t, true, strings.Contains(err.Error(), "parse //not.a.user@%66%6f%6f.com/just/a/path/also"))
- c := dc()
c.SetHostURL("//not.a.user@%66%6f%6f.com")
_, err1 := c.R().Get("/just/a/path/also")
assertEqual(t, true, strings.Contains(err1.Error(), "parse //not.a.user@%66%6f%6f.com/just/a/path/also"))
@@ -1163,13 +1156,13 @@ func TestOutputFileWithBaseDirAndRelativePath(t *testing.T) {
defer ts.Close()
defer cleanupFiles(".testdata/dir-sample")
- DefaultClient = dc()
- SetRedirectPolicy(FlexibleRedirectPolicy(10))
- SetOutputDirectory(filepath.Join(getTestDataPath(), "dir-sample"))
- SetDebug(true)
- SetLogger(ioutil.Discard)
+ client := dc().
+ SetRedirectPolicy(FlexibleRedirectPolicy(10)).
+ SetOutputDirectory(filepath.Join(getTestDataPath(), "dir-sample")).
+ SetDebug(true).
+ SetLogger(ioutil.Discard)
- resp, err := R().
+ resp, err := client.R().
SetOutput("go-resty/test-img-success.png").
Get(ts.URL + "/my-image.png")
@@ -1189,11 +1182,11 @@ func TestOutputPathDirNotExists(t *testing.T) {
defer ts.Close()
defer cleanupFiles(filepath.Join(".testdata", "not-exists-dir"))
- DefaultClient = dc()
- SetRedirectPolicy(FlexibleRedirectPolicy(10))
- SetOutputDirectory(filepath.Join(getTestDataPath(), "not-exists-dir"))
+ client := dc().
+ SetRedirectPolicy(FlexibleRedirectPolicy(10)).
+ SetOutputDirectory(filepath.Join(getTestDataPath(), "not-exists-dir"))
- resp, err := R().
+ resp, err := client.R().
SetOutput("test-img-success.png").
Get(ts.URL + "/my-image.png")
@@ -1217,14 +1210,9 @@ func TestContextInternal(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- r := R().
+ r := dc().R().
SetQueryParam("request_no", strconv.FormatInt(time.Now().Unix(), 10))
- if r.isContextCancelledIfAvailable() {
- t.Error("isContextCancelledIfAvailable != false for vanilla R()")
- }
- r.addContextIfAvailable()
-
resp, err := r.Get(ts.URL + "/")
assertError(t, err)
@@ -1251,7 +1239,7 @@ func TestSRV(t *testing.T) {
}
func TestSRVInvalidService(t *testing.T) {
- _, err := R().
+ _, err := dc().R().
SetSRV(&SRVRecord{"nonexistantservice", "sampledomain"}).
Get("/")
@@ -1259,29 +1247,12 @@ func TestSRVInvalidService(t *testing.T) {
assertType(t, net.DNSError{}, err)
}
-func TestDeprecatedCodeCoverage(t *testing.T) {
- var user1 User
- err := Unmarshal("application/json",
- []byte(`{"username":"testuser", "password":"testpass"}`), &user1)
- assertError(t, err)
- assertEqual(t, "testuser", user1.Username)
- assertEqual(t, "testpass", user1.Password)
-
- var user2 User
- err = Unmarshal("application/xml",
- []byte(`testusertestpass`),
- &user2)
- assertError(t, err)
- assertEqual(t, "testuser", user1.Username)
- assertEqual(t, "testpass", user1.Password)
-}
-
func TestRequestDoNotParseResponse(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- resp, err := dc().R().
- SetDoNotParseResponse(true).
+ client := dc().SetDoNotParseResponse(true)
+ resp, err := client.R().
SetQueryParam("request_no", strconv.FormatInt(time.Now().Unix(), 10)).
Get(ts.URL + "/")
@@ -1303,11 +1274,6 @@ func TestRequestDoNotParseResponse(t *testing.T) {
resp.RawResponse = nil
assertNil(t, resp.RawBody())
-
- // just set test part
- SetDoNotParseResponse(true)
- assertEqual(t, true, DefaultClient.notParseResponse)
- SetDoNotParseResponse(false)
}
type noCtTest struct {
@@ -1353,10 +1319,6 @@ func TestGetPathParams(t *testing.T) {
assertEqual(t, true, strings.Contains(resp.String(), "/v1/users/sample@sample.com/100002/details"))
logResponse(t, resp)
-
- SetPathParams(map[string]string{
- "userId": "sample@sample.com",
- })
}
func TestReportMethodSupportsPayload(t *testing.T) {
@@ -1396,8 +1358,8 @@ func TestRequestOverridesClientAuthorizationHeader(t *testing.T) {
c := dc()
c.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}).
- SetHeader("Authorization", "some token")
- SetHostURL(ts.URL + "/")
+ SetHeader("Authorization", "some token").
+ SetHostURL(ts.URL + "/")
resp, err := c.R().
SetHeader("Authorization", "Bearer 004DDB79-6801-4587-B976-F093E6AC44FF").
@@ -1441,7 +1403,7 @@ func TestHostHeaderOverride(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- resp, err := R().
+ resp, err := dc().R().
SetHeader("Host", "myhostname").
Get(ts.URL + "/host-header")
diff --git a/response.go b/response.go
index ea2a027a..073238b8 100644
--- a/response.go
+++ b/response.go
@@ -13,6 +13,10 @@ import (
"time"
)
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+// Response struct and methods
+//_______________________________________________________________________
+
// Response is an object represents executed request and its values.
type Response struct {
Request *Request
@@ -38,7 +42,6 @@ func (r *Response) Status() string {
if r.RawResponse == nil {
return ""
}
-
return r.RawResponse.Status
}
@@ -48,7 +51,6 @@ func (r *Response) StatusCode() int {
if r.RawResponse == nil {
return 0
}
-
return r.RawResponse.StatusCode
}
@@ -67,7 +69,6 @@ func (r *Response) Header() http.Header {
if r.RawResponse == nil {
return http.Header{}
}
-
return r.RawResponse.Header
}
@@ -76,7 +77,6 @@ func (r *Response) Cookies() []*http.Cookie {
if r.RawResponse == nil {
return make([]*http.Cookie, 0)
}
-
return r.RawResponse.Cookies()
}
@@ -85,7 +85,6 @@ func (r *Response) String() string {
if r.body == nil {
return ""
}
-
return strings.TrimSpace(string(r.body))
}
@@ -130,6 +129,10 @@ func (r *Response) IsError() bool {
return r.StatusCode() > 399
}
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+// Response Unexported methods
+//_______________________________________________________________________
+
func (r *Response) fmtBodyString(sl int64) string {
if r.body != nil {
if int64(len(r.body)) > sl {
diff --git a/resty.go b/resty.go
index b886e9e0..4d16e1ae 100644
--- a/resty.go
+++ b/resty.go
@@ -5,5 +5,23 @@
// Package resty provides Simple HTTP and REST client library for Go.
package resty
+import (
+ "net/http"
+ "net/http/cookiejar"
+
+ "golang.org/x/net/publicsuffix"
+)
+
// Version # of resty
const Version = "2.0.0-alpha.1"
+
+// New method creates a new go-resty client.
+func New() *Client {
+ cookieJar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
+ return createClient(&http.Client{Jar: cookieJar})
+}
+
+// NewWithClient method create a new go-resty client with given `http.Client`.
+func NewWithClient(hc *http.Client) *Client {
+ return createClient(hc)
+}
diff --git a/resty_test.go b/resty_test.go
index 8c2c8dc6..e0cf93f7 100644
--- a/resty_test.go
+++ b/resty_test.go
@@ -540,9 +540,9 @@ func createTestServer(fn func(w http.ResponseWriter, r *http.Request)) *httptest
}
func dc() *Client {
- DefaultClient = New()
- DefaultClient.SetLogger(ioutil.Discard)
- return DefaultClient
+ client := New()
+ client.SetLogger(ioutil.Discard)
+ return client
}
func dcr() *Request {
diff --git a/retry_test.go b/retry_test.go
index 89eaeece..d7509145 100644
--- a/retry_test.go
+++ b/retry_test.go
@@ -144,9 +144,9 @@ func TestConditionalGetDefaultClient(t *testing.T) {
})
// Clear the default client.
- _ = dc()
+ client := dc()
// Proceed to check.
- client := AddRetryCondition(check).SetRetryCount(1)
+ client.AddRetryCondition(check).SetRetryCount(1)
resp, err := client.R().
SetQueryParam("request_no", strconv.FormatInt(time.Now().Unix(), 10)).
Get(ts.URL + "/")
@@ -165,8 +165,7 @@ func TestClientRetryGet(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- c := dc()
- c.SetHTTPMode().
+ c := dc().
SetTimeout(time.Second * 3).
SetRetryCount(3)
@@ -193,8 +192,7 @@ func TestClientRetryWait(t *testing.T) {
retryWaitTime := time.Duration(3) * time.Second
retryMaxWaitTime := time.Duration(9) * time.Second
- c := dc()
- c.SetHTTPMode().
+ c := dc().
SetRetryCount(retryCount).
SetRetryWaitTime(retryWaitTime).
SetRetryMaxWaitTime(retryMaxWaitTime).
diff --git a/util.go b/util.go
index 997cd100..baa9ce6c 100644
--- a/util.go
+++ b/util.go
@@ -6,7 +6,6 @@ package resty
import (
"bytes"
- "encoding/json"
"encoding/xml"
"fmt"
"io"
@@ -22,9 +21,9 @@ import (
"strings"
)
-//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Package Helper methods
-//___________________________________
+//_______________________________________________________________________
// IsStringEmpty method tells whether given string is empty or not
func IsStringEmpty(str string) bool {
@@ -61,18 +60,6 @@ func IsXMLType(ct string) bool {
return xmlCheck.MatchString(ct)
}
-// Unmarshal content into object from JSON or XML
-// Deprecated: kept for backward compatibility
-func Unmarshal(ct string, b []byte, d interface{}) (err error) {
- if IsJSONType(ct) {
- err = json.Unmarshal(b, d)
- } else if IsXMLType(ct) {
- err = xml.Unmarshal(b, d)
- }
-
- return
-}
-
// Unmarshalc content into object from JSON or XML
func Unmarshalc(c *Client, ct string, b []byte, d interface{}) (err error) {
if IsJSONType(ct) {
@@ -84,9 +71,9 @@ func Unmarshalc(c *Client, ct string, b []byte, d interface{}) (err error) {
return
}
-//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// RequestLog and ResponseLog type
-//___________________________________
+//_______________________________________________________________________
// RequestLog struct is used to collected information from resty request
// instance for debug logging. It sent to request log callback before resty
@@ -104,9 +91,9 @@ type ResponseLog struct {
Body string
}
-//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Package Unexported methods
-//___________________________________
+//_______________________________________________________________________
// way to disable the HTML escape as opt-in
func jsonMarshal(c *Client, r *Request, d interface{}) ([]byte, error) {
From 551e301ad534ef422de82d2857911adcee6ddb04 Mon Sep 17 00:00:00 2001
From: Jeevanandam M
Date: Sun, 10 Mar 2019 03:00:53 -0700
Subject: [PATCH 14/28] #216 added on-demand trace feature and godoc update,
cleanup
---
README.md | 74 ++++++++-----
client.go | 276 ++++++++++++++++++++++++++----------------------
middleware.go | 6 ++
redirect.go | 2 +-
request.go | 180 +++++++++++++++++--------------
request_test.go | 48 ++++++++-
response.go | 13 ++-
resty.go | 6 +-
resty_test.go | 4 +-
retry.go | 2 +-
trace.go | 111 +++++++++++++++++++
11 files changed, 480 insertions(+), 242 deletions(-)
create mode 100644 trace.go
diff --git a/README.md b/README.md
index 3257c48e..d988724b 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
Resty Communication Channels
-
![Twitter @go__resty](https://img.shields.io/badge/twitter-@go_resty-55acee.svg)
+
![Twitter @go_resty](https://img.shields.io/badge/twitter-@go__resty-55acee.svg)
## News
@@ -56,7 +56,7 @@
* 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
- * REST and HTTP modes
+ * 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)
* 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`
@@ -118,37 +118,63 @@ import "github.com/go-resty/resty"
```go
// Create a Resty Client
-client := resty.New()
-
-// GET request
-resp, err := client.R().Get("http://httpbin.org/get")
-
-// explore response object
-fmt.Printf("\nError: %v", err)
-fmt.Printf("\nResponse Status Code: %v", resp.StatusCode())
-fmt.Printf("\nResponse Status: %v", resp.Status())
-fmt.Printf("\nResponse Time: %v", resp.Time())
-fmt.Printf("\nResponse Received At: %v", resp.ReceivedAt())
-fmt.Printf("\nResponse Body: %v", resp) // or resp.String() or string(resp.Body())
-// more...
+client := New()
+
+// Fire GET request
+resp, err := client.R().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("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("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)
/* Output
-Error:
-Response Status Code: 200
-Response Status: 200 OK
-Response Time: 160.1151ms
-Response Received At: 2018-10-16 16:28:34.8595663 -0700 PDT m=+0.166119401
-Response Body: {
+Response Info:
+Error :
+Status Code: 200
+Status : 200 OK
+Time : 465.301137ms
+Received At: 2019-03-10 01:52:33.772456 -0800 PST m=+0.466672260
+Body :
+ {
"args": {},
"headers": {
"Accept-Encoding": "gzip",
- "Connection": "close",
"Host": "httpbin.org",
- "User-Agent": "go-resty/1.10.0 (https://github.com/go-resty/resty)"
+ "User-Agent": "go-resty/2.0.0-rc.1 (https://github.com/go-resty/resty)"
},
"origin": "0.0.0.0",
- "url": "http://httpbin.org/get"
+ "url": "https://httpbin.org/get"
}
+
+Request Trace Info:
+DNSLookup : 2.21124ms
+ConnTime : 393.875795ms
+TLSHandshake : 319.313546ms
+ServerTime : 71.109256ms
+ResponseTime : 94.466µs
+TotalTime : 465.301137ms
+IsConnReused : false
+IsConnWasIdle: false
+ConnIdleTime : 0s
*/
```
diff --git a/client.go b/client.go
index 2f1c61ca..09dcd896 100644
--- a/client.go
+++ b/client.go
@@ -70,8 +70,11 @@ var (
bufPool = &sync.Pool{New: func() interface{} { return &bytes.Buffer{} }}
)
-// Client type is used for HTTP/RESTful global values
-// for all request raised from the client
+// Client struct is used to create Resty client with client level settings,
+// these settings are applicable to all the request raised from the client.
+//
+// Resty also provides an options to override most of the client settings
+// at request level.
type Client struct {
HostURL string
QueryParam url.Values
@@ -93,17 +96,17 @@ type Client struct {
JSONUnmarshal func(data []byte, v interface{}) error
jsonEscapeHTML bool
- httpClient *http.Client
setContentLength bool
- isHTTPMode bool
- outputDirectory string
- scheme string
- proxyURL *url.URL
closeConnection bool
notParseResponse bool
+ trace bool
debugBodySizeLimit int64
+ outputDirectory string
+ scheme string
logPrefix string
pathParams map[string]string
+ httpClient *http.Client
+ proxyURL *url.URL
beforeRequest []func(*Client, *Request) error
udBeforeRequest []func(*Client, *Request) error
preReqHook func(*Client, *http.Request) error
@@ -124,11 +127,10 @@ type User struct {
// SetHostURL method is to set Host URL in the client instance. It will be used with request
// raised from this client with relative URL
// // Setting HTTP address
-// resty.SetHostURL("http://myjeeva.com")
+// client.SetHostURL("http://myjeeva.com")
//
// // Setting HTTPS address
-// resty.SetHostURL("https://myjeeva.com")
-//
+// client.SetHostURL("https://myjeeva.com")
func (c *Client) SetHostURL(url string) *Client {
c.HostURL = strings.TrimRight(url, "/")
return c
@@ -136,15 +138,15 @@ func (c *Client) SetHostURL(url string) *Client {
// SetHeader method sets a single header field and its value in the client instance.
// These headers will be applied to all requests raised from this client instance.
-// Also it can be overridden at request level header options, see `resty.R().SetHeader`
-// or `resty.R().SetHeaders`.
+// Also it can be overridden at request level header options.
//
-// Example: To set `Content-Type` and `Accept` as `application/json`
+// See `Request.SetHeader` or `Request.SetHeaders`.
//
-// resty.
+// For Example: To set `Content-Type` and `Accept` as `application/json`
+//
+// client.
// SetHeader("Content-Type", "application/json").
// SetHeader("Accept", "application/json")
-//
func (c *Client) SetHeader(header, value string) *Client {
c.Header.Set(header, value)
return c
@@ -152,15 +154,16 @@ func (c *Client) SetHeader(header, value string) *Client {
// SetHeaders method sets multiple headers field and its values at one go in the client instance.
// These headers will be applied to all requests raised from this client instance. Also it can be
-// overridden at request level headers options, see `resty.R().SetHeaders` or `resty.R().SetHeader`.
+// overridden at request level headers options.
//
-// Example: To set `Content-Type` and `Accept` as `application/json`
+// See `Request.SetHeaders` or `Request.SetHeader`.
//
-// resty.SetHeaders(map[string]string{
+// For Example: To set `Content-Type` and `Accept` as `application/json`
+//
+// client.SetHeaders(map[string]string{
// "Content-Type": "application/json",
// "Accept": "application/json",
// })
-//
func (c *Client) SetHeaders(headers map[string]string) *Client {
for h, v := range headers {
c.Header.Set(h, v)
@@ -169,11 +172,11 @@ func (c *Client) SetHeaders(headers map[string]string) *Client {
}
// SetCookieJar method sets custom http.CookieJar in the resty client. Its way to override default.
-// Example: sometimes we don't want to save cookies in api contacting, we can remove the default
-// CookieJar in resty client.
//
-// resty.SetCookieJar(nil)
+// For Example: sometimes we don't want to save cookies in api contacting, we can remove the default
+// CookieJar in resty client.
//
+// client.SetCookieJar(nil)
func (c *Client) SetCookieJar(jar http.CookieJar) *Client {
c.httpClient.Jar = jar
return c
@@ -181,7 +184,7 @@ func (c *Client) SetCookieJar(jar http.CookieJar) *Client {
// SetCookie method appends a single cookie in the client instance.
// These cookies will be added to all the request raised from this client instance.
-// resty.SetCookie(&http.Cookie{
+// client.SetCookie(&http.Cookie{
// Name:"go-resty",
// Value:"This is cookie value",
// Path: "/",
@@ -190,7 +193,6 @@ func (c *Client) SetCookieJar(jar http.CookieJar) *Client {
// HttpOnly: true,
// Secure: false,
// })
-//
func (c *Client) SetCookie(hc *http.Cookie) *Client {
c.Cookies = append(c.Cookies, hc)
return c
@@ -221,37 +223,40 @@ func (c *Client) SetCookie(hc *http.Cookie) *Client {
// })
//
// // Setting a cookies into resty
-// resty.SetCookies(cookies)
-//
+// client.SetCookies(cookies)
func (c *Client) SetCookies(cs []*http.Cookie) *Client {
c.Cookies = append(c.Cookies, cs...)
return c
}
// SetQueryParam method sets single parameter and its value in the client instance.
-// It will be formed as query string for the request. For example: `search=kitchen%20papers&size=large`
+// It will be formed as query string for the request.
+//
+// For Example: `search=kitchen%20papers&size=large`
// in the URL after `?` mark. These query params will be added to all the request raised from
-// this client instance. Also it can be overridden at request level Query Param options,
-// see `resty.R().SetQueryParam` or `resty.R().SetQueryParams`.
-// resty.
+// this client instance. Also it can be overridden at request level Query Param options.
+//
+// See `Request.SetQueryParam` or `Request.SetQueryParams`.
+// client.
// SetQueryParam("search", "kitchen papers").
// SetQueryParam("size", "large")
-//
func (c *Client) SetQueryParam(param, value string) *Client {
c.QueryParam.Set(param, value)
return c
}
// SetQueryParams method sets multiple parameters and their values at one go in the client instance.
-// It will be formed as query string for the request. For example: `search=kitchen%20papers&size=large`
+// It will be formed as query string for the request.
+//
+// For Example: `search=kitchen%20papers&size=large`
// in the URL after `?` mark. These query params will be added to all the request raised from this
-// client instance. Also it can be overridden at request level Query Param options,
-// see `resty.R().SetQueryParams` or `resty.R().SetQueryParam`.
-// resty.SetQueryParams(map[string]string{
+// client instance. Also it can be overridden at request level Query Param options.
+//
+// See `Request.SetQueryParams` or `Request.SetQueryParam`.
+// client.SetQueryParams(map[string]string{
// "search": "kitchen papers",
// "size": "large",
// })
-//
func (c *Client) SetQueryParams(params map[string]string) *Client {
for p, v := range params {
c.SetQueryParam(p, v)
@@ -262,12 +267,13 @@ func (c *Client) SetQueryParams(params map[string]string) *Client {
// SetFormData method sets Form parameters and their values in the client instance.
// It's applicable only HTTP method `POST` and `PUT` and requets content type would be set as
// `application/x-www-form-urlencoded`. These form data will be added to all the request raised from
-// this client instance. Also it can be overridden at request level form data, see `resty.R().SetFormData`.
-// resty.SetFormData(map[string]string{
+// this client instance. Also it can be overridden at request level form data.
+//
+// See `Request.SetFormData`.
+// client.SetFormData(map[string]string{
// "access_token": "BC594900-518B-4F7E-AC75-BD37F019E08F",
// "user_id": "3455454545",
// })
-//
func (c *Client) SetFormData(data map[string]string) *Client {
for k, v := range data {
c.FormData.Set(k, v)
@@ -275,36 +281,38 @@ func (c *Client) SetFormData(data map[string]string) *Client {
return c
}
-// SetBasicAuth method sets the basic authentication header in the HTTP request. Example:
+// SetBasicAuth method sets the basic authentication header in the HTTP request. For Example:
// Authorization: Basic
//
-// Example: To set the header for username "go-resty" and password "welcome"
-// resty.SetBasicAuth("go-resty", "welcome")
+// For Example: To set the header for username "go-resty" and password "welcome"
+// client.SetBasicAuth("go-resty", "welcome")
//
// This basic auth information gets added to all the request rasied from this client instance.
-// Also it can be overridden or set one at the request level is supported, see `resty.R().SetBasicAuth`.
+// Also it can be overridden or set one at the request level is supported.
//
+// See `Request.SetBasicAuth`.
func (c *Client) SetBasicAuth(username, password string) *Client {
c.UserInfo = &User{Username: username, Password: password}
return c
}
-// SetAuthToken method sets bearer auth token header in the HTTP request. Example:
+// SetAuthToken method sets bearer auth token header in the HTTP request. For Example:
// Authorization: Bearer
//
-// Example: To set auth token BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F
+// For Example: To set auth token BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F
//
-// resty.SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F")
+// client.SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F")
//
// This bearer auth token gets added to all the request rasied from this client instance.
-// Also it can be overridden or set one at the request level is supported, see `resty.R().SetAuthToken`.
+// Also it can be overridden or set one at the request level is supported.
//
+// See `Request.SetAuthToken`.
func (c *Client) SetAuthToken(token string) *Client {
c.Token = token
return c
}
-// R method creates a request instance, its used for Get, Post, Put, Delete, Patch, Head and Options.
+// R method creates a new request instance, its used for Get, Post, Put, Delete, Patch, Head, Options, etc.
func (c *Client) R() *Request {
r := &Request{
QueryParam: url.Values{},
@@ -320,37 +328,35 @@ func (c *Client) R() *Request {
return r
}
-// NewRequest is an alias for R(). Creates a request instance, its used for
-// Get, Post, Put, Delete, Patch, Head and Options.
+// NewRequest is an alias for method `R()`. Creates a new request instance, its used for
+// Get, Post, Put, Delete, Patch, Head, Options, etc.
func (c *Client) NewRequest() *Request {
return c.R()
}
// OnBeforeRequest method appends request middleware into the before request chain.
-// Its gets applied after default `go-resty` request middlewares and before request
-// been sent from `go-resty` to host server.
-// resty.OnBeforeRequest(func(c *resty.Client, r *resty.Request) error {
+// Its gets applied after default Resty request middlewares and before request
+// been sent from Resty to host server.
+// client.OnBeforeRequest(func(c *resty.Client, r *resty.Request) error {
// // Now you have access to Client and Request instance
// // manipulate it as per your need
//
// return nil // if its success otherwise return error
// })
-//
func (c *Client) OnBeforeRequest(m func(*Client, *Request) error) *Client {
c.udBeforeRequest = append(c.udBeforeRequest, m)
return c
}
// OnAfterResponse method appends response middleware into the after response chain.
-// Once we receive response from host server, default `go-resty` response middleware
+// Once we receive response from host server, default Resty response middleware
// gets applied and then user assigened response middlewares applied.
-// resty.OnAfterResponse(func(c *resty.Client, r *resty.Response) error {
+// client.OnAfterResponse(func(c *resty.Client, r *resty.Response) error {
// // Now you have access to Client and Response instance
// // manipulate it as per your need
//
// return nil // if its success otherwise return error
// })
-//
func (c *Client) OnAfterResponse(m func(*Client, *Response) error) *Client {
c.afterResponse = append(c.afterResponse, m)
return c
@@ -359,7 +365,7 @@ func (c *Client) OnAfterResponse(m func(*Client, *Response) error) *Client {
// SetPreRequestHook method sets the given pre-request function into resty client.
// It is called right before the request is fired.
//
-// Note: Only one pre-request hook can be registered. Use `resty.OnBeforeRequest` for mutilple.
+// Note: Only one pre-request hook can be registered. Use `client.OnBeforeRequest` for mutilple.
func (c *Client) SetPreRequestHook(h func(*Client, *http.Request) error) *Client {
if c.preReqHook != nil {
c.Log.Printf("Overwriting an existing pre-request hook: %s", functionName(h))
@@ -368,25 +374,23 @@ func (c *Client) SetPreRequestHook(h func(*Client, *http.Request) error) *Client
return c
}
-// SetDebug method enables the debug mode on `go-resty` client. Client logs details of every request and response.
+// SetDebug method enables the debug mode on Resty client. Client logs details of every request and response.
// For `Request` it logs information such as HTTP verb, Relative URL path, Host, Headers, Body if it has one.
// For `Response` it logs information such as Status, Response Time, Headers, Body if it has one.
-// resty.SetDebug(true)
-//
+// client.SetDebug(true)
func (c *Client) SetDebug(d bool) *Client {
c.Debug = d
return c
}
// SetDebugBodyLimit sets the maximum size for which the response body will be logged in debug mode.
-// resty.SetDebugBodyLimit(1000000)
-//
+// client.SetDebugBodyLimit(1000000)
func (c *Client) SetDebugBodyLimit(sl int64) *Client {
c.debugBodySizeLimit = sl
return c
}
-// OnRequestLog method used to set request log callback into resty. Registered callback gets
+// OnRequestLog method used to set request log callback into Resty. Registered callback gets
// called before the resty actually logs the information.
func (c *Client) OnRequestLog(rl func(*RequestLog) error) *Client {
if c.requestLog != nil {
@@ -396,7 +400,7 @@ func (c *Client) OnRequestLog(rl func(*RequestLog) error) *Client {
return c
}
-// OnResponseLog method used to set response log callback into resty. Registered callback gets
+// OnResponseLog method used to set response log callback into Resty. Registered callback gets
// called before the resty actually logs the information.
func (c *Client) OnResponseLog(rl func(*ResponseLog) error) *Client {
if c.responseLog != nil {
@@ -406,74 +410,69 @@ func (c *Client) OnResponseLog(rl func(*ResponseLog) error) *Client {
return c
}
-// SetDisableWarn method disables the warning message on `go-resty` client.
-// For example: go-resty warns the user when BasicAuth used on HTTP mode.
-// resty.SetDisableWarn(true)
+// SetDisableWarn method disables the warning message on Resty client.
//
+// For Example: Resty warns the user when BasicAuth used on non-TLS mode.
+// client.SetDisableWarn(true)
func (c *Client) SetDisableWarn(d bool) *Client {
c.DisableWarn = d
return c
}
-// SetAllowGetMethodPayload method allows the GET method with payload on `go-resty` client.
-// For example: go-resty allows the user sends request with a payload on HTTP GET method.
-// resty.SetAllowGetMethodPayload(true)
+// SetAllowGetMethodPayload method allows the GET method with payload on Resty client.
//
+// For Example: Resty allows the user sends request with a payload on HTTP GET method.
+// client.SetAllowGetMethodPayload(true)
func (c *Client) SetAllowGetMethodPayload(a bool) *Client {
c.AllowGetMethodPayload = a
return c
}
-// SetLogger method sets given writer for logging go-resty request and response details.
-// Default is os.Stderr
+// SetLogger method sets given writer for logging Resty request and response details.
+// Defaults to os.Stderr.
// file, _ := os.OpenFile("/Users/jeeva/go-resty.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
//
-// resty.SetLogger(file)
-//
+// client.SetLogger(file)
func (c *Client) SetLogger(w io.Writer) *Client {
c.Log = getLogger(w)
return c
}
// SetContentLength method enables the HTTP header `Content-Length` value for every request.
-// By default go-resty won't set `Content-Length`.
-// resty.SetContentLength(true)
-//
-// Also you have an option to enable for particular request. See `resty.R().SetContentLength`
+// By default Resty won't set `Content-Length`.
+// client.SetContentLength(true)
//
+// Also you have an option to enable for particular request. See `Request.SetContentLength`
func (c *Client) SetContentLength(l bool) *Client {
c.setContentLength = l
return c
}
// SetTimeout method sets timeout for request raised from client.
-// resty.SetTimeout(time.Duration(1 * time.Minute))
-//
+// client.SetTimeout(time.Duration(1 * time.Minute))
func (c *Client) SetTimeout(timeout time.Duration) *Client {
c.httpClient.Timeout = timeout
return c
}
-// SetError method is to register the global or client common `Error` object into go-resty.
+// SetError method is to register the global or client common `Error` object into Resty.
// It is used for automatic unmarshalling if response status code is greater than 399 and
// content type either JSON or XML. Can be pointer or non-pointer.
-// resty.SetError(&Error{})
+// client.SetError(&Error{})
// // OR
-// resty.SetError(Error{})
-//
+// client.SetError(Error{})
func (c *Client) SetError(err interface{}) *Client {
c.Error = typeOf(err)
return c
}
-// SetRedirectPolicy method sets the client redirect poilicy. go-resty provides ready to use
-// redirect policies. Wanna create one for yourself refer `redirect.go`.
+// SetRedirectPolicy method sets the client redirect poilicy. Resty provides ready to use
+// redirect policies. Wanna create one for yourself refer to `redirect.go`.
//
-// resty.SetRedirectPolicy(FlexibleRedirectPolicy(20))
+// client.SetRedirectPolicy(FlexibleRedirectPolicy(20))
//
// // Need multiple redirect policies together
-// resty.SetRedirectPolicy(FlexibleRedirectPolicy(20), DomainCheckRedirectPolicy("host1.com", "host2.net"))
-//
+// client.SetRedirectPolicy(FlexibleRedirectPolicy(20), DomainCheckRedirectPolicy("host1.com", "host2.net"))
func (c *Client) SetRedirectPolicy(policies ...interface{}) *Client {
for _, p := range policies {
if _, ok := p.(RedirectPolicy); !ok {
@@ -494,7 +493,7 @@ func (c *Client) SetRedirectPolicy(policies ...interface{}) *Client {
return c
}
-// SetRetryCount method enables retry on `go-resty` client and allows you
+// SetRetryCount method enables retry on Resty client and allows you
// to set no. of retry count. Resty uses a Backoff mechanism.
func (c *Client) SetRetryCount(count int) *Client {
c.RetryCount = count
@@ -503,6 +502,7 @@ func (c *Client) SetRetryCount(count int) *Client {
// SetRetryWaitTime method sets default wait time to sleep before retrying
// request.
+//
// Default is 100 milliseconds.
func (c *Client) SetRetryWaitTime(waitTime time.Duration) *Client {
c.RetryWaitTime = waitTime
@@ -511,6 +511,7 @@ func (c *Client) SetRetryWaitTime(waitTime time.Duration) *Client {
// SetRetryMaxWaitTime method sets max wait time to sleep before retrying
// request.
+//
// Default is 2 seconds.
func (c *Client) SetRetryMaxWaitTime(maxWaitTime time.Duration) *Client {
c.RetryMaxWaitTime = maxWaitTime
@@ -527,14 +528,13 @@ func (c *Client) AddRetryCondition(condition RetryConditionFunc) *Client {
// SetTLSClientConfig method sets TLSClientConfig for underling client Transport.
//
-// Example:
+// For Example:
// // One can set custom root-certificate. Refer: http://golang.org/pkg/crypto/tls/#example_Dial
-// resty.SetTLSClientConfig(&tls.Config{ RootCAs: roots })
+// client.SetTLSClientConfig(&tls.Config{ RootCAs: roots })
//
// // or One can disable security check (https)
-// resty.SetTLSClientConfig(&tls.Config{ InsecureSkipVerify: true })
+// client.SetTLSClientConfig(&tls.Config{ InsecureSkipVerify: true })
// Note: This method overwrites existing `TLSClientConfig`.
-//
func (c *Client) SetTLSClientConfig(config *tls.Config) *Client {
transport, err := c.getTransport()
if err != nil {
@@ -545,12 +545,12 @@ func (c *Client) SetTLSClientConfig(config *tls.Config) *Client {
return c
}
-// SetProxy method sets the Proxy URL and Port for resty client.
-// resty.SetProxy("http://proxyserver:8888")
+// SetProxy method sets the Proxy URL and Port for Resty client.
+// client.SetProxy("http://proxyserver:8888")
//
// OR Without this `SetProxy` method, you could also set Proxy via environment variable.
-// Refer to godoc `http.ProxyFromEnvironment`.
//
+// Refer to godoc `http.ProxyFromEnvironment`.
func (c *Client) SetProxy(proxyURL string) *Client {
transport, err := c.getTransport()
if err != nil {
@@ -569,9 +569,8 @@ func (c *Client) SetProxy(proxyURL string) *Client {
return c
}
-// RemoveProxy method removes the proxy configuration from resty client
-// resty.RemoveProxy()
-//
+// RemoveProxy method removes the proxy configuration from Resty client
+// client.RemoveProxy()
func (c *Client) RemoveProxy() *Client {
transport, err := c.getTransport()
if err != nil {
@@ -583,8 +582,7 @@ func (c *Client) RemoveProxy() *Client {
return c
}
-// SetCertificates method helps to set client certificates into resty conveniently.
-//
+// SetCertificates method helps to set client certificates into Resty conveniently.
func (c *Client) SetCertificates(certs ...tls.Certificate) *Client {
config, err := c.getTLSConfig()
if err != nil {
@@ -595,9 +593,8 @@ func (c *Client) SetCertificates(certs ...tls.Certificate) *Client {
return c
}
-// SetRootCertificate method helps to add one or more root certificates into resty client
-// resty.SetRootCertificate("/path/to/root/pemFile.pem")
-//
+// SetRootCertificate method helps to add one or more root certificates into Resty client
+// client.SetRootCertificate("/path/to/root/pemFile.pem")
func (c *Client) SetRootCertificate(pemFilePath string) *Client {
rootPemData, err := ioutil.ReadFile(pemFilePath)
if err != nil {
@@ -621,8 +618,7 @@ func (c *Client) SetRootCertificate(pemFilePath string) *Client {
// SetOutputDirectory method sets output directory for saving HTTP response into file.
// If the output directory not exists then resty creates one. This setting is optional one,
// if you're planning using absoule path in `Request.SetOutput` and can used together.
-// resty.SetOutputDirectory("/save/http/response/here")
-//
+// client.SetOutputDirectory("/save/http/response/here")
func (c *Client) SetOutputDirectory(dirPath string) *Client {
c.outputDirectory = dirPath
return c
@@ -631,12 +627,12 @@ func (c *Client) SetOutputDirectory(dirPath string) *Client {
// SetTransport method sets custom `*http.Transport` or any `http.RoundTripper`
// compatible interface implementation in the resty client.
//
-// NOTE:
+// Note:
//
// - If transport is not type of `*http.Transport` then you may not be able to
-// take advantage of some of the `resty` client settings.
+// take advantage of some of the Resty client settings.
//
-// - It overwrites the resty client transport instance and it's configurations.
+// - It overwrites the Resty client transport instance and it's configurations.
//
// transport := &http.Transport{
// // somthing like Proxying to httptest.Server, etc...
@@ -645,8 +641,7 @@ func (c *Client) SetOutputDirectory(dirPath string) *Client {
// },
// }
//
-// resty.SetTransport(transport)
-//
+// client.SetTransport(transport)
func (c *Client) SetTransport(transport http.RoundTripper) *Client {
if transport != nil {
c.httpClient.Transport = transport
@@ -654,9 +649,8 @@ func (c *Client) SetTransport(transport http.RoundTripper) *Client {
return c
}
-// SetScheme method sets custom scheme in the resty client. It's way to override default.
-// resty.SetScheme("http")
-//
+// SetScheme method sets custom scheme in the Resty client. It's way to override default.
+// client.SetScheme("http")
func (c *Client) SetScheme(scheme string) *Client {
if !IsStringEmpty(scheme) {
c.scheme = scheme
@@ -675,7 +669,7 @@ func (c *Client) SetCloseConnection(close bool) *Client {
// Resty exposes the raw response body as `io.ReadCloser`. Also do not forget to close the body,
// otherwise you might get into connection leaks, no connection reuse.
//
-// Please Note: Response middlewares are not applicable, if you use this option. Basically you have
+// Note: Response middlewares are not applicable, if you use this option. Basically you have
// taken over the control of response parsing from `Resty`.
func (c *Client) SetDoNotParseResponse(parse bool) *Client {
c.notParseResponse = parse
@@ -690,8 +684,8 @@ func (c *Client) SetLogPrefix(prefix string) *Client {
}
// SetPathParams method sets multiple URL path key-value pairs at one go in the
-// resty client instance.
-// resty.SetPathParams(map[string]string{
+// Resty client instance.
+// client.SetPathParams(map[string]string{
// "userId": "sample@sample.com",
// "subAccountId": "100002",
// })
@@ -710,20 +704,44 @@ func (c *Client) SetPathParams(params map[string]string) *Client {
// SetJSONEscapeHTML method is to enable/disable the HTML escape on JSON marshal.
//
-// NOTE: This option only applicable to standard JSON Marshaller.
+// Note: This option only applicable to standard JSON Marshaller.
func (c *Client) SetJSONEscapeHTML(b bool) *Client {
c.jsonEscapeHTML = b
return c
}
+// EnableTrace method enables the Resty client trace for the requests fired from
+// the client using `httptrace.ClientTrace` and provides insights.
+//
+// client := resty.New().EnableTrace()
+//
+// resp, err := client.R().Get("https://httpbin.org/get")
+// fmt.Println("Error:", err)
+// fmt.Println("Trace Info:", resp.Request.TraceInfo())
+//
+// Also `Request.EnableTrace` available too to get trace info for single request.
+//
+// Since v2.0.0
+func (c *Client) EnableTrace() *Client {
+ c.trace = true
+ return c
+}
+
+// DisableTrace method disables the Resty client trace. Refer to `Client.EnableTrace`.
+//
+// Since v2.0.0
+func (c *Client) DisableTrace() *Client {
+ c.trace = false
+ return c
+}
+
// IsProxySet method returns the true is proxy is set from resty client otherwise
// false. By default proxy is set from environment, refer to `http.ProxyFromEnvironment`.
-//
func (c *Client) IsProxySet() bool {
return c.proxyURL != nil
}
-// GetClient method returns the current http.Client used by the resty client.
+// GetClient method returns the current `http.Client` used by the resty client.
func (c *Client) GetClient() *http.Client {
return c.httpClient
}
@@ -732,7 +750,8 @@ func (c *Client) GetClient() *http.Client {
// Client Unexported methods
//_______________________________________________________________________
-// executes the given `Request` object and returns response
+// Executes method executes the given `Request` object and returns response
+// error.
func (c *Client) execute(req *Request) (*Response, error) {
defer releaseBuffer(req.bodyBuf)
// Apply Request middleware
@@ -770,11 +789,16 @@ func (c *Client) execute(req *Request) (*Response, error) {
req.Time = time.Now()
resp, err := c.httpClient.Do(req.RawRequest)
+ endTime := time.Now()
+
+ if c.trace || req.trace {
+ req.clientTrace.endTime = endTime
+ }
response := &Response{
Request: req,
RawResponse: resp,
- receivedAt: time.Now(),
+ receivedAt: endTime,
}
if err != nil || req.notParseResponse || c.notParseResponse {
@@ -855,7 +879,7 @@ func (c *Client) getTransport() (*http.Transport, error) {
// File struct and its methods
//_______________________________________________________________________
-// File represent file information for multipart request
+// File struct represent file information for multipart request
type File struct {
Name string
ParamName string
@@ -871,7 +895,7 @@ func (f *File) String() string {
// MultipartField struct
//_______________________________________________________________________
-// MultipartField represent custom data part for multipart request
+// MultipartField struct represent custom data part for multipart request
type MultipartField struct {
Param string
FileName string
diff --git a/middleware.go b/middleware.go
index 3524b9a6..4cd0689b 100644
--- a/middleware.go
+++ b/middleware.go
@@ -194,6 +194,12 @@ func createHTTPRequest(c *Client, r *Request) (err error) {
r.RawRequest = r.RawRequest.WithContext(r.ctx)
}
+ // Enable trace
+ if c.trace || r.trace {
+ r.clientTrace = &clientTrace{}
+ r.RawRequest = r.RawRequest.WithContext(r.clientTrace.createContext())
+ }
+
return
}
diff --git a/redirect.go b/redirect.go
index 49960c76..afbe13e8 100644
--- a/redirect.go
+++ b/redirect.go
@@ -87,7 +87,7 @@ func getHostname(host string) (hostname string) {
// By default Golang will not redirect request headers
// after go throughing various discussion comments from thread
// https://github.com/golang/go/issues/4800
-// go-resty will add all the headers during a redirect for the same host
+// Resty will add all the headers during a redirect for the same host
func checkHostAndAddHeaders(cur *http.Request, pre *http.Request) {
curHostname := getHostname(cur.URL.Host)
preHostname := getHostname(pre.URL.Host)
diff --git a/request.go b/request.go
index 07cf4d72..889a2364 100644
--- a/request.go
+++ b/request.go
@@ -24,7 +24,7 @@ import (
// Request struct and methods
//_______________________________________________________________________
-// Request struct is used to compose and fire individual request from
+// Request struct is used to compose and fire individual request from
// resty client. Request provides an options to override client level
// settings and also an options for the request composition.
type Request struct {
@@ -48,15 +48,17 @@ type Request struct {
isSaveResponse bool
notParseResponse bool
jsonEscapeHTML bool
+ trace bool
outputFile string
fallbackContentType string
ctx context.Context
pathParams map[string]string
+ values map[string]interface{}
client *Client
bodyBuf *bytes.Buffer
+ clientTrace *clientTrace
multipartFiles []*File
multipartFields []*MultipartField
- values map[string]interface{}
}
// Context method returns the Context if its already set in request
@@ -78,28 +80,28 @@ func (r *Request) SetContext(ctx context.Context) *Request {
}
// SetHeader method is to set a single header field and its value in the current request.
-// Example: To set `Content-Type` and `Accept` as `application/json`.
-// resty.R().
+//
+// For Example: To set `Content-Type` and `Accept` as `application/json`.
+// client.R().
// SetHeader("Content-Type", "application/json").
// SetHeader("Accept", "application/json")
//
// Also you can override header value, which was set at client instance level.
-//
func (r *Request) SetHeader(header, value string) *Request {
r.Header.Set(header, value)
return r
}
// SetHeaders method sets multiple headers field and its values at one go in the current request.
-// Example: To set `Content-Type` and `Accept` as `application/json`
//
-// resty.R().
+// For Example: To set `Content-Type` and `Accept` as `application/json`
+//
+// client.R().
// SetHeaders(map[string]string{
// "Content-Type": "application/json",
// "Accept": "application/json",
// })
// Also you can override header value, which was set at client instance level.
-//
func (r *Request) SetHeaders(headers map[string]string) *Request {
for h, v := range headers {
r.SetHeader(h, v)
@@ -109,12 +111,12 @@ func (r *Request) SetHeaders(headers map[string]string) *Request {
// SetQueryParam method sets single parameter and its value in the current request.
// It will be formed as query string for the request.
-// Example: `search=kitchen%20papers&size=large` in the URL after `?` mark.
-// resty.R().
+//
+// For Example: `search=kitchen%20papers&size=large` in the URL after `?` mark.
+// client.R().
// SetQueryParam("search", "kitchen papers").
// SetQueryParam("size", "large")
-// Also you can override query params value, which was set at client instance level
-//
+// Also you can override query params value, which was set at client instance level.
func (r *Request) SetQueryParam(param, value string) *Request {
r.QueryParam.Set(param, value)
return r
@@ -122,14 +124,14 @@ func (r *Request) SetQueryParam(param, value string) *Request {
// SetQueryParams method sets multiple parameters and its values at one go in the current request.
// It will be formed as query string for the request.
-// Example: `search=kitchen%20papers&size=large` in the URL after `?` mark.
-// resty.R().
+//
+// For Example: `search=kitchen%20papers&size=large` in the URL after `?` mark.
+// client.R().
// SetQueryParams(map[string]string{
// "search": "kitchen papers",
// "size": "large",
// })
-// Also you can override query params value, which was set at client instance level
-//
+// Also you can override query params value, which was set at client instance level.
func (r *Request) SetQueryParams(params map[string]string) *Request {
for p, v := range params {
r.SetQueryParam(p, v)
@@ -141,13 +143,12 @@ func (r *Request) SetQueryParams(params map[string]string) *Request {
// (`url.Values`) at one go in the current request. It will be formed as
// query string for the request.
//
-// Example: `status=pending&status=approved&status=open` in the URL after `?` mark.
-// resty.R().
+// For Example: `status=pending&status=approved&status=open` in the URL after `?` mark.
+// client.R().
// SetQueryParamsFromValues(url.Values{
// "status": []string{"pending", "approved", "open"},
// })
-// Also you can override query params value, which was set at client instance level
-//
+// Also you can override query params value, which was set at client instance level.
func (r *Request) SetQueryParamsFromValues(params url.Values) *Request {
for p, v := range params {
for _, pv := range v {
@@ -160,9 +161,8 @@ func (r *Request) SetQueryParamsFromValues(params url.Values) *Request {
// SetQueryString method provides ability to use string as an input to set URL query string for the request.
//
// Using String as an input
-// resty.R().
+// client.R().
// SetQueryString("productId=232&template=fresh-sample&cat=resty&source=google&kw=buy a lot more")
-//
func (r *Request) SetQueryString(query string) *Request {
params, err := url.ParseQuery(strings.TrimSpace(query))
if err == nil {
@@ -180,13 +180,12 @@ func (r *Request) SetQueryString(query string) *Request {
// SetFormData method sets Form parameters and their values in the current request.
// It's applicable only HTTP method `POST` and `PUT` and requests content type would be set as
// `application/x-www-form-urlencoded`.
-// resty.R().
+// client.R().
// SetFormData(map[string]string{
// "access_token": "BC594900-518B-4F7E-AC75-BD37F019E08F",
// "user_id": "3455454545",
// })
-// Also you can override form data value, which was set at client instance level
-//
+// Also you can override form data value, which was set at client instance level.
func (r *Request) SetFormData(data map[string]string) *Request {
for k, v := range data {
r.FormData.Set(k, v)
@@ -196,12 +195,11 @@ func (r *Request) SetFormData(data map[string]string) *Request {
// SetFormDataFromValues method appends multiple form parameters with multi-value
// (`url.Values`) at one go in the current request.
-// resty.R().
+// client.R().
// SetFormDataFromValues(url.Values{
// "search_criteria": []string{"book", "glass", "pencil"},
// })
-// Also you can override form data value, which was set at client instance level
-//
+// Also you can override form data value, which was set at client instance level.
func (r *Request) SetFormDataFromValues(data url.Values) *Request {
for k, v := range data {
for _, kv := range v {
@@ -218,17 +216,15 @@ func (r *Request) SetFormDataFromValues(data url.Values) *Request {
//
// Note: `io.Reader` is processed as bufferless mode while sending request.
//
-// Example:
-//
-// Struct as a body input, based on content type, it will be marshalled.
-// resty.R().
+// For Example: Struct as a body input, based on content type, it will be marshalled.
+// client.R().
// SetBody(User{
// Username: "jeeva@myjeeva.com",
// Password: "welcome2resty",
// })
//
// Map as a body input, based on content type, it will be marshalled.
-// resty.R().
+// client.R().
// SetBody(map[string]interface{}{
// "username": "jeeva@myjeeva.com",
// "password": "welcome2resty",
@@ -242,57 +238,53 @@ func (r *Request) SetFormDataFromValues(data url.Values) *Request {
// })
//
// String as a body input. Suitable for any need as a string input.
-// resty.R().
+// client.R().
// SetBody(`{
// "username": "jeeva@getrightcare.com",
// "password": "admin"
// }`)
//
// []byte as a body input. Suitable for raw request such as file upload, serialize & deserialize, etc.
-// resty.R().
+// client.R().
// SetBody([]byte("This is my raw request, sent as-is"))
-//
func (r *Request) SetBody(body interface{}) *Request {
r.Body = body
return r
}
-// SetResult method is to register the response `Result` object for automatic unmarshalling in the RESTful mode
+// SetResult method is to register the response `Result` object for automatic unmarshalling for the request,
// if response status code is between 200 and 299 and content type either JSON or XML.
//
// Note: Result object can be pointer or non-pointer.
-// resty.R().SetResult(&AuthToken{})
+// client.R().SetResult(&AuthToken{})
// // OR
-// resty.R().SetResult(AuthToken{})
+// client.R().SetResult(AuthToken{})
//
-// Accessing a result value
+// Accessing a result value from response instance.
// response.Result().(*AuthToken)
-//
func (r *Request) SetResult(res interface{}) *Request {
r.Result = getPointer(res)
return r
}
-// SetError method is to register the request `Error` object for automatic unmarshalling in the RESTful mode
+// SetError method is to register the request `Error` object for automatic unmarshalling for the request,
// if response status code is greater than 399 and content type either JSON or XML.
//
// Note: Error object can be pointer or non-pointer.
-// resty.R().SetError(&AuthError{})
+// client.R().SetError(&AuthError{})
// // OR
-// resty.R().SetError(AuthError{})
+// client.R().SetError(AuthError{})
//
-// Accessing a error value
+// Accessing a error value from response instance.
// response.Error().(*AuthError)
-//
func (r *Request) SetError(err interface{}) *Request {
r.Error = getPointer(err)
return r
}
// SetFile method is to set single file field name and its path for multipart upload.
-// resty.R().
+// client.R().
// SetFile("my_file", "/Users/jeeva/Gas Bill - Sep.pdf")
-//
func (r *Request) SetFile(param, filePath string) *Request {
r.isMultiPart = true
r.FormData.Set("@"+param, filePath)
@@ -300,13 +292,12 @@ func (r *Request) SetFile(param, filePath string) *Request {
}
// SetFiles method is to set multiple file field name and its path for multipart upload.
-// resty.R().
+// client.R().
// SetFiles(map[string]string{
// "my_file1": "/Users/jeeva/Gas Bill - Sep.pdf",
// "my_file2": "/Users/jeeva/Electricity Bill - Sep.pdf",
// "my_file3": "/Users/jeeva/Water Bill - Sep.pdf",
// })
-//
func (r *Request) SetFiles(files map[string]string) *Request {
r.isMultiPart = true
for f, fp := range files {
@@ -316,10 +307,9 @@ func (r *Request) SetFiles(files map[string]string) *Request {
}
// SetFileReader method is to set single file using io.Reader for multipart upload.
-// resty.R().
+// client.R().
// SetFileReader("profile_img", "my-profile-img.png", bytes.NewReader(profileImgBytes)).
// SetFileReader("notes", "user-notes.txt", bytes.NewReader(notesBytes))
-//
func (r *Request) SetFileReader(param, fileName string, reader io.Reader) *Request {
r.isMultiPart = true
r.multipartFiles = append(r.multipartFiles, &File{
@@ -343,8 +333,9 @@ func (r *Request) SetMultipartField(param, fileName, contentType string, reader
}
// SetMultipartFields method is to set multiple data fields using io.Reader for multipart upload.
-// Example:
-// resty.R().SetMultipartFields(
+//
+// For Example:
+// client.R().SetMultipartFields(
// &resty.MultipartField{
// Param: "uploadManifest1",
// FileName: "upload-file-1.json",
@@ -359,7 +350,7 @@ func (r *Request) SetMultipartField(param, fileName, contentType string, reader
// })
//
// If you have slice already, then simply call-
-// resty.R().SetMultipartFields(fields...)
+// client.R().SetMultipartFields(fields...)
func (r *Request) SetMultipartFields(fields ...*MultipartField) *Request {
r.isMultiPart = true
r.multipartFields = append(r.multipartFields, fields...)
@@ -367,24 +358,25 @@ func (r *Request) SetMultipartFields(fields ...*MultipartField) *Request {
}
// SetContentLength method sets the HTTP header `Content-Length` value for current request.
-// By default go-resty won't set `Content-Length`. Also you have an option to enable for every
-// request. See `resty.SetContentLength`
-// resty.R().SetContentLength(true)
+// By default Resty won't set `Content-Length`. Also you have an option to enable for every
+// request.
//
+// See `Client.SetContentLength`
+// client.R().SetContentLength(true)
func (r *Request) SetContentLength(l bool) *Request {
r.setContentLength = true
return r
}
// SetBasicAuth method sets the basic authentication header in the current HTTP request.
-// For Header example:
+//
+// For Example:
// Authorization: Basic
//
// To set the header for username "go-resty" and password "welcome"
-// resty.R().SetBasicAuth("go-resty", "welcome")
-//
-// This method overrides the credentials set by method `resty.SetBasicAuth`.
+// client.R().SetBasicAuth("go-resty", "welcome")
//
+// This method overrides the credentials set by method `Client.SetBasicAuth`.
func (r *Request) SetBasicAuth(username, password string) *Request {
r.UserInfo = &User{Username: username, Password: password}
return r
@@ -393,12 +385,11 @@ func (r *Request) SetBasicAuth(username, password string) *Request {
// SetAuthToken method sets bearer auth token header in the current HTTP request. Header example:
// Authorization: Bearer
//
-// Example: To set auth token BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F
+// For Example: To set auth token BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F
//
-// resty.R().SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F")
-//
-// This method overrides the Auth token set by method `resty.SetAuthToken`.
+// client.R().SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F")
//
+// This method overrides the Auth token set by method `Client.SetAuthToken`.
func (r *Request) SetAuthToken(token string) *Request {
r.Token = token
return r
@@ -408,7 +399,7 @@ func (r *Request) SetAuthToken(token string) *Request {
// saved into given file. It is similar to `curl -o` flag. Absolute path or relative path can be used.
// If is it relative path then output file goes under the output directory, as mentioned
// in the `Client.SetOutputDirectory`.
-// resty.R().
+// client.R().
// SetOutput("/Users/jeeva/Downloads/ReplyWithHeader-v5.1-beta.zip").
// Get("http://bit.ly/1LouEKr")
//
@@ -421,7 +412,7 @@ func (r *Request) SetOutput(file string) *Request {
// SetSRV method sets the details to query the service SRV record and execute the
// request.
-// resty.R().
+// client.R().
// SetSRV(SRVRecord{"web", "testservice.com"}).
// Get("/get")
func (r *Request) SetSRV(srv *SRVRecord) *Request {
@@ -433,7 +424,7 @@ func (r *Request) SetSRV(srv *SRVRecord) *Request {
// Resty exposes the raw response body as `io.ReadCloser`. Also do not forget to close the body,
// otherwise you might get into connection leaks, no connection reuse.
//
-// Please Note: Response middlewares are not applicable, if you use this option. Basically you have
+// Note: Response middlewares are not applicable, if you use this option. Basically you have
// taken over the control of response parsing from `Resty`.
func (r *Request) SetDoNotParseResponse(parse bool) *Request {
r.notParseResponse = parse
@@ -441,8 +432,8 @@ func (r *Request) SetDoNotParseResponse(parse bool) *Request {
}
// SetPathParams method sets multiple URL path key-value pairs at one go in the
-// resty current request instance.
-// resty.R().SetPathParams(map[string]string{
+// Resty current request instance.
+// client.R().SetPathParams(map[string]string{
// "userId": "sample@sample.com",
// "subAccountId": "100002",
// })
@@ -468,15 +459,50 @@ func (r *Request) ExpectContentType(contentType string) *Request {
// SetJSONEscapeHTML method is to enable/disable the HTML escape on JSON marshal.
//
-// NOTE: This option only applicable to standard JSON Marshaller.
+// Note: This option only applicable to standard JSON Marshaller.
func (r *Request) SetJSONEscapeHTML(b bool) *Request {
r.jsonEscapeHTML = b
return r
}
-//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+// EnableTrace method enables trace for the current request
+// using `httptrace.ClientTrace` and provides insights.
+//
+// client := resty.New()
+//
+// resp, err := client.R().EnableTrace().Get("https://httpbin.org/get")
+// fmt.Println("Error:", err)
+// fmt.Println("Trace Info:", resp.Request.TraceInfo())
+//
+// See `Client.EnableTrace` available too to get trace info for all requests.
+//
+// Since v2.0.0
+func (r *Request) EnableTrace() *Request {
+ r.trace = true
+ return r
+}
+
+// TraceInfo method returns the trace info for the request.
+//
+// Since v2.0.0
+func (r *Request) TraceInfo() TraceInfo {
+ ct := r.clientTrace
+ return TraceInfo{
+ DNSLookup: ct.dnsDone.Sub(ct.dnsStart),
+ ConnTime: ct.gotConn.Sub(ct.getConn),
+ TLSHandshake: ct.tlsHandshakeDone.Sub(ct.tlsHandshakeStart),
+ ServerTime: ct.gotFirstResponseByte.Sub(ct.wroteRequest),
+ ResponseTime: ct.endTime.Sub(ct.gotFirstResponseByte),
+ TotalTime: ct.endTime.Sub(ct.getConn),
+ IsConnReused: ct.gotConnInfo.Reused,
+ IsConnWasIdle: ct.gotConnInfo.WasIdle,
+ ConnIdleTime: ct.gotConnInfo.IdleTime,
+ }
+}
+
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// HTTP verb method starts here
-//___________________________________
+//_______________________________________________________________________
// Get method does GET HTTP request. It's defined in section 4.3.1 of RFC7231.
func (r *Request) Get(url string) (*Response, error) {
@@ -515,8 +541,7 @@ func (r *Request) Patch(url string) (*Response, error) {
// Execute method performs the HTTP request with given HTTP method and URL
// for current `Request`.
-// resp, err := resty.R().Execute(resty.GET, "http://httpbin.org/get")
-//
+// resp, err := client.R().Execute(resty.GET, "http://httpbin.org/get")
func (r *Request) Execute(method, url string) (*Response, error) {
var addrs []*net.SRV
var err error
@@ -572,7 +597,8 @@ func (r *Request) Execute(method, url string) (*Response, error) {
// SRVRecord struct
//_______________________________________________________________________
-// SRVRecord holds the data to query the SRV record for the following service
+// SRVRecord struct holds the data to query the SRV record for the
+// following service.
type SRVRecord struct {
Service string
Domain string
diff --git a/request_test.go b/request_test.go
index 0b862622..cd719874 100644
--- a/request_test.go
+++ b/request_test.go
@@ -700,13 +700,13 @@ func TestMultiPartMultipartFields(t *testing.T) {
jsonStr2 := `{"input": {"name": "Uploaded document 2", "_filename" : ["file2.txt"]}}`
fields := []*MultipartField{
- &MultipartField{
+ {
Param: "uploadManifest1",
FileName: "upload-file-1.json",
ContentType: "application/json",
Reader: strings.NewReader(jsonStr1),
},
- &MultipartField{
+ {
Param: "uploadManifest2",
FileName: "upload-file-2.json",
ContentType: "application/json",
@@ -1420,8 +1420,8 @@ func TestPathParamURLInput(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- c := dc().SetDebug(true).SetLogger(ioutil.Discard)
- c.SetHostURL(ts.URL).
+ c := dc().SetDebug(true).
+ SetHostURL(ts.URL).
SetPathParams(map[string]string{
"userId": "sample@sample.com",
})
@@ -1439,3 +1439,43 @@ func TestPathParamURLInput(t *testing.T) {
logResponse(t, resp)
}
+
+func TestTraceInfo(t *testing.T) {
+ ts := createGetServer(t)
+ defer ts.Close()
+
+ client := dc()
+ client.SetHostURL(ts.URL).EnableTrace()
+ for _, u := range []string{"/", "/json", "/long-text", "/long-json"} {
+ resp, err := client.R().Get(u)
+ assertNil(t, err)
+ assertNotNil(t, resp)
+
+ tr := resp.Request.TraceInfo()
+ assertEqual(t, true, tr.DNSLookup >= 0)
+ assertEqual(t, true, tr.ConnTime > 0)
+ assertEqual(t, true, tr.TLSHandshake >= 0)
+ assertEqual(t, true, tr.ServerTime > 0)
+ assertEqual(t, true, tr.ResponseTime > 0)
+ assertEqual(t, true, tr.TotalTime > 0)
+ }
+
+ client.DisableTrace()
+
+ for _, u := range []string{"/", "/json", "/long-text", "/long-json"} {
+ resp, err := client.R().EnableTrace().Get(u)
+ assertNil(t, err)
+ assertNotNil(t, resp)
+
+ tr := resp.Request.TraceInfo()
+ assertEqual(t, true, tr.DNSLookup >= 0)
+ assertEqual(t, true, tr.ConnTime > 0)
+ assertEqual(t, true, tr.TLSHandshake >= 0)
+ assertEqual(t, true, tr.ServerTime > 0)
+ assertEqual(t, true, tr.ResponseTime > 0)
+ assertEqual(t, true, tr.TotalTime > 0)
+ }
+
+ // for sake of hook funcs
+ _, _ = client.R().EnableTrace().Get("https://httpbin.org/get")
+}
diff --git a/response.go b/response.go
index 073238b8..39a1fe88 100644
--- a/response.go
+++ b/response.go
@@ -17,7 +17,7 @@ import (
// Response struct and methods
//_______________________________________________________________________
-// Response is an object represents executed request and its values.
+// Response struct holds response values of executed request.
type Response struct {
Request *Request
RawResponse *http.Response
@@ -28,6 +28,7 @@ type Response struct {
}
// Body method returns HTTP response as []byte array for the executed request.
+//
// Note: `Response.Body` might be nil, if `Request.SetOutput` is used.
func (r *Response) Body() []byte {
if r.RawResponse == nil {
@@ -89,9 +90,13 @@ func (r *Response) String() string {
}
// Time method returns the time of HTTP response time that from request we sent and received a request.
-// See `response.ReceivedAt` to know when client recevied response and see `response.Request.Time` to know
+//
+// See `Response.ReceivedAt` to know when client recevied response and see `Response.Request.Time` to know
// when client sent a request.
func (r *Response) Time() time.Duration {
+ if r.Request.clientTrace != nil {
+ return r.receivedAt.Sub(r.Request.clientTrace.getConn)
+ }
return r.receivedAt.Sub(r.Request.Time)
}
@@ -119,12 +124,12 @@ func (r *Response) RawBody() io.ReadCloser {
return r.RawResponse.Body
}
-// IsSuccess method returns true if HTTP status code >= 200 and <= 299 otherwise false.
+// IsSuccess method returns true if HTTP status `code >= 200 and <= 299` otherwise false.
func (r *Response) IsSuccess() bool {
return r.StatusCode() > 199 && r.StatusCode() < 300
}
-// IsError method returns true if HTTP status code >= 400 otherwise false.
+// IsError method returns true if HTTP status `code >= 400` otherwise false.
func (r *Response) IsError() bool {
return r.StatusCode() > 399
}
diff --git a/resty.go b/resty.go
index 4d16e1ae..55487505 100644
--- a/resty.go
+++ b/resty.go
@@ -13,15 +13,15 @@ import (
)
// Version # of resty
-const Version = "2.0.0-alpha.1"
+const Version = "2.0.0-rc.1"
-// New method creates a new go-resty client.
+// New method creates a new Resty client.
func New() *Client {
cookieJar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
return createClient(&http.Client{Jar: cookieJar})
}
-// NewWithClient method create a new go-resty client with given `http.Client`.
+// NewWithClient method create a new Resty client with given `http.Client`.
func NewWithClient(hc *http.Client) *Client {
return createClient(hc)
}
diff --git a/resty_test.go b/resty_test.go
index e0cf93f7..771e9663 100644
--- a/resty_test.go
+++ b/resty_test.go
@@ -450,14 +450,14 @@ func createGenServer(t *testing.T) *httptest.Server {
w.Header().Set(hdrContentTypeKey, plainTextType)
w.Header().Set(hdrContentEncodingKey, "gzip")
zw := gzip.NewWriter(w)
- zw.Write([]byte("This is Gzip response testing"))
+ _, _ = zw.Write([]byte("This is Gzip response testing"))
zw.Close()
} else if r.URL.Path == "/gzip-test-gziped-empty-body" {
w.Header().Set(hdrContentTypeKey, plainTextType)
w.Header().Set(hdrContentEncodingKey, "gzip")
zw := gzip.NewWriter(w)
// write gziped empty body
- zw.Write([]byte(""))
+ _, _ = zw.Write([]byte(""))
zw.Close()
} else if r.URL.Path == "/gzip-test-no-gziped-body" {
w.Header().Set(hdrContentTypeKey, plainTextType)
diff --git a/retry.go b/retry.go
index fb35f49b..b4a051e2 100644
--- a/retry.go
+++ b/retry.go
@@ -23,7 +23,7 @@ type (
// RetryConditionFunc type is for retry condition function
RetryConditionFunc func(*Response, error) (bool, error)
- // Options to hold go-resty retry values
+ // Options to hold Resty retry values.
Options struct {
maxRetries int
waitTime time.Duration
diff --git a/trace.go b/trace.go
new file mode 100644
index 00000000..a3882382
--- /dev/null
+++ b/trace.go
@@ -0,0 +1,111 @@
+// Copyright (c) 2015-2019 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
+// resty source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package resty
+
+import (
+ "context"
+ "crypto/tls"
+ "net/http/httptrace"
+ "time"
+)
+
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+// TraceInfo struct
+//_______________________________________________________________________
+
+// TraceInfo struct is used provide request trace info such as DNS lookup
+// duration, Connection obtain duration, Server processing duration, etc.
+//
+// Since v2.0.0
+type TraceInfo struct {
+ // DNSLookup is a duration that transport took to perform
+ // DNS lookup.
+ DNSLookup time.Duration
+
+ // ConnTime is a duration that took to obtain a successful connection.
+ ConnTime time.Duration
+
+ // TLSHandshake is a duration that TLS handshake took place.
+ TLSHandshake time.Duration
+
+ // ServerTime is a duration that server took to respond first byte.
+ ServerTime time.Duration
+
+ // ResponseTime is a duration since first response byte from server to
+ // request completion.
+ ResponseTime time.Duration
+
+ // TotalTime is a duration that total request took end-to-end.
+ TotalTime time.Duration
+
+ // IsConnReused is whether this connection has been previously
+ // used for another HTTP request.
+ IsConnReused bool
+
+ // IsConnWasIdle is whether this connection was obtained from an
+ // idle pool.
+ IsConnWasIdle bool
+
+ // ConnIdleTime is a duration how long the connection was previously
+ // idle, if IsConnWasIdle is true.
+ ConnIdleTime time.Duration
+}
+
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+// CientTrace struct and its methods
+//_______________________________________________________________________
+
+// tracer struct maps the `httptrace.ClientTrace` hooks into Fields
+// with same naming for easy understanding. Plus additional insights
+// Request.
+type clientTrace struct {
+ getConn time.Time
+ gotConn time.Time
+ gotFirstResponseByte time.Time
+ dnsStart time.Time
+ dnsDone time.Time
+ tlsHandshakeStart time.Time
+ tlsHandshakeDone time.Time
+ wroteRequest time.Time
+ endTime time.Time
+ gotConnInfo httptrace.GotConnInfo
+}
+
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+// Trace unexported methods
+//_______________________________________________________________________
+
+func (t *clientTrace) createContext() context.Context {
+ return httptrace.WithClientTrace(
+ context.Background(),
+ &httptrace.ClientTrace{
+ GetConn: func(_ string) {
+ t.getConn = time.Now()
+ },
+ GotConn: func(ci httptrace.GotConnInfo) {
+ t.gotConn = time.Now()
+ t.gotConnInfo = ci
+ },
+ GotFirstResponseByte: func() {
+ t.gotFirstResponseByte = time.Now()
+ },
+ DNSStart: func(_ httptrace.DNSStartInfo) {
+ t.dnsStart = time.Now()
+ },
+ DNSDone: func(_ httptrace.DNSDoneInfo) {
+ t.dnsDone = time.Now()
+ },
+ TLSHandshakeStart: func() {
+ t.tlsHandshakeStart = time.Now()
+ },
+ TLSHandshakeDone: func(_ tls.ConnectionState, _ error) {
+ t.tlsHandshakeDone = time.Now()
+ },
+ WroteRequest: func(_ httptrace.WroteRequestInfo) {
+ t.wroteRequest = time.Now()
+ },
+ },
+ )
+}
From 97a5dbf44a93efa19b01a4c1ea3ca05f742e5f36 Mon Sep 17 00:00:00 2001
From: Jeevanandam M
Date: Mon, 11 Mar 2019 21:04:59 -0700
Subject: [PATCH 15/28] #226 added new method NewWithLocalAddr to dial from
local address and test cases update
---
client.go | 64 +++++++++++++++++++++++++++----------------------
client_test.go | 26 +++++++++++++++-----
request_test.go | 19 ++++++++-------
resty.go | 17 +++++++++++--
4 files changed, 81 insertions(+), 45 deletions(-)
diff --git a/client.go b/client.go
index 09dcd896..96b35fc1 100644
--- a/client.go
+++ b/client.go
@@ -534,9 +534,10 @@ func (c *Client) AddRetryCondition(condition RetryConditionFunc) *Client {
//
// // or One can disable security check (https)
// client.SetTLSClientConfig(&tls.Config{ InsecureSkipVerify: true })
+//
// Note: This method overwrites existing `TLSClientConfig`.
func (c *Client) SetTLSClientConfig(config *tls.Config) *Client {
- transport, err := c.getTransport()
+ transport, err := c.transport()
if err != nil {
c.Log.Printf("ERROR %v", err)
return c
@@ -552,7 +553,7 @@ func (c *Client) SetTLSClientConfig(config *tls.Config) *Client {
//
// Refer to godoc `http.ProxyFromEnvironment`.
func (c *Client) SetProxy(proxyURL string) *Client {
- transport, err := c.getTransport()
+ transport, err := c.transport()
if err != nil {
c.Log.Printf("ERROR %v", err)
return c
@@ -572,7 +573,7 @@ func (c *Client) SetProxy(proxyURL string) *Client {
// RemoveProxy method removes the proxy configuration from Resty client
// client.RemoveProxy()
func (c *Client) RemoveProxy() *Client {
- transport, err := c.getTransport()
+ transport, err := c.transport()
if err != nil {
c.Log.Printf("ERROR %v", err)
return c
@@ -584,7 +585,7 @@ func (c *Client) RemoveProxy() *Client {
// SetCertificates method helps to set client certificates into Resty conveniently.
func (c *Client) SetCertificates(certs ...tls.Certificate) *Client {
- config, err := c.getTLSConfig()
+ config, err := c.tlsConfig()
if err != nil {
c.Log.Printf("ERROR %v", err)
return c
@@ -602,7 +603,7 @@ func (c *Client) SetRootCertificate(pemFilePath string) *Client {
return c
}
- config, err := c.getTLSConfig()
+ config, err := c.tlsConfig()
if err != nil {
c.Log.Printf("ERROR %v", err)
return c
@@ -838,8 +839,8 @@ func (c *Client) execute(req *Request) (*Response, error) {
}
// getting TLS client config if not exists then create one
-func (c *Client) getTLSConfig() (*tls.Config, error) {
- transport, err := c.getTransport()
+func (c *Client) tlsConfig() (*tls.Config, error) {
+ transport, err := c.transport()
if err != nil {
return nil, err
}
@@ -849,26 +850,9 @@ func (c *Client) getTLSConfig() (*tls.Config, error) {
return transport.TLSClientConfig, nil
}
-// returns `*http.Transport` currently in use or error
-// in case currently used `transport` is not an `*http.Transport`
-func (c *Client) getTransport() (*http.Transport, error) {
- if c.httpClient.Transport == nil {
- transport := &http.Transport{
- Proxy: http.ProxyFromEnvironment,
- DialContext: (&net.Dialer{
- Timeout: 30 * time.Second,
- KeepAlive: 30 * time.Second,
- DualStack: true,
- }).DialContext,
- MaxIdleConns: 100,
- IdleConnTimeout: 90 * time.Second,
- TLSHandshakeTimeout: 10 * time.Second,
- ExpectContinueTimeout: 1 * time.Second,
- MaxIdleConnsPerHost: runtime.GOMAXPROCS(0) + 1,
- }
- c.SetTransport(transport)
- }
-
+// Transport method returns `*http.Transport` currently in use or error
+// in case currently used `transport` is not a `*http.Transport`.
+func (c *Client) transport() (*http.Transport, error) {
if transport, ok := c.httpClient.Transport.(*http.Transport); ok {
return transport, nil
}
@@ -908,7 +892,11 @@ type MultipartField struct {
//_______________________________________________________________________
func createClient(hc *http.Client) *Client {
- c := &Client{ // not setting default values
+ if hc.Transport == nil {
+ hc.Transport = createTransport(nil)
+ }
+
+ c := &Client{ // not setting lang default values
QueryParam: url.Values{},
FormData: url.Values{},
Header: http.Header{},
@@ -948,3 +936,23 @@ func createClient(hc *http.Client) *Client {
return c
}
+
+func createTransport(localAddr net.Addr) *http.Transport {
+ dialer := &net.Dialer{
+ Timeout: 30 * time.Second,
+ KeepAlive: 30 * time.Second,
+ DualStack: true,
+ }
+ if localAddr != nil {
+ dialer.LocalAddr = localAddr
+ }
+ return &http.Transport{
+ Proxy: http.ProxyFromEnvironment,
+ DialContext: dialer.DialContext,
+ MaxIdleConns: 100,
+ IdleConnTimeout: 90 * time.Second,
+ TLSHandshakeTimeout: 10 * time.Second,
+ ExpectContinueTimeout: 1 * time.Second,
+ MaxIdleConnsPerHost: runtime.GOMAXPROCS(0) + 1,
+ }
+}
diff --git a/client_test.go b/client_test.go
index 3fa5aab9..71a32b5d 100644
--- a/client_test.go
+++ b/client_test.go
@@ -9,6 +9,7 @@ import (
"crypto/tls"
"errors"
"io/ioutil"
+ "net"
"net/http"
"net/url"
"path/filepath"
@@ -148,7 +149,7 @@ func TestClientSetCertificates(t *testing.T) {
client := dc()
client.SetCertificates(tls.Certificate{})
- transport, err := client.getTransport()
+ transport, err := client.transport()
assertNil(t, err)
assertEqual(t, 1, len(transport.TLSClientConfig.Certificates))
@@ -158,7 +159,7 @@ func TestClientSetRootCertificate(t *testing.T) {
client := dc()
client.SetRootCertificate(filepath.Join(getTestDataPath(), "sample-root.pem"))
- transport, err := client.getTransport()
+ transport, err := client.transport()
assertNil(t, err)
assertNotNil(t, transport.TLSClientConfig.RootCAs)
@@ -168,7 +169,7 @@ func TestClientSetRootCertificateNotExists(t *testing.T) {
client := dc()
client.SetRootCertificate(filepath.Join(getTestDataPath(), "not-exists-sample-root.pem"))
- transport, err := client.getTransport()
+ transport, err := client.transport()
assertNil(t, err)
assertNil(t, transport.TLSClientConfig)
@@ -207,7 +208,7 @@ func TestClientSetTransport(t *testing.T) {
},
}
client.SetTransport(transport)
- transportInUse, err := client.getTransport()
+ transportInUse, err := client.transport()
assertNil(t, err)
assertEqual(t, true, transport == transportInUse)
@@ -308,7 +309,7 @@ func TestClientOptions(t *testing.T) {
}
client.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
- transport, transportErr := client.getTransport()
+ transport, transportErr := client.transport()
assertNil(t, transportErr)
assertEqual(t, true, transport.TLSClientConfig.InsecureSkipVerify)
@@ -383,7 +384,7 @@ func TestClientRoundTripper(t *testing.T) {
rt := &CustomRoundTripper{}
c.SetTransport(rt)
- ct, err := c.getTransport()
+ ct, err := c.transport()
assertNotNil(t, err)
assertNil(t, ct)
assertEqual(t, "current transport is not an *http.Transport instance", err.Error())
@@ -523,3 +524,16 @@ func TestLogCallbacks(t *testing.T) {
assertEqual(t, errors.New("response test error"), err)
assertNotNil(t, resp)
}
+
+func TestNewWithLocalAddr(t *testing.T) {
+ ts := createGetServer(t)
+ defer ts.Close()
+
+ localAddress, _ := net.ResolveTCPAddr("tcp", "127.0.0.1")
+ client := NewWithLocalAddr(localAddress)
+ client.SetHostURL(ts.URL)
+
+ resp, err := client.R().Get("/")
+ assertNil(t, err)
+ assertEqual(t, resp.String(), "TestGet: text response")
+}
diff --git a/request_test.go b/request_test.go
index cd719874..8d4085cc 100644
--- a/request_test.go
+++ b/request_test.go
@@ -949,7 +949,7 @@ func TestRawFileUploadByBody(t *testing.T) {
func TestProxySetting(t *testing.T) {
c := dc()
- transport, err := c.getTransport()
+ transport, err := c.transport()
assertNil(t, err)
@@ -1440,6 +1440,7 @@ func TestPathParamURLInput(t *testing.T) {
logResponse(t, resp)
}
+// This test case is kind of pass always
func TestTraceInfo(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
@@ -1453,11 +1454,11 @@ func TestTraceInfo(t *testing.T) {
tr := resp.Request.TraceInfo()
assertEqual(t, true, tr.DNSLookup >= 0)
- assertEqual(t, true, tr.ConnTime > 0)
+ assertEqual(t, true, tr.ConnTime >= 0)
assertEqual(t, true, tr.TLSHandshake >= 0)
- assertEqual(t, true, tr.ServerTime > 0)
- assertEqual(t, true, tr.ResponseTime > 0)
- assertEqual(t, true, tr.TotalTime > 0)
+ assertEqual(t, true, tr.ServerTime >= 0)
+ assertEqual(t, true, tr.ResponseTime >= 0)
+ assertEqual(t, true, tr.TotalTime >= 0)
}
client.DisableTrace()
@@ -1469,11 +1470,11 @@ func TestTraceInfo(t *testing.T) {
tr := resp.Request.TraceInfo()
assertEqual(t, true, tr.DNSLookup >= 0)
- assertEqual(t, true, tr.ConnTime > 0)
+ assertEqual(t, true, tr.ConnTime >= 0)
assertEqual(t, true, tr.TLSHandshake >= 0)
- assertEqual(t, true, tr.ServerTime > 0)
- assertEqual(t, true, tr.ResponseTime > 0)
- assertEqual(t, true, tr.TotalTime > 0)
+ assertEqual(t, true, tr.ServerTime >= 0)
+ assertEqual(t, true, tr.ResponseTime >= 0)
+ assertEqual(t, true, tr.TotalTime >= 0)
}
// for sake of hook funcs
diff --git a/resty.go b/resty.go
index 55487505..d291efda 100644
--- a/resty.go
+++ b/resty.go
@@ -6,6 +6,7 @@
package resty
import (
+ "net"
"net/http"
"net/http/cookiejar"
@@ -18,10 +19,22 @@ const Version = "2.0.0-rc.1"
// New method creates a new Resty client.
func New() *Client {
cookieJar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
- return createClient(&http.Client{Jar: cookieJar})
+ return createClient(&http.Client{
+ Jar: cookieJar,
+ })
}
-// NewWithClient method create a new Resty client with given `http.Client`.
+// NewWithClient method creates a new Resty client with given `http.Client`.
func NewWithClient(hc *http.Client) *Client {
return createClient(hc)
}
+
+// NewWithLocalAddr method creates a new Resty client with given Local Address
+// to dial from.
+func NewWithLocalAddr(localAddr net.Addr) *Client {
+ cookieJar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
+ return createClient(&http.Client{
+ Jar: cookieJar,
+ Transport: createTransport(localAddr),
+ })
+}
From 9d4440d4b1c22fcb0939c855214bdc5fd0af7a3a Mon Sep 17 00:00:00 2001
From: Jeevanandam M
Date: Tue, 12 Mar 2019 22:48:47 -0700
Subject: [PATCH 16/28] #229 resty logger becomes interface to provides choice
to an user and also provides default stub to get start
---
README.md | 2 +-
client.go | 54 ++++++++++++++++++++++---------------------------
client_test.go | 16 +++++++--------
middleware.go | 4 ++--
request.go | 4 ++--
request_test.go | 22 ++++++++++----------
resty_test.go | 9 ++++-----
util.go | 43 +++++++++++++++++++++++++++++++++++----
8 files changed, 90 insertions(+), 64 deletions(-)
diff --git a/README.md b/README.md
index d988724b..483061bb 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,7 @@
* Auto detects file content type
* Request URL [Path Params (aka URI Params)](https://godoc.org/github.com/go-resty/resty#Request.SetPathParams)
* Backoff Retry Mechanism with retry condition function [reference](retry_test.go)
- * resty client HTTP & REST [Request](https://godoc.org/github.com/go-resty/resty#Client.OnBeforeRequest) and [Response](https://godoc.org/github.com/go-resty/resty#Client.OnAfterResponse) middlewares
+ * Resty client HTTP & REST [Request](https://godoc.org/github.com/go-resty/resty#Client.OnBeforeRequest) and [Response](https://godoc.org/github.com/go-resty/resty#Client.OnAfterResponse) middlewares
* `Request.SetContext` supported
* Authorization option of `BasicAuth` and `Bearer` token
* Set request `ContentLength` value for all request or particular request
diff --git a/client.go b/client.go
index 96b35fc1..a82a9c18 100644
--- a/client.go
+++ b/client.go
@@ -14,12 +14,10 @@ import (
"fmt"
"io"
"io/ioutil"
- "log"
"math"
"net"
"net/http"
"net/url"
- "os"
"reflect"
"regexp"
"runtime"
@@ -87,7 +85,6 @@ type Client struct {
Debug bool
DisableWarn bool
AllowGetMethodPayload bool
- Log *log.Logger
RetryCount int
RetryWaitTime time.Duration
RetryMaxWaitTime time.Duration
@@ -103,8 +100,8 @@ type Client struct {
debugBodySizeLimit int64
outputDirectory string
scheme string
- logPrefix string
pathParams map[string]string
+ log Logger
httpClient *http.Client
proxyURL *url.URL
beforeRequest []func(*Client, *Request) error
@@ -368,7 +365,7 @@ func (c *Client) OnAfterResponse(m func(*Client, *Response) error) *Client {
// Note: Only one pre-request hook can be registered. Use `client.OnBeforeRequest` for mutilple.
func (c *Client) SetPreRequestHook(h func(*Client, *http.Request) error) *Client {
if c.preReqHook != nil {
- c.Log.Printf("Overwriting an existing pre-request hook: %s", functionName(h))
+ c.log.Warnf("Overwriting an existing pre-request hook: %s", functionName(h))
}
c.preReqHook = h
return c
@@ -394,7 +391,8 @@ func (c *Client) SetDebugBodyLimit(sl int64) *Client {
// called before the resty actually logs the information.
func (c *Client) OnRequestLog(rl func(*RequestLog) error) *Client {
if c.requestLog != nil {
- c.Log.Printf("Overwriting an existing on-request-log callback from=%s to=%s", functionName(c.requestLog), functionName(rl))
+ c.log.Warnf("Overwriting an existing on-request-log callback from=%s to=%s",
+ functionName(c.requestLog), functionName(rl))
}
c.requestLog = rl
return c
@@ -404,7 +402,8 @@ func (c *Client) OnRequestLog(rl func(*RequestLog) error) *Client {
// called before the resty actually logs the information.
func (c *Client) OnResponseLog(rl func(*ResponseLog) error) *Client {
if c.responseLog != nil {
- c.Log.Printf("Overwriting an existing on-response-log callback from=%s to=%s", functionName(c.responseLog), functionName(rl))
+ c.log.Warnf("Overwriting an existing on-response-log callback from=%s to=%s",
+ functionName(c.responseLog), functionName(rl))
}
c.responseLog = rl
return c
@@ -429,12 +428,10 @@ func (c *Client) SetAllowGetMethodPayload(a bool) *Client {
}
// SetLogger method sets given writer for logging Resty request and response details.
-// Defaults to os.Stderr.
-// file, _ := os.OpenFile("/Users/jeeva/go-resty.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
//
-// client.SetLogger(file)
-func (c *Client) SetLogger(w io.Writer) *Client {
- c.Log = getLogger(w)
+// Compliant to interface `resty.Logger`.
+func (c *Client) SetLogger(l Logger) *Client {
+ c.log = l
return c
}
@@ -476,7 +473,7 @@ func (c *Client) SetError(err interface{}) *Client {
func (c *Client) SetRedirectPolicy(policies ...interface{}) *Client {
for _, p := range policies {
if _, ok := p.(RedirectPolicy); !ok {
- c.Log.Printf("ERORR: %v does not implement resty.RedirectPolicy (missing Apply method)",
+ c.log.Errorf("%v does not implement resty.RedirectPolicy (missing Apply method)",
functionName(p))
}
}
@@ -539,7 +536,7 @@ func (c *Client) AddRetryCondition(condition RetryConditionFunc) *Client {
func (c *Client) SetTLSClientConfig(config *tls.Config) *Client {
transport, err := c.transport()
if err != nil {
- c.Log.Printf("ERROR %v", err)
+ c.log.Errorf("%v", err)
return c
}
transport.TLSClientConfig = config
@@ -555,13 +552,13 @@ func (c *Client) SetTLSClientConfig(config *tls.Config) *Client {
func (c *Client) SetProxy(proxyURL string) *Client {
transport, err := c.transport()
if err != nil {
- c.Log.Printf("ERROR %v", err)
+ c.log.Errorf("%v", err)
return c
}
pURL, err := url.Parse(proxyURL)
if err != nil {
- c.Log.Printf("ERROR %v", err)
+ c.log.Errorf("%v", err)
return c
}
@@ -575,7 +572,7 @@ func (c *Client) SetProxy(proxyURL string) *Client {
func (c *Client) RemoveProxy() *Client {
transport, err := c.transport()
if err != nil {
- c.Log.Printf("ERROR %v", err)
+ c.log.Errorf("%v", err)
return c
}
c.proxyURL = nil
@@ -587,7 +584,7 @@ func (c *Client) RemoveProxy() *Client {
func (c *Client) SetCertificates(certs ...tls.Certificate) *Client {
config, err := c.tlsConfig()
if err != nil {
- c.Log.Printf("ERROR %v", err)
+ c.log.Errorf("%v", err)
return c
}
config.Certificates = append(config.Certificates, certs...)
@@ -599,13 +596,13 @@ func (c *Client) SetCertificates(certs ...tls.Certificate) *Client {
func (c *Client) SetRootCertificate(pemFilePath string) *Client {
rootPemData, err := ioutil.ReadFile(pemFilePath)
if err != nil {
- c.Log.Printf("ERROR %v", err)
+ c.log.Errorf("%v", err)
return c
}
config, err := c.tlsConfig()
if err != nil {
- c.Log.Printf("ERROR %v", err)
+ c.log.Errorf("%v", err)
return c
}
if config.RootCAs == nil {
@@ -677,13 +674,6 @@ func (c *Client) SetDoNotParseResponse(parse bool) *Client {
return c
}
-// SetLogPrefix method sets the Resty logger prefix value.
-func (c *Client) SetLogPrefix(prefix string) *Client {
- c.logPrefix = prefix
- c.Log.SetPrefix(prefix)
- return c
-}
-
// SetPathParams method sets multiple URL path key-value pairs at one go in the
// Resty client instance.
// client.SetPathParams(map[string]string{
@@ -859,6 +849,11 @@ func (c *Client) transport() (*http.Transport, error) {
return nil, errors.New("current transport is not an *http.Transport instance")
}
+// just an helper method
+func (c *Client) outputLogTo(w io.Writer) {
+ c.log.(*logger).l.SetOutput(w)
+}
+
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// File struct and its methods
//_______________________________________________________________________
@@ -901,7 +896,6 @@ func createClient(hc *http.Client) *Client {
FormData: url.Values{},
Header: http.Header{},
Cookies: make([]*http.Cookie, 0),
- Log: getLogger(os.Stderr),
RetryWaitTime: defaultWaitTime,
RetryMaxWaitTime: defaultMaxWaitTime,
JSONMarshal: json.Marshal,
@@ -912,8 +906,8 @@ func createClient(hc *http.Client) *Client {
pathParams: make(map[string]string),
}
- // Log Prefix
- c.SetLogPrefix("RESTY ")
+ // Logger
+ c.SetLogger(createLogger())
// default before request middlewares
c.beforeRequest = []func(*Client, *Request) error{
diff --git a/client_test.go b/client_test.go
index 71a32b5d..34b174f4 100644
--- a/client_test.go
+++ b/client_test.go
@@ -315,11 +315,11 @@ func TestClientOptions(t *testing.T) {
assertEqual(t, true, transport.TLSClientConfig.InsecureSkipVerify)
client.OnBeforeRequest(func(c *Client, r *Request) error {
- c.Log.Println("I'm in Request middleware")
+ c.log.Debugf("I'm in Request middleware")
return nil // if it success
})
client.OnAfterResponse(func(c *Client, r *Response) error {
- c.Log.Println("I'm in Response middleware")
+ c.log.Debugf("I'm in Response middleware")
return nil // if it success
})
@@ -344,19 +344,17 @@ func TestClientOptions(t *testing.T) {
client.SetCloseConnection(true)
assertEqual(t, client.closeConnection, true)
-
- client.SetLogger(ioutil.Discard)
}
func TestClientPreRequestHook(t *testing.T) {
client := dc()
client.SetPreRequestHook(func(c *Client, r *http.Request) error {
- c.Log.Println("I'm in Pre-Request Hook")
+ c.log.Debugf("I'm in Pre-Request Hook")
return nil
})
client.SetPreRequestHook(func(c *Client, r *http.Request) error {
- c.Log.Println("I'm Overwriting existing Pre-Request Hook")
+ c.log.Debugf("I'm Overwriting existing Pre-Request Hook")
return nil
})
}
@@ -379,7 +377,7 @@ func TestClientAllowsGetMethodPayload(t *testing.T) {
func TestClientRoundTripper(t *testing.T) {
c := NewWithClient(&http.Client{})
- c.SetLogger(ioutil.Discard)
+ c.outputLogTo(ioutil.Discard)
rt := &CustomRoundTripper{}
c.SetTransport(rt)
@@ -409,8 +407,8 @@ func TestDebugBodySizeLimit(t *testing.T) {
var lgr bytes.Buffer
c := dc()
c.SetDebug(true)
- c.SetLogger(&lgr)
c.SetDebugBodyLimit(30)
+ c.outputLogTo(&lgr)
testcases := []struct{ url, want string }{
// Text, does not exceed limit.
@@ -479,7 +477,7 @@ func TestLogCallbacks(t *testing.T) {
c := New().SetDebug(true)
var lgr bytes.Buffer
- c.SetLogger(&lgr)
+ c.outputLogTo(&lgr)
c.OnRequestLog(func(r *RequestLog) error {
// masking authorzation header
diff --git a/middleware.go b/middleware.go
index 4cd0689b..0a2f289b 100644
--- a/middleware.go
+++ b/middleware.go
@@ -216,7 +216,7 @@ func addCredentials(c *Client, r *Request) error {
if !c.DisableWarn {
if isBasicAuth && !strings.HasPrefix(r.URL, "https") {
- c.Log.Println("WARNING - Using Basic Auth in HTTP mode is not secure.")
+ c.log.Warnf("Using Basic Auth in HTTP mode is not secure, use HTTPS")
}
}
@@ -281,7 +281,7 @@ func responseLogger(c *Client, res *Response) error {
}
debugLog += "==============================================================================\n"
- c.Log.Print(debugLog)
+ c.log.Debugf(debugLog)
}
return nil
diff --git a/request.go b/request.go
index 889a2364..cb5de8ca 100644
--- a/request.go
+++ b/request.go
@@ -172,7 +172,7 @@ func (r *Request) SetQueryString(query string) *Request {
}
}
} else {
- r.client.Log.Printf("ERROR %v", err)
+ r.client.log.Errorf("%v", err)
}
return r
}
@@ -574,7 +574,7 @@ func (r *Request) Execute(method, url string) (*Response, error) {
resp, err = r.client.execute(r)
if err != nil {
- r.client.Log.Printf("ERROR %v, Attempt %v", err, attempt)
+ r.client.log.Errorf("%v, Attempt %v", err, attempt)
if r.ctx != nil && r.ctx.Err() != nil {
// stop Backoff from retrying request if request has been
// canceled by context
diff --git a/request_test.go b/request_test.go
index 8d4085cc..3fdc23d9 100644
--- a/request_test.go
+++ b/request_test.go
@@ -69,8 +69,8 @@ func TestGetClientParamRequestParam(t *testing.T) {
c := dc()
c.SetQueryParam("client_param", "true").
SetQueryParams(map[string]string{"req_1": "jeeva", "req_3": "jeeva3"}).
- SetDebug(true).
- SetLogger(ioutil.Discard)
+ SetDebug(true)
+ c.outputLogTo(ioutil.Discard)
resp, err := c.R().
SetQueryParams(map[string]string{"req_1": "req 1 value", "req_2": "req 2 value"}).
@@ -498,8 +498,8 @@ func TestFormData(t *testing.T) {
c := dc()
c.SetFormData(map[string]string{"zip_code": "00000", "city": "Los Angeles"}).
SetContentLength(true).
- SetDebug(true).
- SetLogger(ioutil.Discard)
+ SetDebug(true)
+ c.outputLogTo(ioutil.Discard)
resp, err := c.R().
SetFormData(map[string]string{"first_name": "Jeevanandam", "last_name": "M", "zip_code": "00001"}).
@@ -520,9 +520,8 @@ func TestMultiValueFormData(t *testing.T) {
}
c := dc()
- c.SetContentLength(true).
- SetDebug(true).
- SetLogger(ioutil.Discard)
+ c.SetContentLength(true).SetDebug(true)
+ c.outputLogTo(ioutil.Discard)
resp, err := c.R().
SetQueryParamsFromValues(v).
@@ -541,8 +540,8 @@ func TestFormDataDisableWarn(t *testing.T) {
c.SetFormData(map[string]string{"zip_code": "00000", "city": "Los Angeles"}).
SetContentLength(true).
SetDebug(true).
- SetLogger(ioutil.Discard).
SetDisableWarn(true)
+ c.outputLogTo(ioutil.Discard)
resp, err := c.R().
SetFormData(map[string]string{"first_name": "Jeevanandam", "last_name": "M", "zip_code": "00001"}).
@@ -820,7 +819,8 @@ func TestPutJSONString(t *testing.T) {
return nil
})
- client.SetDebug(true).SetLogger(ioutil.Discard)
+ client.SetDebug(true)
+ client.outputLogTo(ioutil.Discard)
resp, err := client.R().
SetHeaders(map[string]string{hdrContentTypeKey: jsonContentType, hdrAcceptKey: jsonContentType}).
@@ -1159,8 +1159,8 @@ func TestOutputFileWithBaseDirAndRelativePath(t *testing.T) {
client := dc().
SetRedirectPolicy(FlexibleRedirectPolicy(10)).
SetOutputDirectory(filepath.Join(getTestDataPath(), "dir-sample")).
- SetDebug(true).
- SetLogger(ioutil.Discard)
+ SetDebug(true)
+ client.outputLogTo(ioutil.Discard)
resp, err := client.R().
SetOutput("go-resty/test-img-success.png").
diff --git a/resty_test.go b/resty_test.go
index 771e9663..ab534ca0 100644
--- a/resty_test.go
+++ b/resty_test.go
@@ -540,9 +540,9 @@ func createTestServer(fn func(w http.ResponseWriter, r *http.Request)) *httptest
}
func dc() *Client {
- client := New()
- client.SetLogger(ioutil.Discard)
- return client
+ c := New()
+ c.outputLogTo(ioutil.Discard)
+ return c
}
func dcr() *Request {
@@ -552,8 +552,7 @@ func dcr() *Request {
func dclr() *Request {
c := dc()
c.SetDebug(true)
- c.SetLogger(ioutil.Discard)
-
+ c.outputLogTo(ioutil.Discard)
return c.R()
}
diff --git a/util.go b/util.go
index baa9ce6c..ed9129ff 100644
--- a/util.go
+++ b/util.go
@@ -21,6 +21,45 @@ import (
"strings"
)
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+// Logger interface
+//_______________________________________________________________________
+
+// Logger interface is to abstract the logging from Resty. Gives control to
+// the Resty users, choice of the logger.
+type Logger interface {
+ Errorf(format string, v ...interface{})
+ Warnf(format string, v ...interface{})
+ Debugf(format string, v ...interface{})
+}
+
+func createLogger() *logger {
+ l := &logger{l: log.New(os.Stderr, "", log.Ldate|log.Lmicroseconds)}
+ return l
+}
+
+var _ Logger = (*logger)(nil)
+
+type logger struct {
+ l *log.Logger
+}
+
+func (l *logger) Errorf(format string, v ...interface{}) {
+ l.output("ERROR RESTY "+format, v...)
+}
+
+func (l *logger) Warnf(format string, v ...interface{}) {
+ l.output("WARN RESTY "+format, v...)
+}
+
+func (l *logger) Debugf(format string, v ...interface{}) {
+ l.output("DEBUG RESTY "+format, v...)
+}
+
+func (l *logger) output(format string, v ...interface{}) {
+ l.l.Printf(format, v...)
+}
+
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Package Helper methods
//_______________________________________________________________________
@@ -114,10 +153,6 @@ func firstNonEmpty(v ...string) string {
return ""
}
-func getLogger(w io.Writer) *log.Logger {
- return log.New(w, "RESTY ", log.LstdFlags)
-}
-
var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
func escapeQuotes(s string) string {
From 46fc51a18d3355a0af0a773b09bc6227e30c2d26 Mon Sep 17 00:00:00 2001
From: Jeevanandam M
Date: Tue, 12 Mar 2019 23:59:24 -0700
Subject: [PATCH 17/28] readme sample code and go mod update
---
README.md | 2 +-
go.mod | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 483061bb..4cc13bd3 100644
--- a/README.md
+++ b/README.md
@@ -118,7 +118,7 @@ import "github.com/go-resty/resty"
```go
// Create a Resty Client
-client := New()
+client := resty.New()
// Fire GET request
resp, err := client.R().EnableTrace().Get("https://httpbin.org/get")
diff --git a/go.mod b/go.mod
index 32bd9b87..9f3c4954 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,3 @@
module github.com/go-resty/resty
-require golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3
+require golang.org/x/net v0.0.0-20190311183353-d8887717615a
From e5f377d81a22af428bcf39bf5a4da70d82d00faf Mon Sep 17 00:00:00 2001
From: Alexey Neganov
Date: Thu, 28 Mar 2019 07:54:02 +0300
Subject: [PATCH 18/28] change Backoff() algorithm (#237)
1) Add capabilities to handle Retry-After headers and similar info from
server
Motivation: some servers provide Retry-After header or similar info along with 429
or 503 status code, and it is often important to honor such information
on retries, i.e. simple expotential backoff is not optimal.
https://docs.microsoft.com/en-us/sharepoint/dev/general-development/how-to-avoid-getting-throttled-or-blocked-in-sharepoint-online
2) Add option NOT to retry even if operation returns an error (but retry
by default, if no retry conditions are set)
Motivation: error are already passed to condition callback in resty, but
Backoff() still retries the request if error is not nil. It implies excessive,
stillborn retries for non-retryble errors from underlying http client
(i.e. with RoundTripper from oauth2).
3) Remove error return value from condition callback
Motivation: this error is neither passed to caller, nor logged in any
way. It is cleaner to have "needRetry == true" than "needRetry == true
|| conditionErr != nil".
4) Does not use floating-point arithmetics for expotential backoff
Motivation: simplification & performance
---
client.go | 8 ++
request.go | 7 +-
retry.go | 99 +++++++++++----
retry_test.go | 333 +++++++++++++++++++++++++++++++++++++++++++-------
4 files changed, 376 insertions(+), 71 deletions(-)
diff --git a/client.go b/client.go
index a82a9c18..a7f53325 100644
--- a/client.go
+++ b/client.go
@@ -89,6 +89,7 @@ type Client struct {
RetryWaitTime time.Duration
RetryMaxWaitTime time.Duration
RetryConditions []RetryConditionFunc
+ RetryAfter RetryAfterFunc
JSONMarshal func(v interface{}) ([]byte, error)
JSONUnmarshal func(data []byte, v interface{}) error
@@ -515,6 +516,13 @@ func (c *Client) SetRetryMaxWaitTime(maxWaitTime time.Duration) *Client {
return c
}
+// SetRetryAfter sets callback to calculate wait time between retries.
+// Default (nil) implies exponential backoff with jitter
+func (c *Client) SetRetryAfter(callback RetryAfterFunc) *Client {
+ c.RetryAfter = callback
+ return c
+}
+
// AddRetryCondition method adds a retry condition function to array of functions
// that are checked to determine if the request is retried. The request will
// retry if any of the functions return true and error is nil.
diff --git a/request.go b/request.go
index cb5de8ca..8fa4f569 100644
--- a/request.go
+++ b/request.go
@@ -566,7 +566,7 @@ func (r *Request) Execute(method, url string) (*Response, error) {
var resp *Response
attempt := 0
- _ = Backoff(
+ err = Backoff(
func() (*Response, error) {
attempt++
@@ -575,11 +575,6 @@ func (r *Request) Execute(method, url string) (*Response, error) {
resp, err = r.client.execute(r)
if err != nil {
r.client.log.Errorf("%v, Attempt %v", err, attempt)
- if r.ctx != nil && r.ctx.Err() != nil {
- // stop Backoff from retrying request if request has been
- // canceled by context
- return resp, nil
- }
}
return resp, err
diff --git a/retry.go b/retry.go
index b4a051e2..7125057c 100644
--- a/retry.go
+++ b/retry.go
@@ -21,9 +21,16 @@ type (
Option func(*Options)
// RetryConditionFunc type is for retry condition function
- RetryConditionFunc func(*Response, error) (bool, error)
+ // input: non-nil Response OR request execution error
+ RetryConditionFunc func(*Response, error) bool
+
+ // RetryAfterFunc returns time to wait before retry
+ // For example, it can parse HTTP Retry-After header
+ // https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
+ // Non-nil error is returned if it is found that request is not retryable
+ // (0, nil) is a special result means 'use default algorithm'
+ RetryAfterFunc func(*Client, *Response) (time.Duration, error)
- // Options to hold Resty retry values.
Options struct {
maxRetries int
waitTime time.Duration
@@ -79,40 +86,84 @@ func Backoff(operation func() (*Response, error), options ...Option) error {
resp *Response
err error
)
- base := float64(opts.waitTime) // Time to wait between each attempt
- capLevel := float64(opts.maxWaitTime) // Maximum amount of wait time for the retry
+
for attempt := 0; attempt < opts.maxRetries; attempt++ {
resp, err = operation()
+ if resp != nil && resp.Request.ctx != nil && resp.Request.ctx.Err() != nil {
+ return err
+ }
+
+ needsRetry := err != nil // retry on operation errors by default
- var needsRetry bool
- var conditionErr error
for _, condition := range opts.retryConditions {
- needsRetry, conditionErr = condition(resp, err)
- if needsRetry || conditionErr != nil {
+ needsRetry = condition(resp, err)
+ if needsRetry {
break
}
}
- // If the operation returned no error, there was no condition satisfied and
- // there was no error caused by the conditional functions.
- if err == nil && !needsRetry && conditionErr == nil {
- return nil
- }
- // Adding capped exponential backup with jitter
- // See the following article...
- // http://www.awsarchitectureblog.com/2015/03/backoff.html
- temp := math.Min(capLevel, base*math.Exp2(float64(attempt)))
- ri := int(temp / 2)
- if ri <= 0 {
- ri = 1<<31 - 1 // max int for arch 386
+ if !needsRetry {
+ return err
}
- sleepDuration := time.Duration(math.Abs(float64(ri + rand.Intn(ri))))
- if sleepDuration < opts.waitTime {
- sleepDuration = opts.waitTime
+ waitTime, err2 := sleepDuration(resp, opts.waitTime, opts.maxWaitTime, attempt)
+ if err2 != nil {
+ if err == nil {
+ err = err2
+ }
+ return err
}
- time.Sleep(sleepDuration)
+ time.Sleep(waitTime)
}
return err
}
+
+func sleepDuration(resp *Response, min, max time.Duration, attempt int) (time.Duration, error) {
+ const maxInt = 1<<31 -1 // max int for arch 386
+
+ if max < 0 {
+ max = maxInt
+ }
+
+ if resp == nil {
+ goto defaultCase
+ }
+
+ // 1. Check for custom callback
+ if retryAfterFunc := resp.Request.client.RetryAfter; retryAfterFunc != nil {
+ result, err := retryAfterFunc(resp.Request.client, resp)
+ if err != nil {
+ return 0, err // i.e. 'API quota exceeded'
+ }
+ if result == 0 {
+ goto defaultCase
+ }
+ if result < 0 || max < result {
+ result = max
+ }
+ if result < min {
+ result = min
+ }
+ return result, nil
+ }
+
+ // 2. Return capped exponential backoff with jitter
+ // http://www.awsarchitectureblog.com/2015/03/backoff.html
+defaultCase:
+ base := float64(min)
+ capLevel := float64(max)
+
+ temp := math.Min(capLevel, base*math.Exp2(float64(attempt)))
+ ri := int(temp / 2)
+ if ri <= 0 {
+ ri = maxInt // max int for arch 386
+ }
+ result := time.Duration(math.Abs(float64(ri + rand.Intn(ri))))
+
+ if result < min {
+ result = min
+ }
+
+ return result, nil
+}
diff --git a/retry_test.go b/retry_test.go
index d7509145..a64c4f9a 100644
--- a/retry_test.go
+++ b/retry_test.go
@@ -50,8 +50,8 @@ func TestBackoffTenAttemptsSuccess(t *testing.T) {
func TestConditionalBackoffCondition(t *testing.T) {
attempts := 3
counter := 0
- check := RetryConditionFunc(func(*Response, error) (bool, error) {
- return attempts != counter, nil
+ check := RetryConditionFunc(func(*Response, error) bool {
+ return attempts != counter
})
retryErr := Backoff(func() (*Response, error) {
counter++
@@ -62,26 +62,6 @@ func TestConditionalBackoffCondition(t *testing.T) {
assertEqual(t, counter, attempts)
}
-// Check to make sure that errors in the conditional cause a retry
-func TestConditionalBackoffConditionError(t *testing.T) {
- attempts := 3
- counter := 0
- check := RetryConditionFunc(func(*Response, error) (bool, error) {
- if attempts != counter {
- return false, errors.New("attempts not equal Counter")
- }
- return false, nil
- })
-
- retryErr := Backoff(func() (*Response, error) {
- counter++
- return nil, nil
- }, RetryConditions([]RetryConditionFunc{check}))
-
- assertError(t, retryErr)
- assertEqual(t, counter, attempts)
-}
-
// Check to make sure that if the conditional is false we don't retry
func TestConditionalBackoffConditionNonExecution(t *testing.T) {
attempts := 3
@@ -104,12 +84,9 @@ func TestConditionalGet(t *testing.T) {
externalCounter := 0
// This check should pass on first run, and let the response through
- check := RetryConditionFunc(func(*Response, error) (bool, error) {
+ check := RetryConditionFunc(func(*Response, error) bool {
externalCounter++
- if attemptCount != externalCounter {
- return false, errors.New("attempts not equal Counter")
- }
- return false, nil
+ return attemptCount != externalCounter
})
client := dc().AddRetryCondition(check).SetRetryCount(1)
@@ -135,12 +112,9 @@ func TestConditionalGetDefaultClient(t *testing.T) {
externalCounter := 0
// This check should pass on first run, and let the response through
- check := RetryConditionFunc(func(*Response, error) (bool, error) {
+ check := RetryConditionFunc(func(*Response, error) bool {
externalCounter++
- if attemptCount != externalCounter {
- return false, errors.New("attempts not equal Counter")
- }
- return false, nil
+ return attemptCount != externalCounter
})
// Clear the default client.
@@ -197,11 +171,11 @@ func TestClientRetryWait(t *testing.T) {
SetRetryWaitTime(retryWaitTime).
SetRetryMaxWaitTime(retryMaxWaitTime).
AddRetryCondition(
- func(r *Response, err error) (bool, error) {
+ func(r *Response, _ error) bool {
timeSlept, _ := strconv.ParseUint(string(r.Body()), 10, 64)
retryIntervals[attempt] = timeSlept
attempt++
- return true, nil
+ return true
},
)
_, _ = c.R().Get(ts.URL + "/set-retrywaittime-test")
@@ -222,6 +196,286 @@ func TestClientRetryWait(t *testing.T) {
}
}
+func TestClientRetryWaitMaxInfinite(t *testing.T) {
+ ts := createGetServer(t)
+ defer ts.Close()
+
+ attempt := 0
+
+ retryCount := 5
+ retryIntervals := make([]uint64, retryCount)
+
+ // Set retry wait times that do not intersect with default ones
+ retryWaitTime := time.Duration(3) * time.Second
+ retryMaxWaitTime := time.Duration(-1.0) // negative value
+
+ c := dc().
+ SetRetryCount(retryCount).
+ SetRetryWaitTime(retryWaitTime).
+ SetRetryMaxWaitTime(retryMaxWaitTime).
+ AddRetryCondition(
+ func(r *Response, _ error) bool {
+ timeSlept, _ := strconv.ParseUint(string(r.Body()), 10, 64)
+ retryIntervals[attempt] = timeSlept
+ attempt++
+ return true
+ },
+ )
+ _, _ = c.R().Get(ts.URL + "/set-retrywaittime-test")
+
+ // 5 attempts were made
+ assertEqual(t, attempt, 5)
+
+ // Initial attempt has 0 time slept since last request
+ assertEqual(t, retryIntervals[0], uint64(0))
+
+ for i := 1; i < len(retryIntervals); i++ {
+ slept := time.Duration(retryIntervals[i])
+ // Ensure that client has slept some duration between
+ // waitTime and maxWaitTime for consequent requests
+ if slept < retryWaitTime {
+ t.Errorf("Client has slept %f seconds before retry %d", slept.Seconds(), i)
+ }
+ }
+}
+
+func TestClientRetryWaitCallbackError(t *testing.T) {
+ ts := createGetServer(t)
+ defer ts.Close()
+
+ attempt := 0
+
+ retryCount := 5
+ retryIntervals := make([]uint64, retryCount)
+
+ // Set retry wait times that do not intersect with default ones
+ retryWaitTime := 3 * time.Second
+ retryMaxWaitTime := 9 * time.Second
+
+ retryAfter := func(client *Client, resp *Response) (time.Duration, error) {
+ return 0, errors.New("quota exceeded")
+ }
+
+ c := dc().
+ SetRetryCount(retryCount).
+ SetRetryWaitTime(retryWaitTime).
+ SetRetryMaxWaitTime(retryMaxWaitTime).
+ SetRetryAfter(retryAfter).
+ AddRetryCondition(
+ func(r *Response, _ error) bool {
+ timeSlept, _ := strconv.ParseUint(string(r.Body()), 10, 64)
+ retryIntervals[attempt] = timeSlept
+ attempt++
+ return true
+ },
+ )
+
+ _, err := c.R().Get(ts.URL + "/set-retrywaittime-test")
+
+ // 1 attempts were made
+ assertEqual(t, attempt, 1)
+
+ // non-nil error was returned
+ assertNotEqual(t, nil, err)
+}
+
+func TestClientRetryWaitCallback(t *testing.T) {
+ ts := createGetServer(t)
+ defer ts.Close()
+
+ attempt := 0
+
+ retryCount := 5
+ retryIntervals := make([]uint64, retryCount)
+
+ // Set retry wait times that do not intersect with default ones
+ retryWaitTime := 3 * time.Second
+ retryMaxWaitTime := 9 * time.Second
+
+ retryAfter := func(client *Client, resp *Response) (time.Duration, error) {
+ return 5 * time.Second, nil
+ }
+
+ c := dc().
+ SetRetryCount(retryCount).
+ SetRetryWaitTime(retryWaitTime).
+ SetRetryMaxWaitTime(retryMaxWaitTime).
+ SetRetryAfter(retryAfter).
+ AddRetryCondition(
+ func(r *Response, _ error) bool {
+ timeSlept, _ := strconv.ParseUint(string(r.Body()), 10, 64)
+ retryIntervals[attempt] = timeSlept
+ attempt++
+ return true
+ },
+ )
+ _, _ = c.R().Get(ts.URL + "/set-retrywaittime-test")
+
+ // 5 attempts were made
+ assertEqual(t, attempt, 5)
+
+ // Initial attempt has 0 time slept since last request
+ assertEqual(t, retryIntervals[0], uint64(0))
+
+ for i := 1; i < len(retryIntervals); i++ {
+ slept := time.Duration(retryIntervals[i])
+ // Ensure that client has slept some duration between
+ // waitTime and maxWaitTime for consequent requests
+ if slept < 5*time.Second-5*time.Millisecond || 5*time.Second+5*time.Millisecond < slept {
+ t.Errorf("Client has slept %f seconds before retry %d", slept.Seconds(), i)
+ }
+ }
+}
+
+func TestClientRetryWaitCallbackTooShort(t *testing.T) {
+ ts := createGetServer(t)
+ defer ts.Close()
+
+ attempt := 0
+
+ retryCount := 5
+ retryIntervals := make([]uint64, retryCount)
+
+ // Set retry wait times that do not intersect with default ones
+ retryWaitTime := 3 * time.Second
+ retryMaxWaitTime := 9 * time.Second
+
+ retryAfter := func(client *Client, resp *Response) (time.Duration, error) {
+ return 2 * time.Second, nil // too short duration
+ }
+
+ c := dc().
+ SetRetryCount(retryCount).
+ SetRetryWaitTime(retryWaitTime).
+ SetRetryMaxWaitTime(retryMaxWaitTime).
+ SetRetryAfter(retryAfter).
+ AddRetryCondition(
+ func(r *Response, _ error) bool {
+ timeSlept, _ := strconv.ParseUint(string(r.Body()), 10, 64)
+ retryIntervals[attempt] = timeSlept
+ attempt++
+ return true
+ },
+ )
+ _, _ = c.R().Get(ts.URL + "/set-retrywaittime-test")
+
+ // 5 attempts were made
+ assertEqual(t, attempt, 5)
+
+ // Initial attempt has 0 time slept since last request
+ assertEqual(t, retryIntervals[0], uint64(0))
+
+ for i := 1; i < len(retryIntervals); i++ {
+ slept := time.Duration(retryIntervals[i])
+ // Ensure that client has slept some duration between
+ // waitTime and maxWaitTime for consequent requests
+ if slept < retryWaitTime-5*time.Millisecond || retryWaitTime+5*time.Millisecond < slept {
+ t.Errorf("Client has slept %f seconds before retry %d", slept.Seconds(), i)
+ }
+ }
+}
+
+func TestClientRetryWaitCallbackTooLong(t *testing.T) {
+ ts := createGetServer(t)
+ defer ts.Close()
+
+ attempt := 0
+
+ retryCount := 5
+ retryIntervals := make([]uint64, retryCount)
+
+ // Set retry wait times that do not intersect with default ones
+ retryWaitTime := 1 * time.Second
+ retryMaxWaitTime := 3 * time.Second
+
+ retryAfter := func(client *Client, resp *Response) (time.Duration, error) {
+ return 4 * time.Second, nil // too long duration
+ }
+
+ c := dc().
+ SetRetryCount(retryCount).
+ SetRetryWaitTime(retryWaitTime).
+ SetRetryMaxWaitTime(retryMaxWaitTime).
+ SetRetryAfter(retryAfter).
+ AddRetryCondition(
+ func(r *Response, _ error) bool {
+ timeSlept, _ := strconv.ParseUint(string(r.Body()), 10, 64)
+ retryIntervals[attempt] = timeSlept
+ attempt++
+ return true
+ },
+ )
+ _, _ = c.R().Get(ts.URL + "/set-retrywaittime-test")
+
+ // 5 attempts were made
+ assertEqual(t, attempt, 5)
+
+ // Initial attempt has 0 time slept since last request
+ assertEqual(t, retryIntervals[0], uint64(0))
+
+ for i := 1; i < len(retryIntervals); i++ {
+ slept := time.Duration(retryIntervals[i])
+ // Ensure that client has slept some duration between
+ // waitTime and maxWaitTime for consequent requests
+ if slept < retryMaxWaitTime-5*time.Millisecond || retryMaxWaitTime+5*time.Millisecond < slept {
+ t.Errorf("Client has slept %f seconds before retry %d", slept.Seconds(), i)
+ }
+ }
+}
+
+func TestClientRetryWaitCallbackSwitchToDefault(t *testing.T) {
+ ts := createGetServer(t)
+ defer ts.Close()
+
+ attempt := 0
+
+ retryCount := 5
+ retryIntervals := make([]uint64, retryCount)
+
+ // Set retry wait times that do not intersect with default ones
+ retryWaitTime := 1 * time.Second
+ retryMaxWaitTime := 3 * time.Second
+
+ retryAfter := func(client *Client, resp *Response) (time.Duration, error) {
+ return 0, nil // use default algorithm to determine retry-after time
+ }
+
+ c := dc().
+ SetRetryCount(retryCount).
+ SetRetryWaitTime(retryWaitTime).
+ SetRetryMaxWaitTime(retryMaxWaitTime).
+ SetRetryAfter(retryAfter).
+ AddRetryCondition(
+ func(r *Response, _ error) bool {
+ timeSlept, _ := strconv.ParseUint(string(r.Body()), 10, 64)
+ retryIntervals[attempt] = timeSlept
+ attempt++
+ return true
+ },
+ )
+ _, _ = c.R().Get(ts.URL + "/set-retrywaittime-test")
+
+ // 5 attempts were made
+ assertEqual(t, attempt, 5)
+
+ // Initial attempt has 0 time slept since last request
+ assertEqual(t, retryIntervals[0], uint64(0))
+
+ for i := 1; i < len(retryIntervals); i++ {
+ slept := time.Duration(retryIntervals[i])
+ expected := (1 << (uint(i-1))) * time.Second
+ if expected > retryMaxWaitTime {
+ expected = retryMaxWaitTime
+ }
+
+ // Ensure that client has slept some duration between
+ // waitTime and maxWaitTime for consequent requests
+ if slept < expected/2 - 5*time.Millisecond || expected + 5*time.Millisecond < slept {
+ t.Errorf("Client has slept %f seconds before retry %d", slept.Seconds(), i)
+ }
+ }
+}
+
func TestClientRetryPost(t *testing.T) {
ts := createPostServer(t)
defer ts.Close()
@@ -235,11 +489,8 @@ func TestClientRetryPost(t *testing.T) {
c := dc()
c.SetRetryCount(3)
- c.AddRetryCondition(RetryConditionFunc(func(r *Response, err error) (bool, error) {
- if r.StatusCode() >= http.StatusInternalServerError {
- return false, errors.New("error")
- }
- return true, nil
+ c.AddRetryCondition(RetryConditionFunc(func(r *Response, _ error) bool {
+ return r.StatusCode() >= http.StatusInternalServerError
}))
resp, _ := c.R().
@@ -263,6 +514,6 @@ func TestClientRetryPost(t *testing.T) {
}
}
-func filler(*Response, error) (bool, error) {
- return false, nil
+func filler(*Response, error) bool {
+ return false
}
From 84e2832dc94cceda7fa01ae7392d1ea62adb3e66 Mon Sep 17 00:00:00 2001
From: Alexey Neganov
Date: Sun, 7 Apr 2019 06:59:44 +0300
Subject: [PATCH 19/28] Backoff: awake on context cancel (#238)
Stop sleeping between attempts if request context was cancelled
Motivation: it may be important to finish all tasks on context cancel as soon as possible,
i.e. on graceful shutdown, before the process is killed.
---
retry.go | 14 ++++++++++++--
retry_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 56 insertions(+), 2 deletions(-)
diff --git a/retry.go b/retry.go
index 7125057c..4e8e13d7 100644
--- a/retry.go
+++ b/retry.go
@@ -5,6 +5,7 @@
package resty
import (
+ "context"
"math"
"math/rand"
"time"
@@ -89,7 +90,11 @@ func Backoff(operation func() (*Response, error), options ...Option) error {
for attempt := 0; attempt < opts.maxRetries; attempt++ {
resp, err = operation()
- if resp != nil && resp.Request.ctx != nil && resp.Request.ctx.Err() != nil {
+ ctx := context.Background()
+ if resp != nil && resp.Request.ctx != nil {
+ ctx = resp.Request.ctx
+ }
+ if ctx.Err() != nil {
return err
}
@@ -113,7 +118,12 @@ func Backoff(operation func() (*Response, error), options ...Option) error {
}
return err
}
- time.Sleep(waitTime)
+
+ select {
+ case <-time.After(waitTime):
+ case <-ctx.Done():
+ return ctx.Err()
+ }
}
return err
diff --git a/retry_test.go b/retry_test.go
index a64c4f9a..88623f4f 100644
--- a/retry_test.go
+++ b/retry_test.go
@@ -5,6 +5,7 @@
package resty
import (
+ "context"
"encoding/json"
"errors"
"net/http"
@@ -476,6 +477,49 @@ func TestClientRetryWaitCallbackSwitchToDefault(t *testing.T) {
}
}
+func TestClientRetryCancel(t *testing.T) {
+ ts := createGetServer(t)
+ defer ts.Close()
+
+ attempt := 0
+
+ retryCount := 5
+ retryIntervals := make([]uint64, retryCount)
+
+ // Set retry wait times that do not intersect with default ones
+ retryWaitTime := time.Duration(10) * time.Second
+ retryMaxWaitTime := time.Duration(20) * time.Second
+
+ c := dc().
+ SetRetryCount(retryCount).
+ SetRetryWaitTime(retryWaitTime).
+ SetRetryMaxWaitTime(retryMaxWaitTime).
+ AddRetryCondition(
+ func(r *Response, _ error) bool {
+ timeSlept, _ := strconv.ParseUint(string(r.Body()), 10, 64)
+ retryIntervals[attempt] = timeSlept
+ attempt++
+ return true
+ },
+ )
+
+ timeout := 2*time.Second
+
+ ctx, _ := context.WithTimeout(context.Background(), timeout)
+ _, _ = c.R().SetContext(ctx).Get(ts.URL + "/set-retrywaittime-test")
+
+ // 2 attempts were made
+ assertEqual(t, attempt, 1)
+
+ // Initial attempt has 0 time slept since last request
+ assertEqual(t, retryIntervals[0], uint64(0))
+
+ // Second attempt should be interrupted on context timeout
+ if time.Duration(retryIntervals[1]) > timeout {
+ t.Errorf("Client didn't awake on context cancel")
+ }
+}
+
func TestClientRetryPost(t *testing.T) {
ts := createPostServer(t)
defer ts.Close()
From 0fc02574bb686a9b49e5202ce08635a05d813e26 Mon Sep 17 00:00:00 2001
From: larry
Date: Tue, 18 Jun 2019 09:04:53 +0800
Subject: [PATCH 20/28] fix multipart/form-data without filename and content
type (#236)
---
util.go | 16 +++++++++++++---
1 file changed, 13 insertions(+), 3 deletions(-)
diff --git a/util.go b/util.go
index ed9129ff..93498066 100644
--- a/util.go
+++ b/util.go
@@ -161,9 +161,19 @@ func escapeQuotes(s string) string {
func createMultipartHeader(param, fileName, contentType string) textproto.MIMEHeader {
hdr := make(textproto.MIMEHeader)
- hdr.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
- escapeQuotes(param), escapeQuotes(fileName)))
- hdr.Set("Content-Type", contentType)
+
+ var contentDispositionValue string
+ if IsStringEmpty(fileName) {
+ contentDispositionValue = fmt.Sprintf(`form-data; name="%s"`, param)
+ } else {
+ contentDispositionValue = fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
+ param, escapeQuotes(fileName))
+ }
+ hdr.Set("Content-Disposition", contentDispositionValue)
+
+ if !IsStringEmpty(contentType) {
+ hdr.Set(hdrContentTypeKey, contentType)
+ }
return hdr
}
From 7e28ffd9754a82b23d2f6b2226f5ed1cbd2392a7 Mon Sep 17 00:00:00 2001
From: Jeevanandam M
Date: Tue, 18 Jun 2019 23:57:44 -0700
Subject: [PATCH 21/28] #239 addinh http verb patch in to mutlipart payload
support
---
request.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/request.go b/request.go
index 8fa4f569..0973442d 100644
--- a/request.go
+++ b/request.go
@@ -546,7 +546,7 @@ func (r *Request) Execute(method, url string) (*Response, error) {
var addrs []*net.SRV
var err error
- if r.isMultiPart && !(method == MethodPost || method == MethodPut) {
+ if r.isMultiPart && !(method == MethodPost || method == MethodPut || method == MethodPatch) {
return nil, fmt.Errorf("multipart content is not allowed in HTTP verb [%v]", method)
}
From c8a780583105a5a580d271c4d18ef7a273cae1f0 Mon Sep 17 00:00:00 2001
From: Jeevanandam M
Date: Tue, 18 Jun 2019 23:59:28 -0700
Subject: [PATCH 22/28] #236 added test case for multipart file field
---
request_test.go | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/request_test.go b/request_test.go
index 3fdc23d9..b2eaa8af 100644
--- a/request_test.go
+++ b/request_test.go
@@ -711,6 +711,11 @@ func TestMultiPartMultipartFields(t *testing.T) {
ContentType: "application/json",
Reader: strings.NewReader(jsonStr2),
},
+ {
+ Param: "uploadManifest3",
+ ContentType: "application/json",
+ Reader: strings.NewReader(jsonStr2),
+ },
}
resp, err := dclr().
From e5a6a09942948475bc251dd6587ed369732be2ec Mon Sep 17 00:00:00 2001
From: Jeevanandam M
Date: Wed, 19 Jun 2019 00:02:26 -0700
Subject: [PATCH 23/28] go mod dependencies update
---
go.mod | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/go.mod b/go.mod
index 9f3c4954..c48e8edc 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,3 @@
module github.com/go-resty/resty
-require golang.org/x/net v0.0.0-20190311183353-d8887717615a
+require golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b
From 139ef94261e7755fd07930172866d88bd65072cf Mon Sep 17 00:00:00 2001
From: Jeevanandam M
Date: Wed, 19 Jun 2019 00:23:06 -0700
Subject: [PATCH 24/28] godoc, test case and formatting
---
client.go | 4 ++--
retry.go | 3 ++-
retry_test.go | 9 +++++----
3 files changed, 9 insertions(+), 7 deletions(-)
diff --git a/client.go b/client.go
index a7f53325..56e4469c 100644
--- a/client.go
+++ b/client.go
@@ -519,8 +519,8 @@ func (c *Client) SetRetryMaxWaitTime(maxWaitTime time.Duration) *Client {
// SetRetryAfter sets callback to calculate wait time between retries.
// Default (nil) implies exponential backoff with jitter
func (c *Client) SetRetryAfter(callback RetryAfterFunc) *Client {
- c.RetryAfter = callback
- return c
+ c.RetryAfter = callback
+ return c
}
// AddRetryCondition method adds a retry condition function to array of functions
diff --git a/retry.go b/retry.go
index 4e8e13d7..5f34cb6f 100644
--- a/retry.go
+++ b/retry.go
@@ -32,6 +32,7 @@ type (
// (0, nil) is a special result means 'use default algorithm'
RetryAfterFunc func(*Client, *Response) (time.Duration, error)
+ // Options struct is used to hold retry settings.
Options struct {
maxRetries int
waitTime time.Duration
@@ -130,7 +131,7 @@ func Backoff(operation func() (*Response, error), options ...Option) error {
}
func sleepDuration(resp *Response, min, max time.Duration, attempt int) (time.Duration, error) {
- const maxInt = 1<<31 -1 // max int for arch 386
+ const maxInt = 1<<31 - 1 // max int for arch 386
if max < 0 {
max = maxInt
diff --git a/retry_test.go b/retry_test.go
index 88623f4f..de3ba794 100644
--- a/retry_test.go
+++ b/retry_test.go
@@ -464,14 +464,14 @@ func TestClientRetryWaitCallbackSwitchToDefault(t *testing.T) {
for i := 1; i < len(retryIntervals); i++ {
slept := time.Duration(retryIntervals[i])
- expected := (1 << (uint(i-1))) * time.Second
+ expected := (1 << (uint(i - 1))) * time.Second
if expected > retryMaxWaitTime {
expected = retryMaxWaitTime
}
// Ensure that client has slept some duration between
// waitTime and maxWaitTime for consequent requests
- if slept < expected/2 - 5*time.Millisecond || expected + 5*time.Millisecond < slept {
+ if slept < expected/2-5*time.Millisecond || expected+5*time.Millisecond < slept {
t.Errorf("Client has slept %f seconds before retry %d", slept.Seconds(), i)
}
}
@@ -503,9 +503,9 @@ func TestClientRetryCancel(t *testing.T) {
},
)
- timeout := 2*time.Second
+ timeout := 2 * time.Second
- ctx, _ := context.WithTimeout(context.Background(), timeout)
+ ctx, cancelFunc := context.WithTimeout(context.Background(), timeout)
_, _ = c.R().SetContext(ctx).Get(ts.URL + "/set-retrywaittime-test")
// 2 attempts were made
@@ -518,6 +518,7 @@ func TestClientRetryCancel(t *testing.T) {
if time.Duration(retryIntervals[1]) > timeout {
t.Errorf("Client didn't awake on context cancel")
}
+ cancelFunc()
}
func TestClientRetryPost(t *testing.T) {
From be9a31492f0a1d9549946c826eda4180b884901a Mon Sep 17 00:00:00 2001
From: Jeevanandam M
Date: Wed, 19 Jun 2019 01:33:05 -0700
Subject: [PATCH 25/28] version bump and readme update for v2.0.0 release
---
README.md | 28 +++++++++++++++-------------
resty.go | 2 +-
2 files changed, 16 insertions(+), 14 deletions(-)
diff --git a/README.md b/README.md
index 4cc13bd3..45bdd5c8 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@
## News
- * v2.0.0 [released](https://github.com/go-resty/resty/releases/tag/v2.0.0) and tagged on TBD
+ * v2.0.0 [released](https://github.com/go-resty/resty/releases/tag/v2.0.0) and tagged on Jun 19, 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).
@@ -120,8 +120,9 @@ import "github.com/go-resty/resty"
// Create a Resty Client
client := resty.New()
-// Fire GET request
-resp, err := client.R().EnableTrace().Get("https://httpbin.org/get")
+resp, err := client.R().
+ EnableTrace().
+ Get("https://httpbin.org/get")
// Explore response object
fmt.Println("Response Info:")
@@ -606,7 +607,12 @@ client.
SetRetryWaitTime(5 * time.Second).
// MaxWaitTime can be overridden as well.
// Default is 2 seconds.
- SetRetryMaxWaitTime(20 * time.Second)
+ SetRetryMaxWaitTime(20 * time.Second).
+ // SetRetryAfter sets callback to calculate wait time between retries.
+ // Default (nil) implies exponential backoff with jitter
+ SetRetryAfter(func(client *Client, resp *Response) (time.Duration, error) {
+ return 0, errors.New("quota exceeded")
+ })
```
Above setup will result in resty retrying requests returned non nil error up to
@@ -619,11 +625,10 @@ You can optionally provide client with custom retry conditions:
client := resty.New()
client.AddRetryCondition(
- // Condition function will be provided with *resty.Response as a
- // parameter. It is expected to return (bool, error) pair. Resty will retry
- // in case condition returns true or non nil error.
- func(r *resty.Response, err error) (bool, error) {
- return r.StatusCode() == http.StatusTooManyRequests, nil
+ // RetryConditionFunc type is for retry condition function
+ // input: non-nil Response OR request execution error
+ func(r *resty.Response, err error) bool {
+ return r.StatusCode() == http.StatusTooManyRequests
},
)
```
@@ -674,10 +679,6 @@ client := resty.New()
// Enable debug mode
client.SetDebug(true)
-// Using you custom log writer
-logFile, _ := os.OpenFile("/Users/jeeva/go-resty.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
-client.SetLogger(logFile)
-
// Assign Client TLSClientConfig
// One can set custom root-certificate. Refer: http://golang.org/pkg/crypto/tls/#example_Dial
client.SetTLSClientConfig(&tls.Config{ RootCAs: roots })
@@ -792,6 +793,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 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.
diff --git a/resty.go b/resty.go
index d291efda..a99e0a6c 100644
--- a/resty.go
+++ b/resty.go
@@ -14,7 +14,7 @@ import (
)
// Version # of resty
-const Version = "2.0.0-rc.1"
+const Version = "2.0.0"
// New method creates a new Resty client.
func New() *Client {
From a4f3548ed9923f397ca3370cfd702668c84c526b Mon Sep 17 00:00:00 2001
From: Jeevanandam M
Date: Tue, 16 Jul 2019 09:48:43 -0700
Subject: [PATCH 26/28] adding v2 go mod suffix and update dependency version
---
example_test.go | 2 +-
go.mod | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/example_test.go b/example_test.go
index b1491dd1..587540aa 100644
--- a/example_test.go
+++ b/example_test.go
@@ -16,7 +16,7 @@ import (
"golang.org/x/net/proxy"
- "github.com/go-resty/resty"
+ "github.com/go-resty/resty/v2"
)
type DropboxError struct {
diff --git a/go.mod b/go.mod
index c48e8edc..b2b881ac 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,3 @@
-module github.com/go-resty/resty
+module github.com/go-resty/resty/v2
-require golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b
+require golang.org/x/net v0.0.0-20190628185345-da137c7871d7
From af81b3f935b5a450175cca9e5a5b932706f5a754 Mon Sep 17 00:00:00 2001
From: Jeevanandam M
Date: Tue, 16 Jul 2019 21:36:34 -0700
Subject: [PATCH 27/28] readme update for the release
---
README.md | 36 ++++++++++++++++++------------------
1 file changed, 18 insertions(+), 18 deletions(-)
diff --git a/README.md b/README.md
index 45bdd5c8..8eb9adad 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@
## News
- * v2.0.0 [released](https://github.com/go-resty/resty/releases/tag/v2.0.0) and tagged on Jun 19, 2019.
+ * 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.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).
@@ -64,8 +64,6 @@
* Easily mock Resty for testing, [for e.g.](#mocking-http-requests-using-httpmock-library)
* Well tested client library
-Resty supports with `go1.8` and above.
-
### Included Batteries
* Redirect Policies - see [how to use](#redirect-policy)
@@ -79,22 +77,17 @@ Resty supports with `go1.8` and above.
* SRV Record based request instead of Host URL [how to use](resty_test.go#L1412)
* etc (upcoming - throw your idea's [here](https://github.com/go-resty/resty/issues)).
-## Installation
-#### Stable Version - Production Ready
+#### Supported Go Versions
-Please refer section [Versioning](#versioning) for detailed info.
+Initially Resty started supporting `go modules` since `v1.10.0` release.
-##### go.mod
+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:
-```bash
-require github.com/go-resty/resty v2.0.0
-```
+- 1.9.7+
+- 1.10.3+
+- 1.11+
-##### go get
-```bash
-go get -u github.com/go-resty/resty
-```
## It might be beneficial for your project :smile:
@@ -104,14 +97,21 @@ Resty author also published following projects for Go Community.
* [THUMBAI](https://thumbai.app) - Go Mod Repository, Go Vanity Service and Simple Proxy Server.
* [go-model](https://github.com/jeevatkm/go-model) - Robust & Easy to use model mapper and utility methods for Go `struct`.
-## Usage
-The following samples will assist you to become as comfortable as possible with resty library. Resty comes with ready to use DefaultClient.
+## Installation
+
+```bash
+# Go Modules
+require github.com/go-resty/resty/v2 v2.0.0
+```
+
+## Usage
-Import resty into your code and refer it as `resty`.
+The following samples will assist you to become as comfortable as possible with resty library.
```go
-import "github.com/go-resty/resty"
+// Import resty into your code and refer it as `resty`.
+import "github.com/go-resty/resty/v2"
```
#### Simple GET
From 17347718b6c5ed710c98f4758e48646fbc900c44 Mon Sep 17 00:00:00 2001
From: Jeevanandam M
Date: Tue, 16 Jul 2019 21:47:22 -0700
Subject: [PATCH 28/28] update travis build config and bazel config import path
update to v2
---
.travis.yml | 10 +---------
BUILD.bazel | 6 +++---
2 files changed, 4 insertions(+), 12 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index 07b84371..db6b67b9 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,15 +2,7 @@ language: go
sudo: false
-go: # use travis ci resource effectively
-# - 1.3
-# - 1.4
-# - 1.5
-# - 1.6
-# - 1.7
-# - 1.8.x
-# - 1.9.x
- - 1.10.x
+go: # use travis ci resource effectively, keep always latest 2 versions and tip :)
- 1.11.x
- 1.12.x
- tip
diff --git a/BUILD.bazel b/BUILD.bazel
index 3d8681b7..6c47cbbb 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -6,7 +6,7 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
gazelle(
name = "gazelle",
command = "fix",
- prefix = "github.com/go-resty/resty",
+ prefix = "github.com/go-resty/resty/v2",
)
go_library(
@@ -15,7 +15,7 @@ go_library(
["*.go"],
exclude = ["*_test.go"],
),
- importpath = "github.com/go-resty/resty",
+ importpath = "github.com/go-resty/resty/v2",
visibility = ["//visibility:public"],
deps = ["@org_golang_x_net//publicsuffix:go_default_library"],
)
@@ -29,7 +29,7 @@ go_test(
),
data = glob([".testdata/*"]),
embed = [":go_default_library"],
- importpath = "github.com/go-resty/resty",
+ importpath = "github.com/go-resty/resty/v2",
deps = [
"@org_golang_x_net//proxy:go_default_library",
],