diff --git a/src/System.Net.Http/src/Resources/Strings.resx b/src/System.Net.Http/src/Resources/Strings.resx
index 466670154b18..93cdd56ed5d6 100644
--- a/src/System.Net.Http/src/Resources/Strings.resx
+++ b/src/System.Net.Http/src/Resources/Strings.resx
@@ -443,6 +443,9 @@
Error {0} calling {1}, '{2}'.
+
+ The HTTP/2 request failed with protocol error '{0}' (0x{1}).
+
This method is not implemented by this class.
diff --git a/src/System.Net.Http/src/System.Net.Http.csproj b/src/System.Net.Http/src/System.Net.Http.csproj
index 555d6d9e6041..fde227d811b8 100644
--- a/src/System.Net.Http/src/System.Net.Http.csproj
+++ b/src/System.Net.Http/src/System.Net.Http.csproj
@@ -140,6 +140,8 @@
+
+
diff --git a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs
index f1924f6d9d08..029185f952d9 100644
--- a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs
+++ b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs
@@ -162,9 +162,9 @@ private async Task FlushOutgoingBytesAsync()
{
await _stream.WriteAsync(_outgoingBuffer.ActiveMemory).ConfigureAwait(false);
}
- catch (Exception)
+ catch (Exception e)
{
- Abort();
+ Abort(e);
throw;
}
finally
@@ -251,9 +251,9 @@ private async void ProcessIncomingFrames()
}
}
}
- catch (Exception)
+ catch (Exception e)
{
- Abort();
+ Abort(e);
}
}
@@ -604,12 +604,11 @@ private void ProcessRstStreamFrame(FrameHeader frameHeader)
return;
}
- _incomingBuffer.Discard(frameHeader.Length);
+ var protocolError = (Http2ProtocolErrorCode)BinaryPrimitives.ReadInt32BigEndian(_incomingBuffer.ActiveSpan);
- // CONSIDER: We ignore the error code in the RST_STREAM frame.
- // We could read this and report it to the user as part of the request exception.
+ _incomingBuffer.Discard(frameHeader.Length);
- http2Stream.OnResponseAbort();
+ http2Stream.OnResponseAbort(new Http2ProtocolException(protocolError));
RemoveStream(http2Stream);
}
@@ -630,9 +629,10 @@ private void ProcessGoAwayFrame(FrameHeader frameHeader)
throw new Http2ProtocolException(Http2ProtocolErrorCode.ProtocolError);
}
- int lastValidStream = (int)((uint)((_incomingBuffer.ActiveSpan[0] << 24) | (_incomingBuffer.ActiveSpan[1] << 16) | (_incomingBuffer.ActiveSpan[2] << 8) | _incomingBuffer.ActiveSpan[3]) & 0x7FFFFFFF);
+ int lastValidStream = (int)(BinaryPrimitives.ReadUInt32BigEndian(_incomingBuffer.ActiveSpan) & 0x7FFFFFFF);
+ var errorCode = (Http2ProtocolErrorCode)BinaryPrimitives.ReadInt32BigEndian(_incomingBuffer.ActiveSpan.Slice(sizeof(int)));
- AbortStreams(lastValidStream);
+ AbortStreams(lastValidStream, new Http2ProtocolException(errorCode));
_incomingBuffer.Discard(frameHeader.Length);
}
@@ -1113,11 +1113,11 @@ private void WriteFrameHeader(FrameHeader frameHeader)
_outgoingBuffer.Commit(FrameHeader.Size);
}
- private void Abort()
+ private void Abort(Exception abortException)
{
// The connection has failed, e.g. failed IO or a connection-level frame error.
// Abort all streams and cause further processing to fail.
- AbortStreams(0);
+ AbortStreams(0, abortException);
}
private bool IsAborted()
@@ -1160,7 +1160,7 @@ public bool IsExpired(int nowTicks,
return LifetimeExpired(nowTicks, connectionLifetime);
}
- private void AbortStreams(int lastValidStream)
+ private void AbortStreams(int lastValidStream, Exception abortException)
{
lock (SyncObject)
{
@@ -1178,7 +1178,7 @@ private void AbortStreams(int lastValidStream)
if (streamId > lastValidStream)
{
- kvp.Value.OnResponseAbort();
+ kvp.Value.OnResponseAbort(abortException);
_httpStreams.Remove(kvp.Value.StreamId);
}
@@ -1338,8 +1338,6 @@ private enum SettingId : ushort
public sealed override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
- // TODO: ISSUE 31310: Cancellation support
-
Http2Stream http2Stream = null;
try
{
@@ -1354,20 +1352,13 @@ public sealed override async Task SendAsync(HttpRequestMess
}
catch (Exception e)
{
- http2Stream?.Dispose();
+ Exception replacementException = null;
- if (e is IOException)
- {
- throw new HttpRequestException(SR.net_http_client_execution_error, e);
- }
- else if (e is ObjectDisposedException)
- {
- throw new HttpRequestException(SR.net_http_client_execution_error, e);
- }
- else if (e is Http2ProtocolException)
+ if (e is IOException ||
+ e is ObjectDisposedException ||
+ e is Http2ProtocolException)
{
- // ISSUE 31315: Determine if/how to expose HTTP2 error codes
- throw new HttpRequestException(SR.net_http_client_execution_error, e);
+ replacementException = new HttpRequestException(SR.net_http_client_execution_error, e);
}
else if (e is OperationCanceledException oce)
{
@@ -1377,17 +1368,19 @@ public sealed override async Task SendAsync(HttpRequestMess
http2Stream.Cancel();
}
- if (oce.CancellationToken == cancellationToken)
+ if (oce.CancellationToken != cancellationToken)
{
- throw;
+ replacementException = new OperationCanceledException(oce.Message, oce, cancellationToken);
}
-
- throw new OperationCanceledException(cancellationToken);
}
- else
+
+ http2Stream?.Dispose();
+
+ if (replacementException != null)
{
- throw;
+ throw replacementException;
}
+ throw;
}
return http2Stream.Response;
@@ -1401,7 +1394,7 @@ private Http2Stream AddStream(HttpRequestMessage request)
{
// Throw a retryable request exception. This will cause retry logic to kick in
// and perform another connection attempt. The user should never see this exception.
- throw new HttpRequestException(null, null, true);
+ throw new HttpRequestException(null, null, allowRetry: true);
}
int streamId = _nextStream;
@@ -1443,40 +1436,7 @@ private void RemoveStream(Http2Stream http2Stream)
}
}
- // TODO: ISSUE 31315: Should this be public?
- internal enum Http2ProtocolErrorCode
- {
- NoError = 0x0,
- ProtocolError = 0x1,
- InternalError = 0x2,
- FlowControlError = 0x3,
- SettingsTimeout = 0x4,
- StreamClosed = 0x5,
- FrameSizeError = 0x6,
- RefusedStream = 0x7,
- Cancel = 0x8,
- CompressionError = 0x9,
- ConnectError = 0xa,
- EnhanceYourCalm = 0xb,
- InadequateSecurity = 0xc,
- Http11Required = 0xd
- }
-
- // TODO: ISSUE 31315: Should this be public?
- internal class Http2ProtocolException : Exception
- {
- private readonly Http2ProtocolErrorCode _errorCode;
-
- public Http2ProtocolException(Http2ProtocolErrorCode errorCode)
- : base($"Http2 Protocol Error, errorCode = {errorCode}")
- {
- _errorCode = errorCode;
- }
-
- public Http2ProtocolErrorCode ErrorCode => _errorCode;
- }
-
- internal static async ValueTask ReadAtLeastAsync(Stream stream, Memory buffer, int minReadBytes)
+ private static async ValueTask ReadAtLeastAsync(Stream stream, Memory buffer, int minReadBytes)
{
Debug.Assert(buffer.Length >= minReadBytes);
diff --git a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2ProtocolErrorCode.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2ProtocolErrorCode.cs
new file mode 100644
index 000000000000..ffc1e896f7e0
--- /dev/null
+++ b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2ProtocolErrorCode.cs
@@ -0,0 +1,44 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Net.Http
+{
+ // NOTE: If any additional error codes are added here, they should also be added to Http2ProtocolException's mapping.
+
+ ///
+ /// Error codes defined by the HTTP/2 protocol, used in RST_STREAM and GOAWAY frames to convey the reasons for the stream or connection error.
+ /// https://http2.github.io/http2-spec/#PROTOCOL_ERROR
+ ///
+ internal enum Http2ProtocolErrorCode
+ {
+ /// The associated condition is not a result of an error.
+ NoError = 0x0,
+ /// The endpoint detected an unspecific protocol error. This error is for use when a more specific error code is not available.
+ ProtocolError = 0x1,
+ /// The endpoint encountered an unexpected internal error.
+ InternalError = 0x2,
+ /// The endpoint detected that its peer violated the flow-control protocol.
+ FlowControlError = 0x3,
+ /// The endpoint sent a SETTINGS frame but did not receive a response in a timely manner.
+ SettingsTimeout = 0x4,
+ /// The endpoint received a frame after a stream was half-closed.
+ StreamClosed = 0x5,
+ /// The endpoint received a frame with an invalid size.
+ FrameSizeError = 0x6,
+ /// The endpoint refused the stream prior to performing any application processing.
+ RefusedStream = 0x7,
+ /// Used by the endpoint to indicate that the stream is no longer needed.
+ Cancel = 0x8,
+ /// The endpoint is unable to maintain the header compression context for the connection.
+ CompressionError = 0x9,
+ /// The connection established in response to a CONNECT request was reset or abnormally closed.
+ ConnectError = 0xa,
+ /// The endpoint detected that its peer is exhibiting a behavior that might be generating excessive load.
+ EnhanceYourCalm = 0xb,
+ /// The underlying transport has properties that do not meet minimum security requirements.
+ InadequateSecurity = 0xc,
+ /// The endpoint requires that HTTP/1.1 be used instead of HTTP/2.
+ Http11Required = 0xd
+ }
+}
diff --git a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2ProtocolException.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2ProtocolException.cs
new file mode 100644
index 000000000000..f2b135ca8b7e
--- /dev/null
+++ b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2ProtocolException.cs
@@ -0,0 +1,69 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Runtime.Serialization;
+
+namespace System.Net.Http
+{
+ [Serializable]
+ internal sealed class Http2ProtocolException : Exception
+ {
+ public Http2ProtocolException(Http2ProtocolErrorCode protocolError)
+ : base(SR.Format(SR.net_http_http2_protocol_error, GetName(protocolError), ((int)protocolError).ToString("x")))
+ {
+ ProtocolError = protocolError;
+ }
+
+ private Http2ProtocolException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ ProtocolError = (Http2ProtocolErrorCode)info.GetInt32(nameof(ProtocolError));
+ }
+
+ public override void GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ info.AddValue(nameof(ProtocolError), (int)ProtocolError);
+ base.GetObjectData(info, context);
+ }
+
+ internal Http2ProtocolErrorCode ProtocolError { get; }
+
+ private static string GetName(Http2ProtocolErrorCode code)
+ {
+ // These strings are the names used in the HTTP2 spec and should not be localized.
+ switch (code)
+ {
+ case Http2ProtocolErrorCode.NoError:
+ return "NO_ERROR";
+ default: // any unrecognized error code is treated as a protocol error
+ case Http2ProtocolErrorCode.ProtocolError:
+ return "PROTOCOL_ERROR";
+ case Http2ProtocolErrorCode.InternalError:
+ return "INTERNAL_ERROR";
+ case Http2ProtocolErrorCode.FlowControlError:
+ return "FLOW_CONTROL_ERROR";
+ case Http2ProtocolErrorCode.SettingsTimeout:
+ return "SETTINGS_TIMEOUT";
+ case Http2ProtocolErrorCode.StreamClosed:
+ return "STREAM_CLOSED";
+ case Http2ProtocolErrorCode.FrameSizeError:
+ return "FRAME_SIZE_ERROR";
+ case Http2ProtocolErrorCode.RefusedStream:
+ return "REFUSED_STREAM";
+ case Http2ProtocolErrorCode.Cancel:
+ return "CANCEL";
+ case Http2ProtocolErrorCode.CompressionError:
+ return "COMPRESSION_ERROR";
+ case Http2ProtocolErrorCode.ConnectError:
+ return "CONNECT_ERROR";
+ case Http2ProtocolErrorCode.EnhanceYourCalm:
+ return "ENHANCE_YOUR_CALM";
+ case Http2ProtocolErrorCode.InadequateSecurity:
+ return "INADEQUATE_SECURITY";
+ case Http2ProtocolErrorCode.Http11Required:
+ return "HTTP_1_1_REQUIRED";
+ }
+ }
+ }
+}
diff --git a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs
index b322ad7287ae..53f793c515e0 100644
--- a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs
+++ b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs
@@ -43,6 +43,7 @@ private enum StreamState : byte
private StreamState _state;
private bool _disposed;
+ private Exception _abortException;
/// The core logic for the IValueTaskSource implementation.
private ManualResetValueTaskSourceCore _waitSource = new ManualResetValueTaskSourceCore { RunContinuationsAsynchronously = true }; // mutable struct, do not make this readonly
@@ -262,7 +263,7 @@ public void OnResponseData(ReadOnlySpan buffer, bool endStream)
}
}
- public void OnResponseAbort()
+ public void OnResponseAbort(Exception abortException)
{
bool signalWaiter;
lock (SyncObject)
@@ -277,6 +278,7 @@ public void OnResponseAbort()
return;
}
+ _abortException = abortException;
_state = StreamState.Aborted;
signalWaiter = _hasWaiter;
@@ -300,7 +302,7 @@ public void OnResponseAbort()
if (_state == StreamState.Aborted)
{
- throw new IOException(SR.net_http_request_aborted);
+ throw new IOException(SR.net_http_request_aborted, _abortException);
}
else if (_state == StreamState.ExpectingHeaders)
{
@@ -393,7 +395,7 @@ private void ExtendWindow(int amount)
}
else if (_state == StreamState.Aborted)
{
- throw new IOException(SR.net_http_request_aborted);
+ throw new IOException(SR.net_http_request_aborted, _abortException);
}
Debug.Assert(_state == StreamState.ExpectingData || _state == StreamState.ExpectingTrailingHeaders);
@@ -493,6 +495,7 @@ public void Cancel()
lock (SyncObject)
{
Task ignored = _connection.SendRstStreamAsync(_streamId, Http2ProtocolErrorCode.Cancel);
+ _abortException = new OperationCanceledException();
_state = StreamState.Aborted;
signalWaiter = _hasWaiter;
diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs
index d25d7cc7c99a..176b4093e8b6 100644
--- a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs
+++ b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs
@@ -2,7 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System.Collections.Generic;
using System.Diagnostics;
+using System.Linq;
using System.Net.Test.Common;
using System.Threading;
using System.Threading.Tasks;
@@ -21,6 +23,37 @@ public abstract class HttpClientHandlerTest_Http2 : HttpClientHandlerTestBase
public HttpClientHandlerTest_Http2(ITestOutputHelper output) : base(output) { }
+ private async Task AssertProtocolErrorAsync(Task task, ProtocolErrors errorCode)
+ {
+ Exception e = await Assert.ThrowsAsync(() => task);
+ if (UseSocketsHttpHandler)
+ {
+ string text = e.ToString();
+ Assert.Contains(((int)errorCode).ToString("x"), text);
+ Assert.Contains(
+ Enum.IsDefined(typeof(ProtocolErrors), errorCode) ? errorCode.ToString() : ProtocolErrors.PROTOCOL_ERROR.ToString(),
+ text);
+ }
+ }
+
+ public enum ProtocolErrors
+ {
+ NO_ERROR = 0x0,
+ PROTOCOL_ERROR = 0x1,
+ INTERNAL_ERROR = 0x2,
+ FLOW_CONTROL_ERROR = 0x3,
+ SETTINGS_TIMEOUT = 0x4,
+ STREAM_CLOSED = 0x5,
+ FRAME_SIZE_ERROR = 0x6,
+ REFUSED_STREAM = 0x7,
+ CANCEL = 0x8,
+ COMPRESSION_ERROR = 0x9,
+ CONNECT_ERROR = 0xa,
+ ENHANCE_YOUR_CALM = 0xb,
+ INADEQUATE_SECURITY = 0xc,
+ HTTP_1_1_REQUIRED = 0xd
+ }
+
[Fact]
public async Task Http2_ClientPreface_Sent()
{
@@ -81,7 +114,7 @@ public async Task Http2_DataSentBeforeServerPreface_ProtocolError()
DataFrame invalidFrame = new DataFrame(new byte[10], FrameFlags.Padded, 10, 1);
await server.WriteFrameAsync(invalidFrame);
- await Assert.ThrowsAsync(async () => await sendTask);
+ await AssertProtocolErrorAsync(sendTask, ProtocolErrors.PROTOCOL_ERROR);
}
}
@@ -162,10 +195,10 @@ await server.EstablishConnectionAsync(
}
[ConditionalTheory(nameof(SupportsAlpn))]
- [InlineData(SettingId.MaxFrameSize, 16383, true)]
- [InlineData(SettingId.MaxFrameSize, 162777216, true)]
- [InlineData(SettingId.InitialWindowSize, 0x80000000, false)]
- public async Task Http2_ServerSendsInvalidSettingsValue_ProtocolError(SettingId settingId, uint value, bool skipForWinHttp)
+ [InlineData(SettingId.MaxFrameSize, 16383, ProtocolErrors.PROTOCOL_ERROR, true)]
+ [InlineData(SettingId.MaxFrameSize, 162777216, ProtocolErrors.PROTOCOL_ERROR, true)]
+ [InlineData(SettingId.InitialWindowSize, 0x80000000, ProtocolErrors.FLOW_CONTROL_ERROR, false)]
+ public async Task Http2_ServerSendsInvalidSettingsValue_Error(SettingId settingId, uint value, ProtocolErrors expectedError, bool skipForWinHttp)
{
if (IsWinHttpHandler && skipForWinHttp)
{
@@ -181,7 +214,7 @@ public async Task Http2_ServerSendsInvalidSettingsValue_ProtocolError(SettingId
// Send invalid initial SETTINGS value
await server.EstablishConnectionAsync(new SettingsEntry { SettingId = settingId, Value = value });
- await Assert.ThrowsAsync(async () => await sendTask);
+ await AssertProtocolErrorAsync(sendTask, expectedError);
}
}
@@ -204,10 +237,10 @@ public async Task Http2_StreamResetByServerBeforeHeadersSent_RequestFails()
int streamId = await server.ReadRequestHeaderAsync();
// Send a reset stream frame so that the stream moves to a terminal state.
- RstStreamFrame resetStream = new RstStreamFrame(FrameFlags.None, 0x2, streamId);
+ RstStreamFrame resetStream = new RstStreamFrame(FrameFlags.None, (int)ProtocolErrors.INTERNAL_ERROR, streamId);
await server.WriteFrameAsync(resetStream);
- await Assert.ThrowsAsync(async () => await sendTask);
+ await AssertProtocolErrorAsync(sendTask, ProtocolErrors.INTERNAL_ERROR);
}
}
@@ -226,10 +259,10 @@ public async Task Http2_StreamResetByServerAfterHeadersSent_RequestFails()
await server.SendDefaultResponseHeadersAsync(streamId);
// Send a reset stream frame so that the stream moves to a terminal state.
- RstStreamFrame resetStream = new RstStreamFrame(FrameFlags.None, 0x2, streamId);
+ RstStreamFrame resetStream = new RstStreamFrame(FrameFlags.None, (int)ProtocolErrors.INTERNAL_ERROR, streamId);
await server.WriteFrameAsync(resetStream);
- await Assert.ThrowsAsync(async () => await sendTask);
+ await AssertProtocolErrorAsync(sendTask, ProtocolErrors.INTERNAL_ERROR);
}
}
@@ -250,10 +283,10 @@ public async Task Http2_StreamResetByServerAfterPartialBodySent_RequestFails()
await server.WriteFrameAsync(dataFrame);
// Send a reset stream frame so that the stream moves to a terminal state.
- RstStreamFrame resetStream = new RstStreamFrame(FrameFlags.None, 0x2, streamId);
+ RstStreamFrame resetStream = new RstStreamFrame(FrameFlags.None, (int)ProtocolErrors.INTERNAL_ERROR, streamId);
await server.WriteFrameAsync(resetStream);
- await Assert.ThrowsAsync(async () => await sendTask);
+ await AssertProtocolErrorAsync(sendTask, ProtocolErrors.INTERNAL_ERROR);
}
}
@@ -276,7 +309,7 @@ public async Task DataFrame_NoStream_ConnectionError()
await server.WriteFrameAsync(invalidFrame);
// As this is a connection level error, the client should see the request fail.
- await Assert.ThrowsAsync(async () => await sendTask);
+ await AssertProtocolErrorAsync(sendTask, ProtocolErrors.PROTOCOL_ERROR);
// The client should close the connection as this is a fatal connection level error.
Assert.Null(await server.ReadFrameAsync(TimeSpan.FromSeconds(30)));
@@ -308,7 +341,7 @@ public async Task DataFrame_IdleStream_ConnectionError()
await server.WriteFrameAsync(invalidFrame);
// As this is a connection level error, the client should see the request fail.
- await Assert.ThrowsAsync(async () => await sendTask);
+ await AssertProtocolErrorAsync(sendTask, ProtocolErrors.PROTOCOL_ERROR);
// The client should close the connection as this is a fatal connection level error.
Assert.Null(await server.ReadFrameAsync(TimeSpan.FromSeconds(30)));
@@ -339,7 +372,7 @@ public async Task HeadersFrame_IdleStream_ConnectionError()
await server.SendDefaultResponseHeadersAsync(5);
// As this is a connection level error, the client should see the request fail.
- await Assert.ThrowsAsync(async () => await sendTask);
+ await AssertProtocolErrorAsync(sendTask, ProtocolErrors.PROTOCOL_ERROR);
// The client should close the connection as this is a fatal connection level error.
Assert.Null(await server.ReadFrameAsync(TimeSpan.FromSeconds(30)));
@@ -374,7 +407,7 @@ public async Task ResponseStreamFrames_ContinuationBeforeHeaders_ConnectionError
await server.WriteFrameAsync(MakeSimpleContinuationFrame(streamId));
// As this is a connection level error, the client should see the request fail.
- await Assert.ThrowsAsync(async () => await sendTask);
+ await AssertProtocolErrorAsync(sendTask, ProtocolErrors.PROTOCOL_ERROR);
// The client should close the connection as this is a fatal connection level error.
Assert.Null(await server.ReadFrameAsync(TimeSpan.FromSeconds(30)));
@@ -394,7 +427,7 @@ public async Task ResponseStreamFrames_DataBeforeHeaders_ConnectionError()
await server.WriteFrameAsync(MakeSimpleDataFrame(streamId));
// As this is a connection level error, the client should see the request fail.
- await Assert.ThrowsAsync(async () => await sendTask);
+ await AssertProtocolErrorAsync(sendTask, ProtocolErrors.PROTOCOL_ERROR);
// The client should close the connection as this is a fatal connection level error.
Assert.Null(await server.ReadFrameAsync(TimeSpan.FromSeconds(30)));
@@ -415,7 +448,7 @@ public async Task ResponseStreamFrames_HeadersAfterHeadersWithoutEndHeaders_Conn
await server.WriteFrameAsync(MakeSimpleHeadersFrame(streamId, endHeaders: false));
// As this is a connection level error, the client should see the request fail.
- await Assert.ThrowsAsync(async () => await sendTask);
+ await AssertProtocolErrorAsync(sendTask, ProtocolErrors.PROTOCOL_ERROR);
// The client should close the connection as this is a fatal connection level error.
Assert.Null(await server.ReadFrameAsync(TimeSpan.FromSeconds(30)));
@@ -437,50 +470,7 @@ public async Task ResponseStreamFrames_HeadersAfterHeadersAndContinuationWithout
await server.WriteFrameAsync(MakeSimpleHeadersFrame(streamId, endHeaders: false));
// As this is a connection level error, the client should see the request fail.
- await Assert.ThrowsAsync(async () => await sendTask);
-
- // The client should close the connection as this is a fatal connection level error.
- Assert.Null(await server.ReadFrameAsync(TimeSpan.FromSeconds(30)));
- }
- }
-
- [ConditionalFact(nameof(SupportsAlpn))]
- public async Task ResponseStreamFrames_HeadersAfterHeadersWithEndHeaders_ConnectionError()
- {
- using (var server = Http2LoopbackServer.CreateServer())
- using (var client = CreateHttpClient())
- {
- Task sendTask = client.GetAsync(server.Address);
- await server.EstablishConnectionAsync();
- int streamId = await server.ReadRequestHeaderAsync();
-
- await server.WriteFrameAsync(MakeSimpleHeadersFrame(streamId, endHeaders: true));
- await server.WriteFrameAsync(MakeSimpleHeadersFrame(streamId, endHeaders: false));
-
- // As this is a connection level error, the client should see the request fail.
- await Assert.ThrowsAsync(async () => await sendTask);
-
- // The client should close the connection as this is a fatal connection level error.
- Assert.Null(await server.ReadFrameAsync(TimeSpan.FromSeconds(30)));
- }
- }
-
- [ConditionalFact(nameof(SupportsAlpn))]
- public async Task ResponseStreamFrames_HeadersAfterHeadersAndContinuationWithEndHeaders_ConnectionError()
- {
- using (var server = Http2LoopbackServer.CreateServer())
- using (var client = CreateHttpClient())
- {
- Task sendTask = client.GetAsync(server.Address);
- await server.EstablishConnectionAsync();
- int streamId = await server.ReadRequestHeaderAsync();
-
- await server.WriteFrameAsync(MakeSimpleHeadersFrame(streamId, endHeaders: false));
- await server.WriteFrameAsync(MakeSimpleContinuationFrame(streamId, endHeaders: true));
- await server.WriteFrameAsync(MakeSimpleHeadersFrame(streamId, endHeaders: false));
-
- // As this is a connection level error, the client should see the request fail.
- await Assert.ThrowsAsync(async () => await sendTask);
+ await AssertProtocolErrorAsync(sendTask, ProtocolErrors.PROTOCOL_ERROR);
// The client should close the connection as this is a fatal connection level error.
Assert.Null(await server.ReadFrameAsync(TimeSpan.FromSeconds(30)));
@@ -501,7 +491,7 @@ public async Task ResponseStreamFrames_DataAfterHeadersWithoutEndHeaders_Connect
await server.WriteFrameAsync(MakeSimpleDataFrame(streamId));
// As this is a connection level error, the client should see the request fail.
- await Assert.ThrowsAsync(async () => await sendTask);
+ await AssertProtocolErrorAsync(sendTask, ProtocolErrors.PROTOCOL_ERROR);
// The client should close the connection as this is a fatal connection level error.
Assert.Null(await server.ReadFrameAsync(TimeSpan.FromSeconds(30)));
@@ -523,7 +513,7 @@ public async Task ResponseStreamFrames_DataAfterHeadersAndContinuationWithoutEnd
await server.WriteFrameAsync(MakeSimpleDataFrame(streamId));
// As this is a connection level error, the client should see the request fail.
- await Assert.ThrowsAsync(async () => await sendTask);
+ await AssertProtocolErrorAsync(sendTask, ProtocolErrors.PROTOCOL_ERROR);
// The client should close the connection as this is a fatal connection level error.
Assert.Null(await server.ReadFrameAsync(TimeSpan.FromSeconds(30)));
@@ -545,11 +535,11 @@ public async Task GoAwayFrame_NonzeroStream_ConnectionError()
await server.ReadRequestHeaderAsync();
// Send a GoAway frame on stream 1.
- GoAwayFrame invalidFrame = new GoAwayFrame(0, 0, new byte[0], 1);
+ GoAwayFrame invalidFrame = new GoAwayFrame(0, (int)ProtocolErrors.ENHANCE_YOUR_CALM, new byte[0], 1);
await server.WriteFrameAsync(invalidFrame);
// As this is a connection level error, the client should see the request fail.
- await Assert.ThrowsAsync(async () => await sendTask);
+ await AssertProtocolErrorAsync(sendTask, ProtocolErrors.PROTOCOL_ERROR);
// The client should close the connection as this is a fatal connection level error.
Assert.Null(await server.ReadFrameAsync(TimeSpan.FromSeconds(30)));
@@ -572,7 +562,7 @@ public async Task DataFrame_TooLong_ConnectionError()
await server.WriteFrameAsync(invalidFrame);
// As this is a connection level error, the client should see the request fail.
- await Assert.ThrowsAsync(async () => await sendTask);
+ await AssertProtocolErrorAsync(sendTask, ProtocolErrors.FRAME_SIZE_ERROR);
}
}
@@ -663,8 +653,15 @@ public async Task CompletedResponse_WindowUpdateFrameReceived_Success()
}
}
- [ConditionalFact(nameof(SupportsAlpn))]
- public async Task ResetResponseStream_FrameReceived_ConnectionError()
+ public static IEnumerable