Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 36 additions & 23 deletions net/gclient/gclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,31 +58,44 @@
defaultClientAgent = fmt.Sprintf(`GClient %s at %s`, gf.VERSION, hostname)
)

// New creates and returns a new HTTP client object.
// New creates and returns a new HTTP client object with a default timeout of 30 seconds.
func New() *Client {
return NewWithTimeout(30 * time.Second)
}
Comment thread
hailaz marked this conversation as resolved.

// NewWithTimeout creates and returns a new HTTP client object with specified timeout.
func NewWithTimeout(timeout time.Duration) *Client {
// Create a new Transport based on DefaultTransport's defaults, but as an independent copy.
// This avoids modifying the global http.DefaultTransport which is shared across the process.
transport := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},

Check failure

Code scanning / CodeQL

Disabled TLS certificate check High

InsecureSkipVerify should not be used in production code.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
DisableKeepAlives: true,
MaxIdleConnsPerHost: 50,
MaxConnsPerHost: 100,

// Go default
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
Comment thread
hailaz marked this conversation as resolved.
Outdated
defaultClient := http.Client{
Transport: transport,
Timeout: timeout,
}

return NewWithHttpClient(&defaultClient)
}

// NewWithHttpClient creates and returns a new Client with given http.Client.
func NewWithHttpClient(client *http.Client) *Client {
c := &Client{
Comment thread
hailaz marked this conversation as resolved.
Client: http.Client{
Transport: &http.Transport{
// No validation for https certification of the server in default.
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
DisableKeepAlives: true,
MaxIdleConns: 100,
MaxIdleConnsPerHost: 50,
MaxConnsPerHost: 100,
IdleConnTimeout: 90 * time.Second,
ResponseHeaderTimeout: 30 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ForceAttemptHTTP2: true,
DisableCompression: false,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
},
},
Client: *client,
header: make(map[string]string),
cookies: make(map[string]string),
builder: gsel.GetBuilder(),
Expand Down
47 changes: 47 additions & 0 deletions net/gclient/gclient_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,52 @@ func (c *Client) SetPrefix(prefix string) *Client {
}

// SetTimeout sets the request timeout for the client.
// It only updates the client timeout, not transport timeouts.
// Use SetTransportTimeout to configure transport-level timeouts.
func (c *Client) SetTimeout(t time.Duration) *Client {
Comment thread
hailaz marked this conversation as resolved.
c.Client.Timeout = t
return c
}

// SetTransportTimeout sets the transport-level timeouts for the client.
// It configures ResponseHeaderTimeout, TLSHandshakeTimeout, and ExpectContinueTimeout.
// Use this method to set fine-grained timeouts for different phases of the request.
func (c *Client) SetTransportTimeout(responseHeaderTimeout, tlsHandshakeTimeout, expectContinueTimeout time.Duration) *Client {
Comment thread
hailaz marked this conversation as resolved.
if transport, ok := c.Transport.(*http.Transport); ok {
transport.ResponseHeaderTimeout = responseHeaderTimeout
transport.TLSHandshakeTimeout = tlsHandshakeTimeout
transport.ExpectContinueTimeout = expectContinueTimeout
}
return c
}

// SetResponseHeaderTimeout sets the timeout for receiving response headers.
// This is the maximum time to wait for the server to send response headers.
func (c *Client) SetResponseHeaderTimeout(t time.Duration) *Client {
if transport, ok := c.Transport.(*http.Transport); ok {
transport.ResponseHeaderTimeout = t
}
return c
}

// SetTLSHandshakeTimeout sets the timeout for TLS handshake.
// This is the maximum time to wait for TLS handshake to complete.
func (c *Client) SetTLSHandshakeTimeout(t time.Duration) *Client {
if transport, ok := c.Transport.(*http.Transport); ok {
transport.TLSHandshakeTimeout = t
}
return c
}

// SetExpectContinueTimeout sets the timeout for Expect: 100-continue.
// This is the maximum time to wait for the server to respond to Expect: 100-continue header.
func (c *Client) SetExpectContinueTimeout(t time.Duration) *Client {
if transport, ok := c.Transport.(*http.Transport); ok {
transport.ExpectContinueTimeout = t
}
return c
}

// SetBasicAuth sets HTTP basic authentication information for the client.
func (c *Client) SetBasicAuth(user, pass string) *Client {
c.authUser = user
Expand Down Expand Up @@ -216,3 +257,9 @@ func (c *Client) SetBuilder(builder gsel.Builder) {
func (c *Client) SetDiscovery(discovery gsvc.Discovery) {
c.discovery = discovery
}

// SetTransport sets the transport for the client.
func (c *Client) SetTransport(transport http.RoundTripper) *Client {
c.Transport = transport
return c
}
183 changes: 183 additions & 0 deletions net/gclient/gclient_z_unit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -719,3 +719,186 @@ func TestClient_NoUrlEncode(t *testing.T) {
t.Assert(c.NoUrlEncode().GetContent(ctx, `/`, params), `path=/data/binlog`)
})
}

func TestClient_SetTransportTimeout_ShouldUpdateTransportTimeouts(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
client := gclient.New()
responseHeaderTimeout := 5 * time.Second
tlsHandshakeTimeout := 3 * time.Second
expectContinueTimeout := 2 * time.Second

// Set transport timeouts
client.SetTransportTimeout(responseHeaderTimeout, tlsHandshakeTimeout, expectContinueTimeout)

// Verify transport timeouts
transport, ok := client.Transport.(*http.Transport)
t.Assert(ok, true)
t.Assert(transport.ResponseHeaderTimeout, responseHeaderTimeout)
t.Assert(transport.TLSHandshakeTimeout, tlsHandshakeTimeout)
t.Assert(transport.ExpectContinueTimeout, expectContinueTimeout)

// Verify that client timeout remains at default (30s from New())
t.Assert(client.Client.Timeout, 30*time.Second)

// Verify that IdleConnTimeout is not changed (should remain default 90s)
t.Assert(transport.IdleConnTimeout, 90*time.Second)
Comment thread
hailaz marked this conversation as resolved.
Outdated
Comment thread
hailaz marked this conversation as resolved.
Outdated
})
}

func TestClient_SetTimeout_ShouldOnlyUpdateClientTimeout(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
client := gclient.New()
timeout := 5 * time.Second

// Set timeout
client.SetTimeout(timeout)

// Verify client timeout
t.Assert(client.Client.Timeout, timeout)

// Verify transport timeouts are not changed (should remain defaults)
transport, ok := client.Transport.(*http.Transport)
t.Assert(ok, true)
// ResponseHeaderTimeout should be 0 (not set by default)
t.Assert(transport.ResponseHeaderTimeout, 0*time.Second)
// TLSHandshakeTimeout should be 10s (default)
t.Assert(transport.TLSHandshakeTimeout, 10*time.Second)
// ExpectContinueTimeout should be 1s (default)
t.Assert(transport.ExpectContinueTimeout, 1*time.Second)
Comment thread
hailaz marked this conversation as resolved.
Outdated
})
}

func TestClient_SetTimeout_WithDifferentValues(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
client := gclient.New()

// Test with various timeout values
testCases := []time.Duration{
1 * time.Second,
10 * time.Second,
30 * time.Second,
1 * time.Minute,
}

for _, timeout := range testCases {
client.SetTimeout(timeout)

// Verify client timeout is set correctly
t.Assert(client.Client.Timeout, timeout)

// Verify transport timeouts remain unchanged (defaults)
transport, ok := client.Transport.(*http.Transport)
t.Assert(ok, true)
// ResponseHeaderTimeout should remain 0 (not set by default)
t.Assert(transport.ResponseHeaderTimeout, 0*time.Second)
}
})
}

func TestClient_Clone_ShouldPreserveTimeoutSettings(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
original := gclient.New()
timeout := 5 * time.Second
original.SetTimeout(timeout)

// Clone the client
cloned := original.Clone()

// Verify cloned client has same timeout
t.Assert(cloned.Client.Timeout, timeout)

// Verify transport timeouts are preserved (should be defaults)
transport, ok := cloned.Transport.(*http.Transport)
t.Assert(ok, true)
// ResponseHeaderTimeout should be 0 (not set by default)
t.Assert(transport.ResponseHeaderTimeout, 0*time.Second)
// TLSHandshakeTimeout should be 10s (default)
t.Assert(transport.TLSHandshakeTimeout, 10*time.Second)
Comment thread
hailaz marked this conversation as resolved.
Outdated

// Modify cloned client's timeout
newTimeout := 10 * time.Second
cloned.SetTimeout(newTimeout)

// Verify original client's timeout is unchanged
t.Assert(original.Client.Timeout, timeout)

// Verify cloned client has new timeout
t.Assert(cloned.Client.Timeout, newTimeout)
})

gtest.C(t, func(t *gtest.T) {
client := gclient.New()
client.SetResponseHeaderTimeout(5 * time.Second)
transport, ok := client.Transport.(*http.Transport)
t.Assert(ok, true)
t.Assert(transport.ResponseHeaderTimeout, 5*time.Second)

client.SetTLSHandshakeTimeout(3 * time.Second)
t.Assert(transport.TLSHandshakeTimeout, 3*time.Second)

client.SetExpectContinueTimeout(2 * time.Second)
t.Assert(transport.ExpectContinueTimeout, 2*time.Second)

// Verify client timeout is still at default (30s from New())
t.Assert(client.Client.Timeout, 30*time.Second)
})
Comment thread
hailaz marked this conversation as resolved.
}

func TestClient_SetTransportTimeout_WithIndividualMethods(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
client := gclient.New()

// Test SetResponseHeaderTimeout
client.SetResponseHeaderTimeout(5 * time.Second)
transport, ok := client.Transport.(*http.Transport)
t.Assert(ok, true)
t.Assert(transport.ResponseHeaderTimeout, 5*time.Second)

// Test SetTLSHandshakeTimeout
client.SetTLSHandshakeTimeout(3 * time.Second)
t.Assert(transport.TLSHandshakeTimeout, 3*time.Second)

// Test SetExpectContinueTimeout
client.SetExpectContinueTimeout(2 * time.Second)
t.Assert(transport.ExpectContinueTimeout, 2*time.Second)

// Verify client timeout is still at default (30s from New())
t.Assert(client.Client.Timeout, 30*time.Second)
})
Comment thread
hailaz marked this conversation as resolved.
}

func TestClient_SetTransportTimeout_ShouldNotAffectClientTimeout(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
client := gclient.New()
clientTimeout := 10 * time.Second
client.SetTimeout(clientTimeout)

// Set transport timeouts
client.SetTransportTimeout(5*time.Second, 3*time.Second, 2*time.Second)

// Verify client timeout is unchanged
t.Assert(client.Client.Timeout, clientTimeout)

// Verify transport timeouts are set
transport, ok := client.Transport.(*http.Transport)
t.Assert(ok, true)
t.Assert(transport.ResponseHeaderTimeout, 5*time.Second)
t.Assert(transport.TLSHandshakeTimeout, 3*time.Second)
t.Assert(transport.ExpectContinueTimeout, 2*time.Second)
})
}

func TestClient_SetTransportTimeout_ZeroValues(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
client := gclient.New()

// Set zero values (effectively disable timeouts)
client.SetTransportTimeout(0, 0, 0)

transport, ok := client.Transport.(*http.Transport)
t.Assert(ok, true)
t.Assert(transport.ResponseHeaderTimeout, 0*time.Second)
t.Assert(transport.TLSHandshakeTimeout, 0*time.Second)
t.Assert(transport.ExpectContinueTimeout, 0*time.Second)
})
}
Loading