diff --git a/client.go b/client.go
index fcd0e1d5..51b98eed 100644
--- a/client.go
+++ b/client.go
@@ -177,6 +177,7 @@ type Client struct {
retryResetReaders bool
headerAuthorizationKey string
responseBodyLimit int64
+ resBodyUnlimitedReads bool
jsonEscapeHTML bool
setContentLength bool
closeConnection bool
@@ -563,24 +564,26 @@ func (c *Client) R() *Request {
c.lock.RLock()
defer c.lock.RUnlock()
r := &Request{
- QueryParams: url.Values{},
- FormData: url.Values{},
- Header: http.Header{},
- Cookies: make([]*http.Cookie, 0),
- PathParams: make(map[string]string),
- RawPathParams: make(map[string]string),
- Debug: c.debug,
- AuthScheme: c.authScheme,
- AuthToken: c.authToken,
- UserInfo: c.userInfo,
- RetryCount: c.retryCount,
- RetryWaitTime: c.retryWaitTime,
- RetryMaxWaitTime: c.retryMaxWaitTime,
- RetryResetReaders: c.retryResetReaders,
- CloseConnection: c.closeConnection,
- DoNotParseResponse: c.notParseResponse,
- DebugBodyLimit: c.debugBodyLimit,
- ResponseBodyLimit: c.responseBodyLimit,
+ QueryParams: url.Values{},
+ FormData: url.Values{},
+ Header: http.Header{},
+ Cookies: make([]*http.Cookie, 0),
+ PathParams: make(map[string]string),
+ RawPathParams: make(map[string]string),
+ Debug: c.debug,
+ IsTrace: c.isTrace,
+ AuthScheme: c.authScheme,
+ AuthToken: c.authToken,
+ UserInfo: c.userInfo,
+ RetryCount: c.retryCount,
+ RetryWaitTime: c.retryWaitTime,
+ RetryMaxWaitTime: c.retryMaxWaitTime,
+ RetryResetReaders: c.retryResetReaders,
+ CloseConnection: c.closeConnection,
+ DoNotParseResponse: c.notParseResponse,
+ DebugBodyLimit: c.debugBodyLimit,
+ ResponseBodyLimit: c.responseBodyLimit,
+ ResponseBodyUnlimitedReads: c.resBodyUnlimitedReads,
client: c,
multipartFiles: []*File{},
@@ -588,7 +591,6 @@ func (c *Client) R() *Request {
jsonEscapeHTML: c.jsonEscapeHTML,
log: c.log,
setContentLength: c.setContentLength,
- IsTrace: c.isTrace,
generateCurlOnDebug: c.generateCurlOnDebug,
}
@@ -781,6 +783,18 @@ func (c *Client) IsDebug() bool {
return c.debug
}
+// EnableDebug method is a helper method for [Client.SetDebug]
+func (c *Client) EnableDebug() *Client {
+ c.SetDebug(true)
+ return c
+}
+
+// DisableDebug method is a helper method for [Client.SetDebug]
+func (c *Client) DisableDebug() *Client {
+ c.SetDebug(false)
+ return c
+}
+
// SetDebug method enables the debug mode on the Resty client. The client logs details
// of every request and response.
//
@@ -1609,6 +1623,30 @@ func (c *Client) SetGenerateCurlOnDebug(b bool) *Client {
return c
}
+// ResponseBodyUnlimitedReads method returns true if enabled. Otherwise, it returns false
+func (c *Client) ResponseBodyUnlimitedReads() bool {
+ c.lock.RLock()
+ defer c.lock.RUnlock()
+ return c.resBodyUnlimitedReads
+}
+
+// SetResponseBodyUnlimitedReads method is to turn on/off the response body copy
+// that provides an ability to do unlimited reads.
+//
+// It can be overridden at the request level; see [Request.SetResponseBodyUnlimitedReads]
+//
+// NOTE: Turning on this feature uses additional memory to store a copy of the response body buffer.
+//
+// Unlimited reads are possible in a few scenarios, even without enabling this method.
+// - When [Client.SetDebug] set to true
+// - When [Request.SetResult] or [Request.SetError] methods are not used
+func (c *Client) SetResponseBodyUnlimitedReads(b bool) *Client {
+ c.lock.Lock()
+ defer c.lock.Unlock()
+ c.resBodyUnlimitedReads = b
+ return c
+}
+
// IsProxySet method returns the true is proxy is set from the Resty client; otherwise
// false. By default, the proxy is set from the environment variable; refer to [http.ProxyFromEnvironment].
func (c *Client) IsProxySet() bool {
@@ -1639,9 +1677,12 @@ func (c *Client) Clone() *Client {
}
func (c *Client) executeBefore(req *Request) error {
- // Apply Request middleware
var err error
+ if isStringEmpty(req.Method) {
+ req.Method = MethodGet
+ }
+
// user defined on before request methods
// to modify the *resty.Request object
for _, f := range c.beforeRequestMiddlewares() {
@@ -1676,11 +1717,6 @@ func (c *Client) executeBefore(req *Request) error {
}
}
- if err = requestLogger(c, req); err != nil {
- return wrapNoRetryErr(err)
- }
-
- req.RawRequest.Body = newRequestBodyReleaser(req.RawRequest.Body, req.bodyBuf)
return nil
}
@@ -1691,27 +1727,39 @@ func (c *Client) execute(req *Request) (*Response, error) {
return nil, err
}
+ if err := requestDebugLogger(c, req); err != nil {
+ return nil, wrapNoRetryErr(err)
+ }
+
+ req.RawRequest.Body = wrapRequestBufferReleaser(req)
req.Time = time.Now()
resp, err := c.Client().Do(req.RawRequest)
- response := &Response{
- Request: req,
- RawResponse: resp,
- }
-
+ response := &Response{Request: req, RawResponse: resp}
response.setReceivedAt()
+ if err != nil {
+ return response, err
+ }
if resp != nil {
- response.Body = &limitReadCloser{
- r: resp.Body,
+ response.Body = &limitReadCloser{r: resp.Body,
l: req.ResponseBodyLimit,
- f: func(s int64) {
- response.size = s
- },
+ f: func(s int64) { response.size = s },
+ }
+ }
+ if !req.DoNotParseResponse && (req.Debug || req.ResponseBodyUnlimitedReads) {
+ response.wrapReadCopier()
+
+ if err := response.readAllBytes(); err != nil {
+ return response, err
}
}
- if err != nil || req.DoNotParseResponse { // error or do not parse response
- return response, wrapErrors(responseLogger(c, response), err)
+ if err := responseDebugLogger(c, response); err != nil {
+ return response, err
+ }
+
+ if req.DoNotParseResponse {
+ return response, err
}
// Apply Response middleware
@@ -1720,11 +1768,6 @@ func (c *Client) execute(req *Request) (*Response, error) {
return response, err
}
}
- // TODO figure out debug response logger with body copy, etc.
- err = responseLogger(c, response)
- if err != nil {
- return response, wrapNoRetryErr(err)
- }
return response, wrapNoRetryErr(err)
}
diff --git a/client_test.go b/client_test.go
index 90aa876a..b5b4b21a 100644
--- a/client_test.go
+++ b/client_test.go
@@ -29,7 +29,7 @@ func TestClientBasicAuth(t *testing.T) {
ts := createAuthServer(t)
defer ts.Close()
- c := dc()
+ c := dcnl()
c.SetBasicAuth("myuser", "basicauth").
SetBaseURL(ts.URL).
SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
@@ -49,7 +49,7 @@ func TestClientAuthToken(t *testing.T) {
ts := createAuthServer(t)
defer ts.Close()
- c := dc()
+ c := dcnl()
c.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}).
SetAuthToken("004DDB79-6801-4587-B976-F093E6AC44FF").
SetBaseURL(ts.URL + "/")
@@ -64,7 +64,7 @@ func TestClientAuthScheme(t *testing.T) {
ts := createAuthServer(t)
defer ts.Close()
- c := dc()
+ c := dcnl()
// Ensure default Bearer
c.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}).
SetAuthToken("004DDB79-6801-4587-B976-F093E6AC44FF").
@@ -90,7 +90,7 @@ func TestClientDigestAuth(t *testing.T) {
ts := createDigestServer(t, conf)
defer ts.Close()
- c := dc().
+ c := dcnl().
SetBaseURL(ts.URL+"/").
SetDigestAuth(conf.username, conf.password)
@@ -111,7 +111,7 @@ func TestClientDigestSession(t *testing.T) {
ts := createDigestServer(t, conf)
defer ts.Close()
- c := dc().
+ c := dcnl().
SetBaseURL(ts.URL+"/").
SetDigestAuth(conf.username, conf.password)
@@ -148,7 +148,7 @@ func TestClientDigestErrors(t *testing.T) {
tc.mutateConf(conf)
ts := createDigestServer(t, conf)
- c := dc().
+ c := dcnl().
SetBaseURL(ts.URL+"/").
SetDigestAuth(conf.username, conf.password)
@@ -162,7 +162,7 @@ func TestOnAfterMiddleware(t *testing.T) {
ts := createGenericServer(t)
defer ts.Close()
- c := dc()
+ c := dcnl()
c.OnAfterResponse(func(c *Client, res *Response) error {
t.Logf("Request sent at: %v", res.Request.Time)
t.Logf("Response Received at: %v", res.ReceivedAt())
@@ -183,7 +183,7 @@ func TestClientRedirectPolicy(t *testing.T) {
ts := createRedirectServer(t)
defer ts.Close()
- c := dc().SetRedirectPolicy(FlexibleRedirectPolicy(20))
+ c := dcnl().SetRedirectPolicy(FlexibleRedirectPolicy(20))
_, err := c.R().Get(ts.URL + "/redirect-1")
assertEqual(t, true, (err.Error() == "Get /redirect-21: stopped after 20 redirects" ||
@@ -199,7 +199,7 @@ func TestClientTimeout(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- c := dc().SetTimeout(time.Second * 3)
+ c := dcnl().SetTimeout(time.Second * 3)
_, err := c.R().Get(ts.URL + "/set-timeout-test")
assertEqual(t, true, strings.Contains(strings.ToLower(err.Error()), "timeout"))
@@ -209,7 +209,7 @@ func TestClientTimeoutWithinThreshold(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- c := dc().SetTimeout(time.Second * 3)
+ c := dcnl().SetTimeout(time.Second * 3)
resp, err := c.R().Get(ts.URL + "/set-timeout-test-with-sequence")
assertError(t, err)
@@ -225,7 +225,7 @@ func TestClientTimeoutWithinThreshold(t *testing.T) {
}
func TestClientTimeoutInternalError(t *testing.T) {
- c := dc().SetTimeout(time.Second * 1)
+ c := dcnl().SetTimeout(time.Second * 1)
_, _ = c.R().Get("http://localhost:9000/set-timeout-test")
}
@@ -233,7 +233,7 @@ func TestClientProxy(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- c := dc()
+ c := dcnl()
c.SetTimeout(1 * time.Second)
c.SetProxy("http://sampleproxy:8888")
@@ -251,7 +251,7 @@ func TestClientProxy(t *testing.T) {
}
func TestClientSetCertificates(t *testing.T) {
- client := dc()
+ client := dcnl()
client.SetCertificates(tls.Certificate{})
transport, err := client.Transport()
@@ -261,57 +261,78 @@ func TestClientSetCertificates(t *testing.T) {
}
func TestClientSetRootCertificate(t *testing.T) {
- client := dc()
- client.SetRootCertificate(filepath.Join(getTestDataPath(), "sample-root.pem"))
+ t.Run("root cert", func(t *testing.T) {
+ client := dcnl()
+ client.SetRootCertificate(filepath.Join(getTestDataPath(), "sample-root.pem"))
- transport, err := client.Transport()
+ transport, err := client.Transport()
- assertNil(t, err)
- assertNotNil(t, transport.TLSClientConfig.RootCAs)
-}
+ assertNil(t, err)
+ assertNotNil(t, transport.TLSClientConfig.RootCAs)
+ })
-func TestClientSetRootCertificateNotExists(t *testing.T) {
- client := dc()
- client.SetRootCertificate(filepath.Join(getTestDataPath(), "not-exists-sample-root.pem"))
+ t.Run("root cert not exists", func(t *testing.T) {
+ client := dcnl()
+ client.SetRootCertificate(filepath.Join(getTestDataPath(), "not-exists-sample-root.pem"))
- transport, err := client.Transport()
+ transport, err := client.Transport()
- assertNil(t, err)
- assertNil(t, transport.TLSClientConfig)
-}
+ assertNil(t, err)
+ assertNil(t, transport.TLSClientConfig)
+ })
-func TestClientSetRootCertificateFromString(t *testing.T) {
- client := dc()
- rootPemData, err := os.ReadFile(filepath.Join(getTestDataPath(), "sample-root.pem"))
- assertNil(t, err)
+ t.Run("root cert from string", func(t *testing.T) {
+ client := dcnl()
+ rootPemData, err := os.ReadFile(filepath.Join(getTestDataPath(), "sample-root.pem"))
+ assertNil(t, err)
- client.SetRootCertificateFromString(string(rootPemData))
+ client.SetRootCertificateFromString(string(rootPemData))
- transport, err := client.Transport()
+ transport, err := client.Transport()
- assertNil(t, err)
- assertNotNil(t, transport.TLSClientConfig.RootCAs)
+ assertNil(t, err)
+ assertNotNil(t, transport.TLSClientConfig.RootCAs)
+ })
}
-func TestClientSetRootCertificateFromStringErrorTls(t *testing.T) {
- client := NewWithClient(&http.Client{})
- client.outputLogTo(io.Discard)
+func TestClientCACertificateFromStringErrorTls(t *testing.T) {
+ t.Run("root cert string", func(t *testing.T) {
+ client := NewWithClient(&http.Client{})
+ client.outputLogTo(io.Discard)
- rootPemData, err := os.ReadFile(filepath.Join(getTestDataPath(), "sample-root.pem"))
- assertNil(t, err)
- rt := &CustomRoundTripper{}
- client.SetTransport(rt)
- transport, err := client.Transport()
+ rootPemData, err := os.ReadFile(filepath.Join(getTestDataPath(), "sample-root.pem"))
+ assertNil(t, err)
+ rt := &CustomRoundTripper{}
+ client.SetTransport(rt)
+ transport, err := client.Transport()
- client.SetRootCertificateFromString(string(rootPemData))
+ client.SetRootCertificateFromString(string(rootPemData))
- assertNotNil(t, rt)
- assertNotNil(t, err)
- assertNil(t, transport)
+ assertNotNil(t, rt)
+ assertNotNil(t, err)
+ assertNil(t, transport)
+ })
+
+ t.Run("client cert string", func(t *testing.T) {
+ client := NewWithClient(&http.Client{})
+ client.outputLogTo(io.Discard)
+
+ rootPemData, err := os.ReadFile(filepath.Join(getTestDataPath(), "sample-root.pem"))
+ assertNil(t, err)
+ rt := &CustomRoundTripper{}
+ client.SetTransport(rt)
+ transport, err := client.Transport()
+
+ client.SetClientRootCertificateFromString(string(rootPemData))
+
+ assertNotNil(t, rt)
+ assertNotNil(t, err)
+ assertNil(t, transport)
+ })
}
func TestClientSetClientRootCertificate(t *testing.T) {
- client := dc()
+ client := dcnl()
client.SetClientRootCertificate(filepath.Join(getTestDataPath(), "sample-root.pem"))
transport, err := client.Transport()
@@ -321,7 +342,7 @@ func TestClientSetClientRootCertificate(t *testing.T) {
}
func TestClientSetClientRootCertificateNotExists(t *testing.T) {
- client := dc()
+ client := dcnl()
client.SetClientRootCertificate(filepath.Join(getTestDataPath(), "not-exists-sample-root.pem"))
transport, err := client.Transport()
@@ -331,7 +352,7 @@ func TestClientSetClientRootCertificateNotExists(t *testing.T) {
}
func TestClientSetClientRootCertificateFromString(t *testing.T) {
- client := dc()
+ client := dcnl()
rootPemData, err := os.ReadFile(filepath.Join(getTestDataPath(), "sample-root.pem"))
assertNil(t, err)
@@ -343,25 +364,8 @@ func TestClientSetClientRootCertificateFromString(t *testing.T) {
assertNotNil(t, transport.TLSClientConfig.ClientCAs)
}
-func TestClientSetClientRootCertificateFromStringErrorTls(t *testing.T) {
- client := NewWithClient(&http.Client{})
- client.outputLogTo(io.Discard)
-
- rootPemData, err := os.ReadFile(filepath.Join(getTestDataPath(), "sample-root.pem"))
- assertNil(t, err)
- rt := &CustomRoundTripper{}
- client.SetTransport(rt)
- transport, err := client.Transport()
-
- client.SetClientRootCertificateFromString(string(rootPemData))
-
- assertNotNil(t, rt)
- assertNotNil(t, err)
- assertNil(t, transport)
-}
-
func TestClientOnBeforeRequestModification(t *testing.T) {
- tc := dc()
+ tc := dcnl()
tc.OnBeforeRequest(func(c *Client, r *Request) error {
r.SetAuthToken("This is test auth token")
return nil
@@ -385,7 +389,7 @@ func TestClientSetHeaderVerbatim(t *testing.T) {
ts := createPostServer(t)
defer ts.Close()
- c := dc().
+ c := dcnl().
SetHeaderVerbatim("header-lowercase", "value_lowercase").
SetHeader("header-lowercase", "value_standard")
@@ -398,7 +402,7 @@ func TestClientSetHeaderVerbatim(t *testing.T) {
func TestClientSetTransport(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- client := dc()
+ client := dcnl()
transport := &http.Transport{
// something like Proxying to httptest.Server, etc...
@@ -414,7 +418,7 @@ func TestClientSetTransport(t *testing.T) {
}
func TestClientSetScheme(t *testing.T) {
- client := dc()
+ client := dcnl()
client.SetScheme("http")
@@ -422,7 +426,7 @@ func TestClientSetScheme(t *testing.T) {
}
func TestClientSetCookieJar(t *testing.T) {
- client := dc()
+ client := dcnl()
backupJar := client.httpClient.Jar
client.SetCookieJar(nil)
@@ -435,7 +439,7 @@ func TestClientSetCookieJar(t *testing.T) {
// This test methods exist for test coverage purpose
// to validate the getter and setter
func TestClientSettingsCoverage(t *testing.T) {
- c := dc()
+ c := dcnl()
assertNotNil(t, c.CookieJar())
assertNotNil(t, c.ContentTypeEncoders())
@@ -458,8 +462,10 @@ func TestClientSettingsCoverage(t *testing.T) {
c.SetCloseConnection(true)
+ c.DisableDebug()
+
// [Start] Custom Transport scenario
- ct := dc()
+ ct := dcnl()
ct.SetTransport(&CustomRoundTripper{})
_, err := ct.Transport()
assertNotNil(t, err)
@@ -470,13 +476,14 @@ func TestClientSettingsCoverage(t *testing.T) {
ct.SetCertificates(tls.Certificate{})
ct.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
+ ct.SetRootCertificateFromString("root cert")
ct.outputLogTo(io.Discard)
// [End] Custom Transport scenario
}
func TestContentLengthWhenBodyIsNil(t *testing.T) {
- client := dc()
+ client := dcnl()
client.SetPreRequestHook(func(c *Client, r *http.Request) error {
assertEqual(t, "0", r.Header.Get(hdrContentLengthKey))
@@ -487,7 +494,7 @@ func TestContentLengthWhenBodyIsNil(t *testing.T) {
}
func TestClientPreRequestHook(t *testing.T) {
- client := dc()
+ client := dcnl()
client.SetPreRequestHook(func(c *Client, r *http.Request) error {
c.log.Debugf("I'm in Pre-Request Hook")
return nil
@@ -529,7 +536,7 @@ func TestClientAllowsGetMethodPayload(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- c := dc()
+ c := dcnl()
c.SetAllowGetMethodPayload(true)
c.SetPreRequestHook(func(*Client, *http.Request) error { return nil }) // for coverage
@@ -545,7 +552,7 @@ func TestClientAllowsGetMethodPayloadIoReader(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- c := dc()
+ c := dcnl()
c.SetAllowGetMethodPayload(true)
payload := "test-payload"
@@ -561,7 +568,7 @@ func TestClientAllowsGetMethodPayloadDisabled(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- c := dc()
+ c := dcnl()
c.SetAllowGetMethodPayload(false)
payload := bytes.NewReader([]byte("test-payload"))
@@ -595,36 +602,36 @@ func TestDebugBodySizeLimit(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- c := dc().
- SetDebug(true).
- SetDebugBodyLimit(30)
-
- var lgr bytes.Buffer
- c.outputLogTo(&lgr) // internal method
+ c, lb := dcld()
+ c.SetDebugBodyLimit(30)
- testcases := []struct{ url, want string }{
+ testcases := []struct{ url, want, wantErr string }{
// Text, does not exceed limit.
- {ts.URL, "TestGet: text response"},
+ {url: ts.URL, want: "TestGet: text response"},
// Empty response.
- {ts.URL + "/no-content", "***** NO CONTENT *****"},
+ {url: ts.URL + "/no-content", want: "***** NO CONTENT *****"},
// JSON, does not exceed limit.
- {ts.URL + "/json", "{\n \"TestGet\": \"JSON response\"\n}"},
+ {url: ts.URL + "/json", want: "{\n \"TestGet\": \"JSON response\"\n}"},
// Invalid JSON, does not exceed limit.
- {ts.URL + "/json-invalid", "TestGet: Invalid JSON"},
+ {url: ts.URL + "/json-invalid", wantErr: "invalid character 'T' looking for beginning of value"},
// Text, exceeds limit.
- {ts.URL + "/long-text", "RESPONSE TOO LARGE"},
+ {url: ts.URL + "/long-text", want: "RESPONSE TOO LARGE"},
// JSON, exceeds limit.
- {ts.URL + "/long-json", "RESPONSE TOO LARGE"},
+ {url: ts.URL + "/long-json", want: "RESPONSE TOO LARGE"},
}
-
for _, tc := range testcases {
_, err := c.R().Get(tc.url)
- assertError(t, err)
- debugLog := lgr.String()
- if !strings.Contains(debugLog, tc.want) {
- t.Errorf("Expected logs to contain [%v], got [\n%v]", tc.want, debugLog)
+ if tc.wantErr != "" {
+ assertNotNil(t, err)
+ assertEqual(t, tc.wantErr, err.Error())
+ } else if tc.want != "" {
+ assertError(t, err)
+ debugLog := lb.String()
+ if !strings.Contains(debugLog, tc.want) {
+ t.Errorf("Expected logs to contain [%v], got [\n%v]", tc.want, debugLog)
+ }
+ lb.Reset()
}
- lgr.Reset()
}
}
@@ -666,10 +673,7 @@ func TestLogCallbacks(t *testing.T) {
ts := createAuthServer(t)
defer ts.Close()
- c := New().SetDebug(true)
-
- var lgr bytes.Buffer
- c.outputLogTo(&lgr)
+ c, lb := dcld()
c.OnRequestLog(func(r *RequestLog) error {
// masking authorization header
@@ -693,7 +697,7 @@ func TestLogCallbacks(t *testing.T) {
assertEqual(t, http.StatusOK, resp.StatusCode())
// Validating debug log updates
- logInfo := lgr.String()
+ logInfo := lb.String()
assertEqual(t, true, strings.Contains(logInfo, "Bearer *******************************"))
assertEqual(t, true, strings.Contains(logInfo, "X-Debug-Response-Log"))
assertEqual(t, true, strings.Contains(logInfo, "Modified the response body content"))
@@ -719,10 +723,9 @@ func TestLogCallbacks(t *testing.T) {
func TestDebugLogSimultaneously(t *testing.T) {
ts := createGetServer(t)
- c := New().
+ c := dcnl().
SetDebug(true).
- SetBaseURL(ts.URL).
- outputLogTo(io.Discard)
+ SetBaseURL(ts.URL)
t.Cleanup(ts.Close)
for i := 0; i < 50; i++ {
@@ -921,7 +924,7 @@ func TestClientOnResponseError(t *testing.T) {
assertEqual(t, 1, hook6)
}
}()
- c := New().outputLogTo(io.Discard).
+ c := dcnl().
SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}).
SetAuthToken("004DDB79-6801-4587-B976-F093E6AC44FF").
SetRetryCount(0).
@@ -1009,7 +1012,7 @@ func TestHostURLForGH318AndGH407(t *testing.T) {
// test the functionality with httpbin.org locally
// will figure out later
- c := dc()
+ c := dcnl()
// c.SetScheme("http")
// c.SetHostURL(targetURL.Host + "/")
@@ -1040,7 +1043,7 @@ func TestPostRedirectWithBody(t *testing.T) {
t.Log("ts.URL:", ts.URL)
t.Log("targetURL.Host:", targetURL.Host)
- c := dc()
+ c := dcnl()
wg := sync.WaitGroup{}
for i := 0; i < 100; i++ {
wg.Add(1)
@@ -1115,7 +1118,7 @@ func TestResponseBodyLimit(t *testing.T) {
defer ts.Close()
t.Run("Client body limit", func(t *testing.T) {
- c := dc().SetResponseBodyLimit(1024)
+ c := dcnl().SetResponseBodyLimit(1024)
assertEqual(t, int64(1024), c.ResponseBodyLimit())
resp, err := c.R().Get(ts.URL + "/")
assertNotNil(t, err)
@@ -1124,7 +1127,7 @@ func TestResponseBodyLimit(t *testing.T) {
})
t.Run("request body limit", func(t *testing.T) {
- c := dc()
+ c := dcnl()
resp, err := c.R().SetResponseBodyLimit(1024).Get(ts.URL + "/")
assertNotNil(t, err)
@@ -1133,7 +1136,7 @@ func TestResponseBodyLimit(t *testing.T) {
})
t.Run("body less than limit", func(t *testing.T) {
- c := dc()
+ c := dcnl()
res, err := c.R().SetResponseBodyLimit(800*100 + 10).Get(ts.URL + "/")
assertNil(t, err)
@@ -1142,7 +1145,7 @@ func TestResponseBodyLimit(t *testing.T) {
})
t.Run("no body limit", func(t *testing.T) {
- c := dc()
+ c := dcnl()
res, err := c.R().Get(ts.URL + "/")
assertNil(t, err)
@@ -1158,7 +1161,7 @@ func TestResponseBodyLimit(t *testing.T) {
})
defer tse.Close()
- c := dc()
+ c := dcnl()
_, err := c.R().SetResponseBodyLimit(10240).Get(tse.URL + "/")
assertErrorIs(t, gzip.ErrHeader, err)
diff --git a/context_test.go b/context_test.go
index 378358a9..54830399 100644
--- a/context_test.go
+++ b/context_test.go
@@ -20,7 +20,7 @@ func TestSetContext(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- resp, err := dc().R().
+ resp, err := dcnl().R().
SetContext(context.Background()).
Get(ts.URL + "/")
@@ -37,7 +37,7 @@ func TestSetContextWithError(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- resp, err := dcr().
+ resp, err := dcnlr().
SetContext(context.Background()).
Get(ts.URL + "/mypage")
@@ -71,7 +71,7 @@ func TestSetContextCancel(t *testing.T) {
cancel()
}()
- _, err := dc().R().
+ _, err := dcnl().R().
SetContext(ctx).
Get(ts.URL + "/")
@@ -110,7 +110,7 @@ func TestSetContextCancelRetry(t *testing.T) {
cancel()
}()
- c := dc().
+ c := dcnl().
SetTimeout(time.Second * 3).
SetRetryCount(3)
@@ -157,7 +157,7 @@ func TestSetContextCancelWithError(t *testing.T) {
cancel()
}()
- _, err := dc().R().
+ _, err := dcnl().R().
SetContext(ctx).
Get(ts.URL + "/")
@@ -184,7 +184,7 @@ func TestClientRetryWithSetContext(t *testing.T) {
})
defer ts.Close()
- c := dc().
+ c := dcnl().
SetTimeout(time.Second * 1).
SetRetryCount(3)
@@ -199,7 +199,7 @@ func TestClientRetryWithSetContext(t *testing.T) {
}
func TestRequestContext(t *testing.T) {
- client := dc()
+ client := dcnl()
r := client.NewRequest()
assertNotNil(t, r.Context())
diff --git a/curl_cmd_test.go b/curl_cmd_test.go
index 0784fbfb..79754b84 100644
--- a/curl_cmd_test.go
+++ b/curl_cmd_test.go
@@ -10,7 +10,7 @@ import (
// 1. Generate curl for unexecuted request(dry-run)
func TestGenerateUnexecutedCurl(t *testing.T) {
- req := dclr().
+ req := dcnldr().
SetBody(map[string]string{
"name": "Alex",
}).
@@ -18,7 +18,8 @@ func TestGenerateUnexecutedCurl(t *testing.T) {
[]*http.Cookie{
{Name: "count", Value: "1"},
},
- )
+ ).
+ SetMethod(MethodPost)
assertEqual(t, "", req.GenerateCurlCommand())
@@ -26,7 +27,7 @@ func TestGenerateUnexecutedCurl(t *testing.T) {
req.DisableGenerateCurlOnDebug()
if !strings.Contains(curlCmdUnexecuted, "Cookie: count=1") ||
- !strings.Contains(curlCmdUnexecuted, "curl -X GET") ||
+ !strings.Contains(curlCmdUnexecuted, "curl -X POST") ||
!strings.Contains(curlCmdUnexecuted, `-d '{"name":"Alex"}'`) {
t.Fatal("Incomplete curl:", curlCmdUnexecuted)
} else {
@@ -43,7 +44,7 @@ func TestGenerateExecutedCurl(t *testing.T) {
data := map[string]string{
"name": "Alex",
}
- c := dcl()
+ c := dcnl().EnableDebug()
req := c.R().
SetBody(data).
SetCookies(
@@ -114,6 +115,13 @@ func TestDebugModeCurl(t *testing.T) {
}
}
+func TestCurlMiscTestCoverage(t *testing.T) {
+ cookieStr := dumpCurlCookies([]*http.Cookie{
+ {Name: "count", Value: "1"},
+ })
+ assertEqual(t, "Cookie: count=1", cookieStr)
+}
+
func captureStderr() (getOutput func() string, restore func()) {
old := os.Stderr
r, w, err := os.Pipe()
diff --git a/middleware.go b/middleware.go
index 2f16baef..4da98dae 100644
--- a/middleware.go
+++ b/middleware.go
@@ -212,6 +212,7 @@ func createHTTPRequest(c *Client, r *Request) (err error) {
}
} else {
// fix data race: must deep copy.
+ // TODO investigate in details and remove this copy line
bodyBuf := bytes.NewBuffer(append([]byte{}, r.bodyBuf.Bytes()...))
r.RawRequest, err = http.NewRequest(r.Method, r.URL, bodyBuf)
}
@@ -247,19 +248,6 @@ func createHTTPRequest(c *Client, r *Request) (err error) {
r.RawRequest = r.RawRequest.WithContext(r.ctx)
}
- bodyCopy, err := getBodyCopy(r)
- if err != nil {
- return err
- }
-
- // assign get body func for the underlying raw request instance
- r.RawRequest.GetBody = func() (io.ReadCloser, error) {
- if bodyCopy != nil {
- return io.NopCloser(bytes.NewReader(bodyCopy.Bytes())), nil
- }
- return nil, nil
- }
-
return
}
@@ -296,49 +284,51 @@ func createCurlCmd(c *Client, r *Request) (err error) {
if r.resultCurlCmd == nil {
r.resultCurlCmd = new(string)
}
- *r.resultCurlCmd = buildCurlRequest(r.RawRequest, c.Client().Jar)
+ *r.resultCurlCmd = buildCurlRequest(r)
}
return nil
}
-func requestLogger(c *Client, r *Request) error {
- if r.Debug {
- rr := r.RawRequest
- rh := rr.Header.Clone()
- if c.Client().Jar != nil {
- for _, cookie := range c.Client().Jar.Cookies(r.RawRequest.URL) {
- s := fmt.Sprintf("%s=%s", cookie.Name, cookie.Value)
- if c := rh.Get("Cookie"); c != "" {
- rh.Set("Cookie", c+"; "+s)
- } else {
- rh.Set("Cookie", s)
- }
+func requestDebugLogger(c *Client, r *Request) error {
+ if !r.Debug {
+ return nil
+ }
+
+ rr := r.RawRequest
+ rh := rr.Header.Clone()
+ if c.Client().Jar != nil {
+ for _, cookie := range c.Client().Jar.Cookies(r.RawRequest.URL) {
+ s := fmt.Sprintf("%s=%s", cookie.Name, cookie.Value)
+ if c := rh.Get("Cookie"); c != "" {
+ rh.Set("Cookie", c+"; "+s)
+ } else {
+ rh.Set("Cookie", s)
}
}
- rl := &RequestLog{Header: rh, Body: r.fmtBodyString(r.DebugBodyLimit)}
- if c.requestLog != nil {
- if err := c.requestLog(rl); err != nil {
- return err
- }
+ }
+ rl := &RequestLog{Header: rh, Body: r.fmtBodyString(r.DebugBodyLimit)}
+ if c.requestLog != nil {
+ if err := c.requestLog(rl); err != nil {
+ return err
}
+ }
- reqLog := "\n==============================================================================\n"
+ reqLog := "\n==============================================================================\n"
- if r.Debug && r.generateCurlOnDebug {
- reqLog += "~~~ REQUEST(CURL) ~~~\n" +
- fmt.Sprintf(" %v\n", *r.resultCurlCmd)
- }
+ if r.Debug && r.generateCurlOnDebug {
+ reqLog += "~~~ REQUEST(CURL) ~~~\n" +
+ fmt.Sprintf(" %v\n", *r.resultCurlCmd)
+ }
- reqLog += "~~~ REQUEST ~~~\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%s\n", composeHeaders(rl.Header)) +
- fmt.Sprintf("BODY :\n%v\n", rl.Body) +
- "------------------------------------------------------------------------------\n"
+ reqLog += "~~~ REQUEST ~~~\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%s\n", composeHeaders(rl.Header)) +
+ fmt.Sprintf("BODY :\n%v\n", rl.Body) +
+ "------------------------------------------------------------------------------\n"
- r.initValuesMap()
- r.values[debugRequestLogKey] = reqLog
- }
+ r.initValuesMap()
+ r.values[debugRequestLogKey] = reqLog
return nil
}
@@ -347,40 +337,47 @@ func requestLogger(c *Client, r *Request) error {
// Response Middleware(s)
//_______________________________________________________________________
-func responseLogger(c *Client, res *Response) error {
- if res.Request.Debug {
- rl := &ResponseLog{Header: res.Header().Clone(), Body: res.fmtBodyString(res.Request.DebugBodyLimit)}
- if c.responseLog != nil {
- c.lock.RLock()
- defer c.lock.RUnlock()
- if err := c.responseLog(rl); err != nil {
- return err
- }
- }
+func responseDebugLogger(c *Client, res *Response) error {
+ if !res.Request.Debug {
+ return nil
+ }
- debugLog := res.Request.values[debugRequestLogKey].(string)
- debugLog += "~~~ RESPONSE ~~~\n" +
- fmt.Sprintf("STATUS : %s\n", res.Status()) +
- fmt.Sprintf("PROTO : %s\n", res.RawResponse.Proto) +
- fmt.Sprintf("RECEIVED AT : %v\n", res.ReceivedAt().Format(time.RFC3339Nano)) +
- fmt.Sprintf("TIME DURATION: %v\n", res.Time()) +
- "HEADERS :\n" +
- composeHeaders(rl.Header) + "\n"
- if res.Request.isSaveResponse {
- debugLog += "BODY :\n***** RESPONSE WRITTEN INTO FILE *****\n"
- } else {
- debugLog += fmt.Sprintf("BODY :\n%v\n", rl.Body)
+ bodyStr, err := res.fmtBodyString(res.Request.DebugBodyLimit)
+ if err != nil {
+ return err
+ }
+
+ rl := &ResponseLog{Header: res.Header().Clone(), Body: bodyStr}
+ if c.responseLog != nil {
+ c.lock.RLock()
+ defer c.lock.RUnlock()
+ if err := c.responseLog(rl); err != nil {
+ return err
}
- debugLog += "==============================================================================\n"
+ }
- res.Request.log.Debugf("%s", debugLog)
+ debugLog := res.Request.values[debugRequestLogKey].(string)
+ debugLog += "~~~ RESPONSE ~~~\n" +
+ fmt.Sprintf("STATUS : %s\n", res.Status()) +
+ fmt.Sprintf("PROTO : %s\n", res.RawResponse.Proto) +
+ fmt.Sprintf("RECEIVED AT : %v\n", res.ReceivedAt().Format(time.RFC3339Nano)) +
+ fmt.Sprintf("TIME DURATION: %v\n", res.Time()) +
+ "HEADERS :\n" +
+ composeHeaders(rl.Header) + "\n"
+ if res.Request.isSaveResponse {
+ debugLog += "BODY :\n***** RESPONSE WRITTEN INTO FILE *****\n"
+ } else {
+ debugLog += fmt.Sprintf("BODY :\n%v\n", rl.Body)
}
+ debugLog += "==============================================================================\n"
+
+ res.Request.log.Debugf("%s", debugLog)
return nil
}
func parseResponseBody(c *Client, res *Response) (err error) {
- if res.Request.isSaveResponse {
+ if res.Request.DoNotParseResponse || res.Request.isSaveResponse {
return // move on
}
@@ -409,6 +406,7 @@ func parseResponseBody(c *Client, res *Response) (err error) {
res.Request.Error = nil
defer closeq(res.Body)
err = decFunc(res.Body, res.Request.Result)
+ res.IsRead = true
return
}
@@ -422,6 +420,7 @@ func parseResponseBody(c *Client, res *Response) (err error) {
if res.Request.Error != nil {
defer closeq(res.Body)
err = decFunc(res.Body, res.Request.Error)
+ res.IsRead = true
return
}
}
@@ -587,32 +586,3 @@ func saveResponseIntoFile(c *Client, res *Response) error {
return nil
}
-
-func getBodyCopy(r *Request) (*bytes.Buffer, error) {
- // If r.bodyBuf present, return the copy
- if r.bodyBuf != nil {
- bodyCopy := acquireBuffer()
- if _, err := io.Copy(bodyCopy, bytes.NewReader(r.bodyBuf.Bytes())); err != nil {
- // cannot use io.Copy(bodyCopy, r.bodyBuf) because io.Copy reset r.bodyBuf
- return nil, err
- }
- return bodyCopy, nil
- }
-
- // Maybe body is `io.Reader`.
- // Note: Resty user have to watchout for large body size of `io.Reader`
- if r.RawRequest.Body != nil {
- b, err := io.ReadAll(r.RawRequest.Body)
- if err != nil {
- return nil, err
- }
-
- // Restore the Body
- closeq(r.RawRequest.Body)
- r.RawRequest.Body = io.NopCloser(bytes.NewBuffer(b))
-
- // Return the Body bytes
- return bytes.NewBuffer(b), nil
- }
- return nil, nil
-}
diff --git a/request.go b/request.go
index 491dc977..bda317fe 100644
--- a/request.go
+++ b/request.go
@@ -27,32 +27,33 @@ import (
// Resty client. The [Request] provides an option to override client-level
// settings and also an option for the request composition.
type Request struct {
- URL string
- Method string
- AuthToken string
- AuthScheme string
- QueryParams url.Values
- FormData url.Values
- PathParams map[string]string
- RawPathParams map[string]string
- Header http.Header
- Time time.Time
- Body any
- Result any
- Error any
- RawRequest *http.Request
- SRV *SRVRecord
- UserInfo *User
- Cookies []*http.Cookie
- Debug bool
- CloseConnection bool
- DoNotParseResponse bool
- OutputFile string
- ExpectResponseContentType string
- ForceResponseContentType string
- DebugBodyLimit int
- ResponseBodyLimit int64
- IsTrace bool
+ URL string
+ Method string
+ AuthToken string
+ AuthScheme string
+ QueryParams url.Values
+ FormData url.Values
+ PathParams map[string]string
+ RawPathParams map[string]string
+ Header http.Header
+ Time time.Time
+ Body any
+ Result any
+ Error any
+ RawRequest *http.Request
+ SRV *SRVRecord
+ UserInfo *User
+ Cookies []*http.Cookie
+ Debug bool
+ CloseConnection bool
+ DoNotParseResponse bool
+ OutputFile string
+ ExpectResponseContentType string
+ ForceResponseContentType string
+ DebugBodyLimit int
+ ResponseBodyLimit int64
+ ResponseBodyUnlimitedReads bool
+ IsTrace bool
// Retry
RetryCount int
@@ -99,10 +100,16 @@ func (r *Request) GenerateCurlCommand() string {
if r.resultCurlCmd == nil {
r.resultCurlCmd = new(string)
}
- *r.resultCurlCmd = buildCurlRequest(r.RawRequest, r.client.httpClient.Jar)
+ *r.resultCurlCmd = buildCurlRequest(r)
return *r.resultCurlCmd
}
+// SetMethod method used to set the HTTP verb for the request
+func (r *Request) SetMethod(m string) *Request {
+ r.Method = m
+ return r
+}
+
// Context method returns the Context if it is already set in the [Request]
// otherwise, it creates a new one using [context.Background].
func (r *Request) Context() context.Context {
@@ -660,6 +667,21 @@ func (r *Request) SetResponseBodyLimit(v int64) *Request {
return r
}
+// SetResponseBodyUnlimitedReads method is to turn on/off the response body copy
+// that provides an ability to do unlimited reads.
+//
+// It overriddes the value set at client level; see [Client.SetResponseBodyUnlimitedReads]
+//
+// NOTE: Turning on this feature uses additional memory to store a copy of the response body buffer.
+//
+// Unlimited reads are possible in a few scenarios, even without enabling this method.
+// - When [Client.SetDebug] or [Request.SetDebug] set to true
+// - When [Request.SetResult] or [Request.SetError] methods are not used
+func (r *Request) SetResponseBodyUnlimitedReads(b bool) *Request {
+ r.ResponseBodyUnlimitedReads = b
+ return r
+}
+
// SetPathParam method sets a single URL path key-value pair in the
// Resty current request instance.
//
@@ -831,6 +853,18 @@ func (r *Request) SetLogger(l Logger) *Request {
return r
}
+// EnableDebug method is a helper method for [Request.SetDebug]
+func (r *Request) EnableDebug() *Request {
+ r.SetDebug(true)
+ return r
+}
+
+// DisableDebug method is a helper method for [Request.SetDebug]
+func (r *Request) DisableDebug() *Request {
+ r.SetDebug(false)
+ return r
+}
+
// SetDebug method enables the debug mode on the current request. It logs
// the details current request and response.
//
@@ -1250,7 +1284,7 @@ func (r *Request) fmtBodyString(sl int) (body string) {
(kind == reflect.Struct || kind == reflect.Map || kind == reflect.Slice) {
buf := acquireBuffer()
defer releaseBuffer(buf)
- if err = encodeJSONEscapeHTMLIndent(buf, r.Body, false, " "); err == nil {
+ if err = encodeJSONEscapeHTMLIndent(buf, &r.Body, false, " "); err == nil {
prtBodyBytes = buf.Bytes()
}
} else if xmlKey == ctKey && kind == reflect.Struct {
diff --git a/request_test.go b/request_test.go
index 435943d1..372328c3 100644
--- a/request_test.go
+++ b/request_test.go
@@ -36,7 +36,7 @@ func TestGet(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- resp, err := dc().R().
+ resp, err := dcnl().R().
SetQueryParam("request_no", strconv.FormatInt(time.Now().Unix(), 10)).
Get(ts.URL + "/")
@@ -54,7 +54,7 @@ func TestGetGH524(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- resp, err := dc().R().
+ resp, err := dcnl().R().
SetPathParams((map[string]string{
"userId": "sample@sample.com",
"subAccountId": "100002",
@@ -76,7 +76,7 @@ func TestRateLimiter(t *testing.T) {
// Test a burst with a valid capacity and then a consecutive request that must fail.
// Allow a rate of 1 every 100 ms but also allow bursts of 10 requests.
- client := dc().SetRateLimiter(rate.NewLimiter(rate.Every(100*time.Millisecond), 10))
+ client := dcnl().SetRateLimiter(rate.NewLimiter(rate.Every(100*time.Millisecond), 10))
// Execute a burst of 10 requests.
for i := 0; i < 10; i++ {
@@ -95,7 +95,7 @@ func TestRateLimiter(t *testing.T) {
// Test continues request at a valid rate
// Allow a rate of 1 every ms with no burst.
- client = dc().SetRateLimiter(rate.NewLimiter(rate.Every(1*time.Millisecond), 1))
+ client = dcnl().SetRateLimiter(rate.NewLimiter(rate.Every(1*time.Millisecond), 1))
// Sending requests every ms+tiny delta must succeed.
for i := 0; i < 100; i++ {
@@ -111,7 +111,7 @@ func TestIllegalRetryCount(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- resp, err := dc().SetRetryCount(-1).R().Get(ts.URL + "/")
+ resp, err := dcnl().SetRetryCount(-1).R().Get(ts.URL + "/")
assertNil(t, err)
assertNil(t, resp)
@@ -121,7 +121,7 @@ func TestGetCustomUserAgent(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- resp, err := dcr().
+ resp, err := dcnlr().
SetHeader(hdrUserAgentKey, "Test Custom User agent").
SetQueryParam("request_no", strconv.FormatInt(time.Now().Unix(), 10)).
Get(ts.URL + "/")
@@ -139,7 +139,7 @@ func TestGetClientParamRequestParam(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- c := dc()
+ c := dcnl()
c.SetQueryParam("client_param", "true").
SetQueryParams(map[string]string{"req_1": "jeeva", "req_3": "jeeva3"}).
SetDebug(true)
@@ -164,7 +164,7 @@ func TestGetRelativePath(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- c := dc()
+ c := dcnl()
c.SetBaseURL(ts.URL)
resp, err := c.R().Get("mypage2")
@@ -180,7 +180,7 @@ func TestGet400Error(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- resp, err := dcr().Get(ts.URL + "/mypage")
+ resp, err := dcnlr().Get(ts.URL + "/mypage")
assertError(t, err)
assertEqual(t, http.StatusBadRequest, resp.StatusCode())
@@ -193,7 +193,7 @@ func TestPostJSONStringSuccess(t *testing.T) {
ts := createPostServer(t)
defer ts.Close()
- c := dc()
+ c := dcnl()
c.SetHeader(hdrContentTypeKey, "application/json; charset=utf-8").
SetHeaders(map[string]string{hdrUserAgentKey: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) go-resty v0.1", hdrAcceptKey: "application/json; charset=utf-8"})
@@ -221,7 +221,7 @@ func TestPostJSONBytesSuccess(t *testing.T) {
ts := createPostServer(t)
defer ts.Close()
- c := dc()
+ c := dcnl()
c.SetHeader(hdrContentTypeKey, "application/json; charset=utf-8").
SetHeaders(map[string]string{hdrUserAgentKey: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) go-resty v0.7", hdrAcceptKey: "application/json; charset=utf-8"})
@@ -239,7 +239,7 @@ func TestPostJSONBytesIoReader(t *testing.T) {
ts := createPostServer(t)
defer ts.Close()
- c := dc()
+ c := dcnl()
c.SetHeader(hdrContentTypeKey, "application/json; charset=utf-8")
bodyBytes := []byte(`{"username":"testuser", "password":"testpass"}`)
@@ -260,7 +260,7 @@ func TestPostJSONStructSuccess(t *testing.T) {
user := &User{Username: "testuser", Password: "testpass"}
- c := dc().SetJSONEscapeHTML(false)
+ c := dcnl().SetJSONEscapeHTML(false)
resp, err := c.R().
SetHeader(hdrContentTypeKey, "application/json; charset=utf-8").
SetBody(user).
@@ -282,7 +282,7 @@ func TestPostJSONRPCStructSuccess(t *testing.T) {
user := &User{Username: "testuser", Password: "testpass"}
- c := dc().SetJSONEscapeHTML(false)
+ c := dcnl().SetJSONEscapeHTML(false)
resp, err := c.R().
SetHeader(hdrContentTypeKey, "application/json-rpc").
SetBody(user).
@@ -303,7 +303,7 @@ func TestPostJSONStructInvalidLogin(t *testing.T) {
ts := createPostServer(t)
defer ts.Close()
- c := dc()
+ c := dcnl()
c.SetDebug(false)
resp, err := c.R().
@@ -328,7 +328,7 @@ func TestPostJSONErrorRFC7807(t *testing.T) {
ts := createPostServer(t)
defer ts.Close()
- c := dc()
+ c := dcnl()
resp, err := c.R().
SetHeader(hdrContentTypeKey, "application/json; charset=utf-8").
SetBody(User{Username: "testuser", Password: "testpass1"}).
@@ -350,7 +350,7 @@ func TestPostJSONMapSuccess(t *testing.T) {
ts := createPostServer(t)
defer ts.Close()
- c := dc()
+ c := dcnl()
c.SetDebug(false)
resp, err := c.R().
@@ -370,7 +370,7 @@ func TestPostJSONMapInvalidResponseJson(t *testing.T) {
ts := createPostServer(t)
defer ts.Close()
- resp, err := dclr().
+ resp, err := dcnldr().
SetBody(map[string]any{"username": "testuser", "password": "invalidjson"}).
SetResult(&AuthSuccess{}).
Post(ts.URL + "/login")
@@ -400,7 +400,7 @@ func TestPostJSONMarshalError(t *testing.T) {
b := brokenMarshalJSON{}
exp := "b0rk3d"
- _, err := dclr().
+ _, err := dcnldr().
SetHeader(hdrContentTypeKey, "application/json").
SetBody(b).
Post(ts.URL + "/login")
@@ -418,7 +418,7 @@ func TestForceContentTypeForGH276andGH240(t *testing.T) {
defer ts.Close()
retried := 0
- c := dc()
+ c := dcnl()
c.SetDebug(false)
c.SetRetryCount(3)
c.SetRetryAfter(RetryAfterFunc(func(*Client, *Response) (time.Duration, error) {
@@ -446,7 +446,7 @@ func TestPostXMLStringSuccess(t *testing.T) {
ts := createPostServer(t)
defer ts.Close()
- c := dc()
+ c := dcnl()
c.SetDebug(false)
resp, err := c.R().
@@ -475,7 +475,7 @@ func TestPostXMLMarshalError(t *testing.T) {
b := brokenMarshalXML{}
exp := "b0rk3d"
- _, err := dclr().
+ _, err := dcnldr().
SetHeader(hdrContentTypeKey, "application/xml").
SetBody(b).
Post(ts.URL + "/login")
@@ -492,7 +492,7 @@ func TestPostXMLStringError(t *testing.T) {
ts := createPostServer(t)
defer ts.Close()
- resp, err := dclr().
+ resp, err := dcnldr().
SetHeader(hdrContentTypeKey, "application/xml").
SetBody(`testusertestpass`).
Post(ts.URL + "/login")
@@ -508,7 +508,7 @@ func TestPostXMLBytesSuccess(t *testing.T) {
ts := createPostServer(t)
defer ts.Close()
- c := dc()
+ c := dcnl()
c.SetDebug(false)
resp, err := c.R().
@@ -528,7 +528,7 @@ func TestPostXMLStructSuccess(t *testing.T) {
ts := createPostServer(t)
defer ts.Close()
- resp, err := dclr().
+ resp, err := dcnldr().
SetHeader(hdrContentTypeKey, "application/xml").
SetBody(User{Username: "testuser", Password: "testpass"}).
SetContentLength(true).
@@ -547,7 +547,7 @@ func TestPostXMLStructInvalidLogin(t *testing.T) {
ts := createPostServer(t)
defer ts.Close()
- c := dc()
+ c := dcnl()
c.SetError(&AuthError{})
resp, err := c.R().
@@ -568,7 +568,7 @@ func TestPostXMLStructInvalidResponseXml(t *testing.T) {
ts := createPostServer(t)
defer ts.Close()
- resp, err := dclr().
+ resp, err := dcnldr().
SetHeader(hdrContentTypeKey, "application/xml").
SetBody(User{Username: "testuser", Password: "invalidxml"}).
SetResult(&AuthSuccess{}).
@@ -586,7 +586,7 @@ func TestPostXMLMapNotSupported(t *testing.T) {
ts := createPostServer(t)
defer ts.Close()
- _, err := dclr().
+ _, err := dcnldr().
SetHeader(hdrContentTypeKey, "application/xml").
SetBody(map[string]any{"Username": "testuser", "Password": "testpass"}).
Post(ts.URL + "/login")
@@ -598,7 +598,7 @@ func TestRequestBasicAuth(t *testing.T) {
ts := createAuthServer(t)
defer ts.Close()
- c := dc()
+ c := dcnl()
c.SetBaseURL(ts.URL).
SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
@@ -618,7 +618,7 @@ func TestRequestBasicAuthWithBody(t *testing.T) {
ts := createAuthServer(t)
defer ts.Close()
- c := dc()
+ c := dcnl()
c.SetBaseURL(ts.URL).
SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
@@ -643,7 +643,7 @@ func TestRequestInsecureBasicAuth(t *testing.T) {
logger := createLogger()
logger.l.SetOutput(&logBuf)
- c := dc()
+ c := dcnl()
c.SetBaseURL(ts.URL)
resp, err := c.R().
@@ -665,7 +665,7 @@ func TestRequestBasicAuthFail(t *testing.T) {
ts := createAuthServer(t)
defer ts.Close()
- c := dc()
+ c := dcnl()
c.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}).
SetError(AuthError{})
@@ -684,7 +684,7 @@ func TestRequestAuthToken(t *testing.T) {
ts := createAuthServer(t)
defer ts.Close()
- c := dc()
+ c := dcnl()
c.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}).
SetAuthToken("004DDB79-6801-4587-B976-F093E6AC44FF")
@@ -700,7 +700,7 @@ func TestRequestAuthScheme(t *testing.T) {
ts := createAuthServer(t)
defer ts.Close()
- c := dc()
+ c := dcnl()
c.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}).
SetAuthScheme("OAuth").
SetAuthToken("004DDB79-6801-4587-B976-F093E6AC44FF")
@@ -719,7 +719,7 @@ func TestRequestDigestAuth(t *testing.T) {
ts := createDigestServer(t, nil)
defer ts.Close()
- resp, err := dclr().
+ resp, err := dcnldr().
SetDigestAuth(conf.username, conf.password).
SetResult(&AuthSuccess{}).
Get(ts.URL + conf.uri)
@@ -736,7 +736,7 @@ func TestRequestDigestAuthFail(t *testing.T) {
ts := createDigestServer(t, nil)
defer ts.Close()
- resp, err := dclr().
+ resp, err := dcnldr().
SetDigestAuth(conf.username, "wrongPassword").
SetError(AuthError{}).
Get(ts.URL + conf.uri)
@@ -753,7 +753,7 @@ func TestRequestDigestAuthWithBody(t *testing.T) {
ts := createDigestServer(t, nil)
defer ts.Close()
- resp, err := dclr().
+ resp, err := dcnldr().
SetDigestAuth(conf.username, conf.password).
SetResult(&AuthSuccess{}).
SetHeader(hdrContentTypeKey, "application/json").
@@ -771,7 +771,7 @@ func TestFormData(t *testing.T) {
ts := createFormPostServer(t)
defer ts.Close()
- c := dc()
+ c := dcnl()
c.SetFormData(map[string]string{"zip_code": "00000", "city": "Los Angeles"}).
SetContentLength(true).
SetDebug(true)
@@ -795,7 +795,7 @@ func TestMultiValueFormData(t *testing.T) {
"search_criteria": []string{"book", "glass", "pencil"},
}
- c := dc()
+ c := dcnl()
c.SetContentLength(true).SetDebug(true)
c.outputLogTo(io.Discard)
@@ -812,7 +812,7 @@ func TestFormDataDisableWarn(t *testing.T) {
ts := createFormPostServer(t)
defer ts.Close()
- c := dc()
+ c := dcnl()
c.SetFormData(map[string]string{"zip_code": "00000", "city": "Los Angeles"}).
SetContentLength(true).
SetDisableWarn(true)
@@ -836,7 +836,7 @@ func TestMultiPartUploadFile(t *testing.T) {
basePath := getTestDataPath()
- c := dc()
+ c := dcnl()
c.SetFormData(map[string]string{"zip_code": "00001", "city": "Los Angeles"})
resp, err := c.R().
@@ -855,7 +855,7 @@ func TestMultiPartUploadFileViaPatch(t *testing.T) {
basePath := getTestDataPath()
- c := dc()
+ c := dcnl()
c.SetFormData(map[string]string{"zip_code": "00001", "city": "Los Angeles"})
resp, err := c.R().
@@ -874,7 +874,7 @@ func TestMultiPartUploadFileError(t *testing.T) {
basePath := getTestDataPath()
- c := dc()
+ c := dcnl()
c.SetFormData(map[string]string{"zip_code": "00001", "city": "Los Angeles"})
resp, err := c.R().
@@ -896,7 +896,7 @@ func TestMultiPartUploadFiles(t *testing.T) {
basePath := getTestDataPath()
- resp, err := dclr().
+ resp, err := dcnldr().
SetFormDataFromValues(url.Values{
"first_name": []string{"Jeevanandam"},
"last_name": []string{"M"},
@@ -929,7 +929,7 @@ func TestMultiPartIoReaderFiles(t *testing.T) {
}
t.Logf("File Info: %v", file.String())
- resp, err := dclr().
+ resp, err := dcnldr().
SetFormData(map[string]string{"first_name": "Jeevanandam", "last_name": "M"}).
SetFileReader("profile_img", "test-img.png", bytes.NewReader(profileImgBytes)).
SetFileReader("notes", "text-file.txt", bytes.NewReader(notesBytes)).
@@ -950,13 +950,13 @@ func TestMultiPartUploadFileNotOnGetOrDelete(t *testing.T) {
basePath := getTestDataPath()
- _, err := dclr().
+ _, err := dcnldr().
SetFile("profile_img", filepath.Join(basePath, "test-img.png")).
Get(ts.URL + "/upload")
assertEqual(t, "multipart content is not allowed in HTTP verb [GET]", err.Error())
- _, err = dclr().
+ _, err = dcnldr().
SetFile("profile_img", filepath.Join(basePath, "test-img.png")).
Delete(ts.URL + "/upload")
@@ -964,7 +964,7 @@ func TestMultiPartUploadFileNotOnGetOrDelete(t *testing.T) {
var hook1Count int
var hook2Count int
- _, err = dc().
+ _, err = dcnl().
OnInvalid(func(r *Request, err error) {
assertEqual(t, "multipart content is not allowed in HTTP verb [HEAD]", err.Error())
assertNotNil(t, r)
@@ -987,7 +987,7 @@ func TestMultiPartUploadFileNotOnGetOrDelete(t *testing.T) {
func TestMultiPartFormData(t *testing.T) {
ts := createFormPostServer(t)
defer ts.Close()
- resp, err := dclr().
+ resp, err := dcnldr().
SetMultipartFormData(map[string]string{"first_name": "Jeevanandam", "last_name": "M", "zip_code": "00001"}).
SetBasicAuth("myuser", "mypass").
Post(ts.URL + "/profile")
@@ -1004,7 +1004,7 @@ func TestMultiPartMultipartField(t *testing.T) {
jsonBytes := []byte(`{"input": {"name": "Uploaded document", "_filename" : ["file.txt"]}}`)
- resp, err := dclr().
+ resp, err := dcnldr().
SetFormDataFromValues(url.Values{
"first_name": []string{"Jeevanandam"},
"last_name": []string{"M"},
@@ -1047,7 +1047,7 @@ func TestMultiPartMultipartFields(t *testing.T) {
},
}
- resp, err := dclr().
+ resp, err := dcnldr().
SetFormData(map[string]string{"first_name": "Jeevanandam", "last_name": "M"}).
SetMultipartFields(fields...).
Post(ts.URL + "/upload")
@@ -1065,7 +1065,7 @@ func TestMultiPartCustomBoundary(t *testing.T) {
defer ts.Close()
defer cleanupFiles(".testdata/upload")
- _, err := dclr().
+ _, err := dcnldr().
SetMultipartFormData(map[string]string{"first_name": "Jeevanandam", "last_name": "M", "zip_code": "00001"}).
SetMultipartBoundary(`"my-custom-boundary"`).
SetBasicAuth("myuser", "mypass").
@@ -1073,7 +1073,7 @@ func TestMultiPartCustomBoundary(t *testing.T) {
assertEqual(t, "mime: invalid boundary character", err.Error())
- resp, err := dclr().
+ resp, err := dcnldr().
SetMultipartFormData(map[string]string{"first_name": "Jeevanandam", "last_name": "M", "zip_code": "00001"}).
SetMultipartBoundary("my-custom-boundary").
Post(ts.URL + "/profile")
@@ -1087,7 +1087,7 @@ func TestGetWithCookie(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- c := dcl()
+ c := dcnl()
c.SetBaseURL(ts.URL)
c.SetCookie(&http.Cookie{
Name: "go-resty-1",
@@ -1118,7 +1118,7 @@ func TestGetWithCookies(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- c := dc()
+ c := dcnl()
c.SetBaseURL(ts.URL).SetDebug(true)
tu, _ := url.Parse(ts.URL)
@@ -1167,7 +1167,7 @@ func TestPutPlainString(t *testing.T) {
ts := createGenericServer(t)
defer ts.Close()
- resp, err := dc().R().
+ resp, err := dcnl().R().
SetBody("This is plain text body to server").
Put(ts.URL + "/plaintext")
@@ -1180,7 +1180,7 @@ func TestPutJSONString(t *testing.T) {
ts := createGenericServer(t)
defer ts.Close()
- client := dc()
+ client := dcnl()
client.OnBeforeRequest(func(c *Client, r *Request) error {
r.SetHeader("X-Custom-Request-Middleware", "OnBeforeRequest middleware")
@@ -1209,7 +1209,7 @@ func TestPutXMLString(t *testing.T) {
ts := createGenericServer(t)
defer ts.Close()
- resp, err := dc().R().
+ resp, err := dcnl().R().
SetHeaders(map[string]string{hdrContentTypeKey: "application/xml", hdrAcceptKey: "application/xml"}).
SetBody(`XML Content sending to server`).
Put(ts.URL + "/xml")
@@ -1223,7 +1223,7 @@ func TestOnBeforeMiddleware(t *testing.T) {
ts := createGenericServer(t)
defer ts.Close()
- c := dc()
+ c := dcnl()
c.OnBeforeRequest(func(c *Client, r *Request) error {
r.SetHeader("X-Custom-Request-Middleware", "OnBeforeRequest middleware")
return nil
@@ -1247,7 +1247,7 @@ func TestHTTPAutoRedirectUpTo10(t *testing.T) {
ts := createRedirectServer(t)
defer ts.Close()
- _, err := dc().R().Get(ts.URL + "/redirect-1")
+ _, err := dcnl().R().Get(ts.URL + "/redirect-1")
assertEqual(t, true, (err.Error() == "Get /redirect-11: stopped after 10 redirects" ||
err.Error() == "Get \"/redirect-11\": stopped after 10 redirects"))
@@ -1257,7 +1257,7 @@ func TestHostCheckRedirectPolicy(t *testing.T) {
ts := createRedirectServer(t)
defer ts.Close()
- c := dc().
+ c := dcnl().
SetRedirectPolicy(DomainCheckRedirectPolicy("127.0.0.1"))
_, err := c.R().Get(ts.URL + "/redirect-host-check-1")
@@ -1271,14 +1271,14 @@ func TestHttpMethods(t *testing.T) {
defer ts.Close()
t.Run("head method", func(t *testing.T) {
- resp, err := dclr().Head(ts.URL + "/")
+ resp, err := dcnldr().Head(ts.URL + "/")
assertError(t, err)
assertEqual(t, http.StatusOK, resp.StatusCode())
})
t.Run("options method", func(t *testing.T) {
- resp, err := dclr().Options(ts.URL + "/options")
+ resp, err := dcnldr().Options(ts.URL + "/options")
assertError(t, err)
assertEqual(t, http.StatusOK, resp.StatusCode())
@@ -1286,7 +1286,7 @@ func TestHttpMethods(t *testing.T) {
})
t.Run("patch method", func(t *testing.T) {
- resp, err := dclr().Patch(ts.URL + "/patch")
+ resp, err := dcnldr().Patch(ts.URL + "/patch")
assertError(t, err)
assertEqual(t, http.StatusOK, resp.StatusCode())
@@ -1295,7 +1295,7 @@ func TestHttpMethods(t *testing.T) {
})
t.Run("trace method", func(t *testing.T) {
- resp, err := dclr().Trace(ts.URL + "/trace")
+ resp, err := dcnldr().Trace(ts.URL + "/trace")
assertError(t, err)
assertEqual(t, http.StatusOK, resp.StatusCode())
@@ -1304,7 +1304,7 @@ func TestHttpMethods(t *testing.T) {
})
t.Run("connect method", func(t *testing.T) {
- resp, err := dclr().Connect(ts.URL + "/connect")
+ resp, err := dcnldr().Connect(ts.URL + "/connect")
assertError(t, err)
assertEqual(t, http.StatusOK, resp.StatusCode())
@@ -1317,9 +1317,21 @@ func TestSendMethod(t *testing.T) {
ts := createGenericServer(t)
defer ts.Close()
+ t.Run("send-get-implicit", func(t *testing.T) {
+ req := dcnldr()
+ req.URL = ts.URL + "/gzip-test"
+
+ resp, err := req.Send()
+
+ assertError(t, err)
+ assertEqual(t, http.StatusOK, resp.StatusCode())
+
+ assertEqual(t, "This is Gzip response testing", resp.String())
+ })
+
t.Run("send-get", func(t *testing.T) {
- req := dclr()
- req.Method = http.MethodGet
+ req := dcnldr()
+ req.SetMethod(MethodGet)
req.URL = ts.URL + "/gzip-test"
resp, err := req.Send()
@@ -1331,8 +1343,8 @@ func TestSendMethod(t *testing.T) {
})
t.Run("send-options", func(t *testing.T) {
- req := dclr()
- req.Method = http.MethodOptions
+ req := dcnldr()
+ req.SetMethod(MethodOptions)
req.URL = ts.URL + "/options"
resp, err := req.Send()
@@ -1345,8 +1357,8 @@ func TestSendMethod(t *testing.T) {
})
t.Run("send-patch", func(t *testing.T) {
- req := dclr()
- req.Method = http.MethodPatch
+ req := dcnldr()
+ req.SetMethod(MethodPatch)
req.URL = ts.URL + "/patch"
resp, err := req.Send()
@@ -1358,8 +1370,8 @@ func TestSendMethod(t *testing.T) {
})
t.Run("send-put", func(t *testing.T) {
- req := dclr()
- req.Method = http.MethodPut
+ req := dcnldr()
+ req.SetMethod(MethodPut)
req.URL = ts.URL + "/plaintext"
resp, err := req.Send()
@@ -1378,7 +1390,7 @@ func TestRawFileUploadByBody(t *testing.T) {
fileBytes, err := os.ReadFile(filepath.Join(getTestDataPath(), "test-img.png"))
assertNil(t, err)
- resp, err := dclr().
+ resp, err := dcnldr().
SetBody(fileBytes).
SetContentLength(true).
SetAuthToken("004DDB79-6801-4587-B976-F093E6AC44FF").
@@ -1390,7 +1402,7 @@ func TestRawFileUploadByBody(t *testing.T) {
}
func TestProxySetting(t *testing.T) {
- c := dc()
+ c := dcnl()
transport, err := c.Transport()
@@ -1426,7 +1438,7 @@ func TestGetClient(t *testing.T) {
}
func TestIncorrectURL(t *testing.T) {
- c := dc()
+ c := dcnl()
_, 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") ||
strings.Contains(err.Error(), "parse \"//not.a.user@%66%6f%6f.com/just/a/path/also\"")))
@@ -1443,7 +1455,7 @@ func TestDetectContentTypeForPointer(t *testing.T) {
user := &User{Username: "testuser", Password: "testpass"}
- resp, err := dclr().
+ resp, err := dcnldr().
SetBody(user).
SetResult(AuthSuccess{}).
Post(ts.URL + "/login")
@@ -1472,7 +1484,7 @@ func TestDetectContentTypeForPointerWithSlice(t *testing.T) {
{FirstName: "firstname3", LastName: "lastname3", ZipCode: "10003"},
}
- resp, err := dclr().
+ resp, err := dcnldr().
SetBody(users).
Post(ts.URL + "/users")
@@ -1497,7 +1509,7 @@ func TestDetectContentTypeForPointerWithSliceMap(t *testing.T) {
var users []map[string]any
users = append(users, usersmap)
- resp, err := dclr().
+ resp, err := dcnldr().
SetBody(&users).
Post(ts.URL + "/usersmap")
@@ -1519,7 +1531,7 @@ func TestDetectContentTypeForSlice(t *testing.T) {
{FirstName: "firstname3", LastName: "lastname3", ZipCode: "10003"},
}
- resp, err := dclr().
+ resp, err := dcnldr().
SetBody(users).
Post(ts.URL + "/users")
@@ -1535,7 +1547,7 @@ func TestMultiParamsQueryString(t *testing.T) {
ts1 := createGetServer(t)
defer ts1.Close()
- client := dc()
+ client := dcnl()
req1 := client.R()
client.SetQueryParam("status", "open")
@@ -1577,7 +1589,7 @@ func TestSetQueryStringTypical(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- resp, err := dclr().
+ resp, err := dcnldr().
SetQueryString("productId=232&template=fresh-sample&cat=resty&source=google&kw=buy a lot more").
Get(ts.URL)
@@ -1586,7 +1598,7 @@ func TestSetQueryStringTypical(t *testing.T) {
assertEqual(t, "200 OK", resp.Status())
assertEqual(t, "TestGet: text response", resp.String())
- resp, err = dclr().
+ resp, err = dcnldr().
SetQueryString("&%%amp;").
Get(ts.URL)
@@ -1600,7 +1612,7 @@ func TestSetHeaderVerbatim(t *testing.T) {
ts := createPostServer(t)
defer ts.Close()
- r := dclr().
+ r := dcnldr().
SetHeaderVerbatim("header-lowercase", "value_lowercase").
SetHeader("header-lowercase", "value_standard")
@@ -1613,7 +1625,7 @@ func TestSetHeaderMultipleValue(t *testing.T) {
ts := createPostServer(t)
defer ts.Close()
- r := dclr().
+ r := dcnldr().
SetHeaderMultiValues(map[string][]string{
"Content": {"text/*", "text/html", "*"},
"Authorization": {"Bearer xyz"},
@@ -1628,7 +1640,7 @@ func TestOutputFileWithBaseDirAndRelativePath(t *testing.T) {
defer cleanupFiles(".testdata/dir-sample")
baseOutputDir := filepath.Join(getTestDataPath(), "dir-sample")
- client := dc().
+ client := dcnl().
SetRedirectPolicy(FlexibleRedirectPolicy(10)).
SetOutputDirectory(baseOutputDir).
SetDebug(true)
@@ -1649,7 +1661,7 @@ func TestOutputFileWithBaseDirAndRelativePath(t *testing.T) {
}
func TestOutputFileWithBaseDirError(t *testing.T) {
- c := dc().SetRedirectPolicy(FlexibleRedirectPolicy(10)).
+ c := dcnl().SetRedirectPolicy(FlexibleRedirectPolicy(10)).
SetOutputDirectory(filepath.Join(getTestDataPath(), `go-resty\0`))
_ = c
@@ -1660,7 +1672,7 @@ func TestOutputPathDirNotExists(t *testing.T) {
defer ts.Close()
defer cleanupFiles(filepath.Join(".testdata", "not-exists-dir"))
- client := dc().
+ client := dcnl().
SetRedirectPolicy(FlexibleRedirectPolicy(10)).
SetOutputDirectory(filepath.Join(getTestDataPath(), "not-exists-dir"))
@@ -1678,7 +1690,7 @@ func TestOutputFileAbsPath(t *testing.T) {
defer ts.Close()
defer cleanupFiles(filepath.Join(".testdata", "go-resty"))
- _, err := dcr().
+ _, err := dcnlr().
SetOutputFile(filepath.Join(getTestDataPath(), "go-resty", "test-img-success-2.png")).
Get(ts.URL + "/my-image.png")
@@ -1689,7 +1701,7 @@ func TestContextInternal(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- r := dc().R().
+ r := dcnl().R().
SetQueryParam("request_no", strconv.FormatInt(time.Now().Unix(), 10))
resp, err := r.Get(ts.URL + "/")
@@ -1699,7 +1711,7 @@ func TestContextInternal(t *testing.T) {
}
func TestSRV(t *testing.T) {
- c := dc().
+ c := dcnl().
SetRedirectPolicy(FlexibleRedirectPolicy(20)).
SetScheme("http")
@@ -1718,7 +1730,7 @@ func TestSRV(t *testing.T) {
}
func TestSRVInvalidService(t *testing.T) {
- _, err := dc().R().
+ _, err := dcnl().R().
SetSRV(&SRVRecord{"nonexistantservice", "sampledomain"}).
Get("/")
@@ -1730,30 +1742,31 @@ func TestRequestDoNotParseResponse(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- client := dc().SetDoNotParseResponse(true)
- resp, err := client.R().
- SetQueryParam("request_no", strconv.FormatInt(time.Now().Unix(), 10)).
- Get(ts.URL + "/")
-
- assertError(t, err)
+ t.Run("do not parse response 1", func(t *testing.T) {
+ client := dcnl().SetDoNotParseResponse(true)
+ resp, err := client.R().
+ SetQueryParam("request_no", strconv.FormatInt(time.Now().Unix(), 10)).
+ Get(ts.URL + "/")
- buf := acquireBuffer()
- defer releaseBuffer(buf)
- _, _ = io.Copy(buf, resp.Body)
+ assertError(t, err)
- assertEqual(t, "TestGet: text response", buf.String())
- _ = resp.Body.Close()
+ b, err := io.ReadAll(resp.Body)
+ _ = resp.Body.Close()
+ assertError(t, err)
+ assertEqual(t, "TestGet: text response", string(b))
+ })
- // Manually setting RawResponse as nil
- resp, err = dc().R().
- SetDoNotParseResponse(true).
- Get(ts.URL + "/")
+ t.Run("manual reset raw response - do not parse response 2", func(t *testing.T) {
+ resp, err := dcnl().R().
+ SetDoNotParseResponse(true).
+ Get(ts.URL + "/")
- assertError(t, err)
+ assertError(t, err)
- resp.RawResponse = nil
- assertEqual(t, 0, resp.StatusCode())
- assertEqual(t, "", resp.String())
+ resp.RawResponse = nil
+ assertEqual(t, 0, resp.StatusCode())
+ assertEqual(t, "", resp.String())
+ })
}
func TestRequestDoNotParseResponseDebugLog(t *testing.T) {
@@ -1761,7 +1774,7 @@ func TestRequestDoNotParseResponseDebugLog(t *testing.T) {
defer ts.Close()
t.Run("do not parse response debug log client level", func(t *testing.T) {
- c := dc().
+ c := dcnl().
SetDoNotParseResponse(true).
SetDebug(true)
@@ -1777,7 +1790,7 @@ func TestRequestDoNotParseResponseDebugLog(t *testing.T) {
})
t.Run("do not parse response debug log request level", func(t *testing.T) {
- c := dc()
+ c := dcnl()
var lgr bytes.Buffer
c.outputLogTo(&lgr)
@@ -1801,7 +1814,7 @@ func TestRequestExpectContentTypeTest(t *testing.T) {
ts := createGenericServer(t)
defer ts.Close()
- c := dc()
+ c := dcnl()
resp, err := c.R().
SetResult(noCtTest{}).
SetExpectResponseContentType("application/json").
@@ -1819,7 +1832,7 @@ func TestGetPathParamAndPathParams(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- c := dc().
+ c := dcnl().
SetBaseURL(ts.URL).
SetPathParam("userId", "sample@sample.com")
@@ -1840,7 +1853,7 @@ func TestReportMethodSupportsPayload(t *testing.T) {
ts := createGenericServer(t)
defer ts.Close()
- c := dc()
+ c := dcnl()
resp, err := c.R().
SetBody("body").
Execute("REPORT", ts.URL+"/report")
@@ -1870,7 +1883,7 @@ func TestRequestOverridesClientAuthorizationHeader(t *testing.T) {
ts := createAuthServer(t)
defer ts.Close()
- c := dc()
+ c := dcnl()
c.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}).
SetHeader("Authorization", "some token").
SetBaseURL(ts.URL + "/")
@@ -1890,7 +1903,7 @@ func TestRequestFileUploadAsReader(t *testing.T) {
file, _ := os.Open(filepath.Join(getTestDataPath(), "test-img.png"))
defer file.Close()
- resp, err := dclr().
+ resp, err := dcnldr().
SetBody(file).
SetHeader("Content-Type", "image/png").
Post(ts.URL + "/upload")
@@ -1902,7 +1915,7 @@ func TestRequestFileUploadAsReader(t *testing.T) {
file, _ = os.Open(filepath.Join(getTestDataPath(), "test-img.png"))
defer file.Close()
- resp, err = dclr().
+ resp, err = dcnldr().
SetBody(file).
SetHeader("Content-Type", "image/png").
SetContentLength(true).
@@ -1917,7 +1930,7 @@ func TestHostHeaderOverride(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- resp, err := dc().R().
+ resp, err := dcnl().R().
SetHeader("Host", "myhostname").
Get(ts.URL + "/host-header")
@@ -1939,7 +1952,7 @@ func TestNotFoundWithError(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- resp, err := dc().R().
+ resp, err := dcnl().R().
SetHeader(hdrContentTypeKey, "application/json").
SetError(&httpError).
Get(ts.URL + "/not-found-with-error")
@@ -1959,7 +1972,7 @@ func TestNotFoundWithoutError(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- c := dc().outputLogTo(os.Stdout)
+ c := dcnl().outputLogTo(os.Stdout)
resp, err := c.R().
SetError(&httpError).
SetHeader(hdrContentTypeKey, "application/json").
@@ -1978,7 +1991,7 @@ func TestPathParamURLInput(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- c := dc().
+ c := dcnl().
SetBaseURL(ts.URL).
SetPathParams(map[string]string{
"userId": "sample@sample.com",
@@ -2004,7 +2017,7 @@ func TestRawPathParamURLInput(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- c := dc().SetDebug(true).
+ c := dcnl().
SetBaseURL(ts.URL).
SetRawPathParams(map[string]string{
"userId": "sample@sample.com",
@@ -2014,7 +2027,7 @@ func TestRawPathParamURLInput(t *testing.T) {
assertEqual(t, "sample@sample.com", c.RawPathParams()["userId"])
assertEqual(t, "users/developers", c.RawPathParams()["path"])
- resp, err := c.R().
+ resp, err := c.R().EnableDebug().
SetRawPathParams(map[string]string{
"subAccountId": "100002",
"website": "https://example.com",
@@ -2035,7 +2048,7 @@ func TestTraceInfo(t *testing.T) {
serverAddr := ts.URL[strings.LastIndex(ts.URL, "/")+1:]
- client := dc()
+ client := dcnl()
client.SetBaseURL(ts.URL).EnableTrace()
for _, u := range []string{"/", "/json", "/long-text", "/long-json"} {
resp, err := client.R().Get(u)
@@ -2080,7 +2093,7 @@ func TestTraceInfoWithoutEnableTrace(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- client := dc()
+ client := dcnl()
client.SetBaseURL(ts.URL)
for _, u := range []string{"/", "/json", "/long-text", "/long-json"} {
resp, err := client.R().Get(u)
@@ -2098,7 +2111,7 @@ func TestTraceInfoWithoutEnableTrace(t *testing.T) {
}
func TestTraceInfoOnTimeout(t *testing.T) {
- client := dc()
+ client := dcnl()
client.SetBaseURL("http://resty-nowhere.local").EnableTrace()
resp, err := client.R().Get("/")
@@ -2211,7 +2224,7 @@ func TestPostMapTemporaryRedirect(t *testing.T) {
ts := createPostServer(t)
defer ts.Close()
- c := dc()
+ c := dcnl()
resp, err := c.R().SetBody(map[string]string{"username": "testuser", "password": "testpass"}).
Post(ts.URL + "/redirect")
@@ -2220,6 +2233,19 @@ func TestPostMapTemporaryRedirect(t *testing.T) {
assertEqual(t, http.StatusOK, resp.StatusCode())
}
+func TestPostWith204Responset(t *testing.T) {
+ ts := createPostServer(t)
+ defer ts.Close()
+
+ c := dcnl()
+ resp, err := c.R().SetBody(map[string]string{"username": "testuser", "password": "testpass"}).
+ Post(ts.URL + "/204-response")
+
+ assertNil(t, err)
+ assertNotNil(t, resp)
+ assertEqual(t, http.StatusNoContent, resp.StatusCode())
+}
+
type brokenReadCloser struct{}
func (b brokenReadCloser) Read(p []byte) (n int, err error) {
@@ -2234,11 +2260,11 @@ func TestPostBodyError(t *testing.T) {
ts := createPostServer(t)
defer ts.Close()
- c := dc()
+ c := dcnl()
resp, err := c.R().SetBody(brokenReadCloser{}).Post(ts.URL + "/redirect")
assertNotNil(t, err)
- assertEqual(t, "read error", err.Error())
- assertNil(t, resp)
+ assertEqual(t, "read error", errors.Unwrap(err).Error())
+ assertNotNil(t, resp)
}
func TestSetResultMustNotPanicOnNil(t *testing.T) {
@@ -2247,14 +2273,14 @@ func TestSetResultMustNotPanicOnNil(t *testing.T) {
t.Errorf("must not panic")
}
}()
- dc().R().SetResult(nil)
+ dcnl().R().SetResult(nil)
}
func TestRequestClone(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- c := dc()
+ c := dcnl()
parent := c.R()
// set an non-interface value
@@ -2317,10 +2343,43 @@ func TestRequestClone(t *testing.T) {
assertEqual(t, "xmpp-server-clone", clone.SRV.Service)
}
+func TestResponseBodyUnlimitedReads(t *testing.T) {
+ ts := createPostServer(t)
+ defer ts.Close()
+
+ user := &User{Username: "testuser", Password: "testpass"}
+
+ c := dcnl().
+ SetJSONEscapeHTML(false).
+ SetResponseBodyUnlimitedReads(true)
+
+ assertEqual(t, true, c.ResponseBodyUnlimitedReads())
+
+ resp, err := c.R().
+ SetHeader(hdrContentTypeKey, "application/json; charset=utf-8").
+ SetBody(user).
+ SetResult(&AuthSuccess{}).
+ Post(ts.URL + "/login")
+
+ assertError(t, err)
+ assertEqual(t, http.StatusOK, resp.StatusCode())
+ assertEqual(t, int64(50), resp.Size())
+
+ t.Logf("Result Success: %q", resp.Result().(*AuthSuccess))
+
+ for i := 1; i <= 5; i++ {
+ b, err := io.ReadAll(resp.Body)
+ assertNil(t, err)
+ assertEqual(t, `{ "id": "success", "message": "login successful" }`, string(b))
+ }
+
+ logResponse(t, resp)
+}
+
// This test methods exist for test coverage purpose
// to validate the getter and setter
func TestRequestSettingsCoverage(t *testing.T) {
- c := dc()
+ c := dcnl()
c.R().SetCloseConnection(true)
@@ -2329,4 +2388,8 @@ func TestRequestSettingsCoverage(t *testing.T) {
srv := []*net.SRV{}
srv = append(srv, &net.SRV{})
c.R().selectAddr(srv, "/", 1)
+
+ c.R().SetResponseBodyUnlimitedReads(true)
+
+ c.R().DisableDebug()
}
diff --git a/response.go b/response.go
index ca11b1a5..4afa625e 100644
--- a/response.go
+++ b/response.go
@@ -23,6 +23,7 @@ type Response struct {
Request *Request
Body io.ReadCloser
RawResponse *http.Response
+ IsRead bool
bodyBytes []byte
size int64
@@ -35,6 +36,8 @@ type Response struct {
// - [Response.BodyBytes] might be `nil` if [Request.SetOutputFile], [Request.SetDoNotParseResponse],
// [Client.SetDoNotParseResponse] method is used.
// - [Response.BodyBytes] might be `nil` if [Response].Body is already auto-unmarshal performed.
+//
+// TODO remove it
func (r *Response) BodyBytes() []byte {
if r.RawResponse == nil {
return []byte{}
@@ -106,8 +109,8 @@ func (r *Response) Cookies() []*http.Cookie {
// NOTE:
// - Returns an empty string on auto-unmarshal scenarios
func (r *Response) String() string {
- if len(r.bodyBytes) == 0 {
- return ""
+ if len(r.bodyBytes) == 0 && !r.Request.DoNotParseResponse {
+ _ = r.readAllBytes()
}
return strings.TrimSpace(string(r.bodyBytes))
}
@@ -154,35 +157,66 @@ func (r *Response) setReceivedAt() {
}
}
-func (r *Response) fmtBodyString(sl int) string {
+func (r *Response) fmtBodyString(sl int) (string, error) {
if r.Request.DoNotParseResponse {
- return "***** DO NOT PARSE RESPONSE - Enabled *****"
+ return "***** DO NOT PARSE RESPONSE - Enabled *****", nil
+ }
+
+ bl := len(r.bodyBytes)
+ if r.IsRead && bl == 0 {
+ return "***** RESPONSE BODY IS ALREADY READ - see Response.{Result()/Error()} *****", nil
}
- if len(r.bodyBytes) > 0 {
- if len(r.bodyBytes) > sl {
- return fmt.Sprintf("***** RESPONSE TOO LARGE (size - %d) *****", len(r.bodyBytes))
+
+ if bl > 0 {
+ if bl > sl {
+ return fmt.Sprintf("***** RESPONSE TOO LARGE (size - %d) *****", bl), nil
}
+
ct := r.Header().Get(hdrContentTypeKey)
- if isJSONContentType(ct) {
+ ctKey := inferContentTypeMapKey(ct)
+ if jsonKey == ctKey {
out := acquireBuffer()
defer releaseBuffer(out)
err := json.Indent(out, r.bodyBytes, "", " ")
if err != nil {
- return fmt.Sprintf("*** Error: Unable to format response body - \"%s\" ***\n\nLog Body as-is:\n%s", err, r.String())
+ return "", err
}
- return out.String()
+ return out.String(), nil
}
- return r.String()
+ return r.String(), nil
}
- return "***** NO CONTENT *****"
+ return "***** NO CONTENT *****", nil
}
// auto-unmarshal didn't happen, so fallback to
// old behavior of reading response as body bytes
func (r *Response) readAllBytes() (err error) {
- defer closeq(r.Body)
- r.bodyBytes, err = io.ReadAll(r.Body)
- r.Body = io.NopCloser(bytes.NewReader(r.bodyBytes))
+ if r.IsRead {
+ return nil
+ }
+
+ if _, ok := r.Body.(*readCopier); ok {
+ _, err = io.ReadAll(r.Body)
+ } else {
+ r.bodyBytes, err = io.ReadAll(r.Body)
+ closeq(r.Body)
+ r.Body = &readNoOpCloser{r: bytes.NewReader(r.bodyBytes)}
+ }
+
+ r.IsRead = true
return
}
+
+func (r *Response) wrapReadCopier() {
+ r.Body = &readCopier{
+ s: r.Body,
+ t: acquireBuffer(),
+ f: func(b *bytes.Buffer) {
+ r.bodyBytes = append([]byte{}, b.Bytes()...)
+ closeq(r.Body)
+ r.Body = &readNoOpCloser{r: bytes.NewReader(r.bodyBytes)}
+ releaseBuffer(b)
+ },
+ }
+}
diff --git a/resty_test.go b/resty_test.go
index 3d8f62d5..30b54779 100644
--- a/resty_test.go
+++ b/resty_test.go
@@ -5,6 +5,7 @@
package resty
import (
+ "bytes"
"compress/gzip"
"crypto/md5"
"encoding/base64"
@@ -318,6 +319,8 @@ func createPostServer(t *testing.T) *httptest.Server {
}
http.SetCookie(w, &cookie)
w.WriteHeader(http.StatusOK)
+ case "/204-response":
+ w.WriteHeader(http.StatusNoContent)
}
}
})
@@ -817,27 +820,27 @@ func createTestServer(fn func(w http.ResponseWriter, r *http.Request)) *httptest
return httptest.NewServer(http.HandlerFunc(fn))
}
-func dc() *Client {
+func dcnl() *Client {
c := New().
outputLogTo(io.Discard)
return c
}
-func dcl() *Client {
+func dcld() (*Client, *bytes.Buffer) {
+ logBuf := acquireBuffer()
c := New().
- SetDebug(true).
- outputLogTo(io.Discard)
- return c
+ EnableDebug().
+ outputLogTo(logBuf)
+ return c, logBuf
}
-func dcr() *Request {
- return dc().R()
+func dcnlr() *Request {
+ return dcnl().R()
}
-func dclr() *Request {
- c := dc().
- SetDebug(true).
- outputLogTo(io.Discard)
+func dcnldr() *Request {
+ c := dcnl().
+ SetDebug(true)
return c.R()
}
diff --git a/retry_test.go b/retry_test.go
index 75127777..08683018 100644
--- a/retry_test.go
+++ b/retry_test.go
@@ -143,7 +143,7 @@ func TestConditionalGet(t *testing.T) {
return attemptCount != externalCounter
})
- client := dc().AddRetryCondition(check).SetRetryCount(1)
+ client := dcnl().AddRetryCondition(check).SetRetryCount(1)
resp, err := client.R().
SetQueryParam("request_no", strconv.FormatInt(time.Now().Unix(), 10)).
Get(ts.URL + "/")
@@ -171,7 +171,7 @@ func TestConditionalGetRequestLevel(t *testing.T) {
})
// Clear the default client.
- client := dc()
+ client := dcnl()
resp, err := client.R().
AddRetryCondition(check).
SetRetryCount(1).
@@ -195,7 +195,7 @@ func TestClientRetryGet(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- c := dc().
+ c := dcnl().
SetTimeout(time.Second * 3).
SetRetryCount(3)
@@ -224,7 +224,7 @@ func TestClientRetryWait(t *testing.T) {
retryWaitTime := time.Duration(3) * time.Second
retryMaxWaitTime := time.Duration(9) * time.Second
- c := dc().
+ c := dcnl().
SetRetryCount(retryCount).
SetRetryWaitTime(retryWaitTime).
SetRetryMaxWaitTime(retryMaxWaitTime).
@@ -267,7 +267,7 @@ func TestClientRetryWaitMaxInfinite(t *testing.T) {
retryWaitTime := time.Duration(3) * time.Second
retryMaxWaitTime := time.Duration(-1.0) // negative value
- c := dc().
+ c := dcnl().
SetRetryCount(retryCount).
SetRetryWaitTime(retryWaitTime).
SetRetryMaxWaitTime(retryMaxWaitTime).
@@ -303,7 +303,7 @@ func TestClientRetryWaitMaxMinimum(t *testing.T) {
const retryMaxWaitTime = time.Nanosecond // minimal duration value
- c := dc().
+ c := dcnl().
SetRetryCount(1).
SetRetryMaxWaitTime(retryMaxWaitTime).
AddRetryCondition(func(*Response, error) bool { return true })
@@ -328,7 +328,7 @@ func TestClientRetryWaitCallbackError(t *testing.T) {
return 0, errors.New("quota exceeded")
}
- c := dc().
+ c := dcnl().
SetRetryCount(retryCount).
SetRetryWaitTime(retryWaitTime).
SetRetryMaxWaitTime(retryMaxWaitTime).
@@ -368,7 +368,7 @@ func TestClientRetryWaitCallback(t *testing.T) {
return 5 * time.Second, nil
}
- c := dc().
+ c := dcnl().
SetRetryCount(retryCount).
SetRetryWaitTime(retryWaitTime).
SetRetryMaxWaitTime(retryMaxWaitTime).
@@ -416,7 +416,7 @@ func TestClientRetryWaitCallbackTooShort(t *testing.T) {
return 2 * time.Second, nil // too short duration
}
- c := dc().
+ c := dcnl().
SetRetryCount(retryCount).
SetRetryWaitTime(retryWaitTime).
SetRetryMaxWaitTime(retryMaxWaitTime).
@@ -464,7 +464,7 @@ func TestClientRetryWaitCallbackTooLong(t *testing.T) {
return 4 * time.Second, nil // too long duration
}
- c := dc().
+ c := dcnl().
SetRetryCount(retryCount).
SetRetryWaitTime(retryWaitTime).
SetRetryMaxWaitTime(retryMaxWaitTime).
@@ -512,7 +512,7 @@ func TestClientRetryWaitCallbackSwitchToDefault(t *testing.T) {
return 0, nil // use default algorithm to determine retry-after time
}
- c := dc().
+ c := dcnl().
EnableTrace().
SetRetryCount(retryCount).
SetRetryWaitTime(retryWaitTime).
@@ -564,7 +564,7 @@ func TestClientRetryCancel(t *testing.T) {
retryWaitTime := time.Duration(10) * time.Second
retryMaxWaitTime := time.Duration(20) * time.Second
- c := dc().
+ c := dcnl().
SetRetryCount(retryCount).
SetRetryWaitTime(retryWaitTime).
SetRetryMaxWaitTime(retryMaxWaitTime).
@@ -606,7 +606,7 @@ func TestClientRetryPost(t *testing.T) {
var users []map[string]any
users = append(users, usersmap)
- c := dc()
+ c := dcnl()
c.SetRetryCount(3)
c.AddRetryCondition(RetryConditionFunc(func(r *Response, _ error) bool {
return r.StatusCode() >= http.StatusInternalServerError
@@ -637,7 +637,7 @@ func TestClientRetryErrorRecover(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- c := dc().
+ c := dcnl().
SetRetryCount(2).
SetError(AuthError{}).
AddRetryCondition(
@@ -670,7 +670,7 @@ func TestClientRetryCount(t *testing.T) {
attempt := 0
- c := dc().
+ c := dcnl().
SetTimeout(time.Second * 3).
SetRetryCount(1).
AddRetryCondition(
@@ -699,7 +699,7 @@ func TestClientErrorRetry(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
- c := dc().
+ c := dcnl().
SetTimeout(time.Second * 3).
SetRetryCount(1).
AddRetryAfterErrorCondition()
@@ -730,7 +730,7 @@ func TestClientRetryHook(t *testing.T) {
attempt++
}
- c := dc().
+ c := dcnl().
SetRetryCount(2).
SetTimeout(time.Second * 3).
AddRetryHook(retryHook)
@@ -786,7 +786,7 @@ func TestResetMultipartReaderSeekStartError(t *testing.T) {
bytes.NewReader([]byte("test")),
}
- c := dc().
+ c := dcnl().
SetRetryCount(2).
SetTimeout(time.Second * 3).
SetRetryResetReaders(true).
@@ -812,7 +812,7 @@ func TestClientResetMultipartReaders(t *testing.T) {
bufReader := bytes.NewReader(buf)
bufCpy := make([]byte, len(buf))
- c := dc().
+ c := dcnl().
SetRetryCount(2).
SetTimeout(time.Second * 3).
SetRetryResetReaders(true).
@@ -847,7 +847,7 @@ func TestRequestResetMultipartReaders(t *testing.T) {
bufReader := bytes.NewReader(buf)
bufCpy := make([]byte, len(buf))
- c := dc().
+ c := dcnl().
SetTimeout(time.Second * 3).
AddRetryAfterErrorCondition().
AddRetryHook(
diff --git a/stream.go b/stream.go
index 12dd0d8c..e47f0fb1 100644
--- a/stream.go
+++ b/stream.go
@@ -5,6 +5,7 @@
package resty
import (
+ "bytes"
"encoding/json"
"encoding/xml"
"errors"
@@ -97,3 +98,49 @@ func (l *limitReadCloser) Close() error {
}
return nil
}
+
+var _ io.ReadCloser = (*readCopier)(nil)
+
+type readCopier struct {
+ s io.Reader
+ t *bytes.Buffer
+ c bool
+ f func(*bytes.Buffer)
+}
+
+func (r *readCopier) Read(p []byte) (int, error) {
+ n, err := r.s.Read(p)
+ if n > 0 {
+ _, _ = r.t.Write(p[:n])
+ }
+ if err == io.EOF || err == ErrReadExceedsThresholdLimit {
+ if !r.c {
+ r.f(r.t)
+ r.c = true
+ }
+ }
+ return n, err
+}
+
+func (r *readCopier) Close() error {
+ if c, ok := r.s.(io.Closer); ok {
+ return c.Close()
+ }
+ return nil
+}
+
+var _ io.ReadCloser = (*readNoOpCloser)(nil)
+
+type readNoOpCloser struct {
+ r *bytes.Reader
+}
+
+func (r *readNoOpCloser) Read(p []byte) (int, error) {
+ n, err := r.r.Read(p)
+ if err == io.EOF {
+ r.r.Seek(0, 0)
+ }
+ return n, err
+}
+
+func (r *readNoOpCloser) Close() error { return nil }
diff --git a/util.go b/util.go
index 47e6f68f..cd65faa7 100644
--- a/util.go
+++ b/util.go
@@ -265,26 +265,27 @@ func releaseBuffer(buf *bytes.Buffer) {
}
}
-// requestBodyReleaser wraps requests's body and implements custom Close for it.
+func wrapRequestBufferReleaser(r *Request) io.ReadCloser {
+ if r.bodyBuf == nil {
+ return r.RawRequest.Body
+ }
+ return &requestBufferReleaser{
+ reqBuf: r.bodyBuf,
+ ReadCloser: r.RawRequest.Body,
+ }
+}
+
+var _ io.ReadCloser = (*requestBufferReleaser)(nil)
+
+// requestBufferReleaser wraps request body and implements custom Close for it.
// The Close method closes original body and releases request body back to sync.Pool.
-type requestBodyReleaser struct {
+type requestBufferReleaser struct {
releaseOnce sync.Once
reqBuf *bytes.Buffer
io.ReadCloser
}
-func newRequestBodyReleaser(respBody io.ReadCloser, reqBuf *bytes.Buffer) io.ReadCloser {
- if reqBuf == nil {
- return respBody
- }
-
- return &requestBodyReleaser{
- reqBuf: reqBuf,
- ReadCloser: respBody,
- }
-}
-
-func (rr *requestBodyReleaser) Close() error {
+func (rr *requestBufferReleaser) Close() error {
err := rr.ReadCloser.Close()
rr.releaseOnce.Do(func() {
releaseBuffer(rr.reqBuf)
diff --git a/util_curl.go b/util_curl.go
index f8992161..92b51d0d 100644
--- a/util_curl.go
+++ b/util_curl.go
@@ -4,7 +4,6 @@ import (
"bytes"
"io"
"net/http"
- "net/http/cookiejar"
"net/url"
"strings"
@@ -12,33 +11,34 @@ import (
"github.com/go-resty/resty/v3/shellescape"
)
-func buildCurlRequest(req *http.Request, httpCookiejar http.CookieJar) (curl string) {
+func buildCurlRequest(req *Request) (curl string) {
// 1. Generate curl raw headers
-
curl = "curl -X " + req.Method + " "
// req.Host + req.URL.Path + "?" + req.URL.RawQuery + " " + req.Proto + " "
- headers := dumpCurlHeaders(req)
+ headers := dumpCurlHeaders(req.RawRequest)
for _, kv := range *headers {
curl += `-H ` + shellescape.Quote(kv[0]+": "+kv[1]) + ` `
}
// 2. Generate curl cookies
// TODO validate this block of code, I think its not required since cookie captured via Headers
- if cookieJar, ok := httpCookiejar.(*cookiejar.Jar); ok {
- cookies := cookieJar.Cookies(req.URL)
- if len(cookies) > 0 {
+ if cookieJar := req.client.CookieJar(); cookieJar != nil {
+ if cookies := cookieJar.Cookies(req.RawRequest.URL); len(cookies) > 0 {
curl += ` -H ` + shellescape.Quote(dumpCurlCookies(cookies)) + " "
}
}
// 3. Generate curl body
- if req.Body != nil {
- buf, _ := io.ReadAll(req.Body)
- req.Body = io.NopCloser(bytes.NewBuffer(buf)) // important!!
+ if req.RawRequest.GetBody != nil {
+ body, err := req.RawRequest.GetBody()
+ if err != nil {
+ return ""
+ }
+ buf, _ := io.ReadAll(body)
curl += `-d ` + shellescape.Quote(string(bytes.TrimRight(buf, "\n")))
}
- urlString := shellescape.Quote(req.URL.String())
+ urlString := shellescape.Quote(req.RawRequest.URL.String())
if urlString == "''" {
urlString = "'http://unexecuted-request'"
}
diff --git a/util_test.go b/util_test.go
index ccf816c8..0c245032 100644
--- a/util_test.go
+++ b/util_test.go
@@ -9,6 +9,7 @@ import (
"errors"
"mime/multipart"
"net/url"
+ "strings"
"testing"
)
@@ -108,6 +109,8 @@ func TestRestyErrorFuncs(t *testing.T) {
ne1 := errors.New("new error 1")
nie1 := errors.New("inner error 1")
+ assertNil(t, wrapErrors(nil, nil))
+
e := wrapErrors(ne1, nie1)
assertEqual(t, "new error 1", e.Error())
assertEqual(t, "inner error 1", errors.Unwrap(e).Error())
@@ -118,3 +121,20 @@ func TestRestyErrorFuncs(t *testing.T) {
e = wrapErrors(nil, nie1)
assertEqual(t, "inner error 1", e.Error())
}
+
+// This test methods exist for test coverage purpose
+// to validate the getter and setter
+func TestUtilMiscTestCoverage(t *testing.T) {
+ l := &limitReadCloser{r: strings.NewReader("hello test close for no io.Closer")}
+ assertNil(t, l.Close())
+
+ r := &readCopier{s: strings.NewReader("hello test close for no io.Closer")}
+ assertNil(t, r.Close())
+
+ v := struct {
+ ID string `json:"id"`
+ Message string `json:"message"`
+ }{}
+ err := decodeJSON(bytes.NewReader([]byte(`{\" \": \"some value\"}`)), &v)
+ assertEqual(t, "invalid character '\\\\' looking for beginning of object key string", err.Error())
+}