Skip to content

Commit

Permalink
feat(enhancement): add ability to override default value and provide …
Browse files Browse the repository at this point in the history
…transport settings on resty client #814
  • Loading branch information
jeevatkm committed Sep 1, 2024
1 parent 7b974b5 commit e8a6ab7
Show file tree
Hide file tree
Showing 3 changed files with 184 additions and 84 deletions.
69 changes: 51 additions & 18 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,25 +89,58 @@ type (
SuccessHook func(*Client, *Response)
)

// ClientTimeoutSetting struct is used to define custom dialer and transport timeout
// values for the Resty client initialization. The default dialer and transport
// timeout values are -
//
// defaultClientTimeout := &ClientTimeoutSetting{
// DialerTimeout: 30 * time.Second,
// DialerKeepAlive: 30 * time.Second,
// TransportIdleConnTimeout: 90 * time.Second,
// TransportTLSHandshakeTimeout: 10 * time.Second,
// TransportExpectContinueTimeout: 1 * time.Second,
// }
// TransportSettings struct is used to define custom dialer and transport
// values for the Resty client. Please refer to individual
// struct fields to know the default values.
//
// Since v3.0.0
type ClientTimeoutSetting struct {
DialerTimeout time.Duration
DialerKeepAlive time.Duration
TransportIdleConnTimeout time.Duration
TransportTLSHandshakeTimeout time.Duration
TransportExpectContinueTimeout time.Duration
// Also, refer to https://pkg.go.dev/net/http#Transport for more details.
type TransportSettings struct {
// DialerTimeout, default value is `30` seconds.
DialerTimeout time.Duration

// DialerKeepAlive, default value is `30` seconds.
DialerKeepAlive time.Duration

// IdleConnTimeout, default value is `90` seconds.
IdleConnTimeout time.Duration

// TLSHandshakeTimeout, default value is `10` seconds.
TLSHandshakeTimeout time.Duration

// ExpectContinueTimeout, default value is `1` seconds.
ExpectContinueTimeout time.Duration

// ResponseHeaderTimeout, added to provide ability to
// set value. No default value in Resty, the Go
// HTTP client default value applies.
ResponseHeaderTimeout time.Duration

// MaxIdleConns, default value is `100`.
MaxIdleConns int

// MaxIdleConnsPerHost, default value is `runtime.GOMAXPROCS(0) + 1`.
MaxIdleConnsPerHost int

// DisableKeepAlives, default value is `false`.
DisableKeepAlives bool

// DisableCompression, default value is `false`.
DisableCompression bool

// MaxResponseHeaderBytes, added to provide ability to
// set value. No default value in Resty, the Go
// HTTP client default value applies.
MaxResponseHeaderBytes int64

// WriteBufferSize, added to provide ability to
// set value. No default value in Resty, the Go
// HTTP client default value applies.
WriteBufferSize int

// ReadBufferSize, added to provide ability to
// set value. No default value in Resty, the Go
// HTTP client default value applies.
ReadBufferSize int
}

// Client struct is used to create Resty client with client level settings,
Expand Down
45 changes: 37 additions & 8 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -791,25 +791,54 @@ func TestDebugLogSimultaneously(t *testing.T) {
}
}

func TestNewWithTimeout(t *testing.T) {
func TestCustomTransportSettings(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()

customTimeout := &ClientTimeoutSetting{
DialerTimeout: 30 * time.Second,
DialerKeepAlive: 15 * time.Second,
TransportIdleConnTimeout: 120 * time.Second,
TransportTLSHandshakeTimeout: 20 * time.Second,
TransportExpectContinueTimeout: 1 * time.Second,
customTransportSettings := &TransportSettings{
DialerTimeout: 30 * time.Second,
DialerKeepAlive: 15 * time.Second,
IdleConnTimeout: 120 * time.Second,
TLSHandshakeTimeout: 20 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
MaxIdleConns: 50,
MaxIdleConnsPerHost: 3,
ResponseHeaderTimeout: 10 * time.Second,
MaxResponseHeaderBytes: 1 << 10,
WriteBufferSize: 2 << 10,
ReadBufferSize: 2 << 10,
}
client := NewWithTimeout(customTimeout)
client := NewWithTransportSettings(customTransportSettings)
client.SetBaseURL(ts.URL)

resp, err := client.R().Get("/")
assertNil(t, err)
assertEqual(t, resp.String(), "TestGet: text response")
}

func TestDefaultDialerTransportSettings(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()

t.Run("transport-default", func(t *testing.T) {
client := NewWithTransportSettings(nil)
client.SetBaseURL(ts.URL)

resp, err := client.R().Get("/")
assertNil(t, err)
assertEqual(t, resp.String(), "TestGet: text response")
})

t.Run("dialer-transport-default", func(t *testing.T) {
client := NewWithDialerAndTransportSettings(nil, nil)
client.SetBaseURL(ts.URL)

resp, err := client.R().Get("/")
assertNil(t, err)
assertEqual(t, resp.String(), "TestGet: text response")
})
}

func TestNewWithDialer(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
Expand Down
154 changes: 96 additions & 58 deletions resty.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,33 +23,15 @@ import (
// Version # of resty
const Version = "3.0.0-dev"

var (
defaultClientTimeout = &ClientTimeoutSetting{
DialerTimeout: 30 * time.Second,
DialerKeepAlive: 30 * time.Second,
TransportIdleConnTimeout: 90 * time.Second,
TransportTLSHandshakeTimeout: 10 * time.Second,
TransportExpectContinueTimeout: 1 * time.Second,
}
)

// New method creates a new Resty client.
func New() *Client {
return NewWithTimeout(defaultClientTimeout)
return NewWithTransportSettings(nil)
}

// NewWithTimeout method creates a new Resty client with provided
// NewWithTransportSettings method creates a new Resty client with provided
// timeout values.
//
// Since v3.0.0
func NewWithTimeout(timeoutSetting *ClientTimeoutSetting) *Client {
return createClient(&http.Client{
Jar: createCookieJar(),
Transport: createTransport(
createDialer(nil, timeoutSetting),
timeoutSetting,
),
})
func NewWithTransportSettings(transportSettings *TransportSettings) *Client {
return NewWithDialerAndTransportSettings(nil, transportSettings)
}

// NewWithClient method creates a new Resty client with given `http.Client`.
Expand All @@ -59,61 +41,117 @@ func NewWithClient(hc *http.Client) *Client {

// NewWithDialer method creates a new Resty client with given Local Address
// to dial from.
//
// Since v3.0.0
func NewWithDialer(dialer *net.Dialer) *Client {
return NewWithDialerAndTimeout(dialer, defaultClientTimeout)
return NewWithDialerAndTransportSettings(dialer, nil)
}

// NewWithDialer method creates a new Resty client with given Local Address
// NewWithLocalAddr method creates a new Resty client with given Local Address
// to dial from.
//
// Since v3.0.0
func NewWithDialerAndTimeout(dialer *net.Dialer, timeoutSetting *ClientTimeoutSetting) *Client {
return createClient(&http.Client{
Jar: createCookieJar(),
Transport: createTransport(dialer, timeoutSetting),
})
func NewWithLocalAddr(localAddr net.Addr) *Client {
return NewWithDialerAndTransportSettings(
&net.Dialer{LocalAddr: localAddr},
nil,
)
}

// NewWithLocalAddr method creates a new Resty client with given Local Address
// NewWithDialerAndTransportSettings method creates a new Resty client with given Local Address
// to dial from.
func NewWithLocalAddr(localAddr net.Addr) *Client {
func NewWithDialerAndTransportSettings(dialer *net.Dialer, transportSettings *TransportSettings) *Client {
return createClient(&http.Client{
Jar: createCookieJar(),
Transport: createTransport(
createDialer(localAddr, defaultClientTimeout),
defaultClientTimeout,
),
Jar: createCookieJar(),
Transport: createTransport(dialer, transportSettings),
})
}

//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Unexported methods
//_______________________________________________________________________

func createDialer(localAddr net.Addr, timeoutSetting *ClientTimeoutSetting) *net.Dialer {
dialer := &net.Dialer{
Timeout: timeoutSetting.DialerTimeout,
KeepAlive: timeoutSetting.DialerKeepAlive,
func createTransport(dialer *net.Dialer, transportSettings *TransportSettings) *http.Transport {
if transportSettings == nil {
transportSettings = &TransportSettings{}
}

// Dialer

if dialer == nil {
dialer = &net.Dialer{}
}
if localAddr != nil {
dialer.LocalAddr = localAddr

if transportSettings.DialerTimeout > 0 {
dialer.Timeout = transportSettings.DialerTimeout
} else {
dialer.Timeout = 30 * time.Second
}

if transportSettings.DialerKeepAlive > 0 {
dialer.KeepAlive = transportSettings.DialerKeepAlive
} else {
dialer.KeepAlive = 30 * time.Second
}
return dialer
}

func createTransport(dialer *net.Dialer, timeoutSetting *ClientTimeoutSetting) *http.Transport {
return &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: transportDialContext(dialer),
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: timeoutSetting.TransportIdleConnTimeout,
TLSHandshakeTimeout: timeoutSetting.TransportTLSHandshakeTimeout,
ExpectContinueTimeout: timeoutSetting.TransportExpectContinueTimeout,
MaxIdleConnsPerHost: runtime.GOMAXPROCS(0) + 1,
// Transport
t := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: transportDialContext(dialer),
DisableKeepAlives: transportSettings.DisableKeepAlives,
DisableCompression: transportSettings.DisableCompression,
ForceAttemptHTTP2: true,
}

if transportSettings.IdleConnTimeout > 0 {
t.IdleConnTimeout = transportSettings.IdleConnTimeout
} else {
t.IdleConnTimeout = 90 * time.Second
}

if transportSettings.TLSHandshakeTimeout > 0 {
t.TLSHandshakeTimeout = transportSettings.TLSHandshakeTimeout
} else {
t.TLSHandshakeTimeout = 10 * time.Second
}

if transportSettings.ExpectContinueTimeout > 0 {
t.ExpectContinueTimeout = transportSettings.ExpectContinueTimeout
} else {
t.ExpectContinueTimeout = 1 * time.Second
}

if transportSettings.MaxIdleConns > 0 {
t.MaxIdleConns = transportSettings.MaxIdleConns
} else {
t.MaxIdleConns = 100
}

if transportSettings.MaxIdleConnsPerHost > 0 {
t.MaxIdleConnsPerHost = transportSettings.MaxIdleConnsPerHost
} else {
t.MaxIdleConnsPerHost = runtime.GOMAXPROCS(0) + 1
}

//
// No default value in Resty for following settings, added to
// provide ability to set value otherwise the Go HTTP client
// default value applies.
//

if transportSettings.ResponseHeaderTimeout > 0 {
t.ResponseHeaderTimeout = transportSettings.ResponseHeaderTimeout
}

if transportSettings.MaxResponseHeaderBytes > 0 {
t.MaxResponseHeaderBytes = transportSettings.MaxResponseHeaderBytes
}

if transportSettings.WriteBufferSize > 0 {
t.WriteBufferSize = transportSettings.WriteBufferSize
}

if transportSettings.ReadBufferSize > 0 {
t.ReadBufferSize = transportSettings.ReadBufferSize
}

return t
}

func createCookieJar() *cookiejar.Jar {
Expand Down

0 comments on commit e8a6ab7

Please sign in to comment.