diff --git a/client.go b/client.go index 446ba85..006191c 100644 --- a/client.go +++ b/client.go @@ -142,11 +142,11 @@ type Client struct { proxyURL *url.URL beforeRequest []RequestMiddleware udBeforeRequest []RequestMiddleware - udBeforeRequestLock sync.RWMutex + udBeforeRequestLock *sync.RWMutex preReqHook PreRequestHook successHooks []SuccessHook afterResponse []ResponseMiddleware - afterResponseLock sync.RWMutex + afterResponseLock *sync.RWMutex requestLog RequestLogCallback responseLog ResponseLogCallback errorHooks []ErrorHook @@ -1125,6 +1125,25 @@ func (c *Client) GetClient() *http.Client { return c.httpClient } +// Clone returns a clone of the original client. +// +// Be carefull when using this function: +// - Interface values are not deeply cloned. Thus, both the original and the clone will use the +// same value. +// - This function is not safe for concurrent use. You should only use this when you are sure that +// the client is not being used by any other goroutine. +// +// Since v2.12.0 +func (c *Client) Clone() *Client { + // dereference the pointer and copy the value + cc := *c + + // lock values should not be copied - thus new values are used. + cc.afterResponseLock = &sync.RWMutex{} + cc.udBeforeRequestLock = &sync.RWMutex{} + return &cc +} + //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ // Client Unexported methods //_______________________________________________________________________ @@ -1360,9 +1379,11 @@ func createClient(hc *http.Client) *Client { XMLUnmarshal: xml.Unmarshal, HeaderAuthorizationKey: http.CanonicalHeaderKey("Authorization"), - jsonEscapeHTML: true, - httpClient: hc, - debugBodySizeLimit: math.MaxInt32, + jsonEscapeHTML: true, + httpClient: hc, + debugBodySizeLimit: math.MaxInt32, + udBeforeRequestLock: &sync.RWMutex{}, + afterResponseLock: &sync.RWMutex{}, } // Logger diff --git a/client_test.go b/client_test.go index 1f291d1..b91f2e9 100644 --- a/client_test.go +++ b/client_test.go @@ -1069,3 +1069,29 @@ func TestUnixSocket(t *testing.T) { assertNil(t, err) assertEqual(t, "Hello resty client from a server running on endpoint /hello!", res.String()) } + +func TestClone(t *testing.T) { + parent := New() + + // set a non-interface field + parent.SetBaseURL("http://localhost") + + // set an interface field + parent.UserInfo = &User{ + Username: "parent", + } + + clone := parent.Clone() + // update value of non-interface type - change will only happen on clone + clone.SetBaseURL("https://local.host") + // update value of interface type - change will also happen on parent + clone.UserInfo.Username = "clone" + + // asert non-interface type + assertEqual(t, "http://localhost", parent.BaseURL) + assertEqual(t, "https://local.host", clone.BaseURL) + + // assert interface type + assertEqual(t, "clone", parent.UserInfo.Username) + assertEqual(t, "clone", clone.UserInfo.Username) +}