Disable HTTP2 for S3 backend with ?disable_http2=true option

This commit is contained in:
binwiederhier 2026-03-27 13:59:07 -04:00
parent 92fa88cf12
commit 67fc7fe96a
6 changed files with 63 additions and 32 deletions

View file

@ -62,17 +62,10 @@ type Client struct {
func New(config *Config) *Client {
httpClient := config.HTTPClient
if httpClient == nil {
// Force HTTP/1.1 to avoid HTTP/2 stream errors with S3-compatible providers
// (e.g. DigitalOcean Spaces). HTTP/2 can cause non-retryable failures on
// streaming uploads when the server resets the stream mid-transfer.
httpClient = &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
MinVersion: tls.VersionTLS12,
},
ForceAttemptHTTP2: false,
TLSNextProto: make(map[string]func(string, *tls.Conn) http.RoundTripper),
},
if config.DisableHTTP2 {
httpClient = newHTTP1Client()
} else {
httpClient = http.DefaultClient
}
}
return &Client{
@ -312,3 +305,20 @@ func (c *Client) do(ctx context.Context, op, method, reqURL string, body []byte,
}
return respBody, nil
}
// newHTTP1Client creates an HTTP client that forces HTTP/1.1 by disabling HTTP/2
// ALPN negotiation. This works around HTTP/2 stream errors with some S3-compatible
// providers (e.g. DigitalOcean Spaces) that can cause non-retryable failures on
// streaming uploads when the server resets the stream mid-transfer.
// See https://github.com/rclone/rclone/issues/4673, https://github.com/golang/go/issues/42777
func newHTTP1Client() *http.Client {
return &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
MinVersion: tls.VersionTLS12,
},
ForceAttemptHTTP2: false,
TLSNextProto: make(map[string]func(string, *tls.Conn) http.RoundTripper),
},
}
}