HttpClient does not retry when HTTP_1_1_REQUIRED returned in GOAWAY
- Dominant language
- C#
- Stars
- 18.3k
- Forks
- 5.6k
- PR merge metrics
- PR metrics pending
Description
### Description
Given a request is sent using HTTP/2 and a server responds with GOAWAY error code _HTTP_1_1_REQUIRED_, the `HttpClient` appears to retry using HTTP/2 rather than attempting to downgrade to HTTP/1.1
RFC9113 indicates that _HTTP_1_1_REQUIRED_ can be returned per endpoint.
> The endpoint requires that HTTP/1.1 be used instead of HTTP/2.
An example is a reverse proxy that accepts HTTP/2 connections and is configured to forward requests to multiple servers. One server only supports HTTP/1.1, so a policy is configured on the proxy to return _HTTP_1_1_REQUIRED_ back to the client for HTTP/2 requests sent to those specific endpoints.
### Reproduction Steps
```
var handler = new SocketsHttpHandler
{
ConnectCallback = async (context, cancellationToken) =>
{
var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
await socket.ConnectAsync(IPAddress.Loopback, port, cancellationToken);
return new NetworkStream(socket, ownsSocket: true);
},
SslOptions = new SslClientAuthenticationOptions
{
RemoteCertificateValidationCallback = (_, _, _, _) => true
}
};
using var httpClient = new HttpClient(handler)
{
BaseAddress = new Uri("https://example.com"),
DefaultRequestVersion = HttpVersion.Version20,
DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower
};
await httpClient.GetAsync("/");
```
### Expected behavior
`HTTPClient` retries the request using HTTP/1.1 and caches the HTTP version.
### Actual behavior
Retries using HTTP/2 observed when debugging until the following exception gets thrown.
> Exception thrown: 'System.Net.Http.HttpRequestException' in System.Private.CoreLib.dll: 'An error occurred while sending the request.'
Inner exceptions found, see $exception in variables window for more details.
Innermost exception System.ObjectDisposedException : Cannot access a disposed object.
Object name: 'System.Net.Security.SslStream'.
at System.ThrowHelper.ThrowObjectDisposedException(Object instance)
at System.Net.Security.SslStream.g__ThrowExceptional|131_0(ExceptionDispatchInfo e)
at System.Net.Security.SslStream.WriteAsync(ReadOnlyMemory`1 buffer, CancellationToken cancellationToken)
at System.Net.Http.Http2Connection.d__54.MoveNext()
### Regression?
No
### Known Workarounds
_No response_
### Configuration
.NET 10
### Other information
This was discussed on this issue.
https://github.com/dotnet/runtime/issues/63715
However I think the conclusion not to retry with HTTP/1.1 should be revisited. It should not be assumed that the server is misconfigured if it is returning a specific error code.
Contributor guide
Research direction
Start with the SocketsHttpHandler reproduction and trace the HTTP/2 path through Http2Connection. Read the linked issue 63715 for prior discussion, then locate the existing handling for HTTP_1_1_REQUIRED and related version-selection tests. Done means the reproduced request retries with HTTP/1.1 and caches that version without the reported disposed-object exception.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- networking
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100