fix: use a timeout-configured HTTP client for upstream requests
All upstream GET/HEAD and bearer-token requests used http.DefaultClient, which has no timeouts, so a slow or wedged upstream could pin a goroutine and connection indefinitely. Introduce a shared upstreamClient with dial, TLS-handshake and response-header timeouts (no overall Client timeout, so large artifact bodies can still stream, bounded by the request context). Refs #67
This commit is contained in:
@@ -154,7 +154,7 @@ func (e *Engine) fetchFromUpstream(ctx context.Context, remote models.Remote, pa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := http.DefaultClient.Do(req)
|
resp, err := upstreamClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &UpstreamError{Err: err}
|
return nil, &UpstreamError{Err: err}
|
||||||
}
|
}
|
||||||
@@ -170,7 +170,7 @@ func (e *Engine) fetchFromUpstream(ctx context.Context, remote models.Remote, pa
|
|||||||
req2.Header.Set("Accept", accept)
|
req2.Header.Set("Accept", accept)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resp, err = http.DefaultClient.Do(req2)
|
resp, err = upstreamClient.Do(req2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &UpstreamError{Err: err}
|
return nil, &UpstreamError{Err: err}
|
||||||
}
|
}
|
||||||
@@ -302,7 +302,7 @@ func (e *Engine) checkUpstream(ctx context.Context, remote models.Remote, path,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := http.DefaultClient.Do(req)
|
resp, err := upstreamClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, &UpstreamError{Err: err}
|
return false, &UpstreamError{Err: err}
|
||||||
}
|
}
|
||||||
@@ -392,7 +392,7 @@ func fetchBearerToken(ctx context.Context, wwwAuth string, remote models.Remote)
|
|||||||
req.SetBasicAuth(remote.Username, remote.Password)
|
req.SetBasicAuth(remote.Username, remote.Password)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := http.DefaultClient.Do(req)
|
resp, err := upstreamClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// upstreamClient is the shared HTTP client for all upstream requests.
|
||||||
|
//
|
||||||
|
// It deliberately sets no overall Client.Timeout: the proxy streams
|
||||||
|
// arbitrarily large artifacts and the total time is bounded by the request
|
||||||
|
// context instead. Instead it constrains the phases that must never hang —
|
||||||
|
// connect, TLS handshake, and time-to-first-response-header — so a slow or
|
||||||
|
// wedged upstream cannot pin a goroutine and connection indefinitely.
|
||||||
|
var upstreamClient = &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
Proxy: http.ProxyFromEnvironment,
|
||||||
|
DialContext: (&net.Dialer{
|
||||||
|
Timeout: 10 * time.Second,
|
||||||
|
KeepAlive: 30 * time.Second,
|
||||||
|
}).DialContext,
|
||||||
|
MaxIdleConns: 100,
|
||||||
|
MaxIdleConnsPerHost: 10,
|
||||||
|
IdleConnTimeout: 90 * time.Second,
|
||||||
|
TLSHandshakeTimeout: 10 * time.Second,
|
||||||
|
ExpectContinueTimeout: 1 * time.Second,
|
||||||
|
ResponseHeaderTimeout: 30 * time.Second,
|
||||||
|
},
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user