From 37157faf409ebf25974e90285053a7b17f9eeeb1 Mon Sep 17 00:00:00 2001 From: fabiante <7669818+fabiante@users.noreply.github.com> Date: Sun, 3 Mar 2024 20:17:51 +0100 Subject: [PATCH] Add Client.Clone function (#774) * Change Client locks to pointer values * Add Clone function * Add test --------- Co-authored-by: fabiante --- client.go | 31 ++++++++++++++++++++++++++----- client_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/client.go b/client.go index 446ba851..006191cb 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 1f291d12..b91f2e91 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) +}