dotnet / dotnet/runtime

HttpClient does not retry when HTTP_1_1_REQUIRED returned in GOAWAY

Open
#133,317 2 comments 0 reactions 0 assignees View on GitHub
area-System.Net.Http
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.