Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -255,8 +255,10 @@ public async ValueTask SetupAsync(CancellationToken cancellationToken)
throw;
}

// TODO: Review this case!
throw new IOException(SR.net_http_http2_connection_not_established, e);
// Use _abortException if available, as it contains the real reason for the connection failure.
// For example, when ProcessIncomingFramesAsync detects a server-initiated disconnect and calls Abort(),
// _abortException will have the original IOException, while 'e' here may be an uninformative ObjectDisposedException.
throw new IOException(SR.net_http_http2_connection_not_established, _abortException ?? e);
}

// Avoid capturing the initial request's ExecutionContext for the entire lifetime of the new connection.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,48 @@ public async Task GetAsync_ServerSendSettingsWithoutMaxConcurrentStreams_ClientA
}
}

[ConditionalTheory(nameof(SupportsAlpn))]
[InlineData(false)] // server disconnects without sending SETTINGS
[InlineData(true)] // server sends GOAWAY instead of SETTINGS
public async Task ServerDisconnectDuringSetup_PropagatesMeaningfulException(bool sendGoAway)
{
using (Http2LoopbackServer server = Http2LoopbackServer.CreateServer())
using (HttpClient client = CreateHttpClient())
{
Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);

// Accept connection and read client preface, but do NOT send SETTINGS.
Http2LoopbackConnection connection = await server.AcceptConnectionAsync();

if (sendGoAway)
{
// Send GOAWAY instead of SETTINGS, then shut down.
await connection.SendGoAway(0, ProtocolErrors.ENHANCE_YOUR_CALM);
await connection.ShutdownSendAsync();

// The client should throw HttpRequestException(HttpProtocolError) wrapping
// HttpProtocolException with the GOAWAY error code.
await AssertProtocolErrorAsync(sendTask, ProtocolErrors.ENHANCE_YOUR_CALM);
}
else
{
// Immediately shut down the connection without sending SETTINGS.
// This simulates a server-side disconnect during HTTP/2 setup.
await connection.ShutdownSendAsync();

// The client should throw HttpRequestException(InvalidResponse) wrapping
// HttpIOException(InvalidResponse) -> HttpIOException(ResponseEnded),
// indicating the server disconnected before sending SETTINGS.
HttpRequestException ex = await Assert.ThrowsAsync<HttpRequestException>(() => sendTask);
Assert.Equal(HttpRequestError.InvalidResponse, ex.HttpRequestError);
HttpIOException httpIoEx = Assert.IsAssignableFrom<HttpIOException>(ex.InnerException);
Assert.Equal(HttpRequestError.InvalidResponse, httpIoEx.HttpRequestError);
HttpIOException innerHttpIoEx = Assert.IsAssignableFrom<HttpIOException>(httpIoEx.InnerException);
Assert.Equal(HttpRequestError.ResponseEnded, innerHttpIoEx.HttpRequestError);
}
}
}

// This test is based on RFC 7540 section 6.1:
// "If a DATA frame is received whose stream identifier field is 0x0, the recipient MUST
// respond with a connection error (Section 5.4.1) of type PROTOCOL_ERROR."
Expand Down
Loading