diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs index 40202ff846d4a..8b2c37122e56a 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs @@ -72,6 +72,8 @@ internal sealed partial class Http2Connection : HttpConnectionBase // _shutdown above is true, and requests in flight have been (or are being) failed. private Exception? _abortException; + private Http2ProtocolErrorCode? _goAwayErrorCode; + private const int MaxStreamId = int.MaxValue; // Temporary workaround for request burst handling on connection start. @@ -410,7 +412,11 @@ private async ValueTask ReadFrameAsync(bool initialFrame = false) _incomingBuffer.Commit(bytesRead); if (bytesRead == 0) { - if (_incomingBuffer.ActiveLength == 0) + if (_goAwayErrorCode is not null) + { + ThrowProtocolError(_goAwayErrorCode.Value, SR.net_http_http2_connection_close); + } + else if (_incomingBuffer.ActiveLength == 0) { ThrowMissingFrame(); } @@ -1070,6 +1076,7 @@ private void ProcessGoAwayFrame(FrameHeader frameHeader) Debug.Assert(lastStreamId >= 0); Exception resetException = HttpProtocolException.CreateHttp2ConnectionException(errorCode, SR.net_http_http2_connection_close); + _goAwayErrorCode = errorCode; // There is no point sending more PING frames for RTT estimation: _rttEstimator.OnGoAwayReceived(); diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs index 256a04e62da26..1a24fe7e0832a 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs @@ -1051,6 +1051,36 @@ public async Task GoAwayFrame_RequestWithBody_ServerDisconnect_AbortStreamsAndTh } } + [ConditionalFact(nameof(SupportsAlpn))] + public async Task GoAwayFrame_RequestServerDisconnects_ThrowsHttpProtocolExceptionWithProperErrorCode() + { + await Http2LoopbackServer.CreateClientAndServerAsync(async uri => + { + // Client starts an HTTP/2 request and awaits response headers + using HttpClient client = CreateHttpClient(); + HttpResponseMessage response = await client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead); + + // Client reads from response stream + using Stream responseStream = await response.Content.ReadAsStreamAsync(); + Memory buffer = new byte[1024]; + HttpProtocolException exception = await Assert.ThrowsAsync(() => responseStream.ReadAsync(buffer).AsTask()); + Assert.Equal(ProtocolErrors.ENHANCE_YOUR_CALM, (ProtocolErrors) exception.ErrorCode); + Assert.Contains("The HTTP/2 server closed the connection.", exception.Message); + + }, + async server => + { + // Server returns response headers + await using Http2LoopbackConnection connection = await server.EstablishConnectionAsync(); + int streamId = await connection.ReadRequestHeaderAsync(); + await connection.SendDefaultResponseHeadersAsync(streamId); + + // Server sends GOAWAY frame + await connection.SendGoAway(streamId, ProtocolErrors.ENHANCE_YOUR_CALM); + connection.ShutdownSend(); + }); + } + [ConditionalFact(nameof(SupportsAlpn))] public async Task GoAwayFrame_UnprocessedStreamFirstRequestFinishedFirst_RequestRestarted() {