diff --git a/src/libraries/Common/src/System/Net/Http/aspnetcore/Http3/Frames/Http3ErrorCode.cs b/src/libraries/Common/src/System/Net/Http/aspnetcore/Http3/Frames/Http3ErrorCode.cs
index fdfe04cee1580b..ebaa96f12cdc46 100644
--- a/src/libraries/Common/src/System/Net/Http/aspnetcore/Http3/Frames/Http3ErrorCode.cs
+++ b/src/libraries/Common/src/System/Net/Http/aspnetcore/Http3/Frames/Http3ErrorCode.cs
@@ -91,5 +91,20 @@ internal enum Http3ErrorCode : long
/// The requested operation cannot be served over HTTP/3. The peer should retry over HTTP/1.1.
///
VersionFallback = 0x110,
+ ///
+ /// H3_QPACK_DECOMPRESSION_FAILED (0x200):
+ /// The decoder failed to interpret an encoded field section and is not able to continue decoding that field section.
+ ///
+ QPackDecompressionFailed = 0x200,
+ ///
+ /// H3_QPACK_ENCODER_STREAM_ERROR (0x201):
+ /// The decoder failed to interpret an encoder instruction received on the encoder stream.
+ ///
+ QPackEncoderStreamError = 0x201,
+ ///
+ /// H3_QPACK_DECODER_STREAM_ERROR (0x202):
+ /// The encoder failed to interpret an decoder instruction received on the decoder stream.
+ ///
+ QPackDecoderStreamError = 0x202,
}
}
diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3RequestStream.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3RequestStream.cs
index 64ba4089d5dbca..ef2532b2b22d08 100644
--- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3RequestStream.cs
+++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3RequestStream.cs
@@ -277,8 +277,12 @@ await Task.WhenAny(sendRequestTask, readResponseTask).ConfigureAwait(false) == s
}
catch (QuicException ex) when (ex.QuicError == QuicError.OperationAborted && _connection.AbortException != null)
{
- // we close the connection, propagate the AbortException
- throw new HttpRequestException(HttpRequestError.Unknown, SR.net_http_client_execution_error, _connection.AbortException);
+ // we closed the connection already, propagate the AbortException
+ HttpRequestError httpRequestError = _connection.AbortException is HttpProtocolException
+ ? HttpRequestError.HttpProtocolError
+ : HttpRequestError.Unknown;
+
+ throw new HttpRequestException(httpRequestError, SR.net_http_client_execution_error, _connection.AbortException);
}
// It is possible for user's Content code to throw an unexpected OperationCanceledException.
catch (OperationCanceledException ex) when (ex.CancellationToken == _requestBodyCancellationSource.Token || ex.CancellationToken == cancellationToken)
@@ -300,6 +304,16 @@ await Task.WhenAny(sendRequestTask, readResponseTask).ConfigureAwait(false) == s
_connection.Abort(ex);
throw new HttpRequestException(ex.HttpRequestError, SR.net_http_client_execution_error, ex);
}
+ catch (QPackDecodingException ex)
+ {
+ Exception abortException = _connection.Abort(HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.QPackDecompressionFailed));
+ throw new HttpRequestException(HttpRequestError.InvalidResponse, SR.net_http_invalid_response, ex);
+ }
+ catch (QPackEncodingException ex)
+ {
+ _stream.Abort(QuicAbortDirection.Write, (long)Http3ErrorCode.InternalError);
+ throw new HttpRequestException(HttpRequestError.Unknown, SR.net_http_client_execution_error, ex);
+ }
catch (Exception ex)
{
_stream.Abort(QuicAbortDirection.Write, (long)Http3ErrorCode.InternalError);
@@ -307,6 +321,9 @@ await Task.WhenAny(sendRequestTask, readResponseTask).ConfigureAwait(false) == s
{
throw;
}
+
+ // all exceptions should be already handled above
+ Debug.Fail($"Unexpected exception type in Http3RequestStream.SendAsync: {ex}");
throw new HttpRequestException(HttpRequestError.Unknown, SR.net_http_client_execution_error, ex);
}
finally
@@ -1253,6 +1270,13 @@ private void HandleReadResponseContentException(Exception ex, CancellationToken
_connection.Abort(exception);
throw exception;
+ case QuicException e when (e.QuicError == QuicError.OperationAborted && _connection.AbortException != null):
+ // we closed the connection already, propagate the AbortException
+ HttpRequestError httpRequestError = _connection.AbortException is HttpProtocolException
+ ? HttpRequestError.HttpProtocolError
+ : HttpRequestError.Unknown;
+ throw new HttpRequestException(httpRequestError, SR.net_http_client_execution_error, _connection.AbortException);
+
case HttpIOException:
_connection.Abort(ex);
ExceptionDispatchInfo.Throw(ex); // Rethrow.
diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http3.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http3.cs
index 16abc260386b42..5baccdd3785543 100644
--- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http3.cs
+++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http3.cs
@@ -35,7 +35,10 @@ private async Task AssertProtocolErrorAsync(long errorCode, Func task)
{
Exception outerEx = await Assert.ThrowsAnyAsync(task);
_output.WriteLine(outerEx.ToString());
- Assert.IsType(outerEx);
+
+ HttpRequestException httpReqException = Assert.IsType(outerEx);
+ Assert.Equal(HttpRequestError.HttpProtocolError, httpReqException.HttpRequestError);
+
HttpProtocolException protocolEx = Assert.IsType(outerEx.InnerException);
Assert.Equal(errorCode, protocolEx.ErrorCode);
}
@@ -1759,7 +1762,14 @@ public async Task ServerClosesOutboundControlStream_ClientClosesConnection(Close
return;
}
await connection.OutboundControlStream.DisposeAsync();
- await connection.EstablishControlStreamAsync(Array.Empty());
+ try
+ {
+ await connection.EstablishControlStreamAsync(Array.Empty());
+ }
+ catch (QuicException ex) when (ex.QuicError == QuicError.ConnectionAborted && ex.ApplicationErrorCode == Http3LoopbackConnection.H3_CLOSED_CRITICAL_STREAM)
+ {
+ // Data race, connection closed between WaitAsync and EstablishControlStreamAsync. Ignore this.
+ }
await Task.Delay(100);
}
}