From 5b5797f51b39246a5a9e9f9e20eaa31efabab51d Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 26 Apr 2024 07:33:19 +0800 Subject: [PATCH 01/29] Add protocol code to HTTP/2 and HTTP/3 connection metric --- src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs index 890cf2aed347..13058087717f 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs @@ -87,6 +87,7 @@ private static int GetMaximumEnhanceYourCalmCount() private readonly HttpConnectionContext _context; private readonly ConnectionMetricsContext _metricsContext; + private readonly IProtocolErrorCodeFeature _errorCodeFeature; private readonly Http2FrameWriter _frameWriter; private readonly Pipe _input; private readonly Task _inputTask; @@ -139,6 +140,7 @@ public Http2Connection(HttpConnectionContext context) _context = context; _streamLifetimeHandler = this; _metricsContext = context.ConnectionFeatures.GetRequiredFeature().MetricsContext; + _errorCodeFeature = context.ConnectionFeatures.GetRequiredFeature(); // Capture the ExecutionContext before dispatching HTTP/2 middleware. Will be restored by streams when processing request _context.InitialExecutionContext = ExecutionContext.Capture(); From c967c6a5f8acf029c0aec2b87de986b8017b92ee Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 7 May 2024 08:43:02 +0800 Subject: [PATCH 02/29] Add reason to connection exception --- .../Http2/FlowControl/InputFlowControl.cs | 2 +- .../src/Internal/Http2/Http2Connection.cs | 164 +++++++++++------- .../src/Internal/Http2/Http2FrameWriter.cs | 2 +- .../Core/src/Internal/HttpConnection.cs | 6 + .../Internal/Infrastructure/KestrelMetrics.cs | 15 +- ...soft.AspNetCore.Server.Kestrel.Core.csproj | 1 + .../Http2/Http2TestBase.cs | 7 + src/Shared/Metrics/MetricsExtensions.cs | 13 ++ .../Http2/ConnectionErrorReason.cs | 30 ++++ .../Http2/Http2ConnectionErrorException.cs | 4 +- .../Http2/Http2FrameReader.cs | 4 +- 11 files changed, 181 insertions(+), 67 deletions(-) create mode 100644 src/Shared/ServerInfrastructure/Http2/ConnectionErrorReason.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/FlowControl/InputFlowControl.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/FlowControl/InputFlowControl.cs index 91d40f12f406..ed4cac95a677 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/FlowControl/InputFlowControl.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/FlowControl/InputFlowControl.cs @@ -41,7 +41,7 @@ public bool TryAdvance(int bytes) // flow-control window at the time of the abort. if (bytes > _flow.Available) { - throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorFlowControlWindowExceeded, Http2ErrorCode.FLOW_CONTROL_ERROR); + throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorFlowControlWindowExceeded, Http2ErrorCode.FLOW_CONTROL_ERROR, ConnectionErrorReason.FlowControlWindowExceeded); } if (_flow.IsAborted) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs index 13058087717f..3ac2140721bc 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs @@ -18,6 +18,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Http; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; @@ -88,6 +89,7 @@ private static int GetMaximumEnhanceYourCalmCount() private readonly HttpConnectionContext _context; private readonly ConnectionMetricsContext _metricsContext; private readonly IProtocolErrorCodeFeature _errorCodeFeature; + private readonly IConnectionMetricsTagsFeature? _tagsFeature; private readonly Http2FrameWriter _frameWriter; private readonly Pipe _input; private readonly Task _inputTask; @@ -141,6 +143,7 @@ public Http2Connection(HttpConnectionContext context) _streamLifetimeHandler = this; _metricsContext = context.ConnectionFeatures.GetRequiredFeature().MetricsContext; _errorCodeFeature = context.ConnectionFeatures.GetRequiredFeature(); + _tagsFeature = context.ConnectionFeatures.Get(); // Capture the ExecutionContext before dispatching HTTP/2 middleware. Will be restored by streams when processing request _context.InitialExecutionContext = ExecutionContext.Capture(); @@ -211,6 +214,15 @@ public void OnInputOrOutputCompleted() _frameWriter.Abort(useException ? new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient) : null!); } + private void SetError(ConnectionErrorReason reason, Http2ErrorCode errorCode) + { + _errorCodeFeature.Error = (long)errorCode; + if (_tagsFeature != null) + { + _tagsFeature.TryAddTag("ConnectionError", reason.ToString()); + } + } + public void Abort(ConnectionAbortedException ex) { if (TryClose()) @@ -252,6 +264,7 @@ public void StopProcessingNextRequest(bool serverInitiated) { Exception? error = null; var errorCode = Http2ErrorCode.NO_ERROR; + var errorReason = ConnectionErrorReason.Other; try { @@ -337,7 +350,7 @@ public void StopProcessingNextRequest(bool serverInitiated) { // There isn't a good error code to return with the GOAWAY. // NO_ERROR isn't a good choice because it indicates the connection is gracefully shutting down. - throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorKeepAliveTimeout, Http2ErrorCode.INTERNAL_ERROR); + throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorKeepAliveTimeout, Http2ErrorCode.INTERNAL_ERROR, ConnectionErrorReason.KeepAliveTimeout); } } } @@ -374,6 +387,7 @@ public void StopProcessingNextRequest(bool serverInitiated) Log.Http2ConnectionError(ConnectionId, ex); error = ex; errorCode = ex.ErrorCode; + errorReason = ex.ErrorReason; } catch (HPackDecodingException ex) { @@ -382,6 +396,7 @@ public void StopProcessingNextRequest(bool serverInitiated) Log.HPackDecodingError(ConnectionId, _currentHeadersStream.StreamId, ex); error = ex; errorCode = Http2ErrorCode.COMPRESSION_ERROR; + errorReason = ConnectionErrorReason.ErrorReadingHeaders; } catch (Exception ex) { @@ -398,6 +413,7 @@ public void StopProcessingNextRequest(bool serverInitiated) { if (TryClose()) { + SetError(errorReason, errorCode); await _frameWriter.WriteGoAwayAsync(_highestOpenedStreamId, errorCode); } @@ -462,7 +478,7 @@ private void ValidateTlsRequirements() if (tlsFeature.Protocol < SslProtocols.Tls12) { - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorMinTlsVersion(tlsFeature.Protocol), Http2ErrorCode.INADEQUATE_SECURITY); + throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorMinTlsVersion(tlsFeature.Protocol), Http2ErrorCode.INADEQUATE_SECURITY, ConnectionErrorReason.InsufficientTlsVersion); } } @@ -559,7 +575,7 @@ private async Task TryReadPrefaceAsync() // Tested all states. Return HTTP/2 protocol error. if (state == ReadPrefaceState.None) { - throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorInvalidPreface, Http2ErrorCode.PROTOCOL_ERROR); + throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorInvalidPreface, Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.InvalidHandshake); } } @@ -631,7 +647,7 @@ private static bool IsPreface(in ReadOnlySequence buffer, out SequencePosi // a connection error (Section 5.4.1) of type PROTOCOL_ERROR. if (_incomingFrame.StreamId != 0 && (_incomingFrame.StreamId & 1) == 0) { - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdEven(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.PROTOCOL_ERROR); + throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdEven(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.InvalidStreamId); } return _incomingFrame.Type switch @@ -641,7 +657,7 @@ private static bool IsPreface(in ReadOnlySequence buffer, out SequencePosi Http2FrameType.PRIORITY => ProcessPriorityFrameAsync(), Http2FrameType.RST_STREAM => ProcessRstStreamFrameAsync(), Http2FrameType.SETTINGS => ProcessSettingsFrameAsync(payload), - Http2FrameType.PUSH_PROMISE => throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorPushPromiseReceived, Http2ErrorCode.PROTOCOL_ERROR), + Http2FrameType.PUSH_PROMISE => throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorPushPromiseReceived, Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.ReceivedUnsupportedFrame), Http2FrameType.PING => ProcessPingFrameAsync(payload), Http2FrameType.GOAWAY => ProcessGoAwayFrameAsync(), Http2FrameType.WINDOW_UPDATE => ProcessWindowUpdateFrameAsync(), @@ -654,17 +670,17 @@ private Task ProcessDataFrameAsync(in ReadOnlySequence payload) { if (_currentHeadersStream != null) { - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorHeadersInterleaved(_incomingFrame.Type, _incomingFrame.StreamId, _currentHeadersStream.StreamId), Http2ErrorCode.PROTOCOL_ERROR); + throw CreateHeadersInterleavedException(); } if (_incomingFrame.StreamId == 0) { - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR); + throw CreateStreamIdZeroException(); } if (_incomingFrame.DataHasPadding && _incomingFrame.DataPadLength >= _incomingFrame.PayloadLength) { - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorPaddingTooLong(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR); + throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorPaddingTooLong(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.InvalidDataPadding); } ThrowIfIncomingFrameSentToIdleStream(); @@ -674,7 +690,7 @@ private Task ProcessDataFrameAsync(in ReadOnlySequence payload) if (stream.RstStreamReceived) { // Hard abort, do not allow any more frames on this stream. - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamAborted(_incomingFrame.Type, stream.StreamId), Http2ErrorCode.STREAM_CLOSED); + throw CreateReceivedFrameStreamAbortedException(stream); } if (stream.EndStreamReceived) @@ -686,7 +702,7 @@ private Task ProcessDataFrameAsync(in ReadOnlySequence payload) // of type STREAM_CLOSED, unless the frame is permitted as described below. // // (The allowed frame types for this situation are WINDOW_UPDATE, RST_STREAM and PRIORITY) - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(_incomingFrame.Type, stream.StreamId), Http2ErrorCode.STREAM_CLOSED); + throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(_incomingFrame.Type, stream.StreamId), Http2ErrorCode.STREAM_CLOSED, ConnectionErrorReason.ReceivedFrameAfterStreamClose); } return stream.OnDataAsync(_incomingFrame, payload); @@ -703,29 +719,55 @@ private Task ProcessDataFrameAsync(in ReadOnlySequence payload) // // We choose to do that here so we don't have to keep state to track implicitly closed // streams vs. streams closed with END_STREAM or RST_STREAM. - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamClosed(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.STREAM_CLOSED); + throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamClosed(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.STREAM_CLOSED, ConnectionErrorReason.ReceivedFrameUnknownStream); + } + + private Http2ConnectionErrorException CreateReceivedFrameStreamAbortedException(Http2Stream stream) + { + return new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamAborted(_incomingFrame.Type, stream.StreamId), Http2ErrorCode.STREAM_CLOSED, ConnectionErrorReason.ReceivedFrameAfterStreamClose); + } + + private Http2ConnectionErrorException CreateStreamIdZeroException() + { + return new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.InvalidStreamId); + } + + private Http2ConnectionErrorException CreateStreamIdNotZeroException() + { + return new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdNotZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.InvalidStreamId); + } + + private Http2ConnectionErrorException CreateHeadersInterleavedException() + { + Debug.Assert(_currentHeadersStream != null, "Only throw this error if parsing headers."); + return new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorHeadersInterleaved(_incomingFrame.Type, _incomingFrame.StreamId, _currentHeadersStream.StreamId), Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.InvalidFrameForState); + } + + private Http2ConnectionErrorException CreateUnexpectedFrameLengthException(int exceptedLength) + { + return new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(_incomingFrame.Type, exceptedLength), Http2ErrorCode.FRAME_SIZE_ERROR, ConnectionErrorReason.InvalidFrameLength); } private Task ProcessHeadersFrameAsync(IHttpApplication application, in ReadOnlySequence payload) where TContext : notnull { if (_currentHeadersStream != null) { - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorHeadersInterleaved(_incomingFrame.Type, _incomingFrame.StreamId, _currentHeadersStream.StreamId), Http2ErrorCode.PROTOCOL_ERROR); + throw CreateHeadersInterleavedException(); } if (_incomingFrame.StreamId == 0) { - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR); + throw CreateStreamIdZeroException(); } if (_incomingFrame.HeadersHasPadding && _incomingFrame.HeadersPadLength >= _incomingFrame.PayloadLength - 1) { - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorPaddingTooLong(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR); + throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorPaddingTooLong(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.InvalidDataPadding); } if (_incomingFrame.HeadersHasPriority && _incomingFrame.HeadersStreamDependency == _incomingFrame.StreamId) { - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamSelfDependency(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.PROTOCOL_ERROR); + throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamSelfDependency(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.StreamSelfDependency); } if (_streams.TryGetValue(_incomingFrame.StreamId, out var stream)) @@ -733,7 +775,7 @@ private Task ProcessDataFrameAsync(in ReadOnlySequence payload) if (stream.RstStreamReceived) { // Hard abort, do not allow any more frames on this stream. - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamAborted(_incomingFrame.Type, stream.StreamId), Http2ErrorCode.STREAM_CLOSED); + throw CreateReceivedFrameStreamAbortedException(stream); } // http://httpwg.org/specs/rfc7540.html#rfc.section.5.1 @@ -745,13 +787,13 @@ private Task ProcessDataFrameAsync(in ReadOnlySequence payload) // (The allowed frame types after END_STREAM are WINDOW_UPDATE, RST_STREAM and PRIORITY) if (stream.EndStreamReceived) { - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(_incomingFrame.Type, stream.StreamId), Http2ErrorCode.STREAM_CLOSED); + throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(_incomingFrame.Type, stream.StreamId), Http2ErrorCode.STREAM_CLOSED, ConnectionErrorReason.ReceivedFrameAfterStreamClose); } // This is the last chance for the client to send END_STREAM if (!_incomingFrame.HeadersEndStream) { - throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorHeadersWithTrailersNoEndStream, Http2ErrorCode.PROTOCOL_ERROR); + throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorHeadersWithTrailersNoEndStream, Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.MissingStreamEnd); } // Since we found an active stream, this HEADERS frame contains trailers @@ -770,7 +812,7 @@ private Task ProcessDataFrameAsync(in ReadOnlySequence payload) // // If we couldn't find the stream, it was previously closed (either implicitly or with // END_STREAM or RST_STREAM). - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamClosed(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.STREAM_CLOSED); + throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamClosed(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.STREAM_CLOSED, ConnectionErrorReason.InvalidStreamId); } else { @@ -838,22 +880,22 @@ private Task ProcessPriorityFrameAsync() { if (_currentHeadersStream != null) { - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorHeadersInterleaved(_incomingFrame.Type, _incomingFrame.StreamId, _currentHeadersStream.StreamId), Http2ErrorCode.PROTOCOL_ERROR); + throw CreateHeadersInterleavedException(); } if (_incomingFrame.StreamId == 0) { - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR); + throw CreateStreamIdZeroException(); } if (_incomingFrame.PriorityStreamDependency == _incomingFrame.StreamId) { - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamSelfDependency(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.PROTOCOL_ERROR); + throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamSelfDependency(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.StreamSelfDependency); } if (_incomingFrame.PayloadLength != 5) { - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(_incomingFrame.Type, 5), Http2ErrorCode.FRAME_SIZE_ERROR); + throw CreateUnexpectedFrameLengthException(exceptedLength: 5); } return Task.CompletedTask; @@ -863,17 +905,17 @@ private Task ProcessRstStreamFrameAsync() { if (_currentHeadersStream != null) { - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorHeadersInterleaved(_incomingFrame.Type, _incomingFrame.StreamId, _currentHeadersStream.StreamId), Http2ErrorCode.PROTOCOL_ERROR); + throw CreateHeadersInterleavedException(); } if (_incomingFrame.StreamId == 0) { - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR); + throw CreateStreamIdZeroException(); } if (_incomingFrame.PayloadLength != 4) { - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(_incomingFrame.Type, 4), Http2ErrorCode.FRAME_SIZE_ERROR); + throw CreateUnexpectedFrameLengthException(exceptedLength: 4); } ThrowIfIncomingFrameSentToIdleStream(); @@ -903,19 +945,19 @@ private Task ProcessSettingsFrameAsync(in ReadOnlySequence payload) { if (_currentHeadersStream != null) { - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorHeadersInterleaved(_incomingFrame.Type, _incomingFrame.StreamId, _currentHeadersStream.StreamId), Http2ErrorCode.PROTOCOL_ERROR); + throw CreateHeadersInterleavedException(); } if (_incomingFrame.StreamId != 0) { - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdNotZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR); + throw CreateStreamIdNotZeroException(); } if (_incomingFrame.SettingsAck) { if (_incomingFrame.PayloadLength != 0) { - throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorSettingsAckLengthNotZero, Http2ErrorCode.FRAME_SIZE_ERROR); + throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorSettingsAckLengthNotZero, Http2ErrorCode.FRAME_SIZE_ERROR, ConnectionErrorReason.InvalidFrameLength); } return Task.CompletedTask; @@ -923,7 +965,7 @@ private Task ProcessSettingsFrameAsync(in ReadOnlySequence payload) if (_incomingFrame.PayloadLength % 6 != 0) { - throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorSettingsLengthNotMultipleOfSix, Http2ErrorCode.FRAME_SIZE_ERROR); + throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorSettingsLengthNotMultipleOfSix, Http2ErrorCode.FRAME_SIZE_ERROR, ConnectionErrorReason.InvalidFrameLength); } try @@ -957,7 +999,7 @@ private Task ProcessSettingsFrameAsync(in ReadOnlySequence payload) // This means that this caused a stream window to become larger than int.MaxValue. // This can never happen with a well behaved client and MUST be treated as a connection error. // https://httpwg.org/specs/rfc7540.html#rfc.section.6.9.2 - throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorInitialWindowSizeInvalid, Http2ErrorCode.FLOW_CONTROL_ERROR); + throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorInitialWindowSizeInvalid, Http2ErrorCode.FLOW_CONTROL_ERROR, ConnectionErrorReason.InvalidSettings); } } } @@ -975,9 +1017,11 @@ private Task ProcessSettingsFrameAsync(in ReadOnlySequence payload) } catch (Http2SettingsParameterOutOfRangeException ex) { - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorSettingsParameterOutOfRange(ex.Parameter), ex.Parameter == Http2SettingsParameter.SETTINGS_INITIAL_WINDOW_SIZE + var errorCode = ex.Parameter == Http2SettingsParameter.SETTINGS_INITIAL_WINDOW_SIZE ? Http2ErrorCode.FLOW_CONTROL_ERROR - : Http2ErrorCode.PROTOCOL_ERROR); + : Http2ErrorCode.PROTOCOL_ERROR; + + throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorSettingsParameterOutOfRange(ex.Parameter), errorCode, ConnectionErrorReason.InvalidSettings); } } @@ -985,17 +1029,17 @@ private Task ProcessPingFrameAsync(in ReadOnlySequence payload) { if (_currentHeadersStream != null) { - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorHeadersInterleaved(_incomingFrame.Type, _incomingFrame.StreamId, _currentHeadersStream.StreamId), Http2ErrorCode.PROTOCOL_ERROR); + throw CreateHeadersInterleavedException(); } if (_incomingFrame.StreamId != 0) { - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdNotZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR); + throw CreateStreamIdNotZeroException(); } if (_incomingFrame.PayloadLength != 8) { - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(_incomingFrame.Type, 8), Http2ErrorCode.FRAME_SIZE_ERROR); + throw CreateUnexpectedFrameLengthException(exceptedLength: 8); } // Incoming ping resets connection keep alive timeout @@ -1017,12 +1061,12 @@ private Task ProcessGoAwayFrameAsync() { if (_currentHeadersStream != null) { - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorHeadersInterleaved(_incomingFrame.Type, _incomingFrame.StreamId, _currentHeadersStream.StreamId), Http2ErrorCode.PROTOCOL_ERROR); + throw CreateHeadersInterleavedException(); } if (_incomingFrame.StreamId != 0) { - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdNotZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR); + throw CreateStreamIdNotZeroException(); } // StopProcessingNextRequest must be called before RequestClose to ensure it's considered client initiated. @@ -1036,12 +1080,12 @@ private Task ProcessWindowUpdateFrameAsync() { if (_currentHeadersStream != null) { - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorHeadersInterleaved(_incomingFrame.Type, _incomingFrame.StreamId, _currentHeadersStream.StreamId), Http2ErrorCode.PROTOCOL_ERROR); + throw CreateHeadersInterleavedException(); } if (_incomingFrame.PayloadLength != 4) { - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(_incomingFrame.Type, 4), Http2ErrorCode.FRAME_SIZE_ERROR); + throw CreateUnexpectedFrameLengthException(exceptedLength: 4); } ThrowIfIncomingFrameSentToIdleStream(); @@ -1063,14 +1107,14 @@ private Task ProcessWindowUpdateFrameAsync() // Since server initiated stream resets are not yet properly // implemented and tested, we treat all zero length window // increments as connection errors for now. - throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorWindowUpdateIncrementZero, Http2ErrorCode.PROTOCOL_ERROR); + throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorWindowUpdateIncrementZero, Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.WindowUpdateSizeInvalid); } if (_incomingFrame.StreamId == 0) { if (!_frameWriter.TryUpdateConnectionWindow(_incomingFrame.WindowUpdateSizeIncrement)) { - throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorWindowUpdateSizeInvalid, Http2ErrorCode.FLOW_CONTROL_ERROR); + throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorWindowUpdateSizeInvalid, Http2ErrorCode.FLOW_CONTROL_ERROR, ConnectionErrorReason.WindowUpdateSizeInvalid); } } else if (_streams.TryGetValue(_incomingFrame.StreamId, out var stream)) @@ -1078,7 +1122,7 @@ private Task ProcessWindowUpdateFrameAsync() if (stream.RstStreamReceived) { // Hard abort, do not allow any more frames on this stream. - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamAborted(_incomingFrame.Type, stream.StreamId), Http2ErrorCode.STREAM_CLOSED); + throw CreateReceivedFrameStreamAbortedException(stream); } if (!stream.TryUpdateOutputWindow(_incomingFrame.WindowUpdateSizeIncrement)) @@ -1100,12 +1144,12 @@ private Task ProcessContinuationFrameAsync(in ReadOnlySequence payload) { if (_currentHeadersStream == null) { - throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorContinuationWithNoHeaders, Http2ErrorCode.PROTOCOL_ERROR); + throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorContinuationWithNoHeaders, Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.InvalidFrameForState); } if (_incomingFrame.StreamId != _currentHeadersStream.StreamId) { - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorHeadersInterleaved(_incomingFrame.Type, _incomingFrame.StreamId, _currentHeadersStream.StreamId), Http2ErrorCode.PROTOCOL_ERROR); + throw CreateHeadersInterleavedException(); } if (_requestHeaderParsingState == RequestHeaderParsingState.Trailers) @@ -1129,7 +1173,7 @@ private Task ProcessUnknownFrameAsync() { if (_currentHeadersStream != null) { - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorHeadersInterleaved(_incomingFrame.Type, _incomingFrame.StreamId, _currentHeadersStream.StreamId), Http2ErrorCode.PROTOCOL_ERROR); + throw CreateHeadersInterleavedException(); } return Task.CompletedTask; @@ -1240,7 +1284,7 @@ private void StartStream() // This will close the socket - we want to do that right away Abort(new ConnectionAbortedException(CoreStrings.Http2ConnectionFaulted)); // Throwing an exception as well will help us clean up on our end more quickly by (e.g.) skipping processing of already-buffered input - throw new Http2ConnectionErrorException(CoreStrings.Http2ConnectionFaulted, Http2ErrorCode.ENHANCE_YOUR_CALM); + throw new Http2ConnectionErrorException(CoreStrings.Http2ConnectionFaulted, Http2ErrorCode.ENHANCE_YOUR_CALM, ConnectionErrorReason.StreamResetLimitExceeded); } throw new Http2StreamErrorException(_currentHeadersStream.StreamId, CoreStrings.Http2TellClientToCalmDown, Http2ErrorCode.ENHANCE_YOUR_CALM); @@ -1299,7 +1343,7 @@ private void ThrowIfIncomingFrameSentToIdleStream() // initial state for all streams. if (_incomingFrame.StreamId > _highestOpenedStreamId) { - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdle(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.PROTOCOL_ERROR); + throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdle(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.InvalidStreamId); } } @@ -1355,7 +1399,7 @@ private void UpdateCompletedStreams() if (stream == _currentHeadersStream) { // The drain expired out while receiving trailers. The most recent incoming frame is either a header or continuation frame for the timed out stream. - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamClosed(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.STREAM_CLOSED); + throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamClosed(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.STREAM_CLOSED, ConnectionErrorReason.ReceivedFrameAfterStreamClose); } RemoveStream(stream); @@ -1498,7 +1542,7 @@ private void OnHeaderCore(HeaderType headerType, int? staticTableIndex, ReadOnly // Allow a 2x grace before aborting the connection. We'll check the size limit again later where we can send a 431. if (_totalParsedHeaderSize > _context.ServiceContext.ServerOptions.Limits.MaxRequestHeadersTotalSize * 2) { - throw new Http2ConnectionErrorException(CoreStrings.BadRequest_HeadersExceedMaxTotalSize, Http2ErrorCode.PROTOCOL_ERROR); + throw new Http2ConnectionErrorException(CoreStrings.BadRequest_HeadersExceedMaxTotalSize, Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.InvalidRequestHeaders); } try @@ -1559,11 +1603,11 @@ private void OnHeaderCore(HeaderType headerType, int? staticTableIndex, ReadOnly } catch (Microsoft.AspNetCore.Http.BadHttpRequestException bre) { - throw new Http2ConnectionErrorException(bre.Message, Http2ErrorCode.PROTOCOL_ERROR); + throw new Http2ConnectionErrorException(bre.Message, Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.InvalidRequestHeaders); } catch (InvalidOperationException) { - throw new Http2ConnectionErrorException(CoreStrings.BadRequest_MalformedRequestInvalidHeaders, Http2ErrorCode.PROTOCOL_ERROR); + throw new Http2ConnectionErrorException(CoreStrings.BadRequest_MalformedRequestInvalidHeaders, Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.InvalidRequestHeaders); } } @@ -1574,7 +1618,7 @@ private void ValidateHeaderContent(ReadOnlySpan name, ReadOnlySpan v { if (IsConnectionSpecificHeaderField(name, value)) { - throw new Http2ConnectionErrorException(CoreStrings.HttpErrorConnectionSpecificHeaderField, Http2ErrorCode.PROTOCOL_ERROR); + throw new Http2ConnectionErrorException(CoreStrings.HttpErrorConnectionSpecificHeaderField, Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.InvalidRequestHeaders); } // http://httpwg.org/specs/rfc7540.html#rfc.section.8.1.2 @@ -1585,11 +1629,11 @@ private void ValidateHeaderContent(ReadOnlySpan name, ReadOnlySpan v { if (_requestHeaderParsingState == RequestHeaderParsingState.Trailers) { - throw new Http2ConnectionErrorException(CoreStrings.HttpErrorTrailerNameUppercase, Http2ErrorCode.PROTOCOL_ERROR); + throw new Http2ConnectionErrorException(CoreStrings.HttpErrorTrailerNameUppercase, Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.InvalidRequestHeaders); } else { - throw new Http2ConnectionErrorException(CoreStrings.HttpErrorHeaderNameUppercase, Http2ErrorCode.PROTOCOL_ERROR); + throw new Http2ConnectionErrorException(CoreStrings.HttpErrorHeaderNameUppercase, Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.InvalidRequestHeaders); } } } @@ -1618,13 +1662,13 @@ private void UpdateHeaderParsingState(ReadOnlySpan value, PseudoHeaderFiel // All pseudo-header fields MUST appear in the header block before regular header fields. // Any request or response that contains a pseudo-header field that appears in a header // block after a regular header field MUST be treated as malformed (Section 8.1.2.6). - throw new Http2ConnectionErrorException(CoreStrings.HttpErrorPseudoHeaderFieldAfterRegularHeaders, Http2ErrorCode.PROTOCOL_ERROR); + throw new Http2ConnectionErrorException(CoreStrings.HttpErrorPseudoHeaderFieldAfterRegularHeaders, Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.InvalidRequestHeaders); } if (_requestHeaderParsingState == RequestHeaderParsingState.Trailers) { // Pseudo-header fields MUST NOT appear in trailers. - throw new Http2ConnectionErrorException(CoreStrings.HttpErrorTrailersContainPseudoHeaderField, Http2ErrorCode.PROTOCOL_ERROR); + throw new Http2ConnectionErrorException(CoreStrings.HttpErrorTrailersContainPseudoHeaderField, Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.InvalidRequestHeaders); } _requestHeaderParsingState = RequestHeaderParsingState.PseudoHeaderFields; @@ -1633,21 +1677,21 @@ private void UpdateHeaderParsingState(ReadOnlySpan value, PseudoHeaderFiel { // Endpoints MUST treat a request or response that contains undefined or invalid pseudo-header // fields as malformed (Section 8.1.2.6). - throw new Http2ConnectionErrorException(CoreStrings.HttpErrorUnknownPseudoHeaderField, Http2ErrorCode.PROTOCOL_ERROR); + throw new Http2ConnectionErrorException(CoreStrings.HttpErrorUnknownPseudoHeaderField, Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.InvalidRequestHeaders); } if (headerField == PseudoHeaderFields.Status) { // Pseudo-header fields defined for requests MUST NOT appear in responses; pseudo-header fields // defined for responses MUST NOT appear in requests. - throw new Http2ConnectionErrorException(CoreStrings.HttpErrorResponsePseudoHeaderField, Http2ErrorCode.PROTOCOL_ERROR); + throw new Http2ConnectionErrorException(CoreStrings.HttpErrorResponsePseudoHeaderField, Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.InvalidRequestHeaders); } if ((_parsedPseudoHeaderFields & headerField) == headerField) { // http://httpwg.org/specs/rfc7540.html#rfc.section.8.1.2.3 // All HTTP/2 requests MUST include exactly one valid value for the :method, :scheme, and :path pseudo-header fields - throw new Http2ConnectionErrorException(CoreStrings.HttpErrorDuplicatePseudoHeaderField, Http2ErrorCode.PROTOCOL_ERROR); + throw new Http2ConnectionErrorException(CoreStrings.HttpErrorDuplicatePseudoHeaderField, Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.InvalidRequestHeaders); } if (headerField == PseudoHeaderFields.Method) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs index b799ef02797c..cf60af0e8d7a 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs @@ -323,7 +323,7 @@ static bool HasStateFlag(Http2OutputProducer.State state, Http2OutputProducer.St private async Task HandleFlowControlErrorAsync() { - var connectionError = new Http2ConnectionErrorException(CoreStrings.Http2ErrorWindowUpdateSizeInvalid, Http2ErrorCode.FLOW_CONTROL_ERROR); + var connectionError = new Http2ConnectionErrorException(CoreStrings.Http2ErrorWindowUpdateSizeInvalid, Http2ErrorCode.FLOW_CONTROL_ERROR, ConnectionErrorReason.WindowUpdateSizeInvalid); _log.Http2ConnectionError(_connectionId, connectionError); await WriteGoAwayAsync(int.MaxValue, Http2ErrorCode.FLOW_CONTROL_ERROR); diff --git a/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs b/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs index feab55224929..63d57547f596 100644 --- a/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs @@ -64,6 +64,7 @@ public HttpConnection(BaseHttpConnectionContext context) // _http2Connection must be initialized before yielding control to the transport thread, // to prevent a race condition where _http2Connection.Abort() is called just as // _http2Connection is about to be initialized. + _context.ConnectionFeatures.Set(new ProtocolErrorCodeFeature()); requestProcessor = new Http2Connection((HttpConnectionContext)_context); _protocolSelectionState = ProtocolSelectionState.Selected; AddMetricsHttpProtocolTag(KestrelMetrics.Http2); @@ -114,6 +115,11 @@ public HttpConnection(BaseHttpConnectionContext context) } } + private sealed class ProtocolErrorCodeFeature : IProtocolErrorCodeFeature + { + public long Error { get; set; } + } + private void AddMetricsHttpProtocolTag(string httpVersion) { if (_context.ConnectionContext.Features.Get() is { } metricsTags) diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs index 10768d162a21..2e803eeac208 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs @@ -9,6 +9,7 @@ using System.Runtime.CompilerServices; using System.Security.Authentication; using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Http; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; @@ -99,12 +100,17 @@ public void ConnectionStop(in ConnectionMetricsContext metricsContext, Exception { if (metricsContext.CurrentConnectionsCounterEnabled || metricsContext.ConnectionDurationEnabled) { - ConnectionStopCore(metricsContext, exception, customTags, startTimestamp, currentTimestamp); + long? errorCode = null; + if (metricsContext.ConnectionContext.Features.Get() is IProtocolErrorCodeFeature errorCodeFeature && errorCodeFeature.Error != -1) + { + errorCode = errorCodeFeature.Error; + } + ConnectionStopCore(metricsContext, exception, errorCode, customTags, startTimestamp, currentTimestamp); } } [MethodImpl(MethodImplOptions.NoInlining)] - private void ConnectionStopCore(in ConnectionMetricsContext metricsContext, Exception? exception, List>? customTags, long startTimestamp, long currentTimestamp) + private void ConnectionStopCore(in ConnectionMetricsContext metricsContext, Exception? exception, long? protocolErrorCode, List>? customTags, long startTimestamp, long currentTimestamp) { var tags = new TagList(); InitializeConnectionTags(ref tags, metricsContext); @@ -117,6 +123,11 @@ private void ConnectionStopCore(in ConnectionMetricsContext metricsContext, Exce if (metricsContext.ConnectionDurationEnabled) { + if (protocolErrorCode != null) + { + tags.Add("http.connection.protocol_code", protocolErrorCode); + } + if (exception != null) { tags.Add("error.type", exception.GetType().FullName); diff --git a/src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj b/src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj index 1576c0f5a653..6c0bf1237af4 100644 --- a/src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj +++ b/src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj @@ -34,6 +34,7 @@ + diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs index c334ee588dab..ff3c15731544 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs @@ -26,6 +26,7 @@ using Microsoft.Net.Http.Headers; using Moq; using Xunit.Abstractions; +using Microsoft.AspNetCore.Connections.Features; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests; @@ -459,6 +460,7 @@ protected void CreateConnection() var features = new FeatureCollection(); features.Set(new TestConnectionMetricsContextFeature()); + features.Set(new TestProtocolErrorCodeFeature()); _mockConnectionContext.Setup(x => x.Features).Returns(features); var httpConnectionContext = TestContextFactory.CreateHttpConnectionContext( serviceContext: _serviceContext, @@ -484,6 +486,11 @@ private class TestConnectionMetricsContextFeature : IConnectionMetricsContextFea public ConnectionMetricsContext MetricsContext { get; } } + private class TestProtocolErrorCodeFeature : IProtocolErrorCodeFeature + { + public long Error { get; set; } + } + private class LifetimeHandlerInterceptor : IHttp2StreamLifetimeHandler { private readonly IHttp2StreamLifetimeHandler _inner; diff --git a/src/Shared/Metrics/MetricsExtensions.cs b/src/Shared/Metrics/MetricsExtensions.cs index 307bb9517601..456de672a49d 100644 --- a/src/Shared/Metrics/MetricsExtensions.cs +++ b/src/Shared/Metrics/MetricsExtensions.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Http.Features; namespace Microsoft.AspNetCore.Http; @@ -12,6 +13,18 @@ public static bool TryAddTag(this IHttpMetricsTagsFeature feature, string name, { var tags = feature.Tags; + return TryAddTagCore(name, value, tags); + } + + public static bool TryAddTag(this IConnectionMetricsTagsFeature feature, string name, object? value) + { + var tags = feature.Tags; + + return TryAddTagCore(name, value, tags); + } + + private static bool TryAddTagCore(string name, object? value, ICollection> tags) + { // Tags is internally represented as a List. // Prefer looping through the list to avoid allocating an enumerator. if (tags is List> list) diff --git a/src/Shared/ServerInfrastructure/Http2/ConnectionErrorReason.cs b/src/Shared/ServerInfrastructure/Http2/ConnectionErrorReason.cs new file mode 100644 index 000000000000..ca177751f60a --- /dev/null +++ b/src/Shared/ServerInfrastructure/Http2/ConnectionErrorReason.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.AspNetCore.Server.Kestrel.Core; + +internal enum ConnectionErrorReason +{ + ClientAbort, + ServerAbort, + FlowControlWindowExceeded, + KeepAliveTimeout, + InsufficientTlsVersion, + InvalidHandshake, + InvalidStreamId, + ReceivedFrameAfterStreamClose, + ReceivedFrameUnknownStream, + ReceivedUnsupportedFrame, + InvalidFrameForState, + InvalidFrameLength, + InvalidDataPadding, + InvalidRequestHeaders, + StreamResetLimitExceeded, + WindowUpdateSizeInvalid, + StreamSelfDependency, + InvalidSettings, + MissingStreamEnd, + MaxFrameLengthExceeded, + ErrorReadingHeaders, + Other +} diff --git a/src/Shared/ServerInfrastructure/Http2/Http2ConnectionErrorException.cs b/src/Shared/ServerInfrastructure/Http2/Http2ConnectionErrorException.cs index 19fc06d8d24e..5e9ed3127fb8 100644 --- a/src/Shared/ServerInfrastructure/Http2/Http2ConnectionErrorException.cs +++ b/src/Shared/ServerInfrastructure/Http2/Http2ConnectionErrorException.cs @@ -7,11 +7,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; internal sealed class Http2ConnectionErrorException : Exception { - public Http2ConnectionErrorException(string message, Http2ErrorCode errorCode) + public Http2ConnectionErrorException(string message, Http2ErrorCode errorCode, ConnectionErrorReason errorReason) : base($"HTTP/2 connection error ({errorCode}): {message}") { ErrorCode = errorCode; + ErrorReason = errorReason; } public Http2ErrorCode ErrorCode { get; } + public ConnectionErrorReason ErrorReason { get; } } diff --git a/src/Shared/ServerInfrastructure/Http2/Http2FrameReader.cs b/src/Shared/ServerInfrastructure/Http2/Http2FrameReader.cs index f9e9756c95fc..53a379a6411e 100644 --- a/src/Shared/ServerInfrastructure/Http2/Http2FrameReader.cs +++ b/src/Shared/ServerInfrastructure/Http2/Http2FrameReader.cs @@ -45,7 +45,7 @@ public static bool TryReadFrame(ref ReadOnlySequence buffer, Http2Frame fr var payloadLength = (int)Bitshifter.ReadUInt24BigEndian(header); if (payloadLength > maxFrameSize) { - throw new Http2ConnectionErrorException(SharedStrings.FormatHttp2ErrorFrameOverLimit(payloadLength, maxFrameSize), Http2ErrorCode.FRAME_SIZE_ERROR); + throw new Http2ConnectionErrorException(SharedStrings.FormatHttp2ErrorFrameOverLimit(payloadLength, maxFrameSize), Http2ErrorCode.FRAME_SIZE_ERROR, ConnectionErrorReason.MaxFrameLengthExceeded); } // Make sure the whole frame is buffered @@ -77,7 +77,7 @@ private static int ReadExtendedFields(Http2Frame frame, in ReadOnlySequence frame.PayloadLength) { throw new Http2ConnectionErrorException( - SharedStrings.FormatHttp2ErrorUnexpectedFrameLength(frame.Type, expectedLength: extendedHeaderLength), Http2ErrorCode.FRAME_SIZE_ERROR); + SharedStrings.FormatHttp2ErrorUnexpectedFrameLength(frame.Type, expectedLength: extendedHeaderLength), Http2ErrorCode.FRAME_SIZE_ERROR, ConnectionErrorReason.InvalidFrameLength); } var extendedHeaders = readableBuffer.Slice(HeaderLength, extendedHeaderLength).ToSpan(); From f345d6017faccbd725b44a1c4f2a8590667b1c09 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 7 May 2024 10:54:55 +0800 Subject: [PATCH 03/29] More --- .../src/Internal/Http2/Http2Connection.cs | 33 ++++++++++++++----- .../src/Internal/Http2/Http2FrameWriter.cs | 10 +++--- .../Core/src/Internal/HttpConnection.cs | 2 +- .../Http2/Http2ConnectionTests.cs | 2 +- .../Http2/Http2TestBase.cs | 2 +- .../Http2/ConnectionErrorReason.cs | 9 ++++- 6 files changed, 40 insertions(+), 18 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs index 3ac2140721bc..b6c70e452f0f 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs @@ -209,25 +209,36 @@ public Http2Connection(HttpConnectionContext context) public void OnInputOrOutputCompleted() { - TryClose(); + if (TryClose()) + { + SetConnectionErrorCode(ConnectionErrorReason.InputOrOutputCompleted, Http2ErrorCode.PROTOCOL_ERROR); + } var useException = _context.ServiceContext.ServerOptions.FinOnError || _clientActiveStreamCount != 0; _frameWriter.Abort(useException ? new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient) : null!); } - private void SetError(ConnectionErrorReason reason, Http2ErrorCode errorCode) + private void SetConnectionErrorCode(ConnectionErrorReason? reason, Http2ErrorCode errorCode) { + Debug.Assert(_isClosed == 1, "Should only be set when connection is closed."); + Debug.Assert(_errorCodeFeature.Error == -1, "Error code feature should only be set once."); + _errorCodeFeature.Error = (long)errorCode; - if (_tagsFeature != null) + if (reason != null && _tagsFeature != null) { _tagsFeature.TryAddTag("ConnectionError", reason.ToString()); } } public void Abort(ConnectionAbortedException ex) + { + Abort(ex, Http2ErrorCode.INTERNAL_ERROR, ConnectionErrorReason.ServerAbort); + } + + public void Abort(ConnectionAbortedException ex, Http2ErrorCode errorCode, ConnectionErrorReason reason) { if (TryClose()) { - _frameWriter.WriteGoAwayAsync(int.MaxValue, Http2ErrorCode.INTERNAL_ERROR).Preserve(); + _frameWriter.WriteGoAwayAsync(int.MaxValue, errorCode).Preserve(); } _frameWriter.Abort(ex); @@ -239,7 +250,7 @@ public void StopProcessingNextRequest() public void HandleRequestHeadersTimeout() { Log.ConnectionBadRequest(ConnectionId, KestrelBadHttpRequestException.GetException(RequestRejectionReason.RequestHeadersTimeout)); - Abort(new ConnectionAbortedException(CoreStrings.BadRequest_RequestHeadersTimeout)); + Abort(new ConnectionAbortedException(CoreStrings.BadRequest_RequestHeadersTimeout), Http2ErrorCode.INTERNAL_ERROR, ConnectionErrorReason.RequestHeadersTimeout); } public void HandleReadDataRateTimeout() @@ -247,7 +258,7 @@ public void HandleReadDataRateTimeout() Debug.Assert(Limits.MinRequestBodyDataRate != null); Log.RequestBodyMinimumDataRateNotSatisfied(ConnectionId, null, Limits.MinRequestBodyDataRate.BytesPerSecond); - Abort(new ConnectionAbortedException(CoreStrings.BadRequest_RequestBodyTimeout)); + Abort(new ConnectionAbortedException(CoreStrings.BadRequest_RequestBodyTimeout), Http2ErrorCode.INTERNAL_ERROR, ConnectionErrorReason.RequestBodyTimeout); } public void StopProcessingNextRequest(bool serverInitiated) @@ -413,7 +424,7 @@ public void StopProcessingNextRequest(bool serverInitiated) { if (TryClose()) { - SetError(errorReason, errorCode); + SetConnectionErrorCode(errorReason, errorCode); await _frameWriter.WriteGoAwayAsync(_highestOpenedStreamId, errorCode); } @@ -561,7 +572,10 @@ private async Task TryReadPrefaceAsync() await _context.Transport.Output.WriteAsync(responseBytes); // Close connection here so a GOAWAY frame isn't written. - TryClose(); + if (TryClose()) + { + SetConnectionErrorCode(ConnectionErrorReason.InvalidHttpVersion, Http2ErrorCode.PROTOCOL_ERROR); + } return false; } @@ -1282,7 +1296,7 @@ private void StartStream() // messages in case they somehow make it back to the client (not expected) // This will close the socket - we want to do that right away - Abort(new ConnectionAbortedException(CoreStrings.Http2ConnectionFaulted)); + Abort(new ConnectionAbortedException(CoreStrings.Http2ConnectionFaulted), Http2ErrorCode.ENHANCE_YOUR_CALM, ConnectionErrorReason.StreamResetLimitExceeded); // Throwing an exception as well will help us clean up on our end more quickly by (e.g.) skipping processing of already-buffered input throw new Http2ConnectionErrorException(CoreStrings.Http2ConnectionFaulted, Http2ErrorCode.ENHANCE_YOUR_CALM, ConnectionErrorReason.StreamResetLimitExceeded); } @@ -1477,6 +1491,7 @@ private void UpdateConnectionState() { if (TryClose()) { + SetConnectionErrorCode(reason: null, Http2ErrorCode.NO_ERROR); _frameWriter.WriteGoAwayAsync(_highestOpenedStreamId, Http2ErrorCode.NO_ERROR).Preserve(); } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs index cf60af0e8d7a..eb31eea89baa 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs @@ -154,7 +154,7 @@ public void Schedule(Http2OutputProducer producer) // exceeding the channel size. Disconnecting seems appropriate in this case. var ex = new ConnectionAbortedException("HTTP/2 connection exceeded the output operations maximum queue size."); _log.Http2QueueOperationsExceeded(_connectionId, ex); - _http2Connection.Abort(ex); + _http2Connection.Abort(ex, Http2ErrorCode.INTERNAL_ERROR, ConnectionErrorReason.OutputQueueSizeExceeded); } } @@ -330,7 +330,7 @@ private async Task HandleFlowControlErrorAsync() // Prevent Abort() from writing an INTERNAL_ERROR GOAWAY frame after our FLOW_CONTROL_ERROR. Complete(); // Stop processing any more requests and immediately close the connection. - _http2Connection.Abort(new ConnectionAbortedException(CoreStrings.Http2ErrorWindowUpdateSizeInvalid, connectionError)); + _http2Connection.Abort(new ConnectionAbortedException(CoreStrings.Http2ErrorWindowUpdateSizeInvalid, connectionError), Http2ErrorCode.FLOW_CONTROL_ERROR, ConnectionErrorReason.WindowUpdateSizeInvalid); } private bool TryQueueProducerForConnectionWindowUpdate(long actual, Http2OutputProducer producer) @@ -527,7 +527,7 @@ private void WriteResponseHeadersUnsynchronized(int streamId, int statusCode, Ht catch (Exception ex) { _log.HPackEncodingError(_connectionId, streamId, ex); - _http2Connection.Abort(new ConnectionAbortedException(ex.Message, ex)); + _http2Connection.Abort(new ConnectionAbortedException(ex.Message, ex), Http2ErrorCode.INTERNAL_ERROR, ConnectionErrorReason.ErrorWritingHeaders); } } @@ -568,7 +568,7 @@ private ValueTask WriteDataAndTrailersAsync(Http2Stream stream, in catch (Exception ex) { _log.HPackEncodingError(_connectionId, streamId, ex); - _http2Connection.Abort(new ConnectionAbortedException(ex.Message, ex)); + _http2Connection.Abort(new ConnectionAbortedException(ex.Message, ex), Http2ErrorCode.INTERNAL_ERROR, ConnectionErrorReason.ErrorWritingHeaders); } return TimeFlushUnsynchronizedAsync(); @@ -1099,7 +1099,7 @@ private void EnqueueWaitingForMoreConnectionWindow(Http2OutputProducer producer) if (!_aborted && IsFlowControlQueueLimitEnabled && _waitingForMoreConnectionWindow.Count > _maximumFlowControlQueueSize) { _log.Http2FlowControlQueueOperationsExceeded(_connectionId, _maximumFlowControlQueueSize); - _http2Connection.Abort(new ConnectionAbortedException("HTTP/2 connection exceeded the outgoing flow control maximum queue size.")); + _http2Connection.Abort(new ConnectionAbortedException("HTTP/2 connection exceeded the outgoing flow control maximum queue size."), Http2ErrorCode.INTERNAL_ERROR, ConnectionErrorReason.FlowControlQueueSizeExceeded); } } } diff --git a/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs b/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs index 63d57547f596..b7239eb608ef 100644 --- a/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs @@ -117,7 +117,7 @@ public HttpConnection(BaseHttpConnectionContext context) private sealed class ProtocolErrorCodeFeature : IProtocolErrorCodeFeature { - public long Error { get; set; } + public long Error { get; set; } = -1; } private void AddMetricsHttpProtocolTag(string httpVersion) diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs index 0a437a4a2c1d..0ae5776c3a49 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs @@ -1727,7 +1727,7 @@ public async Task AbortConnectionAfterTooManyEnhanceYourCalms() await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: true, expectedLastStreamId: int.MaxValue, - expectedErrorCode: Http2ErrorCode.INTERNAL_ERROR, + expectedErrorCode: Http2ErrorCode.ENHANCE_YOUR_CALM, expectedErrorMessage: CoreStrings.Http2ConnectionFaulted); } diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs index ff3c15731544..1908ba436429 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs @@ -488,7 +488,7 @@ private class TestConnectionMetricsContextFeature : IConnectionMetricsContextFea private class TestProtocolErrorCodeFeature : IProtocolErrorCodeFeature { - public long Error { get; set; } + public long Error { get; set; } = -1; } private class LifetimeHandlerInterceptor : IHttp2StreamLifetimeHandler diff --git a/src/Shared/ServerInfrastructure/Http2/ConnectionErrorReason.cs b/src/Shared/ServerInfrastructure/Http2/ConnectionErrorReason.cs index ca177751f60a..e7a62a696e8c 100644 --- a/src/Shared/ServerInfrastructure/Http2/ConnectionErrorReason.cs +++ b/src/Shared/ServerInfrastructure/Http2/ConnectionErrorReason.cs @@ -26,5 +26,12 @@ internal enum ConnectionErrorReason MissingStreamEnd, MaxFrameLengthExceeded, ErrorReadingHeaders, - Other + ErrorWritingHeaders, + Other, + InputOrOutputCompleted, + InvalidHttpVersion, + RequestHeadersTimeout, + RequestBodyTimeout, + FlowControlQueueSizeExceeded, + OutputQueueSizeExceeded } From 4cbc03a7be6fc0736816aaa7db337337b32b5dc3 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 7 May 2024 10:56:58 +0800 Subject: [PATCH 04/29] Clean up --- .../Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs | 1 + .../test/InMemory.FunctionalTests/Http2/Http2TestBase.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs index 2e803eeac208..b42a12d1a72c 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs @@ -100,6 +100,7 @@ public void ConnectionStop(in ConnectionMetricsContext metricsContext, Exception { if (metricsContext.CurrentConnectionsCounterEnabled || metricsContext.ConnectionDurationEnabled) { + // Add protocol error code if feature is available and it's not the unset value (-1). long? errorCode = null; if (metricsContext.ConnectionContext.Features.Get() is IProtocolErrorCodeFeature errorCodeFeature && errorCodeFeature.Error != -1) { diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs index 1908ba436429..37fb814935cc 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs @@ -12,6 +12,7 @@ using System.Reflection; using System.Text; using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.InternalTesting; @@ -26,7 +27,6 @@ using Microsoft.Net.Http.Headers; using Moq; using Xunit.Abstractions; -using Microsoft.AspNetCore.Connections.Features; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests; From 16a2be6708c21d8678eb7a1b931774b447dd98cb Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 7 May 2024 20:56:53 +0800 Subject: [PATCH 05/29] HTTP/1.1 and HTTP/2 --- .../Core/src/Internal/Http/Http1Connection.cs | 11 +++--- .../src/Internal/Http/Http1OutputProducer.cs | 12 +++++- .../src/Internal/Http/IHttpOutputAborter.cs | 2 +- .../src/Internal/Http2/Http2Connection.cs | 21 +++++----- .../src/Internal/Http2/Http2OutputProducer.cs | 2 +- .../src/Internal/Http3/Http3Connection.cs | 39 ++++++++++++------- .../Http3/Http3ConnectionErrorException.cs | 4 +- .../src/Internal/Http3/Http3ControlStream.cs | 14 +++---- .../src/Internal/Http3/Http3OutputProducer.cs | 2 +- .../Core/src/Internal/Http3/Http3Stream.cs | 10 ++--- .../Core/src/Internal/HttpConnection.cs | 8 ++-- .../Core/src/Internal/IRequestProcessor.cs | 2 +- .../PipeWriterHelpers/TimingPipeFlusher.cs | 2 +- .../Core/test/Http1/Http1ConnectionTests.cs | 14 +++---- .../test/Http1/Http1OutputProducerTests.cs | 10 +++-- .../Kestrel/samples/Http2SampleApp/Program.cs | 3 ++ .../Http2/Http2ConnectionTests.cs | 4 +- .../Http2/ConnectionErrorReason.cs | 11 ++++-- 18 files changed, 104 insertions(+), 67 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs index 48c9e0595fae..e524b531fa89 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs @@ -62,6 +62,7 @@ public Http1Connection(HttpConnectionContext context) _context.ServiceContext.Log, _context.TimeoutControl, minResponseDataRateFeature: this, + _context.ConnectionFeatures.Get(), outputAborter: this); Input = _context.Transport.Input; @@ -98,22 +99,22 @@ protected override void OnRequestProcessingEnded() void IRequestProcessor.OnInputOrOutputCompleted() { // Closed gracefully. - _http1Output.Abort(ServerOptions.FinOnError ? new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient) : null!); + _http1Output.Abort(ServerOptions.FinOnError ? new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient) : null!, ConnectionErrorReason.InputOrOutputCompleted); CancelRequestAbortedToken(); } void IHttpOutputAborter.OnInputOrOutputCompleted() { - _http1Output.Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient)); + _http1Output.Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient), ConnectionErrorReason.InputOrOutputCompleted); CancelRequestAbortedToken(); } /// /// Immediately kill the connection and poison the request body stream with an error. /// - public void Abort(ConnectionAbortedException abortReason) + public void Abort(ConnectionAbortedException abortReason, ConnectionErrorReason reason) { - _http1Output.Abort(abortReason); + _http1Output.Abort(abortReason, reason); CancelRequestAbortedToken(); PoisonBody(abortReason); } @@ -121,7 +122,7 @@ public void Abort(ConnectionAbortedException abortReason) protected override void ApplicationAbort() { Log.ApplicationAbortedConnection(ConnectionId, TraceIdentifier); - Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByApplication)); + Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByApplication), ConnectionErrorReason.AbortedByApplication); } /// diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs index c32d6940f3c7..fac610aedee4 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs @@ -5,6 +5,8 @@ using System.Diagnostics; using System.IO.Pipelines; using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Connections.Features; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; @@ -29,6 +31,7 @@ internal class Http1OutputProducer : IHttpOutputProducer, IDisposable private readonly MemoryPool _memoryPool; private readonly KestrelTrace _log; private readonly IHttpMinResponseDataRateFeature _minResponseDataRateFeature; + private readonly IConnectionMetricsTagsFeature? _metricsTagsFeature; private readonly IHttpOutputAborter _outputAborter; private readonly TimingPipeFlusher _flusher; @@ -74,6 +77,7 @@ internal class Http1OutputProducer : IHttpOutputProducer, IDisposable KestrelTrace log, ITimeoutControl timeoutControl, IHttpMinResponseDataRateFeature minResponseDataRateFeature, + IConnectionMetricsTagsFeature? metricsTagsFeature, IHttpOutputAborter outputAborter) { // Allow appending more data to the PipeWriter when a flush is pending. @@ -83,6 +87,7 @@ internal class Http1OutputProducer : IHttpOutputProducer, IDisposable _memoryPool = memoryPool; _log = log; _minResponseDataRateFeature = minResponseDataRateFeature; + _metricsTagsFeature = metricsTagsFeature; _outputAborter = outputAborter; _flusher = new TimingPipeFlusher(timeoutControl, log); @@ -444,7 +449,7 @@ private void CompletePipe() } } - public void Abort(ConnectionAbortedException error) + public void Abort(ConnectionAbortedException error, ConnectionErrorReason errorReason) { // Abort can be called after Dispose if there's a flush timeout. // It's important to still call _lifetimeFeature.Abort() in this case. @@ -455,6 +460,11 @@ public void Abort(ConnectionAbortedException error) return; } + if (errorReason != ConnectionErrorReason.NoError && _metricsTagsFeature != null) + { + _metricsTagsFeature.TryAddTag("kestrel.connection.error_reason", errorReason.ToString()); + } + _aborted = true; _connectionContext.Abort(error); diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/IHttpOutputAborter.cs b/src/Servers/Kestrel/Core/src/Internal/Http/IHttpOutputAborter.cs index b211893465c9..e8782b93c385 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/IHttpOutputAborter.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/IHttpOutputAborter.cs @@ -7,6 +7,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; internal interface IHttpOutputAborter { - void Abort(ConnectionAbortedException abortReason); + void Abort(ConnectionAbortedException abortReason, ConnectionErrorReason errorReason); void OnInputOrOutputCompleted(); } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs index b6c70e452f0f..fd875a99a5cd 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs @@ -89,7 +89,7 @@ private static int GetMaximumEnhanceYourCalmCount() private readonly HttpConnectionContext _context; private readonly ConnectionMetricsContext _metricsContext; private readonly IProtocolErrorCodeFeature _errorCodeFeature; - private readonly IConnectionMetricsTagsFeature? _tagsFeature; + private readonly IConnectionMetricsTagsFeature? _metricsTagsFeature; private readonly Http2FrameWriter _frameWriter; private readonly Pipe _input; private readonly Task _inputTask; @@ -143,7 +143,7 @@ public Http2Connection(HttpConnectionContext context) _streamLifetimeHandler = this; _metricsContext = context.ConnectionFeatures.GetRequiredFeature().MetricsContext; _errorCodeFeature = context.ConnectionFeatures.GetRequiredFeature(); - _tagsFeature = context.ConnectionFeatures.Get(); + _metricsTagsFeature = context.ConnectionFeatures.Get(); // Capture the ExecutionContext before dispatching HTTP/2 middleware. Will be restored by streams when processing request _context.InitialExecutionContext = ExecutionContext.Capture(); @@ -217,27 +217,28 @@ public void OnInputOrOutputCompleted() _frameWriter.Abort(useException ? new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient) : null!); } - private void SetConnectionErrorCode(ConnectionErrorReason? reason, Http2ErrorCode errorCode) + private void SetConnectionErrorCode(ConnectionErrorReason reason, Http2ErrorCode errorCode) { Debug.Assert(_isClosed == 1, "Should only be set when connection is closed."); Debug.Assert(_errorCodeFeature.Error == -1, "Error code feature should only be set once."); _errorCodeFeature.Error = (long)errorCode; - if (reason != null && _tagsFeature != null) + if (_metricsTagsFeature != null && reason != ConnectionErrorReason.NoError) { - _tagsFeature.TryAddTag("ConnectionError", reason.ToString()); + _metricsTagsFeature.TryAddTag("kestrel.connection.error_reason", reason.ToString()); } } - public void Abort(ConnectionAbortedException ex) + public void Abort(ConnectionAbortedException ex, ConnectionErrorReason reason) { - Abort(ex, Http2ErrorCode.INTERNAL_ERROR, ConnectionErrorReason.ServerAbort); + Abort(ex, Http2ErrorCode.INTERNAL_ERROR, reason); } public void Abort(ConnectionAbortedException ex, Http2ErrorCode errorCode, ConnectionErrorReason reason) { if (TryClose()) { + SetConnectionErrorCode(reason, errorCode); _frameWriter.WriteGoAwayAsync(int.MaxValue, errorCode).Preserve(); } @@ -754,7 +755,7 @@ private Http2ConnectionErrorException CreateStreamIdNotZeroException() private Http2ConnectionErrorException CreateHeadersInterleavedException() { Debug.Assert(_currentHeadersStream != null, "Only throw this error if parsing headers."); - return new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorHeadersInterleaved(_incomingFrame.Type, _incomingFrame.StreamId, _currentHeadersStream.StreamId), Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.InvalidFrameForState); + return new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorHeadersInterleaved(_incomingFrame.Type, _incomingFrame.StreamId, _currentHeadersStream.StreamId), Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.UnexpectedFrame); } private Http2ConnectionErrorException CreateUnexpectedFrameLengthException(int exceptedLength) @@ -1158,7 +1159,7 @@ private Task ProcessContinuationFrameAsync(in ReadOnlySequence payload) { if (_currentHeadersStream == null) { - throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorContinuationWithNoHeaders, Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.InvalidFrameForState); + throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorContinuationWithNoHeaders, Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.UnexpectedFrame); } if (_incomingFrame.StreamId != _currentHeadersStream.StreamId) @@ -1491,7 +1492,7 @@ private void UpdateConnectionState() { if (TryClose()) { - SetConnectionErrorCode(reason: null, Http2ErrorCode.NO_ERROR); + SetConnectionErrorCode(ConnectionErrorReason.NoError, Http2ErrorCode.NO_ERROR); _frameWriter.WriteGoAwayAsync(_highestOpenedStreamId, Http2ErrorCode.NO_ERROR).Preserve(); } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2OutputProducer.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2OutputProducer.cs index f65890adf7cb..39d34070bef5 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2OutputProducer.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2OutputProducer.cs @@ -241,7 +241,7 @@ public void Complete() // This is called when a CancellationToken fires mid-write. In HTTP/1.x, this aborts the entire connection. // For HTTP/2 we abort the stream. - void IHttpOutputAborter.Abort(ConnectionAbortedException abortReason) + void IHttpOutputAborter.Abort(ConnectionAbortedException abortReason, ConnectionErrorReason errorReason) { _stream.ResetAndAbort(abortReason, Http2ErrorCode.INTERNAL_ERROR); } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs index 9a53fddf98e3..ee8e5173eec7 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; @@ -40,6 +41,7 @@ internal sealed class Http3Connection : IHttp3StreamLifetimeHandler, IRequestPro private readonly object _protocolSelectionLock = new(); private readonly StreamCloseAwaitable _streamCompletionAwaitable = new(); private readonly IProtocolErrorCodeFeature _errorCodeFeature; + private readonly IConnectionMetricsTagsFeature? _metricsTagsFeature; private readonly Dictionary? _webtransportSessions; private long _highestOpenedRequestStreamId = DefaultHighestOpenedRequestStreamId; @@ -58,6 +60,7 @@ public Http3Connection(HttpMultiplexedConnectionContext context) MetricsContext = context.ConnectionFeatures.GetRequiredFeature().MetricsContext; _errorCodeFeature = context.ConnectionFeatures.GetRequiredFeature(); + _metricsTagsFeature = context.ConnectionFeatures.Get(); var httpLimits = context.ServiceContext.ServerOptions.Limits; @@ -149,12 +152,12 @@ private bool TryStopAcceptingStreams() return false; } - public void Abort(ConnectionAbortedException ex) + public void Abort(ConnectionAbortedException ex, ConnectionErrorReason reason) { - Abort(ex, Http3ErrorCode.InternalError); + Abort(ex, Http3ErrorCode.InternalError, reason); } - public void Abort(ConnectionAbortedException ex, Http3ErrorCode errorCode) + public void Abort(ConnectionAbortedException ex, Http3ErrorCode errorCode, ConnectionErrorReason reason) { bool previousState; @@ -182,6 +185,10 @@ public void Abort(ConnectionAbortedException ex, Http3ErrorCode errorCode) if (!previousState) { _errorCodeFeature.Error = (long)errorCode; + if (_metricsTagsFeature != null && reason != ConnectionErrorReason.NoError) + { + _metricsTagsFeature.TryAddTag("kestrel.connection.error_reason", reason.ToString()); + } if (TryStopAcceptingStreams()) { @@ -235,7 +242,7 @@ static void ValidateOpenControlStream(Http3ControlStream? stream, Http3Connectio if (stream.StreamTimeoutTimestamp < timestamp) { - connection.OnStreamConnectionError(new Http3ConnectionErrorException("A control stream used by the connection was closed or reset.", Http3ErrorCode.ClosedCriticalStream)); + connection.OnStreamConnectionError(new Http3ConnectionErrorException("A control stream used by the connection was closed or reset.", Http3ErrorCode.ClosedCriticalStream, ConnectionErrorReason.ClosedCriticalStream)); } } } @@ -313,7 +320,7 @@ private void UpdateStreamTimeouts(long timestamp) { // Cancel connection to be consistent with other data rate limits. Log.ResponseMinimumDataRateNotSatisfied(_context.ConnectionId, stream.TraceIdentifier); - OnStreamConnectionError(new Http3ConnectionErrorException(CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied, Http3ErrorCode.InternalError)); + OnStreamConnectionError(new Http3ConnectionErrorException(CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied, Http3ErrorCode.InternalError, ConnectionErrorReason.ResponseMininumDataRateNotSatisfied)); } } } @@ -336,6 +343,7 @@ private void UpdateStreamTimeouts(long timestamp) Http3ControlStream? outboundControlStream = null; ValueTask outboundControlStreamTask = default; bool clientAbort = false; + ConnectionErrorReason errorReason = ConnectionErrorReason.NoError; try { @@ -462,26 +470,31 @@ private void UpdateStreamTimeouts(long timestamp) } } error = ex; + errorReason = ConnectionErrorReason.ClientAbort; clientAbort = true; } catch (IOException ex) { Log.RequestProcessingError(_context.ConnectionId, ex); error = ex; + errorReason = ConnectionErrorReason.Other; } catch (ConnectionAbortedException ex) { Log.RequestProcessingError(_context.ConnectionId, ex); error = ex; + errorReason = ConnectionErrorReason.Other; } catch (Http3ConnectionErrorException ex) { Log.Http3ConnectionError(_context.ConnectionId, ex); error = ex; + errorReason = ex.ErrorReason; } catch (Exception ex) { error = ex; + errorReason = ConnectionErrorReason.Other; } finally { @@ -531,7 +544,7 @@ private void UpdateStreamTimeouts(long timestamp) } // Complete - Abort(CreateConnectionAbortError(error, clientAbort), (Http3ErrorCode)_errorCodeFeature.Error); + Abort(CreateConnectionAbortError(error, clientAbort), (Http3ErrorCode)_errorCodeFeature.Error, errorReason); // Wait for active requests to complete. while (_activeRequestCount > 0) @@ -543,7 +556,7 @@ private void UpdateStreamTimeouts(long timestamp) } catch { - Abort(CreateConnectionAbortError(error, clientAbort), Http3ErrorCode.InternalError); + Abort(CreateConnectionAbortError(error, clientAbort), Http3ErrorCode.InternalError, ConnectionErrorReason.Other); throw; } finally @@ -704,11 +717,11 @@ private async ValueTask ProcessOutboundControlStreamAsync(Http3ControlStream con { Log.Http3OutboundControlStreamError(ConnectionId, ex); - var connectionError = new Http3ConnectionErrorException(CoreStrings.Http3ControlStreamErrorInitializingOutbound, Http3ErrorCode.ClosedCriticalStream); + var connectionError = new Http3ConnectionErrorException(CoreStrings.Http3ControlStreamErrorInitializingOutbound, Http3ErrorCode.ClosedCriticalStream, ConnectionErrorReason.ClosedCriticalStream); Log.Http3ConnectionError(ConnectionId, connectionError); // https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-6.2.1 - Abort(new ConnectionAbortedException(connectionError.Message, connectionError), connectionError.ErrorCode); + Abort(new ConnectionAbortedException(connectionError.Message, connectionError), connectionError.ErrorCode, ConnectionErrorReason.ClosedCriticalStream); } } @@ -841,7 +854,7 @@ void IHttp3StreamLifetimeHandler.OnStreamConnectionError(Http3ConnectionErrorExc private void OnStreamConnectionError(Http3ConnectionErrorException ex) { Log.Http3ConnectionError(ConnectionId, ex); - Abort(new ConnectionAbortedException(ex.Message, ex), ex.ErrorCode); + Abort(new ConnectionAbortedException(ex.Message, ex), ex.ErrorCode, ex.ErrorReason); } void IHttp3StreamLifetimeHandler.OnInboundControlStreamSetting(Http3SettingType type, long value) @@ -874,7 +887,7 @@ void IHttp3StreamLifetimeHandler.OnStreamHeaderReceived(IHttp3Stream stream) public void HandleRequestHeadersTimeout() { Log.ConnectionBadRequest(ConnectionId, KestrelBadHttpRequestException.GetException(RequestRejectionReason.RequestHeadersTimeout)); - Abort(new ConnectionAbortedException(CoreStrings.BadRequest_RequestHeadersTimeout)); + Abort(new ConnectionAbortedException(CoreStrings.BadRequest_RequestHeadersTimeout), ConnectionErrorReason.RequestHeadersTimeout); } public void HandleReadDataRateTimeout() @@ -882,7 +895,7 @@ public void HandleReadDataRateTimeout() Debug.Assert(Limits.MinRequestBodyDataRate != null); Log.RequestBodyMinimumDataRateNotSatisfied(ConnectionId, null, Limits.MinRequestBodyDataRate.BytesPerSecond); - Abort(new ConnectionAbortedException(CoreStrings.BadRequest_RequestBodyTimeout)); + Abort(new ConnectionAbortedException(CoreStrings.BadRequest_RequestBodyTimeout), ConnectionErrorReason.RequestBodyTimeout); } public void OnInputOrOutputCompleted() @@ -890,7 +903,7 @@ public void OnInputOrOutputCompleted() TryStopAcceptingStreams(); // Abort the connection using the error code the client used. For a graceful close, this should be H3_NO_ERROR. - Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient), (Http3ErrorCode)_errorCodeFeature.Error); + Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient), (Http3ErrorCode)_errorCodeFeature.Error, ConnectionErrorReason.InputOrOutputCompleted); } internal WebTransportSession OpenNewWebTransportSession(Http3Stream http3Stream) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ConnectionErrorException.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ConnectionErrorException.cs index 14ad5d095643..213d7790f043 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ConnectionErrorException.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ConnectionErrorException.cs @@ -7,11 +7,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3; internal sealed class Http3ConnectionErrorException : Exception { - public Http3ConnectionErrorException(string message, Http3ErrorCode errorCode) + public Http3ConnectionErrorException(string message, Http3ErrorCode errorCode, ConnectionErrorReason errorReason) : base($"HTTP/3 connection error ({Http3Formatting.ToFormattedErrorCode(errorCode)}): {message}") { ErrorCode = errorCode; + ErrorReason = errorReason; } public Http3ErrorCode ErrorCode { get; } + public ConnectionErrorReason ErrorReason { get; } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs index 599a55f50212..eb2a2b2d4d72 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs @@ -202,7 +202,7 @@ private async ValueTask TryReadStreamHeaderAsync() if (!_context.StreamLifetimeHandler.OnInboundControlStream(this)) { // https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-6.2.1 - throw new Http3ConnectionErrorException(CoreStrings.FormatHttp3ControlStreamErrorMultipleInboundStreams("control"), Http3ErrorCode.StreamCreationError); + throw new Http3ConnectionErrorException(CoreStrings.FormatHttp3ControlStreamErrorMultipleInboundStreams("control"), Http3ErrorCode.StreamCreationError, ConnectionErrorReason.StreamCreationError); } await HandleControlStream(); @@ -211,7 +211,7 @@ private async ValueTask TryReadStreamHeaderAsync() if (!_context.StreamLifetimeHandler.OnInboundEncoderStream(this)) { // https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#section-4.2 - throw new Http3ConnectionErrorException(CoreStrings.FormatHttp3ControlStreamErrorMultipleInboundStreams("encoder"), Http3ErrorCode.StreamCreationError); + throw new Http3ConnectionErrorException(CoreStrings.FormatHttp3ControlStreamErrorMultipleInboundStreams("encoder"), Http3ErrorCode.StreamCreationError, ConnectionErrorReason.StreamCreationError); } await HandleEncodingDecodingTask(); @@ -220,7 +220,7 @@ private async ValueTask TryReadStreamHeaderAsync() if (!_context.StreamLifetimeHandler.OnInboundDecoderStream(this)) { // https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#section-4.2 - throw new Http3ConnectionErrorException(CoreStrings.FormatHttp3ControlStreamErrorMultipleInboundStreams("decoder"), Http3ErrorCode.StreamCreationError); + throw new Http3ConnectionErrorException(CoreStrings.FormatHttp3ControlStreamErrorMultipleInboundStreams("decoder"), Http3ErrorCode.StreamCreationError, ConnectionErrorReason.StreamCreationError); } await HandleEncodingDecodingTask(); break; @@ -302,7 +302,7 @@ private ValueTask ProcessHttp3ControlStream(in ReadOnlySequence payload) case Http3FrameType.Headers: case Http3FrameType.PushPromise: // https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-7.2 - throw new Http3ConnectionErrorException(CoreStrings.FormatHttp3ErrorUnsupportedFrameOnControlStream(_incomingFrame.FormattedType), Http3ErrorCode.UnexpectedFrame); + throw new Http3ConnectionErrorException(CoreStrings.FormatHttp3ErrorUnsupportedFrameOnControlStream(_incomingFrame.FormattedType), Http3ErrorCode.UnexpectedFrame, ConnectionErrorReason.UnexpectedFrame); case Http3FrameType.Settings: return ProcessSettingsFrameAsync(payload); case Http3FrameType.GoAway: @@ -321,7 +321,7 @@ private ValueTask ProcessSettingsFrameAsync(ReadOnlySequence payload) if (_haveReceivedSettingsFrame) { // https://quicwg.org/base-drafts/draft-ietf-quic-http.html#name-settings - throw new Http3ConnectionErrorException(CoreStrings.Http3ErrorControlStreamMultipleSettingsFrames, Http3ErrorCode.UnexpectedFrame); + throw new Http3ConnectionErrorException(CoreStrings.Http3ErrorControlStreamMultipleSettingsFrames, Http3ErrorCode.UnexpectedFrame, ConnectionErrorReason.UnexpectedFrame); } _haveReceivedSettingsFrame = true; @@ -367,7 +367,7 @@ private void ProcessSetting(long id, long value) // HTTP/2 settings are reserved. // https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-7.2.4.1-5 var message = CoreStrings.FormatHttp3ErrorControlStreamReservedSetting("0x" + id.ToString("X", CultureInfo.InvariantCulture)); - throw new Http3ConnectionErrorException(message, Http3ErrorCode.SettingsError); + throw new Http3ConnectionErrorException(message, Http3ErrorCode.SettingsError, ConnectionErrorReason.InvalidSettings); case (long)Http3SettingType.QPackMaxTableCapacity: case (long)Http3SettingType.MaxFieldSectionSize: case (long)Http3SettingType.QPackBlockedStreams: @@ -431,7 +431,7 @@ private void EnsureSettingsFrame(Http3FrameType frameType) if (!_haveReceivedSettingsFrame) { var message = CoreStrings.FormatHttp3ErrorControlStreamFrameReceivedBeforeSettings(Http3Formatting.ToFormattedType(frameType)); - throw new Http3ConnectionErrorException(message, Http3ErrorCode.MissingSettings); + throw new Http3ConnectionErrorException(message, Http3ErrorCode.MissingSettings, ConnectionErrorReason.InvalidSettings); } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3OutputProducer.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3OutputProducer.cs index d387d3629662..461a76611f87 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3OutputProducer.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3OutputProducer.cs @@ -95,7 +95,7 @@ public void Dispose() } } - void IHttpOutputAborter.Abort(ConnectionAbortedException abortReason) + void IHttpOutputAborter.Abort(ConnectionAbortedException abortReason, ConnectionErrorReason errorReason) { _stream.Abort(abortReason, Http3ErrorCode.InternalError); } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs index 3ce5cdcad632..de96a5bc781e 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs @@ -757,10 +757,10 @@ Http3FrameType.Settings or Http3FrameType.CancelPush or Http3FrameType.GoAway or Http3FrameType.MaxPushId => throw new Http3ConnectionErrorException( - CoreStrings.FormatHttp3ErrorUnsupportedFrameOnRequestStream(_incomingFrame.FormattedType), Http3ErrorCode.UnexpectedFrame), + CoreStrings.FormatHttp3ErrorUnsupportedFrameOnRequestStream(_incomingFrame.FormattedType), Http3ErrorCode.UnexpectedFrame, ConnectionErrorReason.ReceivedUnsupportedFrame), // The server should never receive push promise Http3FrameType.PushPromise => throw new Http3ConnectionErrorException( - CoreStrings.FormatHttp3ErrorUnsupportedFrameOnServer(_incomingFrame.FormattedType), Http3ErrorCode.UnexpectedFrame), + CoreStrings.FormatHttp3ErrorUnsupportedFrameOnServer(_incomingFrame.FormattedType), Http3ErrorCode.UnexpectedFrame, ConnectionErrorReason.ReceivedUnsupportedFrame), _ => ProcessUnknownFrameAsync(), }; } @@ -778,7 +778,7 @@ private static Task ProcessUnknownFrameAsync() // https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-4.1 if (_requestHeaderParsingState == RequestHeaderParsingState.Trailers) { - throw new Http3ConnectionErrorException(CoreStrings.FormatHttp3StreamErrorFrameReceivedAfterTrailers(Http3Formatting.ToFormattedType(Http3FrameType.Headers)), Http3ErrorCode.UnexpectedFrame); + throw new Http3ConnectionErrorException(CoreStrings.FormatHttp3StreamErrorFrameReceivedAfterTrailers(Http3Formatting.ToFormattedType(Http3FrameType.Headers)), Http3ErrorCode.UnexpectedFrame, ConnectionErrorReason.UnexpectedFrame); } if (_requestHeaderParsingState == RequestHeaderParsingState.Body) @@ -877,7 +877,7 @@ private Task ProcessDataFrameAsync(in ReadOnlySequence payload) // https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-4.1 if (_requestHeaderParsingState == RequestHeaderParsingState.Ready) { - throw new Http3ConnectionErrorException(CoreStrings.Http3StreamErrorDataReceivedBeforeHeaders, Http3ErrorCode.UnexpectedFrame); + throw new Http3ConnectionErrorException(CoreStrings.Http3StreamErrorDataReceivedBeforeHeaders, Http3ErrorCode.UnexpectedFrame, ConnectionErrorReason.UnexpectedFrame); } // DATA frame after trailing headers is invalid. @@ -885,7 +885,7 @@ private Task ProcessDataFrameAsync(in ReadOnlySequence payload) if (_requestHeaderParsingState == RequestHeaderParsingState.Trailers) { var message = CoreStrings.FormatHttp3StreamErrorFrameReceivedAfterTrailers(Http3Formatting.ToFormattedType(Http3FrameType.Data)); - throw new Http3ConnectionErrorException(message, Http3ErrorCode.UnexpectedFrame); + throw new Http3ConnectionErrorException(message, Http3ErrorCode.UnexpectedFrame, ConnectionErrorReason.UnexpectedFrame); } if (InputRemaining.HasValue) diff --git a/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs b/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs index b7239eb608ef..b52ed2829aee 100644 --- a/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs @@ -175,7 +175,7 @@ private void OnConnectionClosed() } } - private void Abort(ConnectionAbortedException ex) + private void Abort(ConnectionAbortedException ex, ConnectionErrorReason errorReason) { ProtocolSelectionState previousState; @@ -190,7 +190,7 @@ private void Abort(ConnectionAbortedException ex) switch (previousState) { case ProtocolSelectionState.Selected: - _requestProcessor!.Abort(ex); + _requestProcessor!.Abort(ex, errorReason); break; case ProtocolSelectionState.Aborted: break; @@ -275,11 +275,11 @@ public void OnTimeout(TimeoutReason reason) break; case TimeoutReason.WriteDataRate: Log.ResponseMinimumDataRateNotSatisfied(_context.ConnectionId, _http1Connection?.TraceIdentifier); - Abort(new ConnectionAbortedException(CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied)); + Abort(new ConnectionAbortedException(CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied), ConnectionErrorReason.ResponseMininumDataRateNotSatisfied); break; case TimeoutReason.RequestBodyDrain: case TimeoutReason.TimeoutFeature: - Abort(new ConnectionAbortedException(CoreStrings.ConnectionTimedOutByServer)); + Abort(new ConnectionAbortedException(CoreStrings.ConnectionTimedOutByServer), ConnectionErrorReason.ServerTimeout); break; default: Debug.Assert(false, "Invalid TimeoutReason"); diff --git a/src/Servers/Kestrel/Core/src/Internal/IRequestProcessor.cs b/src/Servers/Kestrel/Core/src/Internal/IRequestProcessor.cs index b1b5cfddbb14..e66e45d0191c 100644 --- a/src/Servers/Kestrel/Core/src/Internal/IRequestProcessor.cs +++ b/src/Servers/Kestrel/Core/src/Internal/IRequestProcessor.cs @@ -14,5 +14,5 @@ internal interface IRequestProcessor void HandleReadDataRateTimeout(); void OnInputOrOutputCompleted(); void Tick(long timestamp); - void Abort(ConnectionAbortedException ex); + void Abort(ConnectionAbortedException ex, ConnectionErrorReason reason); } diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/TimingPipeFlusher.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/TimingPipeFlusher.cs index 2a9b8863969c..1227628a0073 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/TimingPipeFlusher.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/TimingPipeFlusher.cs @@ -92,7 +92,7 @@ private async ValueTask TimeFlushAsyncAwaited(ValueTask(new[] { (byte)'d' })); Assert.NotEqual(original, _http1Connection.RequestAborted); - _http1Connection.Abort(new ConnectionAbortedException()); + _http1Connection.Abort(new ConnectionAbortedException(), ConnectionErrorReason.AbortedByApplication); Assert.False(original.IsCancellationRequested); Assert.False(_http1Connection.RequestAborted.IsCancellationRequested); @@ -702,7 +702,7 @@ public async Task RequestAbortedTokenIsResetBeforeLastWriteAsyncWithContentLengt await _http1Connection.WriteAsync(new ArraySegment(new[] { (byte)'d' }), default(CancellationToken)); Assert.NotEqual(original, _http1Connection.RequestAborted); - _http1Connection.Abort(new ConnectionAbortedException()); + _http1Connection.Abort(new ConnectionAbortedException(), ConnectionErrorReason.AbortedByApplication); Assert.False(original.IsCancellationRequested); Assert.False(_http1Connection.RequestAborted.IsCancellationRequested); @@ -717,7 +717,7 @@ public async void BodyWriter_OnAbortedConnection_ReturnsFlushResultWithIsComplet var successResult = await writer.WriteAsync(payload); Assert.False(successResult.IsCompleted); - _http1Connection.Abort(new ConnectionAbortedException()); + _http1Connection.Abort(new ConnectionAbortedException(), ConnectionErrorReason.AbortedByApplication); var failResult = await _http1Connection.FlushPipeAsync(new CancellationToken()); Assert.True(failResult.IsCompleted); } @@ -762,7 +762,7 @@ public async Task RequestAbortedTokenIsResetBeforeLastWriteAsyncAwaitedWithConte await _http1Connection.WriteAsync(new ArraySegment(new[] { (byte)'d' }), default(CancellationToken)); Assert.NotEqual(original, _http1Connection.RequestAborted); - _http1Connection.Abort(new ConnectionAbortedException()); + _http1Connection.Abort(new ConnectionAbortedException(), ConnectionErrorReason.AbortedByApplication); Assert.False(original.IsCancellationRequested); Assert.False(_http1Connection.RequestAborted.IsCancellationRequested); @@ -780,7 +780,7 @@ public async Task RequestAbortedTokenIsResetBeforeLastWriteWithChunkedEncoding() await _http1Connection.ProduceEndAsync(); Assert.NotEqual(original, _http1Connection.RequestAborted); - _http1Connection.Abort(new ConnectionAbortedException()); + _http1Connection.Abort(new ConnectionAbortedException(), ConnectionErrorReason.AbortedByApplication); Assert.False(original.IsCancellationRequested); Assert.False(_http1Connection.RequestAborted.IsCancellationRequested); @@ -792,7 +792,7 @@ public void RequestAbortedTokenIsFullyUsableAfterCancellation() var originalToken = _http1Connection.RequestAborted; var originalRegistration = originalToken.Register(() => { }); - _http1Connection.Abort(new ConnectionAbortedException()); + _http1Connection.Abort(new ConnectionAbortedException(), ConnectionErrorReason.AbortedByApplication); Assert.True(originalToken.WaitHandle.WaitOne(TestConstants.DefaultTimeout)); Assert.True(_http1Connection.RequestAborted.WaitHandle.WaitOne(TestConstants.DefaultTimeout)); @@ -806,7 +806,7 @@ public void RequestAbortedTokenIsUsableAfterCancellation() var originalToken = _http1Connection.RequestAborted; var originalRegistration = originalToken.Register(() => { }); - _http1Connection.Abort(new ConnectionAbortedException()); + _http1Connection.Abort(new ConnectionAbortedException(), ConnectionErrorReason.AbortedByApplication); // The following line will throw an ODE because the original CTS backing the token has been diposed. // See https://github.com/dotnet/aspnetcore/pull/4447 for the history behind this test. diff --git a/src/Servers/Kestrel/Core/test/Http1/Http1OutputProducerTests.cs b/src/Servers/Kestrel/Core/test/Http1/Http1OutputProducerTests.cs index 8b38ec829ef1..f3ddb67f8f8e 100644 --- a/src/Servers/Kestrel/Core/test/Http1/Http1OutputProducerTests.cs +++ b/src/Servers/Kestrel/Core/test/Http1/Http1OutputProducerTests.cs @@ -12,6 +12,7 @@ using Microsoft.AspNetCore.InternalTesting; using Moq; using Xunit; +using Microsoft.AspNetCore.Connections.Features; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests; @@ -124,11 +125,11 @@ public void AbortsTransportEvenAfterDispose() mockConnectionContext.Verify(f => f.Abort(It.IsAny()), Times.Never()); - outputProducer.Abort(null); + outputProducer.Abort(null, ConnectionErrorReason.AbortedByApplication); mockConnectionContext.Verify(f => f.Abort(null), Times.Once()); - outputProducer.Abort(null); + outputProducer.Abort(null, ConnectionErrorReason.AbortedByApplication); mockConnectionContext.Verify(f => f.Abort(null), Times.Once()); } @@ -233,6 +234,7 @@ public void ReusesFakeMemory() serviceContext.Log, Mock.Of(), Mock.Of(), + Mock.Of(), Mock.Of()); return socketOutput; @@ -240,8 +242,8 @@ public void ReusesFakeMemory() private class TestHttpOutputProducer : Http1OutputProducer { - public TestHttpOutputProducer(Pipe pipe, string connectionId, ConnectionContext connectionContext, MemoryPool memoryPool, KestrelTrace log, ITimeoutControl timeoutControl, IHttpMinResponseDataRateFeature minResponseDataRateFeature, IHttpOutputAborter outputAborter) - : base(pipe.Writer, connectionId, connectionContext, memoryPool, log, timeoutControl, minResponseDataRateFeature, outputAborter) + public TestHttpOutputProducer(Pipe pipe, string connectionId, ConnectionContext connectionContext, MemoryPool memoryPool, KestrelTrace log, ITimeoutControl timeoutControl, IHttpMinResponseDataRateFeature minResponseDataRateFeature, IConnectionMetricsTagsFeature metricsTagsFeature, IHttpOutputAborter outputAborter) + : base(pipe.Writer, connectionId, connectionContext, memoryPool, log, timeoutControl, minResponseDataRateFeature, metricsTagsFeature, outputAborter) { Pipe = pipe; } diff --git a/src/Servers/Kestrel/samples/Http2SampleApp/Program.cs b/src/Servers/Kestrel/samples/Http2SampleApp/Program.cs index beded5607dee..8830c9331005 100644 --- a/src/Servers/Kestrel/samples/Http2SampleApp/Program.cs +++ b/src/Servers/Kestrel/samples/Http2SampleApp/Program.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics; using System.IO; using System.Security.Authentication; using Microsoft.AspNetCore.Connections; @@ -71,6 +72,8 @@ public static void Main(string[] args) factory.AddConsole(); }); + Console.WriteLine($"Process ID: {Environment.ProcessId}"); + hostBuilder.Build().Run(); } } diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs index 0ae5776c3a49..c42ccee2fd8b 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs @@ -1660,7 +1660,7 @@ public void MinimumMaxTrackedStreams() CreateConnection(); // Kestrel always tracks at least 100 streams Assert.Equal(100u, _connection.MaxTrackedStreams); - _connection.Abort(new ConnectionAbortedException()); + _connection.Abort(new ConnectionAbortedException(), ConnectionErrorReason.AbortedByApplication); } [Fact] @@ -4809,7 +4809,7 @@ public async Task AbortSendsFinalGOAWAY() { await InitializeConnectionAsync(_noopApplication); - _connection.Abort(new ConnectionAbortedException()); + _connection.Abort(new ConnectionAbortedException(), ConnectionErrorReason.AbortedByApplication); await _closedStateReached.Task.DefaultTimeout(); VerifyGoAway(await ReceiveFrameAsync(), int.MaxValue, Http2ErrorCode.INTERNAL_ERROR); diff --git a/src/Shared/ServerInfrastructure/Http2/ConnectionErrorReason.cs b/src/Shared/ServerInfrastructure/Http2/ConnectionErrorReason.cs index e7a62a696e8c..7d9be7b4c480 100644 --- a/src/Shared/ServerInfrastructure/Http2/ConnectionErrorReason.cs +++ b/src/Shared/ServerInfrastructure/Http2/ConnectionErrorReason.cs @@ -5,8 +5,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core; internal enum ConnectionErrorReason { + NoError, ClientAbort, - ServerAbort, FlowControlWindowExceeded, KeepAliveTimeout, InsufficientTlsVersion, @@ -15,7 +15,7 @@ internal enum ConnectionErrorReason ReceivedFrameAfterStreamClose, ReceivedFrameUnknownStream, ReceivedUnsupportedFrame, - InvalidFrameForState, + UnexpectedFrame, InvalidFrameLength, InvalidDataPadding, InvalidRequestHeaders, @@ -33,5 +33,10 @@ internal enum ConnectionErrorReason RequestHeadersTimeout, RequestBodyTimeout, FlowControlQueueSizeExceeded, - OutputQueueSizeExceeded + OutputQueueSizeExceeded, + ClosedCriticalStream, + ResponseMininumDataRateNotSatisfied, + AbortedByApplication, + ServerTimeout, + StreamCreationError } From 20043b497208da18768d08baba912b310fd78820 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Wed, 8 May 2024 15:38:06 +0800 Subject: [PATCH 06/29] Tests --- .../Core/src/Internal/Http/Http1Connection.cs | 4 +- .../src/Internal/Http/Http1MessageBody.cs | 4 +- .../src/Internal/Http2/Http2Connection.cs | 22 +- .../src/Internal/Http3/Http3Connection.cs | 21 +- .../Core/src/Internal/HttpConnection.cs | 8 +- .../Core/src/Internal/IRequestProcessor.cs | 2 +- .../Internal/Infrastructure/KestrelMetrics.cs | 2 + .../Core/test/Http1/Http1ConnectionTests.cs | 6 +- .../test/FunctionalTests/RequestTests.cs | 24 +- .../test/FunctionalTests/ResponseTests.cs | 53 +++- .../Http2/Http2ConnectionTests.cs | 279 +++++++++++++++++- .../Http2/Http2TestBase.cs | 15 +- .../Http2/Http2TimeoutTests.cs | 28 ++ .../Sockets.FunctionalTests.csproj | 1 + .../Http2/ConnectionErrorReason.cs | 9 +- 15 files changed, 437 insertions(+), 41 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs index e524b531fa89..b873cd7fcece 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs @@ -99,7 +99,7 @@ protected override void OnRequestProcessingEnded() void IRequestProcessor.OnInputOrOutputCompleted() { // Closed gracefully. - _http1Output.Abort(ServerOptions.FinOnError ? new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient) : null!, ConnectionErrorReason.InputOrOutputCompleted); + _http1Output.Abort(ServerOptions.FinOnError ? new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient) : null!, ConnectionErrorReason.NoError); CancelRequestAbortedToken(); } @@ -130,7 +130,7 @@ protected override void ApplicationAbort() /// Called on all active connections when the server wants to initiate a shutdown /// and after a keep-alive timeout. /// - public void StopProcessingNextRequest() + public void StopProcessingNextRequest(ConnectionErrorReason errorReason) { _keepAlive = false; Input.CancelPendingRead(); diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs index dbdf2a3f5fc2..4c30110c2ce0 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs @@ -72,7 +72,7 @@ protected override Task OnConsumeAsync() _context.ReportApplicationError(connectionAbortedException); // Have to abort the connection because we can't finish draining the request - _context.StopProcessingNextRequest(); + _context.StopProcessingNextRequest(ConnectionErrorReason.AbortedByApplication); return Task.CompletedTask; } @@ -108,7 +108,7 @@ protected async Task OnConsumeAsyncAwaited() _context.ReportApplicationError(connectionAbortedException); // Have to abort the connection because we can't finish draining the request - _context.StopProcessingNextRequest(); + _context.StopProcessingNextRequest(ConnectionErrorReason.AbortedByApplication); } finally { diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs index fd875a99a5cd..dbe773adb2e3 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs @@ -126,6 +126,7 @@ private static int GetMaximumEnhanceYourCalmCount() private readonly ConcurrentQueue _completedStreams = new ConcurrentQueue(); private readonly StreamCloseAwaitable _streamCompletionAwaitable = new StreamCloseAwaitable(); private int _gracefulCloseInitiator; + private ConnectionErrorReason _gracefulCloseReason; private int _isClosed; // Internal for testing @@ -209,11 +210,12 @@ public Http2Connection(HttpConnectionContext context) public void OnInputOrOutputCompleted() { + var hasActiveStreams = _clientActiveStreamCount != 0; if (TryClose()) { - SetConnectionErrorCode(ConnectionErrorReason.InputOrOutputCompleted, Http2ErrorCode.PROTOCOL_ERROR); + SetConnectionErrorCode(hasActiveStreams ? ConnectionErrorReason.ConnectionReset : ConnectionErrorReason.NoError, Http2ErrorCode.NO_ERROR); } - var useException = _context.ServiceContext.ServerOptions.FinOnError || _clientActiveStreamCount != 0; + var useException = _context.ServiceContext.ServerOptions.FinOnError || hasActiveStreams; _frameWriter.Abort(useException ? new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient) : null!); } @@ -245,8 +247,8 @@ public void Abort(ConnectionAbortedException ex, Http2ErrorCode errorCode, Conne _frameWriter.Abort(ex); } - public void StopProcessingNextRequest() - => StopProcessingNextRequest(serverInitiated: true); + public void StopProcessingNextRequest(ConnectionErrorReason reason) + => StopProcessingNextRequest(serverInitiated: true, reason); public void HandleRequestHeadersTimeout() { @@ -262,12 +264,13 @@ public void HandleReadDataRateTimeout() Abort(new ConnectionAbortedException(CoreStrings.BadRequest_RequestBodyTimeout), Http2ErrorCode.INTERNAL_ERROR, ConnectionErrorReason.RequestBodyTimeout); } - public void StopProcessingNextRequest(bool serverInitiated) + public void StopProcessingNextRequest(bool serverInitiated, ConnectionErrorReason reason) { var initiator = serverInitiated ? GracefulCloseInitiator.Server : GracefulCloseInitiator.Client; if (Interlocked.CompareExchange(ref _gracefulCloseInitiator, initiator, GracefulCloseInitiator.None) == GracefulCloseInitiator.None) { + _gracefulCloseReason = reason; Input.CancelPendingRead(); } } @@ -276,7 +279,7 @@ public void StopProcessingNextRequest(bool serverInitiated) { Exception? error = null; var errorCode = Http2ErrorCode.NO_ERROR; - var errorReason = ConnectionErrorReason.Other; + var errorReason = ConnectionErrorReason.NoError; try { @@ -380,6 +383,7 @@ public void StopProcessingNextRequest(bool serverInitiated) if (_clientActiveStreamCount > 0) { Log.RequestProcessingError(ConnectionId, ex); + errorReason = ConnectionErrorReason.ConnectionReset; } error = ex; @@ -388,6 +392,7 @@ public void StopProcessingNextRequest(bool serverInitiated) { Log.RequestProcessingError(ConnectionId, ex); error = ex; + errorReason = ConnectionErrorReason.IOError; } catch (ConnectionAbortedException ex) { @@ -415,6 +420,7 @@ public void StopProcessingNextRequest(bool serverInitiated) Log.LogWarning(0, ex, CoreStrings.RequestProcessingEndError); error = ex; errorCode = Http2ErrorCode.INTERNAL_ERROR; + errorReason = ConnectionErrorReason.UnexpectedError; } finally { @@ -1085,7 +1091,7 @@ private Task ProcessGoAwayFrameAsync() } // StopProcessingNextRequest must be called before RequestClose to ensure it's considered client initiated. - StopProcessingNextRequest(serverInitiated: false); + StopProcessingNextRequest(serverInitiated: false, ConnectionErrorReason.ClientGoAway); _context.ConnectionFeatures.Get()?.RequestClose(); return Task.CompletedTask; @@ -1492,7 +1498,7 @@ private void UpdateConnectionState() { if (TryClose()) { - SetConnectionErrorCode(ConnectionErrorReason.NoError, Http2ErrorCode.NO_ERROR); + SetConnectionErrorCode(_gracefulCloseReason, Http2ErrorCode.NO_ERROR); _frameWriter.WriteGoAwayAsync(_highestOpenedStreamId, Http2ErrorCode.NO_ERROR).Preserve(); } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs index ee8e5173eec7..a603a9a3f787 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs @@ -47,6 +47,7 @@ internal sealed class Http3Connection : IHttp3StreamLifetimeHandler, IRequestPro private long _highestOpenedRequestStreamId = DefaultHighestOpenedRequestStreamId; private bool _aborted; private int _gracefulCloseInitiator; + private ConnectionErrorReason _gracefulCloseReason; private int _stoppedAcceptingStreams; private bool _gracefulCloseStarted; private int _activeRequestCount; @@ -104,10 +105,10 @@ private void UpdateHighestOpenedRequestStreamId(long streamId) public string ConnectionId => _context.ConnectionId; public ITimeoutControl TimeoutControl => _context.TimeoutControl; - public void StopProcessingNextRequest() - => StopProcessingNextRequest(serverInitiated: true); + public void StopProcessingNextRequest(ConnectionErrorReason reason) + => StopProcessingNextRequest(serverInitiated: true, reason); - public void StopProcessingNextRequest(bool serverInitiated) + public void StopProcessingNextRequest(bool serverInitiated, ConnectionErrorReason reason) { bool previousState; lock (_protocolSelectionLock) @@ -121,6 +122,8 @@ public void StopProcessingNextRequest(bool serverInitiated) if (Interlocked.CompareExchange(ref _gracefulCloseInitiator, initiator, GracefulCloseInitiator.None) == GracefulCloseInitiator.None) { + _gracefulCloseReason = reason; + // Break out of AcceptStreams so connection state can be updated. _acceptStreamsCts.Cancel(); } @@ -467,23 +470,25 @@ private void UpdateStreamTimeouts(long timestamp) if (_activeRequestCount > 0) { Log.RequestProcessingError(_context.ConnectionId, ex); + errorReason = ConnectionErrorReason.ConnectionReset; } } error = ex; - errorReason = ConnectionErrorReason.ClientAbort; clientAbort = true; } catch (IOException ex) { Log.RequestProcessingError(_context.ConnectionId, ex); error = ex; - errorReason = ConnectionErrorReason.Other; + errorReason = ConnectionErrorReason.IOError; } catch (ConnectionAbortedException ex) { Log.RequestProcessingError(_context.ConnectionId, ex); error = ex; - errorReason = ConnectionErrorReason.Other; + errorReason = ConnectionErrorReason.UnexpectedError; + + Debug.Fail("Figure out error reason"); } catch (Http3ConnectionErrorException ex) { @@ -494,7 +499,7 @@ private void UpdateStreamTimeouts(long timestamp) catch (Exception ex) { error = ex; - errorReason = ConnectionErrorReason.Other; + errorReason = ConnectionErrorReason.UnexpectedError; } finally { @@ -556,7 +561,7 @@ private void UpdateStreamTimeouts(long timestamp) } catch { - Abort(CreateConnectionAbortError(error, clientAbort), Http3ErrorCode.InternalError, ConnectionErrorReason.Other); + Abort(CreateConnectionAbortError(error, clientAbort), Http3ErrorCode.InternalError, ConnectionErrorReason.UnexpectedError); throw; } finally diff --git a/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs b/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs index b52ed2829aee..e73d5b96118d 100644 --- a/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs @@ -101,7 +101,7 @@ public HttpConnection(BaseHttpConnectionContext context) connectionHeartbeatFeature?.OnHeartbeat(state => ((HttpConnection)state).Tick(), this); // Register for graceful shutdown of the server - using var shutdownRegistration = connectionLifetimeNotificationFeature?.ConnectionClosedRequested.Register(state => ((HttpConnection)state!).StopProcessingNextRequest(), this); + using var shutdownRegistration = connectionLifetimeNotificationFeature?.ConnectionClosedRequested.Register(state => ((HttpConnection)state!).StopProcessingNextRequest(ConnectionErrorReason.ApplicationShutdown), this); // Register for connection close using var closedRegistration = _context.ConnectionContext.ConnectionClosed.Register(state => ((HttpConnection)state!).OnConnectionClosed(), this); @@ -137,7 +137,7 @@ internal void Initialize(IRequestProcessor requestProcessor) _protocolSelectionState = ProtocolSelectionState.Selected; } - private void StopProcessingNextRequest() + private void StopProcessingNextRequest(ConnectionErrorReason reason) { ProtocolSelectionState previousState; lock (_protocolSelectionLock) @@ -149,7 +149,7 @@ private void StopProcessingNextRequest() switch (previousState) { case ProtocolSelectionState.Selected: - _requestProcessor!.StopProcessingNextRequest(); + _requestProcessor!.StopProcessingNextRequest(reason); break; case ProtocolSelectionState.Aborted: break; @@ -265,7 +265,7 @@ public void OnTimeout(TimeoutReason reason) switch (reason) { case TimeoutReason.KeepAlive: - _requestProcessor!.StopProcessingNextRequest(); + _requestProcessor!.StopProcessingNextRequest(ConnectionErrorReason.KeepAliveTimeout); break; case TimeoutReason.RequestHeaders: _requestProcessor!.HandleRequestHeadersTimeout(); diff --git a/src/Servers/Kestrel/Core/src/Internal/IRequestProcessor.cs b/src/Servers/Kestrel/Core/src/Internal/IRequestProcessor.cs index e66e45d0191c..a6562b4fe0b9 100644 --- a/src/Servers/Kestrel/Core/src/Internal/IRequestProcessor.cs +++ b/src/Servers/Kestrel/Core/src/Internal/IRequestProcessor.cs @@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal; internal interface IRequestProcessor { Task ProcessRequestsAsync(IHttpApplication application) where TContext : notnull; - void StopProcessingNextRequest(); + void StopProcessingNextRequest(ConnectionErrorReason errorReason); void HandleRequestHeadersTimeout(); void HandleReadDataRateTimeout(); void OnInputOrOutputCompleted(); diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs index b42a12d1a72c..a63e429751d6 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs @@ -19,6 +19,8 @@ internal sealed class KestrelMetrics // Note: Dot separated instead of dash. public const string MeterName = "Microsoft.AspNetCore.Server.Kestrel"; + public const string KestrelConnectionErrorReason = "kestrel.connection.error_reason"; + public const string Http11 = "1.1"; public const string Http2 = "2"; public const string Http3 = "3"; diff --git a/src/Servers/Kestrel/Core/test/Http1/Http1ConnectionTests.cs b/src/Servers/Kestrel/Core/test/Http1/Http1ConnectionTests.cs index 274c51f6b972..45b125b30907 100644 --- a/src/Servers/Kestrel/Core/test/Http1/Http1ConnectionTests.cs +++ b/src/Servers/Kestrel/Core/test/Http1/Http1ConnectionTests.cs @@ -246,7 +246,7 @@ static bool TryReadResponse(ReadResult read, out SequencePosition consumed, out } Assert.Equal($"{connectionId}:{count:X8}", feature.TraceIdentifier); - _http1Connection.StopProcessingNextRequest(); + _http1Connection.StopProcessingNextRequest(ConnectionErrorReason.ApplicationShutdown); await requestProcessingTask.DefaultTimeout(); } @@ -572,7 +572,7 @@ public async Task ProcessRequestsAsyncEnablesKeepAliveTimeout() var expectedKeepAliveTimeout = _serviceContext.ServerOptions.Limits.KeepAliveTimeout; _timeoutControl.Verify(cc => cc.SetTimeout(expectedKeepAliveTimeout, TimeoutReason.KeepAlive)); - _http1Connection.StopProcessingNextRequest(); + _http1Connection.StopProcessingNextRequest(ConnectionErrorReason.ApplicationShutdown); _application.Output.Complete(); await requestProcessingTask.DefaultTimeout(); @@ -657,7 +657,7 @@ public async Task RequestProcessingTaskIsUnwrapped() var data = Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\nHost:\r\n\r\n"); await _application.Output.WriteAsync(data); - _http1Connection.StopProcessingNextRequest(); + _http1Connection.StopProcessingNextRequest(ConnectionErrorReason.ApplicationShutdown); Assert.IsNotType>(requestProcessingTask); await requestProcessingTask.DefaultTimeout(); diff --git a/src/Servers/Kestrel/test/FunctionalTests/RequestTests.cs b/src/Servers/Kestrel/test/FunctionalTests/RequestTests.cs index b4df79959dd0..32412a9dfaac 100644 --- a/src/Servers/Kestrel/test/FunctionalTests/RequestTests.cs +++ b/src/Servers/Kestrel/test/FunctionalTests/RequestTests.cs @@ -30,6 +30,8 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Xunit; +using Microsoft.Extensions.Diagnostics.Metrics.Testing; +using System.Diagnostics.Metrics; #if SOCKETS namespace Microsoft.AspNetCore.Server.Kestrel.Sockets.FunctionalTests; @@ -583,6 +585,9 @@ public async Task ThrowsOnReadAfterConnectionError() [Fact] public async Task RequestAbortedTokenFiredOnClientFIN() { + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + var appStarted = new SemaphoreSlim(0); var requestAborted = new SemaphoreSlim(0); var builder = TransportSelector.GetHostBuilder() @@ -600,7 +605,8 @@ public async Task RequestAbortedTokenFiredOnClientFIN() await requestAborted.WaitAsync().DefaultTimeout(); })); }) - .ConfigureServices(AddTestLogging); + .ConfigureServices(AddTestLogging) + .ConfigureServices(s => s.AddSingleton(testMeterFactory)); using (var host = builder.Build()) { @@ -617,6 +623,11 @@ public async Task RequestAbortedTokenFiredOnClientFIN() await host.StopAsync(); } + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => + { + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, m.Tags.Keys); + }); } [Fact] @@ -669,6 +680,9 @@ public async Task RequestAbortedTokenUnchangedOnAbort() [InlineData(false)] public async Task AbortingTheConnection(bool fin) { + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + var builder = TransportSelector.GetHostBuilder() .ConfigureWebHost(webHostBuilder => { @@ -688,7 +702,8 @@ public async Task AbortingTheConnection(bool fin) return Task.CompletedTask; })); }) - .ConfigureServices(AddTestLogging); + .ConfigureServices(AddTestLogging) + .ConfigureServices(s => s.AddSingleton(testMeterFactory)); using (var host = builder.Build()) { @@ -711,6 +726,11 @@ public async Task AbortingTheConnection(bool fin) await host.StopAsync(); } + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => + { + Assert.Equal(nameof(ConnectionErrorReason.AbortedByApplication), (string)m.Tags[KestrelMetrics.KestrelConnectionErrorReason]); + }); } [Theory] diff --git a/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs b/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs index eb01e1eebe09..b6be76a93b66 100644 --- a/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs +++ b/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs @@ -31,6 +31,7 @@ using Microsoft.Extensions.Primitives; using Moq; using Xunit; +using Microsoft.Extensions.Diagnostics.Metrics.Testing; #if SOCKETS namespace Microsoft.AspNetCore.Server.Kestrel.Sockets.FunctionalTests; @@ -207,6 +208,11 @@ public async Task ThrowsOnWriteWithRequestAbortedTokenAfterRequestIsAborted(List var requestAbortedWh = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var requestStartWh = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + + var testServiceContext = new TestServiceContext(LoggerFactory, metrics: new KestrelMetrics(testMeterFactory)); + await using (var server = new TestServer(async httpContext => { requestStartWh.SetResult(); @@ -233,7 +239,7 @@ public async Task ThrowsOnWriteWithRequestAbortedTokenAfterRequestIsAborted(List } writeTcs.SetException(new Exception("This shouldn't be reached.")); - }, new TestServiceContext(LoggerFactory), listenOptions)) + }, testServiceContext, listenOptions)) { using (var connection = server.CreateConnection()) { @@ -254,6 +260,11 @@ public async Task ThrowsOnWriteWithRequestAbortedTokenAfterRequestIsAborted(List // RequestAborted tripped await requestAbortedWh.Task.DefaultTimeout(); } + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => + { + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, m.Tags.Keys); + }); } [Theory] @@ -427,7 +438,10 @@ public async Task ClientAbortingConnectionImmediatelyIsNotLoggedHigherThanDebug( // There's no guarantee that the app even gets invoked in this test. The connection reset can be observed // as early as accept. - var testServiceContext = new TestServiceContext(LoggerFactory); + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + + var testServiceContext = new TestServiceContext(LoggerFactory, metrics: new KestrelMetrics(testMeterFactory)); await using (var server = new TestServer(context => Task.CompletedTask, testServiceContext, listenOptions)) { for (var i = 0; i < numConnections; i++) @@ -453,6 +467,11 @@ await using (var server = new TestServer(context => Task.CompletedTask, testServ Assert.Empty(transportLogs.Where(w => w.LogLevel > LogLevel.Debug)); Assert.Empty(coreLogs.Where(w => w.LogLevel > LogLevel.Information)); + + await connectionDuration.WaitForMeasurementsAsync(minCount: 1).DefaultTimeout(); + + var measurement = connectionDuration.GetMeasurementSnapshot().First(); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, measurement.Tags.Keys); } [Theory] @@ -492,7 +511,10 @@ public async Task ConnectionClosedWhenResponseDoesNotSatisfyMinimumDataRate(bool } }; - var testContext = new TestServiceContext(LoggerFactory) + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + + var testContext = new TestServiceContext(LoggerFactory, metrics: new KestrelMetrics(testMeterFactory)) { ServerOptions = { @@ -584,6 +606,11 @@ async Task App(HttpContext context) logger.LogInformation("Connection was aborted after {totalMilliseconds}ms.", sw.ElapsedMilliseconds); } } + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => + { + Assert.Equal(nameof(ConnectionErrorReason.ResponseMininumDataRateNotSatisfied), (string)m.Tags[KestrelMetrics.KestrelConnectionErrorReason]); + }); } [Theory] @@ -742,7 +769,10 @@ public async Task ConnectionClosedWhenBothRequestAndResponseExperienceBackPressu } }; - var testContext = new TestServiceContext(LoggerFactory) + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + + var testContext = new TestServiceContext(LoggerFactory, metrics: new KestrelMetrics(testMeterFactory)) { ServerOptions = { @@ -827,6 +857,11 @@ async Task App(HttpContext context) await AssertStreamAborted(connection.Stream, responseSize); } } + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => + { + Assert.Equal(nameof(ConnectionErrorReason.ResponseMininumDataRateNotSatisfied), (string)m.Tags[KestrelMetrics.KestrelConnectionErrorReason]); + }); } [ConditionalFact] @@ -1000,7 +1035,10 @@ public async Task ClientCanReceiveFullConnectionCloseResponseWithoutErrorAtALowD var requestAborted = false; var appFuncCompleted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var testContext = new TestServiceContext(LoggerFactory) + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + + var testContext = new TestServiceContext(LoggerFactory, metrics: new KestrelMetrics(testMeterFactory)) { ServerOptions = { @@ -1063,6 +1101,11 @@ await using (var server = new TestServer(App, testContext, listenOptions)) Assert.Equal(0, TestSink.Writes.Count(w => w.EventId.Name == "ResponseMinimumDataRateNotSatisfied")); Assert.Equal(1, TestSink.Writes.Count(w => w.EventId.Name == "ConnectionStop")); Assert.False(requestAborted); + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => + { + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, m.Tags.Keys); + }); } private async Task AssertStreamAborted(Stream stream, int totalBytes) diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs index c42ccee2fd8b..d078f50e6b3b 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs @@ -22,6 +22,7 @@ using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; using Moq; +using Microsoft.Extensions.Diagnostics.Metrics.Testing; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests; @@ -47,6 +48,7 @@ public async Task MaxConcurrentStreamsLogging_ReachLimit_MessageLogged() Assert.Equal(1, LogMessages.Count(m => m.EventId.Name == "Http2MaxConcurrentStreamsReached")); await StopConnectionAsync(expectedLastStreamId: 5, ignoreNonGoAwayFrames: false); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -79,6 +81,7 @@ public async Task FlowControl_NoAvailability_ResponseHeadersStillFlushed() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -157,6 +160,7 @@ public async Task FlowControl_OneStream_CorrectlyAwaited() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -206,6 +210,7 @@ public async Task RequestHeaderStringReuse_MultipleStreams_KnownHeaderReused() Assert.Same(path1, path2); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -273,6 +278,7 @@ public async Task RequestHeaderStringReuse_MultipleStreams_KnownHeaderClearedIfN Assert.Equal(StringValues.Empty, contentTypeValue2); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } private class ResponseTrailersWrapper : IHeaderDictionary @@ -390,6 +396,7 @@ public async Task ResponseTrailers_MultipleStreams_Reset() Assert.NotSame(trailersFirst, trailersLast); await StopConnectionAsync(expectedLastStreamId: 5, ignoreNonGoAwayFrames: false); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -423,6 +430,7 @@ public async Task StreamPool_SingleStream_ReturnedToPool() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -547,6 +555,7 @@ public async Task StreamPool_MultipleStreamsInSequence_PooledStreamReused() withStreamId: 3); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -614,6 +623,7 @@ public async Task StreamPool_EndedStreamErrorsOnStart_NotReturnedToPool() Assert.Equal(0, _connection.StreamPool.Count); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -643,6 +653,7 @@ public async Task StreamPool_UnendedStreamErrorsOnStart_NotReturnedToPool() Assert.Equal(0, _connection.StreamPool.Count); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -684,6 +695,7 @@ public async Task StreamPool_UnusedExpiredStream_RemovedFromPool() Assert.True(((Http2OutputProducer)pooledStream.Output)._disposed); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -701,6 +713,7 @@ public async Task Frame_Received_OverMaxSize_FrameError() expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorFrameOverLimit(length, Http2PeerSettings.MinAllowedMaxFrameSize)); + Assert.Equal(nameof(ConnectionErrorReason.MaxFrameLengthExceeded), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -733,6 +746,7 @@ public async Task ServerSettings_ChangesRequestMaxFrameSize() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -757,6 +771,7 @@ public async Task DATA_Received_ReadByStream() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); Assert.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.PayloadSequence.ToArray())); } @@ -784,6 +799,7 @@ public async Task DATA_Received_MaxSize_ReadByStream() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame.PayloadSequence.ToArray())); } @@ -878,6 +894,7 @@ public async Task DATA_Received_GreaterThanInitialWindowSize_ReadByStream() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); foreach (var frame in dataFrames) { @@ -917,6 +934,7 @@ public async Task DATA_Received_RightAtWindowLimit_DoesNotPausePipe() await SendDataAsync(1, new Memory(), endStream: true); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -947,6 +965,7 @@ public async Task DATA_Received_Multiple_ReadByStream() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); Assert.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.PayloadSequence.ToArray())); } @@ -1010,6 +1029,7 @@ public async Task DATA_Received_Multiplexed_ReadByStreams() withStreamId: 3); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); Assert.True(_helloBytes.AsSpan().SequenceEqual(stream1DataFrame1.PayloadSequence.ToArray())); Assert.True(_worldBytes.AsSpan().SequenceEqual(stream1DataFrame2.PayloadSequence.ToArray())); @@ -1122,6 +1142,7 @@ public async Task DATA_Received_Multiplexed_GreaterThanInitialWindowSize_ReadByS withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); foreach (var frame in dataFrames) { @@ -1200,6 +1221,7 @@ public async Task DATA_Received_Multiplexed_AppMustNotBlockOtherFrames() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Theory] @@ -1227,6 +1249,7 @@ public async Task DATA_Received_WithPadding_ReadByStream(byte padLength) withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); Assert.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.PayloadSequence.ToArray())); } @@ -1296,6 +1319,7 @@ public async Task DATA_Received_WithPadding_CountsTowardsInputFlowControl(byte p withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame3.PayloadSequence.ToArray())); @@ -1336,6 +1360,7 @@ public async Task DATA_Received_ButNotConsumedByApp_CountsTowardsInputFlowContro withStreamId: 0); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); var updateSize = ((framesConnectionInWindow / 2) + 1) * _maxData.Length; Assert.Equal(updateSize, connectionWindowUpdateFrame.WindowUpdateSizeIncrement); @@ -1427,6 +1452,7 @@ public async Task DATA_BufferRequestBodyLargerThanStreamSizeSmallerThanConnectio withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); foreach (var frame in dataFrames) { @@ -1451,6 +1477,7 @@ public async Task DATA_Received_StreamIdZero_ConnectionError() expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdZero(Http2FrameType.DATA)); + Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -1465,6 +1492,7 @@ public async Task DATA_Received_StreamIdEven_ConnectionError() expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdEven(Http2FrameType.DATA, streamId: 2)); + Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -1480,6 +1508,7 @@ public async Task DATA_Received_PaddingEqualToFramePayloadLength_ConnectionError expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorPaddingTooLong(Http2FrameType.DATA)); + Assert.Equal(nameof(ConnectionErrorReason.InvalidDataPadding), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -1495,6 +1524,7 @@ public async Task DATA_Received_PaddingGreaterThanFramePayloadLength_ConnectionE expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorPaddingTooLong(Http2FrameType.DATA)); + Assert.Equal(nameof(ConnectionErrorReason.InvalidDataPadding), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -1510,6 +1540,7 @@ public async Task DATA_Received_FrameLengthZeroPaddingZero_ConnectionError() expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.DATA, expectedLength: 1)); + Assert.Equal(nameof(ConnectionErrorReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -1525,6 +1556,7 @@ public async Task DATA_Received_InterleavedWithHeaders_ConnectionError() expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.DATA, streamId: 1, headersStreamId: 1)); + Assert.Equal(nameof(ConnectionErrorReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -1539,6 +1571,7 @@ public async Task DATA_Received_StreamIdle_ConnectionError() expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdle(Http2FrameType.DATA, streamId: 1)); + Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -1556,6 +1589,7 @@ public async Task DATA_Received_StreamHalfClosedRemote_ConnectionError() expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.DATA, streamId: 1)); + Assert.Equal(nameof(ConnectionErrorReason.ReceivedFrameAfterStreamClose), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -1581,6 +1615,7 @@ public async Task DATA_Received_StreamClosed_ConnectionError() CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.DATA, streamId: 1), CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.DATA, streamId: 1) }); + Assert.Equal(nameof(ConnectionErrorReason.ReceivedFrameUnknownStream), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -1615,6 +1650,7 @@ public async Task Frame_MultipleStreams_CanBeCreatedIfClientCountIsLessThanActua firstRequestBlock.SetResult(); await StopConnectionAsync(3, ignoreNonGoAwayFrames: false); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -1627,6 +1663,7 @@ public async Task MaxTrackedStreams_SmallMaxConcurrentStreams_LowerLimitOf100Asy Assert.Equal((uint)100, _connection.MaxTrackedStreams); await StopConnectionAsync(0, ignoreNonGoAwayFrames: false); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -1639,6 +1676,7 @@ public async Task MaxTrackedStreams_DefaultMaxConcurrentStreams_DoubleLimit() Assert.Equal((uint)200, _connection.MaxTrackedStreams); await StopConnectionAsync(0, ignoreNonGoAwayFrames: false); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -1651,6 +1689,7 @@ public async Task MaxTrackedStreams_LargeMaxConcurrentStreams_DoubleLimit() Assert.Equal((uint)int.MaxValue * 2, _connection.MaxTrackedStreams); await StopConnectionAsync(0, ignoreNonGoAwayFrames: false); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -1661,6 +1700,7 @@ public void MinimumMaxTrackedStreams() // Kestrel always tracks at least 100 streams Assert.Equal(100u, _connection.MaxTrackedStreams); _connection.Abort(new ConnectionAbortedException(), ConnectionErrorReason.AbortedByApplication); + Assert.Equal(nameof(ConnectionErrorReason.AbortedByApplication), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -1729,6 +1769,8 @@ public async Task AbortConnectionAfterTooManyEnhanceYourCalms() expectedLastStreamId: int.MaxValue, expectedErrorCode: Http2ErrorCode.ENHANCE_YOUR_CALM, expectedErrorMessage: CoreStrings.Http2ConnectionFaulted); + + Assert.Equal(nameof(ConnectionErrorReason.StreamResetLimitExceeded), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } private async Task RequestUntilEnhanceYourCalm(int maxStreamsPerConnection, int sentStreams) @@ -1758,6 +1800,7 @@ private async Task RequestUntilEnhanceYourCalm(int maxStreamsPerConnection, int tcs.SetResult(); await StopConnectionAsync(streamId, ignoreNonGoAwayFrames: false); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -1786,6 +1829,8 @@ public async Task DATA_Received_StreamClosedImplicitly_ConnectionError() expectedLastStreamId: 3, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.DATA, streamId: 1)); + + Assert.Equal(nameof(ConnectionErrorReason.ReceivedFrameUnknownStream), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -1808,6 +1853,8 @@ public async Task DATA_Received_NoStreamWindowSpace_ConnectionError() expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.FLOW_CONTROL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorFlowControlWindowExceeded); + + Assert.Equal(nameof(ConnectionErrorReason.FlowControlWindowExceeded), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -1837,6 +1884,8 @@ public async Task DATA_Received_NoConnectionWindowSpace_ConnectionError() expectedLastStreamId: 3, expectedErrorCode: Http2ErrorCode.FLOW_CONTROL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorFlowControlWindowExceeded); + + Assert.Equal(nameof(ConnectionErrorReason.FlowControlWindowExceeded), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -1918,6 +1967,8 @@ public async Task DATA_Sent_DespiteConnectionOutputFlowControl_IfEmptyAndEndsStr await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); await WaitForAllStreamsAsync(); + + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -2002,6 +2053,8 @@ public async Task DATA_Sent_DespiteStreamOutputFlowControl_IfEmptyAndEndsStream( withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -2054,6 +2107,8 @@ public async Task StreamWindow_BiggerThan_ConnectionWindow() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -2071,6 +2126,8 @@ public async Task HEADERS_Received_Decoded() VerifyDecodedRequestHeaders(_browserRequestHeaders); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Theory] @@ -2091,6 +2148,8 @@ public async Task HEADERS_Received_WithPadding_Decoded(byte padLength) VerifyDecodedRequestHeaders(_browserRequestHeaders); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -2108,6 +2167,8 @@ public async Task HEADERS_Received_WithPriority_Decoded() VerifyDecodedRequestHeaders(_browserRequestHeaders); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Theory] @@ -2128,6 +2189,8 @@ public async Task HEADERS_Received_WithPriorityAndPadding_Decoded(byte padLength VerifyDecodedRequestHeaders(_browserRequestHeaders); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Theory] @@ -2174,6 +2237,8 @@ public async Task HEADERS_Received_WithTrailers_Available(bool sendData) } await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); + + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -2206,6 +2271,8 @@ public async Task HEADERS_Received_ContainsExpect100Continue_100ContinueSent() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -2255,6 +2322,8 @@ public async Task HEADERS_Received_AppCannotBlockOtherFrames() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); + + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -2289,6 +2358,8 @@ public async Task HEADERS_HeaderTableSizeLimitZero_Received_DynamicTableUpdate() withStreamId: 3); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); + + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -2447,6 +2518,8 @@ public async Task HEADERS_Received_StreamIdZero_ConnectionError() expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdZero(Http2FrameType.HEADERS)); + + Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -2461,6 +2534,8 @@ public async Task HEADERS_Received_StreamIdEven_ConnectionError() expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdEven(Http2FrameType.HEADERS, streamId: 2)); + + Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -2487,6 +2562,8 @@ public async Task HEADERS_Received_StreamClosed_ConnectionError() CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1), CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.HEADERS, streamId: 1) }); + + Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -2504,6 +2581,8 @@ public async Task HEADERS_Received_StreamHalfClosedRemote_ConnectionError() expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.HEADERS, streamId: 1)); + + Assert.Equal(nameof(ConnectionErrorReason.ReceivedFrameAfterStreamClose), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -2526,6 +2605,8 @@ public async Task HEADERS_Received_StreamClosedImplicitly_ConnectionError() expectedLastStreamId: 3, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1)); + + Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Theory] @@ -2543,6 +2624,8 @@ public async Task HEADERS_Received_PaddingEqualToFramePayloadLength_ConnectionEr expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorPaddingTooLong(Http2FrameType.HEADERS)); + + Assert.Equal(nameof(ConnectionErrorReason.InvalidDataPadding), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -2557,6 +2640,8 @@ public async Task HEADERS_Received_PaddingFieldMissing_ConnectionError() expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.HEADERS, expectedLength: 1)); + + Assert.Equal(nameof(ConnectionErrorReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Theory] @@ -2573,6 +2658,8 @@ public async Task HEADERS_Received_PaddingGreaterThanFramePayloadLength_Connecti expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorPaddingTooLong(Http2FrameType.HEADERS)); + + Assert.Equal(nameof(ConnectionErrorReason.InvalidDataPadding), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -2588,6 +2675,8 @@ public async Task HEADERS_Received_InterleavedWithHeaders_ConnectionError() expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.HEADERS, streamId: 3, headersStreamId: 1)); + + Assert.Equal(nameof(ConnectionErrorReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -2602,6 +2691,8 @@ public async Task HEADERS_Received_WithPriority_StreamDependencyOnSelf_Connectio expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamSelfDependency(Http2FrameType.HEADERS, streamId: 1)); + + Assert.Equal(nameof(ConnectionErrorReason.StreamSelfDependency), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -2616,6 +2707,8 @@ public async Task HEADERS_Received_IncompleteHeaderBlock_ConnectionError() expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.COMPRESSION_ERROR, expectedErrorMessage: SR.net_http_hpack_incomplete_header_block); + + Assert.Equal(nameof(ConnectionErrorReason.ErrorReadingHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -2644,6 +2737,8 @@ public async Task HEADERS_Received_IntegerOverLimit_ConnectionError() expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.COMPRESSION_ERROR, expectedErrorMessage: SR.net_http_hpack_bad_integer); + + Assert.Equal(nameof(ConnectionErrorReason.ErrorReadingHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Theory] @@ -2660,6 +2755,8 @@ public async Task HEADERS_Received_WithTrailers_ContainsIllegalTrailer_Connectio expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: expectedErrorMessage); + + Assert.Equal(nameof(ConnectionErrorReason.InvalidRequestHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Theory] @@ -2678,6 +2775,8 @@ public async Task HEADERS_Received_WithTrailers_EndStreamNotSet_ConnectionError( expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorHeadersWithTrailersNoEndStream); + + Assert.Equal(nameof(ConnectionErrorReason.MissingStreamEnd), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Theory] @@ -2692,6 +2791,8 @@ public async Task HEADERS_Received_HeaderNameContainsUpperCaseCharacter_Connecti expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.HttpErrorHeaderNameUppercase); + + Assert.Equal(nameof(ConnectionErrorReason.InvalidRequestHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -2743,6 +2844,8 @@ public async Task HEADERS_Received_HeaderBlockDoesNotContainMandatoryPseudoHeade withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Theory] @@ -2761,6 +2864,8 @@ private async Task HEADERS_Received_InvalidHeaderFields_ConnectionError(IEnumera expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: expectedErrorMessage); + + Assert.Equal(nameof(ConnectionErrorReason.InvalidRequestHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Theory] @@ -2782,6 +2887,8 @@ public async Task HEADERS_Received_HeaderBlockDoesNotContainMandatoryPseudoHeade expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1)); + + Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -2896,6 +3003,8 @@ public async Task HEADERS_Received_HeaderBlockContainsTEHeader_ValueIsTrailers_N withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -2914,6 +3023,8 @@ public async Task HEADERS_Received_RequestLineLength_StreamError() await WaitForStreamErrorAsync(1, Http2ErrorCode.PROTOCOL_ERROR, CoreStrings.BadRequest_RequestLineTooLong); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -2955,6 +3066,8 @@ public async Task PRIORITY_Received_StreamIdZero_ConnectionError() expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdZero(Http2FrameType.PRIORITY)); + + Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -2969,6 +3082,8 @@ public async Task PRIORITY_Received_StreamIdEven_ConnectionError() expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdEven(Http2FrameType.PRIORITY, streamId: 2)); + + Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Theory] @@ -2985,6 +3100,8 @@ public async Task PRIORITY_Received_LengthNotFive_ConnectionError(int length) expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.PRIORITY, expectedLength: 5)); + + Assert.Equal(nameof(ConnectionErrorReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -3000,6 +3117,8 @@ public async Task PRIORITY_Received_InterleavedWithHeaders_ConnectionError() expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.PRIORITY, streamId: 1, headersStreamId: 1)); + + Assert.Equal(nameof(ConnectionErrorReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -3014,6 +3133,8 @@ public async Task PRIORITY_Received_StreamDependencyOnSelf_ConnectionError() expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamSelfDependency(Http2FrameType.PRIORITY, 1)); + + Assert.Equal(nameof(ConnectionErrorReason.StreamSelfDependency), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -3134,6 +3255,8 @@ public async Task RST_STREAM_Received_ContinuesAppsAwaitingConnectionOutputFlowC await WaitForAllStreamsAsync(); Assert.Contains(1, _abortedStreamIds); Assert.Contains(3, _abortedStreamIds); + + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -3218,6 +3341,8 @@ async Task VerifyStreamBackpressure(int streamId, int headersLength) Assert.Contains(1, _abortedStreamIds); Assert.Contains(3, _abortedStreamIds); Assert.Contains(5, _abortedStreamIds); + + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -3266,6 +3391,8 @@ public async Task RST_STREAM_Received_StreamIdZero_ConnectionError() expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdZero(Http2FrameType.RST_STREAM)); + + Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -3280,6 +3407,8 @@ public async Task RST_STREAM_Received_StreamIdEven_ConnectionError() expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdEven(Http2FrameType.RST_STREAM, streamId: 2)); + + Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -3294,6 +3423,8 @@ public async Task RST_STREAM_Received_StreamIdle_ConnectionError() expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdle(Http2FrameType.RST_STREAM, streamId: 1)); + + Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Theory] @@ -3313,6 +3444,8 @@ public async Task RST_STREAM_Received_LengthNotFour_ConnectionError(int length) expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.RST_STREAM, expectedLength: 4)); + + Assert.Equal(nameof(ConnectionErrorReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -3328,6 +3461,8 @@ public async Task RST_STREAM_Received_InterleavedWithHeaders_ConnectionError() expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.RST_STREAM, streamId: 1, headersStreamId: 1)); + + Assert.Equal(nameof(ConnectionErrorReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } // Compare to h2spec http2/5.1/8 @@ -3353,6 +3488,8 @@ public async Task RST_STREAM_IncompleteRequest_AdditionalDataFrames_ConnectionAb await WaitForConnectionErrorAsync(ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, Http2ErrorCode.STREAM_CLOSED, CoreStrings.FormatHttp2ErrorStreamAborted(Http2FrameType.DATA, 1)); + + Assert.Equal(nameof(ConnectionErrorReason.ReceivedFrameAfterStreamClose), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -3377,6 +3514,8 @@ public async Task RST_STREAM_IncompleteRequest_AdditionalTrailerFrames_Connectio await WaitForConnectionErrorAsync(ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, Http2ErrorCode.STREAM_CLOSED, CoreStrings.FormatHttp2ErrorStreamAborted(Http2FrameType.HEADERS, 1)); + + Assert.Equal(nameof(ConnectionErrorReason.ReceivedFrameAfterStreamClose), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -3399,6 +3538,8 @@ public async Task RST_STREAM_IncompleteRequest_AdditionalResetFrame_IgnoreAdditi tcs.TrySetResult(); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/53744")] @@ -3473,6 +3614,8 @@ public async Task SETTINGS_KestrelDefaults_Sent() withStreamId: 0); await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false); + + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -3532,6 +3675,8 @@ public async Task SETTINGS_Custom_Sent() withStreamId: 0); await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false); + + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -3567,6 +3712,8 @@ public async Task SETTINGS_Received_StreamIdNotZero_ConnectionError() expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdNotZero(Http2FrameType.SETTINGS)); + + Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Theory] @@ -3593,6 +3740,8 @@ public async Task SETTINGS_Received_InvalidParameterValue_ConnectionError(int in expectedLastStreamId: 0, expectedErrorCode: expectedErrorCode, expectedErrorMessage: CoreStrings.FormatHttp2ErrorSettingsParameterOutOfRange(parameter)); + + Assert.Equal(nameof(ConnectionErrorReason.InvalidSettings), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -3608,6 +3757,8 @@ public async Task SETTINGS_Received_InterleavedWithHeaders_ConnectionError() expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.SETTINGS, streamId: 0, headersStreamId: 1)); + + Assert.Equal(nameof(ConnectionErrorReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Theory] @@ -3624,6 +3775,8 @@ public async Task SETTINGS_Received_WithACK_LengthNotZero_ConnectionError(int le expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorSettingsAckLengthNotZero); + + Assert.Equal(nameof(ConnectionErrorReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Theory] @@ -3643,6 +3796,8 @@ public async Task SETTINGS_Received_LengthNotMultipleOfSix_ConnectionError(int l expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorSettingsLengthNotMultipleOfSix); + + Assert.Equal(nameof(ConnectionErrorReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -3667,6 +3822,8 @@ public async Task SETTINGS_Received_WithInitialWindowSizePushingStreamWindowOver expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.FLOW_CONTROL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorInitialWindowSizeInvalid); + + Assert.Equal(nameof(ConnectionErrorReason.InvalidSettings), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -3724,6 +3881,8 @@ public async Task SETTINGS_Received_ChangesAllowedResponseMaxFrameSize() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -3763,6 +3922,8 @@ public async Task SETTINGS_Received_ClientMaxFrameSizeCannotExceedServerMaxFrame withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -3844,6 +4005,8 @@ public async Task PUSH_PROMISE_Received_ConnectionError() expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorPushPromiseReceived); + + Assert.Equal(nameof(ConnectionErrorReason.ReceivedUnsupportedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -3883,6 +4046,8 @@ public async Task PING_Received_InterleavedWithHeaders_ConnectionError() expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.PING, streamId: 0, headersStreamId: 1)); + + Assert.Equal(nameof(ConnectionErrorReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -3897,6 +4062,8 @@ public async Task PING_Received_StreamIdNotZero_ConnectionError() expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdNotZero(Http2FrameType.PING)); + + Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Theory] @@ -3915,6 +4082,8 @@ public async Task PING_Received_LengthNotEight_ConnectionError(int length) expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.PING, expectedLength: 8)); + + Assert.Equal(nameof(ConnectionErrorReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -3925,6 +4094,8 @@ public async Task GOAWAY_Received_ConnectionStops() await SendGoAwayAsync(); await WaitForConnectionStopAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false); + + Assert.Equal(nameof(ConnectionErrorReason.ClientGoAway), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -3938,6 +4109,8 @@ public async Task GOAWAY_Received_ConnectionLifetimeNotification_Cancelled() Assert.True(lifetime.ConnectionClosedRequested.IsCancellationRequested); await WaitForConnectionStopAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false); + + Assert.Equal(nameof(ConnectionErrorReason.ClientGoAway), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -3983,6 +4156,8 @@ public async Task GOAWAY_Received_SetsConnectionStateToClosingAndWaitForAllStrea TriggerTick(); await WaitForConnectionStopAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); await _closedStateReached.Task.DefaultTimeout(); + + Assert.Equal(nameof(ConnectionErrorReason.ClientGoAway), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -4086,6 +4261,8 @@ public async Task GOAWAY_Received_ContinuesAppsAwaitingConnectionOutputFlowContr Assert.Contains(1, _abortedStreamIds); Assert.Contains(3, _abortedStreamIds); Assert.Contains(5, _abortedStreamIds); + + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -4163,6 +4340,8 @@ async Task VerifyStreamBackpressure(int streamId, int headersLength) Assert.Contains(1, _abortedStreamIds); Assert.Contains(3, _abortedStreamIds); Assert.Contains(5, _abortedStreamIds); + + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -4177,6 +4356,8 @@ public async Task GOAWAY_Received_StreamIdNotZero_ConnectionError() expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdNotZero(Http2FrameType.GOAWAY)); + + Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -4192,6 +4373,8 @@ public async Task GOAWAY_Received_InterleavedWithHeaders_ConnectionError() expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.GOAWAY, streamId: 0, headersStreamId: 1)); + + Assert.Equal(nameof(ConnectionErrorReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -4206,6 +4389,8 @@ public async Task WINDOW_UPDATE_Received_StreamIdEven_ConnectionError() expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdEven(Http2FrameType.WINDOW_UPDATE, streamId: 2)); + + Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -4221,6 +4406,8 @@ public async Task WINDOW_UPDATE_Received_InterleavedWithHeaders_ConnectionError( expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.WINDOW_UPDATE, streamId: 1, headersStreamId: 1)); + + Assert.Equal(nameof(ConnectionErrorReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Theory] @@ -4239,6 +4426,8 @@ public async Task WINDOW_UPDATE_Received_LengthNotFour_ConnectionError(int strea expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.WINDOW_UPDATE, expectedLength: 4)); + + Assert.Equal(nameof(ConnectionErrorReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -4253,6 +4442,8 @@ public async Task WINDOW_UPDATE_Received_OnConnection_SizeIncrementZero_Connecti expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorWindowUpdateIncrementZero); + + Assert.Equal(nameof(ConnectionErrorReason.WindowUpdateSizeInvalid), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -4268,6 +4459,8 @@ public async Task WINDOW_UPDATE_Received_OnStream_SizeIncrementZero_ConnectionEr expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorWindowUpdateIncrementZero); + + Assert.Equal(nameof(ConnectionErrorReason.WindowUpdateSizeInvalid), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -4282,6 +4475,8 @@ public async Task WINDOW_UPDATE_Received_StreamIdle_ConnectionError() expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdle(Http2FrameType.WINDOW_UPDATE, streamId: 1)); + + Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -4299,6 +4494,8 @@ public async Task WINDOW_UPDATE_Received_OnConnection_IncreasesWindowAboveMaxVal expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.FLOW_CONTROL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorWindowUpdateSizeInvalid); + + Assert.Equal(nameof(ConnectionErrorReason.WindowUpdateSizeInvalid), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -4318,6 +4515,8 @@ public async Task WINDOW_UPDATE_Received_OnStream_IncreasesWindowAboveMaxValue_S expectedErrorMessage: CoreStrings.Http2ErrorWindowUpdateSizeInvalid); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -4409,6 +4608,8 @@ public async Task WINDOW_UPDATE_Received_OnConnection_Respected() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -4596,6 +4797,8 @@ public async Task CONTINUATION_Received_StreamIdMismatch_ConnectionError() expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.CONTINUATION, streamId: 3, headersStreamId: 1)); + + Assert.Equal(nameof(ConnectionErrorReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -4611,6 +4814,8 @@ public async Task CONTINUATION_Received_IncompleteHeaderBlock_ConnectionError() expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.COMPRESSION_ERROR, expectedErrorMessage: SR.net_http_hpack_incomplete_header_block); + + Assert.Equal(nameof(ConnectionErrorReason.ErrorReadingHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Theory] @@ -4628,6 +4833,8 @@ public async Task CONTINUATION_Received_WithTrailers_ContainsIllegalTrailer_Conn expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: expectedErrorMessage); + + Assert.Equal(nameof(ConnectionErrorReason.InvalidRequestHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Theory] @@ -4651,6 +4858,8 @@ public async Task CONTINUATION_Received_HeaderBlockDoesNotContainMandatoryPseudo expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1)); + + Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Theory] @@ -4668,6 +4877,8 @@ public async Task CONTINUATION_Received_HeaderBlockDoesNotContainMandatoryPseudo withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -4708,6 +4919,8 @@ public async Task CONTINUATION_Sent_WhenHeadersLargerThanFrameLength() Assert.Equal(_4kHeaderValue, _decodedHeaders["f"]); Assert.Equal(_4kHeaderValue, _decodedHeaders["g"]); Assert.Equal(_4kHeaderValue, _decodedHeaders["h"]); + + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -4725,6 +4938,8 @@ public async Task UnknownFrameType_Received_Ignored() withStreamId: 0); await StopConnectionAsync(0, ignoreNonGoAwayFrames: false); + + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -4740,6 +4955,8 @@ public async Task UnknownFrameType_Received_InterleavedWithHeaders_ConnectionErr expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(frameType: 42, streamId: 1, headersStreamId: 1)); + + Assert.Equal(nameof(ConnectionErrorReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -4765,6 +4982,8 @@ public async Task ConnectionErrorAbortsAllStreams() Assert.Contains(1, _abortedStreamIds); Assert.Contains(3, _abortedStreamIds); Assert.Contains(5, _abortedStreamIds); + + Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -4778,6 +4997,8 @@ public async Task ConnectionResetLoggedWithActiveStreams() await StopConnectionAsync(1, ignoreNonGoAwayFrames: false); Assert.Single(LogMessages, m => m.Exception is ConnectionResetException); + + Assert.Equal(nameof(ConnectionErrorReason.ConnectionReset), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -4789,6 +5010,8 @@ public async Task ConnectionResetNotLoggedWithNoActiveStreams() await WaitForConnectionStopAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false); Assert.DoesNotContain(LogMessages, m => m.Exception is ConnectionResetException); + + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -4802,6 +5025,8 @@ public async Task OnInputOrOutputCompletedCompletesOutput() var result = await _pair.Application.Input.ReadAsync().AsTask().DefaultTimeout(); Assert.True(result.IsCompleted); Assert.True(result.Buffer.IsEmpty); + + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -4813,6 +5038,8 @@ public async Task AbortSendsFinalGOAWAY() await _closedStateReached.Task.DefaultTimeout(); VerifyGoAway(await ReceiveFrameAsync(), int.MaxValue, Http2ErrorCode.INTERNAL_ERROR); + + Assert.Equal(nameof(ConnectionErrorReason.AbortedByApplication), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -4825,6 +5052,8 @@ public async Task CompletionSendsFinalGOAWAY() await _closedStateReached.Task.DefaultTimeout(); VerifyGoAway(await ReceiveFrameAsync(), 0, Http2ErrorCode.NO_ERROR); + + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -4869,6 +5098,8 @@ public async Task StopProcessingNextRequestSendsGracefulGOAWAYAndWaitsForStreams _pair.Application.Input.CancelPendingRead(); result = await readTask; Assert.True(result.IsCompleted); + + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -4878,7 +5109,7 @@ public async Task StopProcessingNextRequestSendsGracefulGOAWAYThenFinalGOAWAYWhe await StartStreamAsync(1, _browserRequestHeaders, endStream: false); - _connection.StopProcessingNextRequest(); + _connection.StopProcessingNextRequest(ConnectionErrorReason.ApplicationShutdown); await _closingStateReached.Task.DefaultTimeout(); VerifyGoAway(await ReceiveFrameAsync(), Int32.MaxValue, Http2ErrorCode.NO_ERROR); @@ -4900,6 +5131,8 @@ public async Task StopProcessingNextRequestSendsGracefulGOAWAYThenFinalGOAWAYWhe TriggerTick(); await _closedStateReached.Task.DefaultTimeout(); VerifyGoAway(await ReceiveFrameAsync(), 1, Http2ErrorCode.NO_ERROR); + + Assert.Equal(nameof(ConnectionErrorReason.ApplicationShutdown), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -4910,7 +5143,7 @@ public async Task AcceptNewStreamsDuringClosingConnection() await StartStreamAsync(1, _browserRequestHeaders, endStream: false); - _connection.StopProcessingNextRequest(); + _connection.StopProcessingNextRequest(ConnectionErrorReason.ApplicationShutdown); VerifyGoAway(await ReceiveFrameAsync(), Int32.MaxValue, Http2ErrorCode.NO_ERROR); await _closingStateReached.Task.DefaultTimeout(); @@ -4947,6 +5180,8 @@ public async Task AcceptNewStreamsDuringClosingConnection() TriggerTick(); await WaitForConnectionStopAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); + + Assert.Equal(nameof(ConnectionErrorReason.ApplicationShutdown), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -4967,6 +5202,8 @@ public async Task IgnoreNewStreamsDuringClosedConnection() var result = await _pair.Application.Input.ReadAsync().AsTask().DefaultTimeout(); Assert.True(result.IsCompleted); Assert.True(result.Buffer.IsEmpty); + + Assert.Equal(nameof(ConnectionErrorReason.ConnectionReset), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -4985,6 +5222,8 @@ public async Task IOExceptionDuringFrameProcessingIsNotLoggedHigherThanDebug() Assert.Equal("Connection id \"TestConnectionId\" request processing ended abnormally.", logMessage.Message); Assert.Same(ioException, logMessage.Exception); + + Assert.Equal(nameof(ConnectionErrorReason.IOError), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -5002,6 +5241,8 @@ public async Task UnexpectedExceptionDuringFrameProcessingLoggedAWarning() Assert.Equal(LogLevel.Warning, logMessage.LogLevel); Assert.Equal(CoreStrings.RequestProcessingEndError, logMessage.Message); Assert.Same(exception, logMessage.Exception); + + Assert.Equal(nameof(ConnectionErrorReason.UnexpectedError), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Theory] @@ -5053,6 +5294,8 @@ public async Task AppDoesNotReadRequestBody_ResetsAndDrainsRequest(int intFinalF } await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Theory] @@ -5097,6 +5340,8 @@ public async Task AbortedStream_ResetsAndDrainsRequest(int intFinalFrameType) } await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Theory] @@ -5141,6 +5386,8 @@ public async Task ResetStream_ResetsAndDrainsRequest(int intFinalFrameType) } await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Theory] @@ -5225,6 +5472,8 @@ async Task CompletePipeOnTaskCompletion() } await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -5295,6 +5544,8 @@ async Task CompletePipeOnTaskCompletion() await WaitForStreamErrorAsync(7, Http2ErrorCode.REFUSED_STREAM, "HTTP/2 stream ID 7 error (REFUSED_STREAM): A new stream was refused because this connection has reached its stream limit."); requestBlock.SetResult(); await StopConnectionAsync(expectedLastStreamId: 7, ignoreNonGoAwayFrames: true); + + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -5339,6 +5590,7 @@ public async Task FramesInBatchAreStillProcessedAfterStreamError_WithoutHeartbea Assert.Equal(streamPayload, streamResponse); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: true); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Theory] @@ -5377,6 +5629,7 @@ public async Task AbortedStream_ResetsAndDrainsRequest_RefusesFramesAfterEndOfSt CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.DATA, streamId: 1), CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.DATA, streamId: 1) }); + Assert.Equal(nameof(ConnectionErrorReason.ReceivedFrameUnknownStream), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); break; case Http2FrameType.HEADERS: @@ -5393,6 +5646,7 @@ public async Task AbortedStream_ResetsAndDrainsRequest_RefusesFramesAfterEndOfSt CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1), CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.HEADERS, streamId: 1) }); + Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); break; case Http2FrameType.CONTINUATION: @@ -5410,6 +5664,7 @@ public async Task AbortedStream_ResetsAndDrainsRequest_RefusesFramesAfterEndOfSt CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1), CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.HEADERS, streamId: 1) }); + Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); break; default: throw new NotImplementedException(finalFrameType.ToString()); @@ -5461,6 +5716,20 @@ public async Task AbortedStream_ResetsAndDrainsRequest_RefusesFramesAfterClientR CoreStrings.FormatHttp2ErrorStreamClosed(finalFrameType, streamId: 1), CoreStrings.FormatHttp2ErrorStreamAborted(finalFrameType, streamId: 1) }); + + switch (finalFrameType) + { + case Http2FrameType.DATA: + Assert.Equal(nameof(ConnectionErrorReason.ReceivedFrameUnknownStream), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + break; + + case Http2FrameType.HEADERS: + Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + break; + + default: + throw new NotImplementedException(finalFrameType.ToString()); + } } [Fact] @@ -5476,6 +5745,7 @@ public async Task StartConnection_SendPreface_ReturnSettings() withStreamId: 0); await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: true); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -5489,6 +5759,8 @@ public async Task StartConnection_SendHttp1xRequest_ReturnHttp11Status400() Assert.NotNull(Http2Connection.InvalidHttp1xErrorResponseBytes); Assert.Equal(Http2Connection.InvalidHttp1xErrorResponseBytes, data); + Assert.Equal(nameof(ConnectionErrorReason.InvalidHttpVersion), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(Http2ErrorCode.PROTOCOL_ERROR, (Http2ErrorCode)_errorCodeFeature.Error); } [Fact] @@ -5503,6 +5775,7 @@ public async Task StartConnection_SendHttp1xRequest_ExceedsRequestLineLimit_Prot expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorInvalidPreface); + Assert.Equal(nameof(ConnectionErrorReason.InvalidHandshake), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -5519,6 +5792,7 @@ public async Task StartTlsConnection_SendHttp1xRequest_NoError() await SendAsync(Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\n")); await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } [Fact] @@ -5527,6 +5801,7 @@ public async Task StartConnection_SendNothing_NoError() InitializeConnectionWithoutPreface(_noopApplication); await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); } public static TheoryData UpperCaseHeaderNameData diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs index 37fb814935cc..181b90ca0129 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs @@ -155,10 +155,14 @@ public class Http2TestBase : TestApplicationErrorLoggerLoggedTest, IDisposable, internal TestServiceContext _serviceContext; internal DuplexPipe.DuplexPipePair _pair; + internal IConnectionMetricsTagsFeature _metricsTagsFeature; + internal IProtocolErrorCodeFeature _errorCodeFeature; internal Http2Connection _connection; protected Task _connectionTask; protected long _bytesReceived; + internal IDictionary ConnectionTags => _metricsTagsFeature.Tags.ToDictionary(t => t.Key, t => t.Value); + public Http2TestBase() { _hpackDecoder = new HPackDecoder((int)_clientSettings.HeaderTableSize, MaxRequestHeaderFieldSize); @@ -458,9 +462,13 @@ protected void CreateConnection() _pair = DuplexPipe.CreateConnectionPair(inputPipeOptions, outputPipeOptions); + _metricsTagsFeature = new TestConnectionMetricsTagsFeature(); + _errorCodeFeature = new TestProtocolErrorCodeFeature(); + var features = new FeatureCollection(); features.Set(new TestConnectionMetricsContextFeature()); - features.Set(new TestProtocolErrorCodeFeature()); + features.Set(_metricsTagsFeature); + features.Set(_errorCodeFeature); _mockConnectionContext.Setup(x => x.Features).Returns(features); var httpConnectionContext = TestContextFactory.CreateHttpConnectionContext( serviceContext: _serviceContext, @@ -481,6 +489,11 @@ protected void CreateConnection() _timeoutControl.Initialize(); } + private sealed class TestConnectionMetricsTagsFeature : IConnectionMetricsTagsFeature + { + public ICollection> Tags { get; } = new List>(); + } + private class TestConnectionMetricsContextFeature : IConnectionMetricsContextFeature { public ConnectionMetricsContext MetricsContext { get; } diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs index 74f390495468..56709c313f56 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs @@ -99,6 +99,7 @@ public async Task HEADERS_NotReceivedAfterFirstRequest_WithinKeepAliveTimeout_Cl _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.KeepAlive), Times.Once); await WaitForConnectionStopAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + Assert.Equal(nameof(ConnectionErrorReason.KeepAliveTimeout), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); _mockTimeoutHandler.VerifyNoOtherCalls(); } @@ -193,6 +194,7 @@ public async Task HEADERS_ReceivedWithoutAllCONTINUATIONs_WithinRequestHeadersTi expectedLastStreamId: int.MaxValue, Http2ErrorCode.INTERNAL_ERROR, CoreStrings.BadRequest_RequestHeadersTimeout); + Assert.Equal(nameof(ConnectionErrorReason.RequestHeadersTimeout), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.BadRequest_RequestHeadersTimeout)), Times.Once); @@ -211,6 +213,7 @@ public async Task ResponseDrain_SlowerThanMinimumDataRate_AbortsConnection() await SendGoAwayAsync(); await WaitForConnectionStopAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false); + Assert.Equal(nameof(ConnectionErrorReason.ClientGoAway), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); AdvanceTime(TimeSpan.FromSeconds(_bytesReceived / limits.MinResponseDataRate.BytesPerSecond) + limits.MinResponseDataRate.GracePeriod + Heartbeat.Interval - TimeSpan.FromSeconds(.5)); @@ -298,6 +301,20 @@ async Task AdvanceClockAndSendFrames() Http2ErrorCode.STREAM_CLOSED, CoreStrings.FormatHttp2ErrorStreamClosed(finalFrameType, 1)); + switch (finalFrameType) + { + case Http2FrameType.DATA: + Assert.Equal(nameof(ConnectionErrorReason.ReceivedFrameUnknownStream), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + break; + + case Http2FrameType.CONTINUATION: + Assert.Equal(nameof(ConnectionErrorReason.ReceivedFrameAfterStreamClose), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + break; + + default: + throw new NotImplementedException(finalFrameType.ToString()); + } + closed = true; await sendTask.DefaultTimeout(); @@ -371,6 +388,7 @@ public async Task DATA_Sent_TooSlowlyDueToSocketBackPressureOnSmallWrite_AbortsC withStreamId: 1); Assert.True((await _pair.Application.Input.ReadAsync().AsTask().DefaultTimeout()).IsCompleted); + Assert.Equal(nameof(ConnectionErrorReason.ResponseMininumDataRateNotSatisfied), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied)), Times.Once); @@ -425,6 +443,7 @@ public async Task DATA_Sent_TooSlowlyDueToSocketBackPressureOnLargeWrite_AbortsC withStreamId: 1); Assert.True((await _pair.Application.Input.ReadAsync().AsTask().DefaultTimeout()).IsCompleted); + Assert.Equal(nameof(ConnectionErrorReason.ResponseMininumDataRateNotSatisfied), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied)), Times.Once); @@ -476,6 +495,7 @@ public async Task DATA_Sent_TooSlowlyDueToFlowControlOnSmallWrite_AbortsConnecti expectedLastStreamId: int.MaxValue, Http2ErrorCode.INTERNAL_ERROR, null); + Assert.Equal(nameof(ConnectionErrorReason.ResponseMininumDataRateNotSatisfied), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied)), Times.Once); @@ -529,6 +549,7 @@ public async Task DATA_Sent_TooSlowlyDueToOutputFlowControlOnLargeWrite_AbortsCo expectedLastStreamId: int.MaxValue, Http2ErrorCode.INTERNAL_ERROR, null); + Assert.Equal(nameof(ConnectionErrorReason.ResponseMininumDataRateNotSatisfied), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied)), Times.Once); @@ -594,6 +615,7 @@ public async Task DATA_Sent_TooSlowlyDueToOutputFlowControlOnMultipleStreams_Abo expectedLastStreamId: int.MaxValue, Http2ErrorCode.INTERNAL_ERROR, null); + Assert.Equal(nameof(ConnectionErrorReason.ResponseMininumDataRateNotSatisfied), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied)), Times.Once); @@ -640,6 +662,7 @@ public async Task DATA_Received_TooSlowlyOnSmallRead_AbortsConnectionAfterGraceP expectedLastStreamId: int.MaxValue, Http2ErrorCode.INTERNAL_ERROR, null); + Assert.Equal(nameof(ConnectionErrorReason.RequestBodyTimeout), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.BadRequest_RequestBodyTimeout)), Times.Once); @@ -690,6 +713,7 @@ public async Task DATA_Received_TooSlowlyOnLargeRead_AbortsConnectionAfterRateTi expectedLastStreamId: int.MaxValue, Http2ErrorCode.INTERNAL_ERROR, null); + Assert.Equal(nameof(ConnectionErrorReason.RequestBodyTimeout), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.BadRequest_RequestBodyTimeout)), Times.Once); @@ -756,6 +780,7 @@ public async Task DATA_Received_TooSlowlyOnMultipleStreams_AbortsConnectionAfter expectedLastStreamId: int.MaxValue, Http2ErrorCode.INTERNAL_ERROR, null); + Assert.Equal(nameof(ConnectionErrorReason.RequestBodyTimeout), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.BadRequest_RequestBodyTimeout)), Times.Once); @@ -823,6 +848,7 @@ public async Task DATA_Received_TooSlowlyOnSecondStream_AbortsConnectionAfterNon expectedLastStreamId: int.MaxValue, Http2ErrorCode.INTERNAL_ERROR, null); + Assert.Equal(nameof(ConnectionErrorReason.RequestBodyTimeout), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.BadRequest_RequestBodyTimeout)), Times.Once); @@ -877,6 +903,7 @@ public async Task DATA_Received_SlowlyWhenRateLimitDisabledPerRequest_DoesNotAbo withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); _mockTimeoutHandler.VerifyNoOtherCalls(); _mockConnectionContext.VerifyNoOtherCalls(); @@ -962,6 +989,7 @@ public async Task DATA_Received_SlowlyDueToConnectionFlowControl_DoesNotAbortCon expectedLastStreamId: int.MaxValue, Http2ErrorCode.INTERNAL_ERROR, null); + Assert.Equal(nameof(ConnectionErrorReason.RequestBodyTimeout), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.BadRequest_RequestBodyTimeout)), Times.Once); diff --git a/src/Servers/Kestrel/test/Sockets.FunctionalTests/Sockets.FunctionalTests.csproj b/src/Servers/Kestrel/test/Sockets.FunctionalTests/Sockets.FunctionalTests.csproj index 2924c934924c..5f03a326d44b 100644 --- a/src/Servers/Kestrel/test/Sockets.FunctionalTests/Sockets.FunctionalTests.csproj +++ b/src/Servers/Kestrel/test/Sockets.FunctionalTests/Sockets.FunctionalTests.csproj @@ -33,6 +33,7 @@ + diff --git a/src/Shared/ServerInfrastructure/Http2/ConnectionErrorReason.cs b/src/Shared/ServerInfrastructure/Http2/ConnectionErrorReason.cs index 7d9be7b4c480..0376c40660e2 100644 --- a/src/Shared/ServerInfrastructure/Http2/ConnectionErrorReason.cs +++ b/src/Shared/ServerInfrastructure/Http2/ConnectionErrorReason.cs @@ -6,7 +6,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core; internal enum ConnectionErrorReason { NoError, - ClientAbort, + ConnectionReset, FlowControlWindowExceeded, KeepAliveTimeout, InsufficientTlsVersion, @@ -27,7 +27,7 @@ internal enum ConnectionErrorReason MaxFrameLengthExceeded, ErrorReadingHeaders, ErrorWritingHeaders, - Other, + UnexpectedError, InputOrOutputCompleted, InvalidHttpVersion, RequestHeadersTimeout, @@ -38,5 +38,8 @@ internal enum ConnectionErrorReason ResponseMininumDataRateNotSatisfied, AbortedByApplication, ServerTimeout, - StreamCreationError + StreamCreationError, + IOError, + ClientGoAway, + ApplicationShutdown } From 559e9c49c258a308305730dc008260410a865cf4 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Wed, 8 May 2024 15:52:24 +0800 Subject: [PATCH 07/29] Fix build --- .../Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs index eb2a2b2d4d72..20a37fdf6ed9 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs @@ -387,7 +387,7 @@ private ValueTask ProcessGoAwayFrameAsync() EnsureSettingsFrame(Http3FrameType.GoAway); // StopProcessingNextRequest must be called before RequestClose to ensure it's considered client initiated. - _context.Connection.StopProcessingNextRequest(serverInitiated: false); + _context.Connection.StopProcessingNextRequest(serverInitiated: false, ConnectionErrorReason.ClientGoAway); _context.ConnectionContext.Features.Get()?.RequestClose(); // https://quicwg.org/base-drafts/draft-ietf-quic-http.html#name-goaway From e414abf525bfa87527dadbd63dddcec945d4d6ad Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Wed, 8 May 2024 16:02:57 +0800 Subject: [PATCH 08/29] Fix microbenchmarks --- .../Http2/Http2ConnectionBenchmarkBase.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/Http2/Http2ConnectionBenchmarkBase.cs b/src/Servers/Kestrel/perf/Microbenchmarks/Http2/Http2ConnectionBenchmarkBase.cs index 204019ca9014..8c4f22ddfc42 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/Http2/Http2ConnectionBenchmarkBase.cs +++ b/src/Servers/Kestrel/perf/Microbenchmarks/Http2/Http2ConnectionBenchmarkBase.cs @@ -16,6 +16,7 @@ using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; using Http2HeadersEnumerator = Microsoft.AspNetCore.Server.Kestrel.Core.Tests.Http2HeadersEnumerator; +using Microsoft.AspNetCore.Connections.Features; namespace Microsoft.AspNetCore.Server.Kestrel.Microbenchmarks; @@ -75,6 +76,8 @@ public virtual void GlobalSetup() var featureCollection = new FeatureCollection(); featureCollection.Set(new TestConnectionMetricsContextFeature()); + featureCollection.Set(new TestConnectionMetricsTagsFeature()); + featureCollection.Set(new TestProtocolErrorCodeFeature()); var connectionContext = TestContextFactory.CreateHttpConnectionContext( serviceContext: serviceContext, connectionContext: null, @@ -191,4 +194,14 @@ private sealed class TestConnectionMetricsContextFeature : IConnectionMetricsCon { public ConnectionMetricsContext MetricsContext { get; } } + + private sealed class TestConnectionMetricsTagsFeature : IConnectionMetricsTagsFeature + { + public ICollection> Tags { get; } + } + + private sealed class TestProtocolErrorCodeFeature : IProtocolErrorCodeFeature + { + public long Error { get; set; } = -1; + } } From 0ab148b2257b4a945abe4edc8cccbb67bd40c0b2 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 9 May 2024 10:48:20 +0800 Subject: [PATCH 09/29] Tests --- .../InMemory.FunctionalTests/Http2/Http2StreamTests.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs index 8628af4117d9..29d6ff3fcdf7 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs @@ -11,6 +11,7 @@ using Microsoft.AspNetCore.InternalTesting; using Microsoft.AspNetCore.Server.Kestrel.Core.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; +using Microsoft.AspNetCore.InternalTesting; using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; @@ -37,6 +38,7 @@ public async Task HEADERS_Received_NewLineCharactersInValue_ConnectionError(stri await StartStreamAsync(1, headers, endStream: true); await WaitForConnectionErrorAsync(ignoreNonGoAwayFrames: false, 1, Http2ErrorCode.PROTOCOL_ERROR, "Malformed request: invalid headers."); + Assert.Equal(nameof(ConnectionErrorReason.InvalidRequestHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -2231,6 +2233,7 @@ public async Task ResponseHeaders_WithInvalidValuesAndCustomEncoder_AbortsConnec await StartStreamAsync(1, _browserRequestHeaders, endStream: true); await WaitForConnectionErrorAsync(ignoreNonGoAwayFrames: false, int.MaxValue, Http2ErrorCode.INTERNAL_ERROR); + Assert.Equal(nameof(ConnectionErrorReason.ErrorWritingHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -2581,6 +2584,7 @@ public async Task ResponseTrailers_WithInvalidValuesAndCustomEncoder_AbortsConne Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]); await WaitForConnectionErrorAsync(ignoreNonGoAwayFrames: false, int.MaxValue, Http2ErrorCode.INTERNAL_ERROR); + Assert.Equal(nameof(ConnectionErrorReason.ErrorWritingHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -3774,6 +3778,7 @@ public async Task ResponseHeader_OneMegaByte_SplitsHeaderToContinuationFrames() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: true); + Assert.Equal(nameof(ConnectionErrorReason.ErrorWritingHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -5807,6 +5812,7 @@ public async Task HEADERS_Received_Latin1_RejectedWhenLatin1OptionIsNotConfigure expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.BadRequest_MalformedRequestInvalidHeaders); + Assert.Equal(nameof(ConnectionErrorReason.InvalidRequestHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] @@ -5826,6 +5832,7 @@ public async Task HEADERS_Received_CustomEncoding_InvalidCharacters_AbortsConnec await WaitForConnectionErrorAsync(ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, Http2ErrorCode.PROTOCOL_ERROR, CoreStrings.BadRequest_MalformedRequestInvalidHeaders); + Assert.Equal(nameof(ConnectionErrorReason.InvalidRequestHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); } [Fact] From 4c87f7f09d39fcba8a90606ca01ca9cb7fd42bf1 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 9 May 2024 13:26:03 +0800 Subject: [PATCH 10/29] Rename --- .../Core/src/Internal/Http/Http1Connection.cs | 10 +- .../src/Internal/Http/Http1MessageBody.cs | 4 +- .../src/Internal/Http/Http1OutputProducer.cs | 6 +- .../src/Internal/Http/IHttpOutputAborter.cs | 2 +- .../Http2/FlowControl/InputFlowControl.cs | 2 +- .../src/Internal/Http2/Http2Connection.cs | 118 +++---- .../src/Internal/Http2/Http2FrameWriter.cs | 12 +- .../src/Internal/Http2/Http2OutputProducer.cs | 2 +- .../src/Internal/Http3/Http3Connection.cs | 46 +-- .../Http3/Http3ConnectionErrorException.cs | 4 +- .../src/Internal/Http3/Http3ControlStream.cs | 16 +- .../src/Internal/Http3/Http3OutputProducer.cs | 2 +- .../Core/src/Internal/Http3/Http3Stream.cs | 10 +- .../Core/src/Internal/HttpConnection.cs | 12 +- .../Core/src/Internal/IRequestProcessor.cs | 4 +- .../Internal/Infrastructure/KestrelMetrics.cs | 2 +- .../PipeWriterHelpers/TimingPipeFlusher.cs | 2 +- .../Core/test/Http1/Http1ConnectionTests.cs | 20 +- .../test/Http1/Http1OutputProducerTests.cs | 4 +- .../shared/test/Http3/Http3InMemory.cs | 8 +- .../test/FunctionalTests/RequestTests.cs | 4 +- .../test/FunctionalTests/ResponseTests.cs | 10 +- .../Http2/Http2ConnectionTests.cs | 320 +++++++++--------- .../Http2/Http2StreamTests.cs | 12 +- .../Http2/Http2TimeoutTests.cs | 32 +- .../Http3/Http3ConnectionTests.cs | 9 + .../Http3/Http3StreamTests.cs | 24 +- .../Http3/Http3TimeoutTests.cs | 9 + ...nErrorReason.cs => ConnectionEndReason.cs} | 12 +- .../Http2/Http2ConnectionErrorException.cs | 4 +- .../Http2/Http2FrameReader.cs | 4 +- 31 files changed, 383 insertions(+), 343 deletions(-) rename src/Shared/ServerInfrastructure/Http2/{ConnectionErrorReason.cs => ConnectionEndReason.cs} (83%) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs index b873cd7fcece..a45078076d1d 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs @@ -99,20 +99,20 @@ protected override void OnRequestProcessingEnded() void IRequestProcessor.OnInputOrOutputCompleted() { // Closed gracefully. - _http1Output.Abort(ServerOptions.FinOnError ? new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient) : null!, ConnectionErrorReason.NoError); + _http1Output.Abort(ServerOptions.FinOnError ? new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient) : null!, ConnectionEndReason.NoError); CancelRequestAbortedToken(); } void IHttpOutputAborter.OnInputOrOutputCompleted() { - _http1Output.Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient), ConnectionErrorReason.InputOrOutputCompleted); + _http1Output.Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient), ConnectionEndReason.InputOrOutputCompleted); CancelRequestAbortedToken(); } /// /// Immediately kill the connection and poison the request body stream with an error. /// - public void Abort(ConnectionAbortedException abortReason, ConnectionErrorReason reason) + public void Abort(ConnectionAbortedException abortReason, ConnectionEndReason reason) { _http1Output.Abort(abortReason, reason); CancelRequestAbortedToken(); @@ -122,7 +122,7 @@ public void Abort(ConnectionAbortedException abortReason, ConnectionErrorReason protected override void ApplicationAbort() { Log.ApplicationAbortedConnection(ConnectionId, TraceIdentifier); - Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByApplication), ConnectionErrorReason.AbortedByApplication); + Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByApplication), ConnectionEndReason.AbortedByApplication); } /// @@ -130,7 +130,7 @@ protected override void ApplicationAbort() /// Called on all active connections when the server wants to initiate a shutdown /// and after a keep-alive timeout. /// - public void StopProcessingNextRequest(ConnectionErrorReason errorReason) + public void StopProcessingNextRequest(ConnectionEndReason errorReason) { _keepAlive = false; Input.CancelPendingRead(); diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs index 4c30110c2ce0..f9adf4525c59 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs @@ -72,7 +72,7 @@ protected override Task OnConsumeAsync() _context.ReportApplicationError(connectionAbortedException); // Have to abort the connection because we can't finish draining the request - _context.StopProcessingNextRequest(ConnectionErrorReason.AbortedByApplication); + _context.StopProcessingNextRequest(ConnectionEndReason.AbortedByApplication); return Task.CompletedTask; } @@ -108,7 +108,7 @@ protected async Task OnConsumeAsyncAwaited() _context.ReportApplicationError(connectionAbortedException); // Have to abort the connection because we can't finish draining the request - _context.StopProcessingNextRequest(ConnectionErrorReason.AbortedByApplication); + _context.StopProcessingNextRequest(ConnectionEndReason.AbortedByApplication); } finally { diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs index fac610aedee4..437f65436054 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs @@ -449,7 +449,7 @@ private void CompletePipe() } } - public void Abort(ConnectionAbortedException error, ConnectionErrorReason errorReason) + public void Abort(ConnectionAbortedException error, ConnectionEndReason errorReason) { // Abort can be called after Dispose if there's a flush timeout. // It's important to still call _lifetimeFeature.Abort() in this case. @@ -460,9 +460,9 @@ public void Abort(ConnectionAbortedException error, ConnectionErrorReason errorR return; } - if (errorReason != ConnectionErrorReason.NoError && _metricsTagsFeature != null) + if (errorReason != ConnectionEndReason.NoError && _metricsTagsFeature != null) { - _metricsTagsFeature.TryAddTag("kestrel.connection.error_reason", errorReason.ToString()); + _metricsTagsFeature.TryAddTag(KestrelMetrics.KestrelConnectionEndReason, errorReason.ToString()); } _aborted = true; diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/IHttpOutputAborter.cs b/src/Servers/Kestrel/Core/src/Internal/Http/IHttpOutputAborter.cs index e8782b93c385..08250a744e36 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/IHttpOutputAborter.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/IHttpOutputAborter.cs @@ -7,6 +7,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; internal interface IHttpOutputAborter { - void Abort(ConnectionAbortedException abortReason, ConnectionErrorReason errorReason); + void Abort(ConnectionAbortedException abortReason, ConnectionEndReason errorReason); void OnInputOrOutputCompleted(); } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/FlowControl/InputFlowControl.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/FlowControl/InputFlowControl.cs index ed4cac95a677..afabff46e6d9 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/FlowControl/InputFlowControl.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/FlowControl/InputFlowControl.cs @@ -41,7 +41,7 @@ public bool TryAdvance(int bytes) // flow-control window at the time of the abort. if (bytes > _flow.Available) { - throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorFlowControlWindowExceeded, Http2ErrorCode.FLOW_CONTROL_ERROR, ConnectionErrorReason.FlowControlWindowExceeded); + throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorFlowControlWindowExceeded, Http2ErrorCode.FLOW_CONTROL_ERROR, ConnectionEndReason.FlowControlWindowExceeded); } if (_flow.IsAborted) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs index dbe773adb2e3..b8cdcc97d4f5 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs @@ -126,7 +126,7 @@ private static int GetMaximumEnhanceYourCalmCount() private readonly ConcurrentQueue _completedStreams = new ConcurrentQueue(); private readonly StreamCloseAwaitable _streamCompletionAwaitable = new StreamCloseAwaitable(); private int _gracefulCloseInitiator; - private ConnectionErrorReason _gracefulCloseReason; + private ConnectionEndReason _gracefulCloseReason; private int _isClosed; // Internal for testing @@ -213,30 +213,30 @@ public void OnInputOrOutputCompleted() var hasActiveStreams = _clientActiveStreamCount != 0; if (TryClose()) { - SetConnectionErrorCode(hasActiveStreams ? ConnectionErrorReason.ConnectionReset : ConnectionErrorReason.NoError, Http2ErrorCode.NO_ERROR); + SetConnectionErrorCode(hasActiveStreams ? ConnectionEndReason.ConnectionReset : ConnectionEndReason.NoError, Http2ErrorCode.NO_ERROR); } var useException = _context.ServiceContext.ServerOptions.FinOnError || hasActiveStreams; _frameWriter.Abort(useException ? new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient) : null!); } - private void SetConnectionErrorCode(ConnectionErrorReason reason, Http2ErrorCode errorCode) + private void SetConnectionErrorCode(ConnectionEndReason reason, Http2ErrorCode errorCode) { Debug.Assert(_isClosed == 1, "Should only be set when connection is closed."); Debug.Assert(_errorCodeFeature.Error == -1, "Error code feature should only be set once."); _errorCodeFeature.Error = (long)errorCode; - if (_metricsTagsFeature != null && reason != ConnectionErrorReason.NoError) + if (_metricsTagsFeature != null && reason != ConnectionEndReason.NoError) { - _metricsTagsFeature.TryAddTag("kestrel.connection.error_reason", reason.ToString()); + _metricsTagsFeature.TryAddTag(KestrelMetrics.KestrelConnectionEndReason, reason.ToString()); } } - public void Abort(ConnectionAbortedException ex, ConnectionErrorReason reason) + public void Abort(ConnectionAbortedException ex, ConnectionEndReason reason) { Abort(ex, Http2ErrorCode.INTERNAL_ERROR, reason); } - public void Abort(ConnectionAbortedException ex, Http2ErrorCode errorCode, ConnectionErrorReason reason) + public void Abort(ConnectionAbortedException ex, Http2ErrorCode errorCode, ConnectionEndReason reason) { if (TryClose()) { @@ -247,13 +247,13 @@ public void Abort(ConnectionAbortedException ex, Http2ErrorCode errorCode, Conne _frameWriter.Abort(ex); } - public void StopProcessingNextRequest(ConnectionErrorReason reason) + public void StopProcessingNextRequest(ConnectionEndReason reason) => StopProcessingNextRequest(serverInitiated: true, reason); public void HandleRequestHeadersTimeout() { Log.ConnectionBadRequest(ConnectionId, KestrelBadHttpRequestException.GetException(RequestRejectionReason.RequestHeadersTimeout)); - Abort(new ConnectionAbortedException(CoreStrings.BadRequest_RequestHeadersTimeout), Http2ErrorCode.INTERNAL_ERROR, ConnectionErrorReason.RequestHeadersTimeout); + Abort(new ConnectionAbortedException(CoreStrings.BadRequest_RequestHeadersTimeout), Http2ErrorCode.INTERNAL_ERROR, ConnectionEndReason.RequestHeadersTimeout); } public void HandleReadDataRateTimeout() @@ -261,10 +261,10 @@ public void HandleReadDataRateTimeout() Debug.Assert(Limits.MinRequestBodyDataRate != null); Log.RequestBodyMinimumDataRateNotSatisfied(ConnectionId, null, Limits.MinRequestBodyDataRate.BytesPerSecond); - Abort(new ConnectionAbortedException(CoreStrings.BadRequest_RequestBodyTimeout), Http2ErrorCode.INTERNAL_ERROR, ConnectionErrorReason.RequestBodyTimeout); + Abort(new ConnectionAbortedException(CoreStrings.BadRequest_RequestBodyTimeout), Http2ErrorCode.INTERNAL_ERROR, ConnectionEndReason.MinRequestBodyDataRate); } - public void StopProcessingNextRequest(bool serverInitiated, ConnectionErrorReason reason) + public void StopProcessingNextRequest(bool serverInitiated, ConnectionEndReason reason) { var initiator = serverInitiated ? GracefulCloseInitiator.Server : GracefulCloseInitiator.Client; @@ -279,7 +279,7 @@ public void StopProcessingNextRequest(bool serverInitiated, ConnectionErrorReaso { Exception? error = null; var errorCode = Http2ErrorCode.NO_ERROR; - var errorReason = ConnectionErrorReason.NoError; + var errorReason = ConnectionEndReason.NoError; try { @@ -365,7 +365,7 @@ public void StopProcessingNextRequest(bool serverInitiated, ConnectionErrorReaso { // There isn't a good error code to return with the GOAWAY. // NO_ERROR isn't a good choice because it indicates the connection is gracefully shutting down. - throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorKeepAliveTimeout, Http2ErrorCode.INTERNAL_ERROR, ConnectionErrorReason.KeepAliveTimeout); + throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorKeepAliveTimeout, Http2ErrorCode.INTERNAL_ERROR, ConnectionEndReason.KeepAliveTimeout); } } } @@ -383,7 +383,7 @@ public void StopProcessingNextRequest(bool serverInitiated, ConnectionErrorReaso if (_clientActiveStreamCount > 0) { Log.RequestProcessingError(ConnectionId, ex); - errorReason = ConnectionErrorReason.ConnectionReset; + errorReason = ConnectionEndReason.ConnectionReset; } error = ex; @@ -392,7 +392,7 @@ public void StopProcessingNextRequest(bool serverInitiated, ConnectionErrorReaso { Log.RequestProcessingError(ConnectionId, ex); error = ex; - errorReason = ConnectionErrorReason.IOError; + errorReason = ConnectionEndReason.IOError; } catch (ConnectionAbortedException ex) { @@ -413,14 +413,14 @@ public void StopProcessingNextRequest(bool serverInitiated, ConnectionErrorReaso Log.HPackDecodingError(ConnectionId, _currentHeadersStream.StreamId, ex); error = ex; errorCode = Http2ErrorCode.COMPRESSION_ERROR; - errorReason = ConnectionErrorReason.ErrorReadingHeaders; + errorReason = ConnectionEndReason.ErrorReadingHeaders; } catch (Exception ex) { Log.LogWarning(0, ex, CoreStrings.RequestProcessingEndError); error = ex; errorCode = Http2ErrorCode.INTERNAL_ERROR; - errorReason = ConnectionErrorReason.UnexpectedError; + errorReason = ConnectionEndReason.UnexpectedError; } finally { @@ -496,7 +496,7 @@ private void ValidateTlsRequirements() if (tlsFeature.Protocol < SslProtocols.Tls12) { - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorMinTlsVersion(tlsFeature.Protocol), Http2ErrorCode.INADEQUATE_SECURITY, ConnectionErrorReason.InsufficientTlsVersion); + throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorMinTlsVersion(tlsFeature.Protocol), Http2ErrorCode.INADEQUATE_SECURITY, ConnectionEndReason.InsufficientTlsVersion); } } @@ -581,7 +581,7 @@ private async Task TryReadPrefaceAsync() // Close connection here so a GOAWAY frame isn't written. if (TryClose()) { - SetConnectionErrorCode(ConnectionErrorReason.InvalidHttpVersion, Http2ErrorCode.PROTOCOL_ERROR); + SetConnectionErrorCode(ConnectionEndReason.InvalidHttpVersion, Http2ErrorCode.PROTOCOL_ERROR); } return false; @@ -596,7 +596,7 @@ private async Task TryReadPrefaceAsync() // Tested all states. Return HTTP/2 protocol error. if (state == ReadPrefaceState.None) { - throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorInvalidPreface, Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.InvalidHandshake); + throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorInvalidPreface, Http2ErrorCode.PROTOCOL_ERROR, ConnectionEndReason.InvalidHandshake); } } @@ -668,7 +668,7 @@ private static bool IsPreface(in ReadOnlySequence buffer, out SequencePosi // a connection error (Section 5.4.1) of type PROTOCOL_ERROR. if (_incomingFrame.StreamId != 0 && (_incomingFrame.StreamId & 1) == 0) { - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdEven(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.InvalidStreamId); + throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdEven(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.PROTOCOL_ERROR, ConnectionEndReason.InvalidStreamId); } return _incomingFrame.Type switch @@ -678,7 +678,7 @@ private static bool IsPreface(in ReadOnlySequence buffer, out SequencePosi Http2FrameType.PRIORITY => ProcessPriorityFrameAsync(), Http2FrameType.RST_STREAM => ProcessRstStreamFrameAsync(), Http2FrameType.SETTINGS => ProcessSettingsFrameAsync(payload), - Http2FrameType.PUSH_PROMISE => throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorPushPromiseReceived, Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.ReceivedUnsupportedFrame), + Http2FrameType.PUSH_PROMISE => throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorPushPromiseReceived, Http2ErrorCode.PROTOCOL_ERROR, ConnectionEndReason.UnsupportedFrame), Http2FrameType.PING => ProcessPingFrameAsync(payload), Http2FrameType.GOAWAY => ProcessGoAwayFrameAsync(), Http2FrameType.WINDOW_UPDATE => ProcessWindowUpdateFrameAsync(), @@ -701,7 +701,7 @@ private Task ProcessDataFrameAsync(in ReadOnlySequence payload) if (_incomingFrame.DataHasPadding && _incomingFrame.DataPadLength >= _incomingFrame.PayloadLength) { - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorPaddingTooLong(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.InvalidDataPadding); + throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorPaddingTooLong(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR, ConnectionEndReason.InvalidDataPadding); } ThrowIfIncomingFrameSentToIdleStream(); @@ -723,7 +723,7 @@ private Task ProcessDataFrameAsync(in ReadOnlySequence payload) // of type STREAM_CLOSED, unless the frame is permitted as described below. // // (The allowed frame types for this situation are WINDOW_UPDATE, RST_STREAM and PRIORITY) - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(_incomingFrame.Type, stream.StreamId), Http2ErrorCode.STREAM_CLOSED, ConnectionErrorReason.ReceivedFrameAfterStreamClose); + throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(_incomingFrame.Type, stream.StreamId), Http2ErrorCode.STREAM_CLOSED, ConnectionEndReason.FrameAfterStreamClose); } return stream.OnDataAsync(_incomingFrame, payload); @@ -740,33 +740,33 @@ private Task ProcessDataFrameAsync(in ReadOnlySequence payload) // // We choose to do that here so we don't have to keep state to track implicitly closed // streams vs. streams closed with END_STREAM or RST_STREAM. - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamClosed(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.STREAM_CLOSED, ConnectionErrorReason.ReceivedFrameUnknownStream); + throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamClosed(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.STREAM_CLOSED, ConnectionEndReason.UnknownStream); } private Http2ConnectionErrorException CreateReceivedFrameStreamAbortedException(Http2Stream stream) { - return new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamAborted(_incomingFrame.Type, stream.StreamId), Http2ErrorCode.STREAM_CLOSED, ConnectionErrorReason.ReceivedFrameAfterStreamClose); + return new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamAborted(_incomingFrame.Type, stream.StreamId), Http2ErrorCode.STREAM_CLOSED, ConnectionEndReason.FrameAfterStreamClose); } private Http2ConnectionErrorException CreateStreamIdZeroException() { - return new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.InvalidStreamId); + return new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR, ConnectionEndReason.InvalidStreamId); } private Http2ConnectionErrorException CreateStreamIdNotZeroException() { - return new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdNotZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.InvalidStreamId); + return new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdNotZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR, ConnectionEndReason.InvalidStreamId); } private Http2ConnectionErrorException CreateHeadersInterleavedException() { Debug.Assert(_currentHeadersStream != null, "Only throw this error if parsing headers."); - return new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorHeadersInterleaved(_incomingFrame.Type, _incomingFrame.StreamId, _currentHeadersStream.StreamId), Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.UnexpectedFrame); + return new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorHeadersInterleaved(_incomingFrame.Type, _incomingFrame.StreamId, _currentHeadersStream.StreamId), Http2ErrorCode.PROTOCOL_ERROR, ConnectionEndReason.UnexpectedFrame); } private Http2ConnectionErrorException CreateUnexpectedFrameLengthException(int exceptedLength) { - return new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(_incomingFrame.Type, exceptedLength), Http2ErrorCode.FRAME_SIZE_ERROR, ConnectionErrorReason.InvalidFrameLength); + return new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(_incomingFrame.Type, exceptedLength), Http2ErrorCode.FRAME_SIZE_ERROR, ConnectionEndReason.InvalidFrameLength); } private Task ProcessHeadersFrameAsync(IHttpApplication application, in ReadOnlySequence payload) where TContext : notnull @@ -783,12 +783,12 @@ private Http2ConnectionErrorException CreateUnexpectedFrameLengthException(int e if (_incomingFrame.HeadersHasPadding && _incomingFrame.HeadersPadLength >= _incomingFrame.PayloadLength - 1) { - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorPaddingTooLong(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.InvalidDataPadding); + throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorPaddingTooLong(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR, ConnectionEndReason.InvalidDataPadding); } if (_incomingFrame.HeadersHasPriority && _incomingFrame.HeadersStreamDependency == _incomingFrame.StreamId) { - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamSelfDependency(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.StreamSelfDependency); + throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamSelfDependency(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.PROTOCOL_ERROR, ConnectionEndReason.StreamSelfDependency); } if (_streams.TryGetValue(_incomingFrame.StreamId, out var stream)) @@ -808,13 +808,13 @@ private Http2ConnectionErrorException CreateUnexpectedFrameLengthException(int e // (The allowed frame types after END_STREAM are WINDOW_UPDATE, RST_STREAM and PRIORITY) if (stream.EndStreamReceived) { - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(_incomingFrame.Type, stream.StreamId), Http2ErrorCode.STREAM_CLOSED, ConnectionErrorReason.ReceivedFrameAfterStreamClose); + throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(_incomingFrame.Type, stream.StreamId), Http2ErrorCode.STREAM_CLOSED, ConnectionEndReason.FrameAfterStreamClose); } // This is the last chance for the client to send END_STREAM if (!_incomingFrame.HeadersEndStream) { - throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorHeadersWithTrailersNoEndStream, Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.MissingStreamEnd); + throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorHeadersWithTrailersNoEndStream, Http2ErrorCode.PROTOCOL_ERROR, ConnectionEndReason.MissingStreamEnd); } // Since we found an active stream, this HEADERS frame contains trailers @@ -833,7 +833,7 @@ private Http2ConnectionErrorException CreateUnexpectedFrameLengthException(int e // // If we couldn't find the stream, it was previously closed (either implicitly or with // END_STREAM or RST_STREAM). - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamClosed(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.STREAM_CLOSED, ConnectionErrorReason.InvalidStreamId); + throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamClosed(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.STREAM_CLOSED, ConnectionEndReason.InvalidStreamId); } else { @@ -911,7 +911,7 @@ private Task ProcessPriorityFrameAsync() if (_incomingFrame.PriorityStreamDependency == _incomingFrame.StreamId) { - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamSelfDependency(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.StreamSelfDependency); + throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamSelfDependency(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.PROTOCOL_ERROR, ConnectionEndReason.StreamSelfDependency); } if (_incomingFrame.PayloadLength != 5) @@ -978,7 +978,7 @@ private Task ProcessSettingsFrameAsync(in ReadOnlySequence payload) { if (_incomingFrame.PayloadLength != 0) { - throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorSettingsAckLengthNotZero, Http2ErrorCode.FRAME_SIZE_ERROR, ConnectionErrorReason.InvalidFrameLength); + throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorSettingsAckLengthNotZero, Http2ErrorCode.FRAME_SIZE_ERROR, ConnectionEndReason.InvalidFrameLength); } return Task.CompletedTask; @@ -986,7 +986,7 @@ private Task ProcessSettingsFrameAsync(in ReadOnlySequence payload) if (_incomingFrame.PayloadLength % 6 != 0) { - throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorSettingsLengthNotMultipleOfSix, Http2ErrorCode.FRAME_SIZE_ERROR, ConnectionErrorReason.InvalidFrameLength); + throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorSettingsLengthNotMultipleOfSix, Http2ErrorCode.FRAME_SIZE_ERROR, ConnectionEndReason.InvalidFrameLength); } try @@ -1020,7 +1020,7 @@ private Task ProcessSettingsFrameAsync(in ReadOnlySequence payload) // This means that this caused a stream window to become larger than int.MaxValue. // This can never happen with a well behaved client and MUST be treated as a connection error. // https://httpwg.org/specs/rfc7540.html#rfc.section.6.9.2 - throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorInitialWindowSizeInvalid, Http2ErrorCode.FLOW_CONTROL_ERROR, ConnectionErrorReason.InvalidSettings); + throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorInitialWindowSizeInvalid, Http2ErrorCode.FLOW_CONTROL_ERROR, ConnectionEndReason.InvalidSettings); } } } @@ -1042,7 +1042,7 @@ private Task ProcessSettingsFrameAsync(in ReadOnlySequence payload) ? Http2ErrorCode.FLOW_CONTROL_ERROR : Http2ErrorCode.PROTOCOL_ERROR; - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorSettingsParameterOutOfRange(ex.Parameter), errorCode, ConnectionErrorReason.InvalidSettings); + throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorSettingsParameterOutOfRange(ex.Parameter), errorCode, ConnectionEndReason.InvalidSettings); } } @@ -1091,7 +1091,7 @@ private Task ProcessGoAwayFrameAsync() } // StopProcessingNextRequest must be called before RequestClose to ensure it's considered client initiated. - StopProcessingNextRequest(serverInitiated: false, ConnectionErrorReason.ClientGoAway); + StopProcessingNextRequest(serverInitiated: false, ConnectionEndReason.ClientGoAway); _context.ConnectionFeatures.Get()?.RequestClose(); return Task.CompletedTask; @@ -1128,14 +1128,14 @@ private Task ProcessWindowUpdateFrameAsync() // Since server initiated stream resets are not yet properly // implemented and tested, we treat all zero length window // increments as connection errors for now. - throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorWindowUpdateIncrementZero, Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.WindowUpdateSizeInvalid); + throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorWindowUpdateIncrementZero, Http2ErrorCode.PROTOCOL_ERROR, ConnectionEndReason.WindowUpdateSizeInvalid); } if (_incomingFrame.StreamId == 0) { if (!_frameWriter.TryUpdateConnectionWindow(_incomingFrame.WindowUpdateSizeIncrement)) { - throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorWindowUpdateSizeInvalid, Http2ErrorCode.FLOW_CONTROL_ERROR, ConnectionErrorReason.WindowUpdateSizeInvalid); + throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorWindowUpdateSizeInvalid, Http2ErrorCode.FLOW_CONTROL_ERROR, ConnectionEndReason.WindowUpdateSizeInvalid); } } else if (_streams.TryGetValue(_incomingFrame.StreamId, out var stream)) @@ -1165,7 +1165,7 @@ private Task ProcessContinuationFrameAsync(in ReadOnlySequence payload) { if (_currentHeadersStream == null) { - throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorContinuationWithNoHeaders, Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.UnexpectedFrame); + throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorContinuationWithNoHeaders, Http2ErrorCode.PROTOCOL_ERROR, ConnectionEndReason.UnexpectedFrame); } if (_incomingFrame.StreamId != _currentHeadersStream.StreamId) @@ -1303,9 +1303,9 @@ private void StartStream() // messages in case they somehow make it back to the client (not expected) // This will close the socket - we want to do that right away - Abort(new ConnectionAbortedException(CoreStrings.Http2ConnectionFaulted), Http2ErrorCode.ENHANCE_YOUR_CALM, ConnectionErrorReason.StreamResetLimitExceeded); + Abort(new ConnectionAbortedException(CoreStrings.Http2ConnectionFaulted), Http2ErrorCode.ENHANCE_YOUR_CALM, ConnectionEndReason.StreamResetLimitExceeded); // Throwing an exception as well will help us clean up on our end more quickly by (e.g.) skipping processing of already-buffered input - throw new Http2ConnectionErrorException(CoreStrings.Http2ConnectionFaulted, Http2ErrorCode.ENHANCE_YOUR_CALM, ConnectionErrorReason.StreamResetLimitExceeded); + throw new Http2ConnectionErrorException(CoreStrings.Http2ConnectionFaulted, Http2ErrorCode.ENHANCE_YOUR_CALM, ConnectionEndReason.StreamResetLimitExceeded); } throw new Http2StreamErrorException(_currentHeadersStream.StreamId, CoreStrings.Http2TellClientToCalmDown, Http2ErrorCode.ENHANCE_YOUR_CALM); @@ -1364,7 +1364,7 @@ private void ThrowIfIncomingFrameSentToIdleStream() // initial state for all streams. if (_incomingFrame.StreamId > _highestOpenedStreamId) { - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdle(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.InvalidStreamId); + throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdle(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.PROTOCOL_ERROR, ConnectionEndReason.InvalidStreamId); } } @@ -1420,7 +1420,7 @@ private void UpdateCompletedStreams() if (stream == _currentHeadersStream) { // The drain expired out while receiving trailers. The most recent incoming frame is either a header or continuation frame for the timed out stream. - throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamClosed(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.STREAM_CLOSED, ConnectionErrorReason.ReceivedFrameAfterStreamClose); + throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamClosed(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.STREAM_CLOSED, ConnectionEndReason.FrameAfterStreamClose); } RemoveStream(stream); @@ -1564,7 +1564,7 @@ private void OnHeaderCore(HeaderType headerType, int? staticTableIndex, ReadOnly // Allow a 2x grace before aborting the connection. We'll check the size limit again later where we can send a 431. if (_totalParsedHeaderSize > _context.ServiceContext.ServerOptions.Limits.MaxRequestHeadersTotalSize * 2) { - throw new Http2ConnectionErrorException(CoreStrings.BadRequest_HeadersExceedMaxTotalSize, Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.InvalidRequestHeaders); + throw new Http2ConnectionErrorException(CoreStrings.BadRequest_HeadersExceedMaxTotalSize, Http2ErrorCode.PROTOCOL_ERROR, ConnectionEndReason.InvalidRequestHeaders); } try @@ -1625,11 +1625,11 @@ private void OnHeaderCore(HeaderType headerType, int? staticTableIndex, ReadOnly } catch (Microsoft.AspNetCore.Http.BadHttpRequestException bre) { - throw new Http2ConnectionErrorException(bre.Message, Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.InvalidRequestHeaders); + throw new Http2ConnectionErrorException(bre.Message, Http2ErrorCode.PROTOCOL_ERROR, ConnectionEndReason.InvalidRequestHeaders); } catch (InvalidOperationException) { - throw new Http2ConnectionErrorException(CoreStrings.BadRequest_MalformedRequestInvalidHeaders, Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.InvalidRequestHeaders); + throw new Http2ConnectionErrorException(CoreStrings.BadRequest_MalformedRequestInvalidHeaders, Http2ErrorCode.PROTOCOL_ERROR, ConnectionEndReason.InvalidRequestHeaders); } } @@ -1640,7 +1640,7 @@ private void ValidateHeaderContent(ReadOnlySpan name, ReadOnlySpan v { if (IsConnectionSpecificHeaderField(name, value)) { - throw new Http2ConnectionErrorException(CoreStrings.HttpErrorConnectionSpecificHeaderField, Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.InvalidRequestHeaders); + throw new Http2ConnectionErrorException(CoreStrings.HttpErrorConnectionSpecificHeaderField, Http2ErrorCode.PROTOCOL_ERROR, ConnectionEndReason.InvalidRequestHeaders); } // http://httpwg.org/specs/rfc7540.html#rfc.section.8.1.2 @@ -1651,11 +1651,11 @@ private void ValidateHeaderContent(ReadOnlySpan name, ReadOnlySpan v { if (_requestHeaderParsingState == RequestHeaderParsingState.Trailers) { - throw new Http2ConnectionErrorException(CoreStrings.HttpErrorTrailerNameUppercase, Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.InvalidRequestHeaders); + throw new Http2ConnectionErrorException(CoreStrings.HttpErrorTrailerNameUppercase, Http2ErrorCode.PROTOCOL_ERROR, ConnectionEndReason.InvalidRequestHeaders); } else { - throw new Http2ConnectionErrorException(CoreStrings.HttpErrorHeaderNameUppercase, Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.InvalidRequestHeaders); + throw new Http2ConnectionErrorException(CoreStrings.HttpErrorHeaderNameUppercase, Http2ErrorCode.PROTOCOL_ERROR, ConnectionEndReason.InvalidRequestHeaders); } } } @@ -1684,13 +1684,13 @@ private void UpdateHeaderParsingState(ReadOnlySpan value, PseudoHeaderFiel // All pseudo-header fields MUST appear in the header block before regular header fields. // Any request or response that contains a pseudo-header field that appears in a header // block after a regular header field MUST be treated as malformed (Section 8.1.2.6). - throw new Http2ConnectionErrorException(CoreStrings.HttpErrorPseudoHeaderFieldAfterRegularHeaders, Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.InvalidRequestHeaders); + throw new Http2ConnectionErrorException(CoreStrings.HttpErrorPseudoHeaderFieldAfterRegularHeaders, Http2ErrorCode.PROTOCOL_ERROR, ConnectionEndReason.InvalidRequestHeaders); } if (_requestHeaderParsingState == RequestHeaderParsingState.Trailers) { // Pseudo-header fields MUST NOT appear in trailers. - throw new Http2ConnectionErrorException(CoreStrings.HttpErrorTrailersContainPseudoHeaderField, Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.InvalidRequestHeaders); + throw new Http2ConnectionErrorException(CoreStrings.HttpErrorTrailersContainPseudoHeaderField, Http2ErrorCode.PROTOCOL_ERROR, ConnectionEndReason.InvalidRequestHeaders); } _requestHeaderParsingState = RequestHeaderParsingState.PseudoHeaderFields; @@ -1699,21 +1699,21 @@ private void UpdateHeaderParsingState(ReadOnlySpan value, PseudoHeaderFiel { // Endpoints MUST treat a request or response that contains undefined or invalid pseudo-header // fields as malformed (Section 8.1.2.6). - throw new Http2ConnectionErrorException(CoreStrings.HttpErrorUnknownPseudoHeaderField, Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.InvalidRequestHeaders); + throw new Http2ConnectionErrorException(CoreStrings.HttpErrorUnknownPseudoHeaderField, Http2ErrorCode.PROTOCOL_ERROR, ConnectionEndReason.InvalidRequestHeaders); } if (headerField == PseudoHeaderFields.Status) { // Pseudo-header fields defined for requests MUST NOT appear in responses; pseudo-header fields // defined for responses MUST NOT appear in requests. - throw new Http2ConnectionErrorException(CoreStrings.HttpErrorResponsePseudoHeaderField, Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.InvalidRequestHeaders); + throw new Http2ConnectionErrorException(CoreStrings.HttpErrorResponsePseudoHeaderField, Http2ErrorCode.PROTOCOL_ERROR, ConnectionEndReason.InvalidRequestHeaders); } if ((_parsedPseudoHeaderFields & headerField) == headerField) { // http://httpwg.org/specs/rfc7540.html#rfc.section.8.1.2.3 // All HTTP/2 requests MUST include exactly one valid value for the :method, :scheme, and :path pseudo-header fields - throw new Http2ConnectionErrorException(CoreStrings.HttpErrorDuplicatePseudoHeaderField, Http2ErrorCode.PROTOCOL_ERROR, ConnectionErrorReason.InvalidRequestHeaders); + throw new Http2ConnectionErrorException(CoreStrings.HttpErrorDuplicatePseudoHeaderField, Http2ErrorCode.PROTOCOL_ERROR, ConnectionEndReason.InvalidRequestHeaders); } if (headerField == PseudoHeaderFields.Method) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs index eb31eea89baa..0b38d7dedd87 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs @@ -154,7 +154,7 @@ public void Schedule(Http2OutputProducer producer) // exceeding the channel size. Disconnecting seems appropriate in this case. var ex = new ConnectionAbortedException("HTTP/2 connection exceeded the output operations maximum queue size."); _log.Http2QueueOperationsExceeded(_connectionId, ex); - _http2Connection.Abort(ex, Http2ErrorCode.INTERNAL_ERROR, ConnectionErrorReason.OutputQueueSizeExceeded); + _http2Connection.Abort(ex, Http2ErrorCode.INTERNAL_ERROR, ConnectionEndReason.OutputQueueSizeExceeded); } } @@ -323,14 +323,14 @@ static bool HasStateFlag(Http2OutputProducer.State state, Http2OutputProducer.St private async Task HandleFlowControlErrorAsync() { - var connectionError = new Http2ConnectionErrorException(CoreStrings.Http2ErrorWindowUpdateSizeInvalid, Http2ErrorCode.FLOW_CONTROL_ERROR, ConnectionErrorReason.WindowUpdateSizeInvalid); + var connectionError = new Http2ConnectionErrorException(CoreStrings.Http2ErrorWindowUpdateSizeInvalid, Http2ErrorCode.FLOW_CONTROL_ERROR, ConnectionEndReason.WindowUpdateSizeInvalid); _log.Http2ConnectionError(_connectionId, connectionError); await WriteGoAwayAsync(int.MaxValue, Http2ErrorCode.FLOW_CONTROL_ERROR); // Prevent Abort() from writing an INTERNAL_ERROR GOAWAY frame after our FLOW_CONTROL_ERROR. Complete(); // Stop processing any more requests and immediately close the connection. - _http2Connection.Abort(new ConnectionAbortedException(CoreStrings.Http2ErrorWindowUpdateSizeInvalid, connectionError), Http2ErrorCode.FLOW_CONTROL_ERROR, ConnectionErrorReason.WindowUpdateSizeInvalid); + _http2Connection.Abort(new ConnectionAbortedException(CoreStrings.Http2ErrorWindowUpdateSizeInvalid, connectionError), Http2ErrorCode.FLOW_CONTROL_ERROR, ConnectionEndReason.WindowUpdateSizeInvalid); } private bool TryQueueProducerForConnectionWindowUpdate(long actual, Http2OutputProducer producer) @@ -527,7 +527,7 @@ private void WriteResponseHeadersUnsynchronized(int streamId, int statusCode, Ht catch (Exception ex) { _log.HPackEncodingError(_connectionId, streamId, ex); - _http2Connection.Abort(new ConnectionAbortedException(ex.Message, ex), Http2ErrorCode.INTERNAL_ERROR, ConnectionErrorReason.ErrorWritingHeaders); + _http2Connection.Abort(new ConnectionAbortedException(ex.Message, ex), Http2ErrorCode.INTERNAL_ERROR, ConnectionEndReason.ErrorWritingHeaders); } } @@ -568,7 +568,7 @@ private ValueTask WriteDataAndTrailersAsync(Http2Stream stream, in catch (Exception ex) { _log.HPackEncodingError(_connectionId, streamId, ex); - _http2Connection.Abort(new ConnectionAbortedException(ex.Message, ex), Http2ErrorCode.INTERNAL_ERROR, ConnectionErrorReason.ErrorWritingHeaders); + _http2Connection.Abort(new ConnectionAbortedException(ex.Message, ex), Http2ErrorCode.INTERNAL_ERROR, ConnectionEndReason.ErrorWritingHeaders); } return TimeFlushUnsynchronizedAsync(); @@ -1099,7 +1099,7 @@ private void EnqueueWaitingForMoreConnectionWindow(Http2OutputProducer producer) if (!_aborted && IsFlowControlQueueLimitEnabled && _waitingForMoreConnectionWindow.Count > _maximumFlowControlQueueSize) { _log.Http2FlowControlQueueOperationsExceeded(_connectionId, _maximumFlowControlQueueSize); - _http2Connection.Abort(new ConnectionAbortedException("HTTP/2 connection exceeded the outgoing flow control maximum queue size."), Http2ErrorCode.INTERNAL_ERROR, ConnectionErrorReason.FlowControlQueueSizeExceeded); + _http2Connection.Abort(new ConnectionAbortedException("HTTP/2 connection exceeded the outgoing flow control maximum queue size."), Http2ErrorCode.INTERNAL_ERROR, ConnectionEndReason.FlowControlQueueSizeExceeded); } } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2OutputProducer.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2OutputProducer.cs index 39d34070bef5..9380ce6711dd 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2OutputProducer.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2OutputProducer.cs @@ -241,7 +241,7 @@ public void Complete() // This is called when a CancellationToken fires mid-write. In HTTP/1.x, this aborts the entire connection. // For HTTP/2 we abort the stream. - void IHttpOutputAborter.Abort(ConnectionAbortedException abortReason, ConnectionErrorReason errorReason) + void IHttpOutputAborter.Abort(ConnectionAbortedException abortReason, ConnectionEndReason errorReason) { _stream.ResetAndAbort(abortReason, Http2ErrorCode.INTERNAL_ERROR); } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs index a603a9a3f787..03983e55823f 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs @@ -47,7 +47,7 @@ internal sealed class Http3Connection : IHttp3StreamLifetimeHandler, IRequestPro private long _highestOpenedRequestStreamId = DefaultHighestOpenedRequestStreamId; private bool _aborted; private int _gracefulCloseInitiator; - private ConnectionErrorReason _gracefulCloseReason; + private ConnectionEndReason _gracefulCloseReason; private int _stoppedAcceptingStreams; private bool _gracefulCloseStarted; private int _activeRequestCount; @@ -105,10 +105,10 @@ private void UpdateHighestOpenedRequestStreamId(long streamId) public string ConnectionId => _context.ConnectionId; public ITimeoutControl TimeoutControl => _context.TimeoutControl; - public void StopProcessingNextRequest(ConnectionErrorReason reason) + public void StopProcessingNextRequest(ConnectionEndReason reason) => StopProcessingNextRequest(serverInitiated: true, reason); - public void StopProcessingNextRequest(bool serverInitiated, ConnectionErrorReason reason) + public void StopProcessingNextRequest(bool serverInitiated, ConnectionEndReason reason) { bool previousState; lock (_protocolSelectionLock) @@ -155,12 +155,12 @@ private bool TryStopAcceptingStreams() return false; } - public void Abort(ConnectionAbortedException ex, ConnectionErrorReason reason) + public void Abort(ConnectionAbortedException ex, ConnectionEndReason reason) { Abort(ex, Http3ErrorCode.InternalError, reason); } - public void Abort(ConnectionAbortedException ex, Http3ErrorCode errorCode, ConnectionErrorReason reason) + public void Abort(ConnectionAbortedException ex, Http3ErrorCode errorCode, ConnectionEndReason reason) { bool previousState; @@ -188,9 +188,9 @@ public void Abort(ConnectionAbortedException ex, Http3ErrorCode errorCode, Conne if (!previousState) { _errorCodeFeature.Error = (long)errorCode; - if (_metricsTagsFeature != null && reason != ConnectionErrorReason.NoError) + if (_metricsTagsFeature != null && reason != ConnectionEndReason.NoError) { - _metricsTagsFeature.TryAddTag("kestrel.connection.error_reason", reason.ToString()); + _metricsTagsFeature.TryAddTag(KestrelMetrics.KestrelConnectionEndReason, reason.ToString()); } if (TryStopAcceptingStreams()) @@ -245,7 +245,7 @@ static void ValidateOpenControlStream(Http3ControlStream? stream, Http3Connectio if (stream.StreamTimeoutTimestamp < timestamp) { - connection.OnStreamConnectionError(new Http3ConnectionErrorException("A control stream used by the connection was closed or reset.", Http3ErrorCode.ClosedCriticalStream, ConnectionErrorReason.ClosedCriticalStream)); + connection.OnStreamConnectionError(new Http3ConnectionErrorException(CoreStrings.Http3ErrorControlStreamClosed, Http3ErrorCode.ClosedCriticalStream, ConnectionEndReason.ClosedCriticalStream)); } } } @@ -323,7 +323,7 @@ private void UpdateStreamTimeouts(long timestamp) { // Cancel connection to be consistent with other data rate limits. Log.ResponseMinimumDataRateNotSatisfied(_context.ConnectionId, stream.TraceIdentifier); - OnStreamConnectionError(new Http3ConnectionErrorException(CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied, Http3ErrorCode.InternalError, ConnectionErrorReason.ResponseMininumDataRateNotSatisfied)); + OnStreamConnectionError(new Http3ConnectionErrorException(CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied, Http3ErrorCode.InternalError, ConnectionEndReason.MinResponseDataRate)); } } } @@ -346,7 +346,7 @@ private void UpdateStreamTimeouts(long timestamp) Http3ControlStream? outboundControlStream = null; ValueTask outboundControlStreamTask = default; bool clientAbort = false; - ConnectionErrorReason errorReason = ConnectionErrorReason.NoError; + ConnectionEndReason errorReason = ConnectionEndReason.NoError; try { @@ -470,7 +470,7 @@ private void UpdateStreamTimeouts(long timestamp) if (_activeRequestCount > 0) { Log.RequestProcessingError(_context.ConnectionId, ex); - errorReason = ConnectionErrorReason.ConnectionReset; + errorReason = ConnectionEndReason.ConnectionReset; } } error = ex; @@ -480,13 +480,13 @@ private void UpdateStreamTimeouts(long timestamp) { Log.RequestProcessingError(_context.ConnectionId, ex); error = ex; - errorReason = ConnectionErrorReason.IOError; + errorReason = ConnectionEndReason.IOError; } catch (ConnectionAbortedException ex) { Log.RequestProcessingError(_context.ConnectionId, ex); error = ex; - errorReason = ConnectionErrorReason.UnexpectedError; + errorReason = ConnectionEndReason.UnexpectedError; Debug.Fail("Figure out error reason"); } @@ -499,7 +499,7 @@ private void UpdateStreamTimeouts(long timestamp) catch (Exception ex) { error = ex; - errorReason = ConnectionErrorReason.UnexpectedError; + errorReason = ConnectionEndReason.UnexpectedError; } finally { @@ -548,6 +548,12 @@ private void UpdateStreamTimeouts(long timestamp) await outboundControlStreamTask; } + // Use graceful close reason if it has been set. + if (errorReason == ConnectionEndReason.NoError && _gracefulCloseReason != ConnectionEndReason.NoError) + { + errorReason = _gracefulCloseReason; + } + // Complete Abort(CreateConnectionAbortError(error, clientAbort), (Http3ErrorCode)_errorCodeFeature.Error, errorReason); @@ -561,7 +567,7 @@ private void UpdateStreamTimeouts(long timestamp) } catch { - Abort(CreateConnectionAbortError(error, clientAbort), Http3ErrorCode.InternalError, ConnectionErrorReason.UnexpectedError); + Abort(CreateConnectionAbortError(error, clientAbort), Http3ErrorCode.InternalError, ConnectionEndReason.UnexpectedError); throw; } finally @@ -722,11 +728,11 @@ private async ValueTask ProcessOutboundControlStreamAsync(Http3ControlStream con { Log.Http3OutboundControlStreamError(ConnectionId, ex); - var connectionError = new Http3ConnectionErrorException(CoreStrings.Http3ControlStreamErrorInitializingOutbound, Http3ErrorCode.ClosedCriticalStream, ConnectionErrorReason.ClosedCriticalStream); + var connectionError = new Http3ConnectionErrorException(CoreStrings.Http3ControlStreamErrorInitializingOutbound, Http3ErrorCode.ClosedCriticalStream, ConnectionEndReason.ClosedCriticalStream); Log.Http3ConnectionError(ConnectionId, connectionError); // https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-6.2.1 - Abort(new ConnectionAbortedException(connectionError.Message, connectionError), connectionError.ErrorCode, ConnectionErrorReason.ClosedCriticalStream); + Abort(new ConnectionAbortedException(connectionError.Message, connectionError), connectionError.ErrorCode, ConnectionEndReason.ClosedCriticalStream); } } @@ -892,7 +898,7 @@ void IHttp3StreamLifetimeHandler.OnStreamHeaderReceived(IHttp3Stream stream) public void HandleRequestHeadersTimeout() { Log.ConnectionBadRequest(ConnectionId, KestrelBadHttpRequestException.GetException(RequestRejectionReason.RequestHeadersTimeout)); - Abort(new ConnectionAbortedException(CoreStrings.BadRequest_RequestHeadersTimeout), ConnectionErrorReason.RequestHeadersTimeout); + Abort(new ConnectionAbortedException(CoreStrings.BadRequest_RequestHeadersTimeout), ConnectionEndReason.RequestHeadersTimeout); } public void HandleReadDataRateTimeout() @@ -900,7 +906,7 @@ public void HandleReadDataRateTimeout() Debug.Assert(Limits.MinRequestBodyDataRate != null); Log.RequestBodyMinimumDataRateNotSatisfied(ConnectionId, null, Limits.MinRequestBodyDataRate.BytesPerSecond); - Abort(new ConnectionAbortedException(CoreStrings.BadRequest_RequestBodyTimeout), ConnectionErrorReason.RequestBodyTimeout); + Abort(new ConnectionAbortedException(CoreStrings.BadRequest_RequestBodyTimeout), ConnectionEndReason.MinRequestBodyDataRate); } public void OnInputOrOutputCompleted() @@ -908,7 +914,7 @@ public void OnInputOrOutputCompleted() TryStopAcceptingStreams(); // Abort the connection using the error code the client used. For a graceful close, this should be H3_NO_ERROR. - Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient), (Http3ErrorCode)_errorCodeFeature.Error, ConnectionErrorReason.InputOrOutputCompleted); + Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient), (Http3ErrorCode)_errorCodeFeature.Error, ConnectionEndReason.InputOrOutputCompleted); } internal WebTransportSession OpenNewWebTransportSession(Http3Stream http3Stream) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ConnectionErrorException.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ConnectionErrorException.cs index 213d7790f043..2a000ff217e7 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ConnectionErrorException.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ConnectionErrorException.cs @@ -7,7 +7,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3; internal sealed class Http3ConnectionErrorException : Exception { - public Http3ConnectionErrorException(string message, Http3ErrorCode errorCode, ConnectionErrorReason errorReason) + public Http3ConnectionErrorException(string message, Http3ErrorCode errorCode, ConnectionEndReason errorReason) : base($"HTTP/3 connection error ({Http3Formatting.ToFormattedErrorCode(errorCode)}): {message}") { ErrorCode = errorCode; @@ -15,5 +15,5 @@ public Http3ConnectionErrorException(string message, Http3ErrorCode errorCode, C } public Http3ErrorCode ErrorCode { get; } - public ConnectionErrorReason ErrorReason { get; } + public ConnectionEndReason ErrorReason { get; } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs index 20a37fdf6ed9..dbd99d838a0e 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs @@ -202,7 +202,7 @@ private async ValueTask TryReadStreamHeaderAsync() if (!_context.StreamLifetimeHandler.OnInboundControlStream(this)) { // https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-6.2.1 - throw new Http3ConnectionErrorException(CoreStrings.FormatHttp3ControlStreamErrorMultipleInboundStreams("control"), Http3ErrorCode.StreamCreationError, ConnectionErrorReason.StreamCreationError); + throw new Http3ConnectionErrorException(CoreStrings.FormatHttp3ControlStreamErrorMultipleInboundStreams("control"), Http3ErrorCode.StreamCreationError, ConnectionEndReason.StreamCreationError); } await HandleControlStream(); @@ -211,7 +211,7 @@ private async ValueTask TryReadStreamHeaderAsync() if (!_context.StreamLifetimeHandler.OnInboundEncoderStream(this)) { // https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#section-4.2 - throw new Http3ConnectionErrorException(CoreStrings.FormatHttp3ControlStreamErrorMultipleInboundStreams("encoder"), Http3ErrorCode.StreamCreationError, ConnectionErrorReason.StreamCreationError); + throw new Http3ConnectionErrorException(CoreStrings.FormatHttp3ControlStreamErrorMultipleInboundStreams("encoder"), Http3ErrorCode.StreamCreationError, ConnectionEndReason.StreamCreationError); } await HandleEncodingDecodingTask(); @@ -220,7 +220,7 @@ private async ValueTask TryReadStreamHeaderAsync() if (!_context.StreamLifetimeHandler.OnInboundDecoderStream(this)) { // https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#section-4.2 - throw new Http3ConnectionErrorException(CoreStrings.FormatHttp3ControlStreamErrorMultipleInboundStreams("decoder"), Http3ErrorCode.StreamCreationError, ConnectionErrorReason.StreamCreationError); + throw new Http3ConnectionErrorException(CoreStrings.FormatHttp3ControlStreamErrorMultipleInboundStreams("decoder"), Http3ErrorCode.StreamCreationError, ConnectionEndReason.StreamCreationError); } await HandleEncodingDecodingTask(); break; @@ -302,7 +302,7 @@ private ValueTask ProcessHttp3ControlStream(in ReadOnlySequence payload) case Http3FrameType.Headers: case Http3FrameType.PushPromise: // https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-7.2 - throw new Http3ConnectionErrorException(CoreStrings.FormatHttp3ErrorUnsupportedFrameOnControlStream(_incomingFrame.FormattedType), Http3ErrorCode.UnexpectedFrame, ConnectionErrorReason.UnexpectedFrame); + throw new Http3ConnectionErrorException(CoreStrings.FormatHttp3ErrorUnsupportedFrameOnControlStream(_incomingFrame.FormattedType), Http3ErrorCode.UnexpectedFrame, ConnectionEndReason.UnexpectedFrame); case Http3FrameType.Settings: return ProcessSettingsFrameAsync(payload); case Http3FrameType.GoAway: @@ -321,7 +321,7 @@ private ValueTask ProcessSettingsFrameAsync(ReadOnlySequence payload) if (_haveReceivedSettingsFrame) { // https://quicwg.org/base-drafts/draft-ietf-quic-http.html#name-settings - throw new Http3ConnectionErrorException(CoreStrings.Http3ErrorControlStreamMultipleSettingsFrames, Http3ErrorCode.UnexpectedFrame, ConnectionErrorReason.UnexpectedFrame); + throw new Http3ConnectionErrorException(CoreStrings.Http3ErrorControlStreamMultipleSettingsFrames, Http3ErrorCode.UnexpectedFrame, ConnectionEndReason.UnexpectedFrame); } _haveReceivedSettingsFrame = true; @@ -367,7 +367,7 @@ private void ProcessSetting(long id, long value) // HTTP/2 settings are reserved. // https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-7.2.4.1-5 var message = CoreStrings.FormatHttp3ErrorControlStreamReservedSetting("0x" + id.ToString("X", CultureInfo.InvariantCulture)); - throw new Http3ConnectionErrorException(message, Http3ErrorCode.SettingsError, ConnectionErrorReason.InvalidSettings); + throw new Http3ConnectionErrorException(message, Http3ErrorCode.SettingsError, ConnectionEndReason.InvalidSettings); case (long)Http3SettingType.QPackMaxTableCapacity: case (long)Http3SettingType.MaxFieldSectionSize: case (long)Http3SettingType.QPackBlockedStreams: @@ -387,7 +387,7 @@ private ValueTask ProcessGoAwayFrameAsync() EnsureSettingsFrame(Http3FrameType.GoAway); // StopProcessingNextRequest must be called before RequestClose to ensure it's considered client initiated. - _context.Connection.StopProcessingNextRequest(serverInitiated: false, ConnectionErrorReason.ClientGoAway); + _context.Connection.StopProcessingNextRequest(serverInitiated: false, ConnectionEndReason.ClientGoAway); _context.ConnectionContext.Features.Get()?.RequestClose(); // https://quicwg.org/base-drafts/draft-ietf-quic-http.html#name-goaway @@ -431,7 +431,7 @@ private void EnsureSettingsFrame(Http3FrameType frameType) if (!_haveReceivedSettingsFrame) { var message = CoreStrings.FormatHttp3ErrorControlStreamFrameReceivedBeforeSettings(Http3Formatting.ToFormattedType(frameType)); - throw new Http3ConnectionErrorException(message, Http3ErrorCode.MissingSettings, ConnectionErrorReason.InvalidSettings); + throw new Http3ConnectionErrorException(message, Http3ErrorCode.MissingSettings, ConnectionEndReason.InvalidSettings); } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3OutputProducer.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3OutputProducer.cs index 461a76611f87..8e0715bc9960 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3OutputProducer.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3OutputProducer.cs @@ -95,7 +95,7 @@ public void Dispose() } } - void IHttpOutputAborter.Abort(ConnectionAbortedException abortReason, ConnectionErrorReason errorReason) + void IHttpOutputAborter.Abort(ConnectionAbortedException abortReason, ConnectionEndReason errorReason) { _stream.Abort(abortReason, Http3ErrorCode.InternalError); } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs index de96a5bc781e..823be2934dfb 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs @@ -757,10 +757,10 @@ Http3FrameType.Settings or Http3FrameType.CancelPush or Http3FrameType.GoAway or Http3FrameType.MaxPushId => throw new Http3ConnectionErrorException( - CoreStrings.FormatHttp3ErrorUnsupportedFrameOnRequestStream(_incomingFrame.FormattedType), Http3ErrorCode.UnexpectedFrame, ConnectionErrorReason.ReceivedUnsupportedFrame), + CoreStrings.FormatHttp3ErrorUnsupportedFrameOnRequestStream(_incomingFrame.FormattedType), Http3ErrorCode.UnexpectedFrame, ConnectionEndReason.UnsupportedFrame), // The server should never receive push promise Http3FrameType.PushPromise => throw new Http3ConnectionErrorException( - CoreStrings.FormatHttp3ErrorUnsupportedFrameOnServer(_incomingFrame.FormattedType), Http3ErrorCode.UnexpectedFrame, ConnectionErrorReason.ReceivedUnsupportedFrame), + CoreStrings.FormatHttp3ErrorUnsupportedFrameOnServer(_incomingFrame.FormattedType), Http3ErrorCode.UnexpectedFrame, ConnectionEndReason.UnsupportedFrame), _ => ProcessUnknownFrameAsync(), }; } @@ -778,7 +778,7 @@ private static Task ProcessUnknownFrameAsync() // https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-4.1 if (_requestHeaderParsingState == RequestHeaderParsingState.Trailers) { - throw new Http3ConnectionErrorException(CoreStrings.FormatHttp3StreamErrorFrameReceivedAfterTrailers(Http3Formatting.ToFormattedType(Http3FrameType.Headers)), Http3ErrorCode.UnexpectedFrame, ConnectionErrorReason.UnexpectedFrame); + throw new Http3ConnectionErrorException(CoreStrings.FormatHttp3StreamErrorFrameReceivedAfterTrailers(Http3Formatting.ToFormattedType(Http3FrameType.Headers)), Http3ErrorCode.UnexpectedFrame, ConnectionEndReason.UnexpectedFrame); } if (_requestHeaderParsingState == RequestHeaderParsingState.Body) @@ -877,7 +877,7 @@ private Task ProcessDataFrameAsync(in ReadOnlySequence payload) // https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-4.1 if (_requestHeaderParsingState == RequestHeaderParsingState.Ready) { - throw new Http3ConnectionErrorException(CoreStrings.Http3StreamErrorDataReceivedBeforeHeaders, Http3ErrorCode.UnexpectedFrame, ConnectionErrorReason.UnexpectedFrame); + throw new Http3ConnectionErrorException(CoreStrings.Http3StreamErrorDataReceivedBeforeHeaders, Http3ErrorCode.UnexpectedFrame, ConnectionEndReason.UnexpectedFrame); } // DATA frame after trailing headers is invalid. @@ -885,7 +885,7 @@ private Task ProcessDataFrameAsync(in ReadOnlySequence payload) if (_requestHeaderParsingState == RequestHeaderParsingState.Trailers) { var message = CoreStrings.FormatHttp3StreamErrorFrameReceivedAfterTrailers(Http3Formatting.ToFormattedType(Http3FrameType.Data)); - throw new Http3ConnectionErrorException(message, Http3ErrorCode.UnexpectedFrame, ConnectionErrorReason.UnexpectedFrame); + throw new Http3ConnectionErrorException(message, Http3ErrorCode.UnexpectedFrame, ConnectionEndReason.UnexpectedFrame); } if (InputRemaining.HasValue) diff --git a/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs b/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs index e73d5b96118d..922540557f97 100644 --- a/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs @@ -101,7 +101,7 @@ public HttpConnection(BaseHttpConnectionContext context) connectionHeartbeatFeature?.OnHeartbeat(state => ((HttpConnection)state).Tick(), this); // Register for graceful shutdown of the server - using var shutdownRegistration = connectionLifetimeNotificationFeature?.ConnectionClosedRequested.Register(state => ((HttpConnection)state!).StopProcessingNextRequest(ConnectionErrorReason.ApplicationShutdown), this); + using var shutdownRegistration = connectionLifetimeNotificationFeature?.ConnectionClosedRequested.Register(state => ((HttpConnection)state!).StopProcessingNextRequest(ConnectionEndReason.ApplicationShutdown), this); // Register for connection close using var closedRegistration = _context.ConnectionContext.ConnectionClosed.Register(state => ((HttpConnection)state!).OnConnectionClosed(), this); @@ -137,7 +137,7 @@ internal void Initialize(IRequestProcessor requestProcessor) _protocolSelectionState = ProtocolSelectionState.Selected; } - private void StopProcessingNextRequest(ConnectionErrorReason reason) + private void StopProcessingNextRequest(ConnectionEndReason reason) { ProtocolSelectionState previousState; lock (_protocolSelectionLock) @@ -175,7 +175,7 @@ private void OnConnectionClosed() } } - private void Abort(ConnectionAbortedException ex, ConnectionErrorReason errorReason) + private void Abort(ConnectionAbortedException ex, ConnectionEndReason errorReason) { ProtocolSelectionState previousState; @@ -265,7 +265,7 @@ public void OnTimeout(TimeoutReason reason) switch (reason) { case TimeoutReason.KeepAlive: - _requestProcessor!.StopProcessingNextRequest(ConnectionErrorReason.KeepAliveTimeout); + _requestProcessor!.StopProcessingNextRequest(ConnectionEndReason.KeepAliveTimeout); break; case TimeoutReason.RequestHeaders: _requestProcessor!.HandleRequestHeadersTimeout(); @@ -275,11 +275,11 @@ public void OnTimeout(TimeoutReason reason) break; case TimeoutReason.WriteDataRate: Log.ResponseMinimumDataRateNotSatisfied(_context.ConnectionId, _http1Connection?.TraceIdentifier); - Abort(new ConnectionAbortedException(CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied), ConnectionErrorReason.ResponseMininumDataRateNotSatisfied); + Abort(new ConnectionAbortedException(CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied), ConnectionEndReason.MinResponseDataRate); break; case TimeoutReason.RequestBodyDrain: case TimeoutReason.TimeoutFeature: - Abort(new ConnectionAbortedException(CoreStrings.ConnectionTimedOutByServer), ConnectionErrorReason.ServerTimeout); + Abort(new ConnectionAbortedException(CoreStrings.ConnectionTimedOutByServer), ConnectionEndReason.ServerTimeout); break; default: Debug.Assert(false, "Invalid TimeoutReason"); diff --git a/src/Servers/Kestrel/Core/src/Internal/IRequestProcessor.cs b/src/Servers/Kestrel/Core/src/Internal/IRequestProcessor.cs index a6562b4fe0b9..124da112402f 100644 --- a/src/Servers/Kestrel/Core/src/Internal/IRequestProcessor.cs +++ b/src/Servers/Kestrel/Core/src/Internal/IRequestProcessor.cs @@ -9,10 +9,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal; internal interface IRequestProcessor { Task ProcessRequestsAsync(IHttpApplication application) where TContext : notnull; - void StopProcessingNextRequest(ConnectionErrorReason errorReason); + void StopProcessingNextRequest(ConnectionEndReason errorReason); void HandleRequestHeadersTimeout(); void HandleReadDataRateTimeout(); void OnInputOrOutputCompleted(); void Tick(long timestamp); - void Abort(ConnectionAbortedException ex, ConnectionErrorReason reason); + void Abort(ConnectionAbortedException ex, ConnectionEndReason reason); } diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs index a63e429751d6..f94dc5f1fe92 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs @@ -19,7 +19,7 @@ internal sealed class KestrelMetrics // Note: Dot separated instead of dash. public const string MeterName = "Microsoft.AspNetCore.Server.Kestrel"; - public const string KestrelConnectionErrorReason = "kestrel.connection.error_reason"; + public const string KestrelConnectionEndReason = "kestrel.connection.end_reason"; public const string Http11 = "1.1"; public const string Http2 = "2"; diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/TimingPipeFlusher.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/TimingPipeFlusher.cs index 1227628a0073..288d7c557b08 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/TimingPipeFlusher.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/TimingPipeFlusher.cs @@ -92,7 +92,7 @@ private async ValueTask TimeFlushAsyncAwaited(ValueTask cc.SetTimeout(expectedKeepAliveTimeout, TimeoutReason.KeepAlive)); - _http1Connection.StopProcessingNextRequest(ConnectionErrorReason.ApplicationShutdown); + _http1Connection.StopProcessingNextRequest(ConnectionEndReason.ApplicationShutdown); _application.Output.Complete(); await requestProcessingTask.DefaultTimeout(); @@ -657,7 +657,7 @@ public async Task RequestProcessingTaskIsUnwrapped() var data = Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\nHost:\r\n\r\n"); await _application.Output.WriteAsync(data); - _http1Connection.StopProcessingNextRequest(ConnectionErrorReason.ApplicationShutdown); + _http1Connection.StopProcessingNextRequest(ConnectionEndReason.ApplicationShutdown); Assert.IsNotType>(requestProcessingTask); await requestProcessingTask.DefaultTimeout(); @@ -680,7 +680,7 @@ public async Task RequestAbortedTokenIsResetBeforeLastWriteWithContentLength() await _http1Connection.WriteAsync(new ArraySegment(new[] { (byte)'d' })); Assert.NotEqual(original, _http1Connection.RequestAborted); - _http1Connection.Abort(new ConnectionAbortedException(), ConnectionErrorReason.AbortedByApplication); + _http1Connection.Abort(new ConnectionAbortedException(), ConnectionEndReason.AbortedByApplication); Assert.False(original.IsCancellationRequested); Assert.False(_http1Connection.RequestAborted.IsCancellationRequested); @@ -702,7 +702,7 @@ public async Task RequestAbortedTokenIsResetBeforeLastWriteAsyncWithContentLengt await _http1Connection.WriteAsync(new ArraySegment(new[] { (byte)'d' }), default(CancellationToken)); Assert.NotEqual(original, _http1Connection.RequestAborted); - _http1Connection.Abort(new ConnectionAbortedException(), ConnectionErrorReason.AbortedByApplication); + _http1Connection.Abort(new ConnectionAbortedException(), ConnectionEndReason.AbortedByApplication); Assert.False(original.IsCancellationRequested); Assert.False(_http1Connection.RequestAborted.IsCancellationRequested); @@ -717,7 +717,7 @@ public async void BodyWriter_OnAbortedConnection_ReturnsFlushResultWithIsComplet var successResult = await writer.WriteAsync(payload); Assert.False(successResult.IsCompleted); - _http1Connection.Abort(new ConnectionAbortedException(), ConnectionErrorReason.AbortedByApplication); + _http1Connection.Abort(new ConnectionAbortedException(), ConnectionEndReason.AbortedByApplication); var failResult = await _http1Connection.FlushPipeAsync(new CancellationToken()); Assert.True(failResult.IsCompleted); } @@ -762,7 +762,7 @@ public async Task RequestAbortedTokenIsResetBeforeLastWriteAsyncAwaitedWithConte await _http1Connection.WriteAsync(new ArraySegment(new[] { (byte)'d' }), default(CancellationToken)); Assert.NotEqual(original, _http1Connection.RequestAborted); - _http1Connection.Abort(new ConnectionAbortedException(), ConnectionErrorReason.AbortedByApplication); + _http1Connection.Abort(new ConnectionAbortedException(), ConnectionEndReason.AbortedByApplication); Assert.False(original.IsCancellationRequested); Assert.False(_http1Connection.RequestAborted.IsCancellationRequested); @@ -780,7 +780,7 @@ public async Task RequestAbortedTokenIsResetBeforeLastWriteWithChunkedEncoding() await _http1Connection.ProduceEndAsync(); Assert.NotEqual(original, _http1Connection.RequestAborted); - _http1Connection.Abort(new ConnectionAbortedException(), ConnectionErrorReason.AbortedByApplication); + _http1Connection.Abort(new ConnectionAbortedException(), ConnectionEndReason.AbortedByApplication); Assert.False(original.IsCancellationRequested); Assert.False(_http1Connection.RequestAborted.IsCancellationRequested); @@ -792,7 +792,7 @@ public void RequestAbortedTokenIsFullyUsableAfterCancellation() var originalToken = _http1Connection.RequestAborted; var originalRegistration = originalToken.Register(() => { }); - _http1Connection.Abort(new ConnectionAbortedException(), ConnectionErrorReason.AbortedByApplication); + _http1Connection.Abort(new ConnectionAbortedException(), ConnectionEndReason.AbortedByApplication); Assert.True(originalToken.WaitHandle.WaitOne(TestConstants.DefaultTimeout)); Assert.True(_http1Connection.RequestAborted.WaitHandle.WaitOne(TestConstants.DefaultTimeout)); @@ -806,7 +806,7 @@ public void RequestAbortedTokenIsUsableAfterCancellation() var originalToken = _http1Connection.RequestAborted; var originalRegistration = originalToken.Register(() => { }); - _http1Connection.Abort(new ConnectionAbortedException(), ConnectionErrorReason.AbortedByApplication); + _http1Connection.Abort(new ConnectionAbortedException(), ConnectionEndReason.AbortedByApplication); // The following line will throw an ODE because the original CTS backing the token has been diposed. // See https://github.com/dotnet/aspnetcore/pull/4447 for the history behind this test. diff --git a/src/Servers/Kestrel/Core/test/Http1/Http1OutputProducerTests.cs b/src/Servers/Kestrel/Core/test/Http1/Http1OutputProducerTests.cs index f3ddb67f8f8e..381a1ccd2483 100644 --- a/src/Servers/Kestrel/Core/test/Http1/Http1OutputProducerTests.cs +++ b/src/Servers/Kestrel/Core/test/Http1/Http1OutputProducerTests.cs @@ -125,11 +125,11 @@ public void AbortsTransportEvenAfterDispose() mockConnectionContext.Verify(f => f.Abort(It.IsAny()), Times.Never()); - outputProducer.Abort(null, ConnectionErrorReason.AbortedByApplication); + outputProducer.Abort(null, ConnectionEndReason.AbortedByApplication); mockConnectionContext.Verify(f => f.Abort(null), Times.Once()); - outputProducer.Abort(null, ConnectionErrorReason.AbortedByApplication); + outputProducer.Abort(null, ConnectionEndReason.AbortedByApplication); mockConnectionContext.Verify(f => f.Abort(null), Times.Once()); } diff --git a/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs b/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs index 3bc7d793be57..8aae0764d2e7 100644 --- a/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs +++ b/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs @@ -93,6 +93,8 @@ public void OnTimeout(TimeoutReason reason) internal TestMultiplexedConnectionContext MultiplexedConnectionContext { get; set; } + internal IDictionary ConnectionTags => MultiplexedConnectionContext.Tags.ToDictionary(t => t.Key, t => t.Value); + internal long GetStreamId(long mask) { var id = (_currentStreamId << 2) | mask; @@ -981,7 +983,7 @@ internal void VerifyGoAway(Http3FrameWithPayload frame, long expectedLastStreamI } } -internal class TestMultiplexedConnectionContext : MultiplexedConnectionContext, IConnectionLifetimeNotificationFeature, IConnectionLifetimeFeature, IConnectionHeartbeatFeature, IProtocolErrorCodeFeature, IConnectionMetricsContextFeature +internal class TestMultiplexedConnectionContext : MultiplexedConnectionContext, IConnectionLifetimeNotificationFeature, IConnectionLifetimeFeature, IConnectionHeartbeatFeature, IProtocolErrorCodeFeature, IConnectionMetricsContextFeature, IConnectionMetricsTagsFeature { public readonly Channel ToServerAcceptQueue = Channel.CreateUnbounded(new UnboundedChannelOptions { @@ -1006,6 +1008,7 @@ public TestMultiplexedConnectionContext(Http3InMemory testBase) Features.Set(this); Features.Set(this); Features.Set(this); + Features.Set(this); ConnectionClosedRequested = ConnectionClosingCts.Token; } @@ -1024,8 +1027,11 @@ public long Error get => _error ?? -1; set => _error = value; } + public ConnectionMetricsContext MetricsContext { get; } + public ICollection> Tags { get; } = new List>(); + public override void Abort() { Abort(new ConnectionAbortedException()); diff --git a/src/Servers/Kestrel/test/FunctionalTests/RequestTests.cs b/src/Servers/Kestrel/test/FunctionalTests/RequestTests.cs index 32412a9dfaac..8947b6a6d324 100644 --- a/src/Servers/Kestrel/test/FunctionalTests/RequestTests.cs +++ b/src/Servers/Kestrel/test/FunctionalTests/RequestTests.cs @@ -626,7 +626,7 @@ public async Task RequestAbortedTokenFiredOnClientFIN() Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => { - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, m.Tags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, m.Tags.Keys); }); } @@ -729,7 +729,7 @@ public async Task AbortingTheConnection(bool fin) Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => { - Assert.Equal(nameof(ConnectionErrorReason.AbortedByApplication), (string)m.Tags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.AbortedByApplication), (string)m.Tags[KestrelMetrics.KestrelConnectionEndReason]); }); } diff --git a/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs b/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs index b6be76a93b66..f1b0db9ed083 100644 --- a/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs +++ b/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs @@ -263,7 +263,7 @@ public async Task ThrowsOnWriteWithRequestAbortedTokenAfterRequestIsAborted(List Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => { - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, m.Tags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, m.Tags.Keys); }); } @@ -471,7 +471,7 @@ await using (var server = new TestServer(context => Task.CompletedTask, testServ await connectionDuration.WaitForMeasurementsAsync(minCount: 1).DefaultTimeout(); var measurement = connectionDuration.GetMeasurementSnapshot().First(); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, measurement.Tags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, measurement.Tags.Keys); } [Theory] @@ -609,7 +609,7 @@ async Task App(HttpContext context) Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => { - Assert.Equal(nameof(ConnectionErrorReason.ResponseMininumDataRateNotSatisfied), (string)m.Tags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.MinResponseDataRate), (string)m.Tags[KestrelMetrics.KestrelConnectionEndReason]); }); } @@ -860,7 +860,7 @@ async Task App(HttpContext context) Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => { - Assert.Equal(nameof(ConnectionErrorReason.ResponseMininumDataRateNotSatisfied), (string)m.Tags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.MinResponseDataRate), (string)m.Tags[KestrelMetrics.KestrelConnectionEndReason]); }); } @@ -1104,7 +1104,7 @@ await using (var server = new TestServer(App, testContext, listenOptions)) Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => { - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, m.Tags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, m.Tags.Keys); }); } diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs index d078f50e6b3b..2af67e1a7bdd 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs @@ -48,7 +48,7 @@ public async Task MaxConcurrentStreamsLogging_ReachLimit_MessageLogged() Assert.Equal(1, LogMessages.Count(m => m.EventId.Name == "Http2MaxConcurrentStreamsReached")); await StopConnectionAsync(expectedLastStreamId: 5, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -81,7 +81,7 @@ public async Task FlowControl_NoAvailability_ResponseHeadersStillFlushed() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -160,7 +160,7 @@ public async Task FlowControl_OneStream_CorrectlyAwaited() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -210,7 +210,7 @@ public async Task RequestHeaderStringReuse_MultipleStreams_KnownHeaderReused() Assert.Same(path1, path2); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -278,7 +278,7 @@ public async Task RequestHeaderStringReuse_MultipleStreams_KnownHeaderClearedIfN Assert.Equal(StringValues.Empty, contentTypeValue2); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } private class ResponseTrailersWrapper : IHeaderDictionary @@ -396,7 +396,7 @@ public async Task ResponseTrailers_MultipleStreams_Reset() Assert.NotSame(trailersFirst, trailersLast); await StopConnectionAsync(expectedLastStreamId: 5, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -430,7 +430,7 @@ public async Task StreamPool_SingleStream_ReturnedToPool() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -555,7 +555,7 @@ public async Task StreamPool_MultipleStreamsInSequence_PooledStreamReused() withStreamId: 3); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -623,7 +623,7 @@ public async Task StreamPool_EndedStreamErrorsOnStart_NotReturnedToPool() Assert.Equal(0, _connection.StreamPool.Count); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -653,7 +653,7 @@ public async Task StreamPool_UnendedStreamErrorsOnStart_NotReturnedToPool() Assert.Equal(0, _connection.StreamPool.Count); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -695,7 +695,7 @@ public async Task StreamPool_UnusedExpiredStream_RemovedFromPool() Assert.True(((Http2OutputProducer)pooledStream.Output)._disposed); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -713,7 +713,7 @@ public async Task Frame_Received_OverMaxSize_FrameError() expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorFrameOverLimit(length, Http2PeerSettings.MinAllowedMaxFrameSize)); - Assert.Equal(nameof(ConnectionErrorReason.MaxFrameLengthExceeded), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.MaxFrameLengthExceeded), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -746,7 +746,7 @@ public async Task ServerSettings_ChangesRequestMaxFrameSize() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -771,7 +771,7 @@ public async Task DATA_Received_ReadByStream() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); Assert.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.PayloadSequence.ToArray())); } @@ -799,7 +799,7 @@ public async Task DATA_Received_MaxSize_ReadByStream() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame.PayloadSequence.ToArray())); } @@ -894,7 +894,7 @@ public async Task DATA_Received_GreaterThanInitialWindowSize_ReadByStream() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); foreach (var frame in dataFrames) { @@ -934,7 +934,7 @@ public async Task DATA_Received_RightAtWindowLimit_DoesNotPausePipe() await SendDataAsync(1, new Memory(), endStream: true); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -965,7 +965,7 @@ public async Task DATA_Received_Multiple_ReadByStream() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); Assert.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.PayloadSequence.ToArray())); } @@ -1029,7 +1029,7 @@ public async Task DATA_Received_Multiplexed_ReadByStreams() withStreamId: 3); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); Assert.True(_helloBytes.AsSpan().SequenceEqual(stream1DataFrame1.PayloadSequence.ToArray())); Assert.True(_worldBytes.AsSpan().SequenceEqual(stream1DataFrame2.PayloadSequence.ToArray())); @@ -1142,7 +1142,7 @@ public async Task DATA_Received_Multiplexed_GreaterThanInitialWindowSize_ReadByS withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); foreach (var frame in dataFrames) { @@ -1221,7 +1221,7 @@ public async Task DATA_Received_Multiplexed_AppMustNotBlockOtherFrames() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Theory] @@ -1249,7 +1249,7 @@ public async Task DATA_Received_WithPadding_ReadByStream(byte padLength) withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); Assert.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.PayloadSequence.ToArray())); } @@ -1319,7 +1319,7 @@ public async Task DATA_Received_WithPadding_CountsTowardsInputFlowControl(byte p withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame3.PayloadSequence.ToArray())); @@ -1360,7 +1360,7 @@ public async Task DATA_Received_ButNotConsumedByApp_CountsTowardsInputFlowContro withStreamId: 0); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); var updateSize = ((framesConnectionInWindow / 2) + 1) * _maxData.Length; Assert.Equal(updateSize, connectionWindowUpdateFrame.WindowUpdateSizeIncrement); @@ -1452,7 +1452,7 @@ public async Task DATA_BufferRequestBodyLargerThanStreamSizeSmallerThanConnectio withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); foreach (var frame in dataFrames) { @@ -1477,7 +1477,7 @@ public async Task DATA_Received_StreamIdZero_ConnectionError() expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdZero(Http2FrameType.DATA)); - Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -1492,7 +1492,7 @@ public async Task DATA_Received_StreamIdEven_ConnectionError() expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdEven(Http2FrameType.DATA, streamId: 2)); - Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -1508,7 +1508,7 @@ public async Task DATA_Received_PaddingEqualToFramePayloadLength_ConnectionError expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorPaddingTooLong(Http2FrameType.DATA)); - Assert.Equal(nameof(ConnectionErrorReason.InvalidDataPadding), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidDataPadding), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -1524,7 +1524,7 @@ public async Task DATA_Received_PaddingGreaterThanFramePayloadLength_ConnectionE expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorPaddingTooLong(Http2FrameType.DATA)); - Assert.Equal(nameof(ConnectionErrorReason.InvalidDataPadding), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidDataPadding), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -1540,7 +1540,7 @@ public async Task DATA_Received_FrameLengthZeroPaddingZero_ConnectionError() expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.DATA, expectedLength: 1)); - Assert.Equal(nameof(ConnectionErrorReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -1556,7 +1556,7 @@ public async Task DATA_Received_InterleavedWithHeaders_ConnectionError() expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.DATA, streamId: 1, headersStreamId: 1)); - Assert.Equal(nameof(ConnectionErrorReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -1571,7 +1571,7 @@ public async Task DATA_Received_StreamIdle_ConnectionError() expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdle(Http2FrameType.DATA, streamId: 1)); - Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -1589,7 +1589,7 @@ public async Task DATA_Received_StreamHalfClosedRemote_ConnectionError() expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.DATA, streamId: 1)); - Assert.Equal(nameof(ConnectionErrorReason.ReceivedFrameAfterStreamClose), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.FrameAfterStreamClose), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -1615,7 +1615,7 @@ public async Task DATA_Received_StreamClosed_ConnectionError() CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.DATA, streamId: 1), CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.DATA, streamId: 1) }); - Assert.Equal(nameof(ConnectionErrorReason.ReceivedFrameUnknownStream), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.UnknownStream), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -1650,7 +1650,7 @@ public async Task Frame_MultipleStreams_CanBeCreatedIfClientCountIsLessThanActua firstRequestBlock.SetResult(); await StopConnectionAsync(3, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -1663,7 +1663,7 @@ public async Task MaxTrackedStreams_SmallMaxConcurrentStreams_LowerLimitOf100Asy Assert.Equal((uint)100, _connection.MaxTrackedStreams); await StopConnectionAsync(0, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -1676,7 +1676,7 @@ public async Task MaxTrackedStreams_DefaultMaxConcurrentStreams_DoubleLimit() Assert.Equal((uint)200, _connection.MaxTrackedStreams); await StopConnectionAsync(0, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -1689,7 +1689,7 @@ public async Task MaxTrackedStreams_LargeMaxConcurrentStreams_DoubleLimit() Assert.Equal((uint)int.MaxValue * 2, _connection.MaxTrackedStreams); await StopConnectionAsync(0, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -1699,8 +1699,8 @@ public void MinimumMaxTrackedStreams() CreateConnection(); // Kestrel always tracks at least 100 streams Assert.Equal(100u, _connection.MaxTrackedStreams); - _connection.Abort(new ConnectionAbortedException(), ConnectionErrorReason.AbortedByApplication); - Assert.Equal(nameof(ConnectionErrorReason.AbortedByApplication), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + _connection.Abort(new ConnectionAbortedException(), ConnectionEndReason.AbortedByApplication); + Assert.Equal(nameof(ConnectionEndReason.AbortedByApplication), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -1770,7 +1770,7 @@ public async Task AbortConnectionAfterTooManyEnhanceYourCalms() expectedErrorCode: Http2ErrorCode.ENHANCE_YOUR_CALM, expectedErrorMessage: CoreStrings.Http2ConnectionFaulted); - Assert.Equal(nameof(ConnectionErrorReason.StreamResetLimitExceeded), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.StreamResetLimitExceeded), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } private async Task RequestUntilEnhanceYourCalm(int maxStreamsPerConnection, int sentStreams) @@ -1800,7 +1800,7 @@ private async Task RequestUntilEnhanceYourCalm(int maxStreamsPerConnection, int tcs.SetResult(); await StopConnectionAsync(streamId, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -1830,7 +1830,7 @@ public async Task DATA_Received_StreamClosedImplicitly_ConnectionError() expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.DATA, streamId: 1)); - Assert.Equal(nameof(ConnectionErrorReason.ReceivedFrameUnknownStream), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.UnknownStream), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -1854,7 +1854,7 @@ public async Task DATA_Received_NoStreamWindowSpace_ConnectionError() expectedErrorCode: Http2ErrorCode.FLOW_CONTROL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorFlowControlWindowExceeded); - Assert.Equal(nameof(ConnectionErrorReason.FlowControlWindowExceeded), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.FlowControlWindowExceeded), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -1885,7 +1885,7 @@ public async Task DATA_Received_NoConnectionWindowSpace_ConnectionError() expectedErrorCode: Http2ErrorCode.FLOW_CONTROL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorFlowControlWindowExceeded); - Assert.Equal(nameof(ConnectionErrorReason.FlowControlWindowExceeded), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.FlowControlWindowExceeded), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -1968,7 +1968,7 @@ public async Task DATA_Sent_DespiteConnectionOutputFlowControl_IfEmptyAndEndsStr await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); await WaitForAllStreamsAsync(); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -2054,7 +2054,7 @@ public async Task DATA_Sent_DespiteStreamOutputFlowControl_IfEmptyAndEndsStream( await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -2108,7 +2108,7 @@ public async Task StreamWindow_BiggerThan_ConnectionWindow() await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -2127,7 +2127,7 @@ public async Task HEADERS_Received_Decoded() await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Theory] @@ -2149,7 +2149,7 @@ public async Task HEADERS_Received_WithPadding_Decoded(byte padLength) await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -2168,7 +2168,7 @@ public async Task HEADERS_Received_WithPriority_Decoded() await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Theory] @@ -2190,7 +2190,7 @@ public async Task HEADERS_Received_WithPriorityAndPadding_Decoded(byte padLength await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Theory] @@ -2238,7 +2238,7 @@ public async Task HEADERS_Received_WithTrailers_Available(bool sendData) await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -2272,7 +2272,7 @@ public async Task HEADERS_Received_ContainsExpect100Continue_100ContinueSent() await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -2323,7 +2323,7 @@ public async Task HEADERS_Received_AppCannotBlockOtherFrames() await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -2359,7 +2359,7 @@ public async Task HEADERS_HeaderTableSizeLimitZero_Received_DynamicTableUpdate() await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -2519,7 +2519,7 @@ public async Task HEADERS_Received_StreamIdZero_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdZero(Http2FrameType.HEADERS)); - Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -2535,7 +2535,7 @@ public async Task HEADERS_Received_StreamIdEven_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdEven(Http2FrameType.HEADERS, streamId: 2)); - Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -2563,7 +2563,7 @@ public async Task HEADERS_Received_StreamClosed_ConnectionError() CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.HEADERS, streamId: 1) }); - Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -2582,7 +2582,7 @@ public async Task HEADERS_Received_StreamHalfClosedRemote_ConnectionError() expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.HEADERS, streamId: 1)); - Assert.Equal(nameof(ConnectionErrorReason.ReceivedFrameAfterStreamClose), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.FrameAfterStreamClose), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -2606,7 +2606,7 @@ public async Task HEADERS_Received_StreamClosedImplicitly_ConnectionError() expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1)); - Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Theory] @@ -2625,7 +2625,7 @@ public async Task HEADERS_Received_PaddingEqualToFramePayloadLength_ConnectionEr expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorPaddingTooLong(Http2FrameType.HEADERS)); - Assert.Equal(nameof(ConnectionErrorReason.InvalidDataPadding), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidDataPadding), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -2641,7 +2641,7 @@ public async Task HEADERS_Received_PaddingFieldMissing_ConnectionError() expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.HEADERS, expectedLength: 1)); - Assert.Equal(nameof(ConnectionErrorReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Theory] @@ -2659,7 +2659,7 @@ public async Task HEADERS_Received_PaddingGreaterThanFramePayloadLength_Connecti expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorPaddingTooLong(Http2FrameType.HEADERS)); - Assert.Equal(nameof(ConnectionErrorReason.InvalidDataPadding), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidDataPadding), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -2676,7 +2676,7 @@ public async Task HEADERS_Received_InterleavedWithHeaders_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.HEADERS, streamId: 3, headersStreamId: 1)); - Assert.Equal(nameof(ConnectionErrorReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -2692,7 +2692,7 @@ public async Task HEADERS_Received_WithPriority_StreamDependencyOnSelf_Connectio expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamSelfDependency(Http2FrameType.HEADERS, streamId: 1)); - Assert.Equal(nameof(ConnectionErrorReason.StreamSelfDependency), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.StreamSelfDependency), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -2708,7 +2708,7 @@ public async Task HEADERS_Received_IncompleteHeaderBlock_ConnectionError() expectedErrorCode: Http2ErrorCode.COMPRESSION_ERROR, expectedErrorMessage: SR.net_http_hpack_incomplete_header_block); - Assert.Equal(nameof(ConnectionErrorReason.ErrorReadingHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.ErrorReadingHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -2738,7 +2738,7 @@ public async Task HEADERS_Received_IntegerOverLimit_ConnectionError() expectedErrorCode: Http2ErrorCode.COMPRESSION_ERROR, expectedErrorMessage: SR.net_http_hpack_bad_integer); - Assert.Equal(nameof(ConnectionErrorReason.ErrorReadingHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.ErrorReadingHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Theory] @@ -2756,7 +2756,7 @@ public async Task HEADERS_Received_WithTrailers_ContainsIllegalTrailer_Connectio expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: expectedErrorMessage); - Assert.Equal(nameof(ConnectionErrorReason.InvalidRequestHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidRequestHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Theory] @@ -2776,7 +2776,7 @@ public async Task HEADERS_Received_WithTrailers_EndStreamNotSet_ConnectionError( expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorHeadersWithTrailersNoEndStream); - Assert.Equal(nameof(ConnectionErrorReason.MissingStreamEnd), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.MissingStreamEnd), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Theory] @@ -2792,7 +2792,7 @@ public async Task HEADERS_Received_HeaderNameContainsUpperCaseCharacter_Connecti expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.HttpErrorHeaderNameUppercase); - Assert.Equal(nameof(ConnectionErrorReason.InvalidRequestHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidRequestHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -2845,7 +2845,7 @@ public async Task HEADERS_Received_HeaderBlockDoesNotContainMandatoryPseudoHeade await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Theory] @@ -2865,7 +2865,7 @@ private async Task HEADERS_Received_InvalidHeaderFields_ConnectionError(IEnumera expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: expectedErrorMessage); - Assert.Equal(nameof(ConnectionErrorReason.InvalidRequestHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidRequestHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Theory] @@ -2888,7 +2888,7 @@ public async Task HEADERS_Received_HeaderBlockDoesNotContainMandatoryPseudoHeade expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1)); - Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -3004,7 +3004,7 @@ public async Task HEADERS_Received_HeaderBlockContainsTEHeader_ValueIsTrailers_N await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -3024,7 +3024,7 @@ public async Task HEADERS_Received_RequestLineLength_StreamError() await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -3067,7 +3067,7 @@ public async Task PRIORITY_Received_StreamIdZero_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdZero(Http2FrameType.PRIORITY)); - Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -3083,7 +3083,7 @@ public async Task PRIORITY_Received_StreamIdEven_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdEven(Http2FrameType.PRIORITY, streamId: 2)); - Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Theory] @@ -3101,7 +3101,7 @@ public async Task PRIORITY_Received_LengthNotFive_ConnectionError(int length) expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.PRIORITY, expectedLength: 5)); - Assert.Equal(nameof(ConnectionErrorReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -3118,7 +3118,7 @@ public async Task PRIORITY_Received_InterleavedWithHeaders_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.PRIORITY, streamId: 1, headersStreamId: 1)); - Assert.Equal(nameof(ConnectionErrorReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -3134,7 +3134,7 @@ public async Task PRIORITY_Received_StreamDependencyOnSelf_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamSelfDependency(Http2FrameType.PRIORITY, 1)); - Assert.Equal(nameof(ConnectionErrorReason.StreamSelfDependency), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.StreamSelfDependency), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -3256,7 +3256,7 @@ public async Task RST_STREAM_Received_ContinuesAppsAwaitingConnectionOutputFlowC Assert.Contains(1, _abortedStreamIds); Assert.Contains(3, _abortedStreamIds); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -3342,7 +3342,7 @@ async Task VerifyStreamBackpressure(int streamId, int headersLength) Assert.Contains(3, _abortedStreamIds); Assert.Contains(5, _abortedStreamIds); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -3392,7 +3392,7 @@ public async Task RST_STREAM_Received_StreamIdZero_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdZero(Http2FrameType.RST_STREAM)); - Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -3408,7 +3408,7 @@ public async Task RST_STREAM_Received_StreamIdEven_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdEven(Http2FrameType.RST_STREAM, streamId: 2)); - Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -3424,7 +3424,7 @@ public async Task RST_STREAM_Received_StreamIdle_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdle(Http2FrameType.RST_STREAM, streamId: 1)); - Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Theory] @@ -3445,7 +3445,7 @@ public async Task RST_STREAM_Received_LengthNotFour_ConnectionError(int length) expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.RST_STREAM, expectedLength: 4)); - Assert.Equal(nameof(ConnectionErrorReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -3462,7 +3462,7 @@ public async Task RST_STREAM_Received_InterleavedWithHeaders_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.RST_STREAM, streamId: 1, headersStreamId: 1)); - Assert.Equal(nameof(ConnectionErrorReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } // Compare to h2spec http2/5.1/8 @@ -3489,7 +3489,7 @@ public async Task RST_STREAM_IncompleteRequest_AdditionalDataFrames_ConnectionAb await WaitForConnectionErrorAsync(ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, Http2ErrorCode.STREAM_CLOSED, CoreStrings.FormatHttp2ErrorStreamAborted(Http2FrameType.DATA, 1)); - Assert.Equal(nameof(ConnectionErrorReason.ReceivedFrameAfterStreamClose), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.FrameAfterStreamClose), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -3515,7 +3515,7 @@ public async Task RST_STREAM_IncompleteRequest_AdditionalTrailerFrames_Connectio await WaitForConnectionErrorAsync(ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, Http2ErrorCode.STREAM_CLOSED, CoreStrings.FormatHttp2ErrorStreamAborted(Http2FrameType.HEADERS, 1)); - Assert.Equal(nameof(ConnectionErrorReason.ReceivedFrameAfterStreamClose), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.FrameAfterStreamClose), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -3539,7 +3539,7 @@ public async Task RST_STREAM_IncompleteRequest_AdditionalResetFrame_IgnoreAdditi await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/53744")] @@ -3615,7 +3615,7 @@ public async Task SETTINGS_KestrelDefaults_Sent() await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -3676,7 +3676,7 @@ public async Task SETTINGS_Custom_Sent() await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -3713,7 +3713,7 @@ public async Task SETTINGS_Received_StreamIdNotZero_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdNotZero(Http2FrameType.SETTINGS)); - Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Theory] @@ -3741,7 +3741,7 @@ public async Task SETTINGS_Received_InvalidParameterValue_ConnectionError(int in expectedErrorCode: expectedErrorCode, expectedErrorMessage: CoreStrings.FormatHttp2ErrorSettingsParameterOutOfRange(parameter)); - Assert.Equal(nameof(ConnectionErrorReason.InvalidSettings), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidSettings), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -3758,7 +3758,7 @@ public async Task SETTINGS_Received_InterleavedWithHeaders_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.SETTINGS, streamId: 0, headersStreamId: 1)); - Assert.Equal(nameof(ConnectionErrorReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Theory] @@ -3776,7 +3776,7 @@ public async Task SETTINGS_Received_WithACK_LengthNotZero_ConnectionError(int le expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorSettingsAckLengthNotZero); - Assert.Equal(nameof(ConnectionErrorReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Theory] @@ -3797,7 +3797,7 @@ public async Task SETTINGS_Received_LengthNotMultipleOfSix_ConnectionError(int l expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorSettingsLengthNotMultipleOfSix); - Assert.Equal(nameof(ConnectionErrorReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -3823,7 +3823,7 @@ public async Task SETTINGS_Received_WithInitialWindowSizePushingStreamWindowOver expectedErrorCode: Http2ErrorCode.FLOW_CONTROL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorInitialWindowSizeInvalid); - Assert.Equal(nameof(ConnectionErrorReason.InvalidSettings), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidSettings), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -3882,7 +3882,7 @@ public async Task SETTINGS_Received_ChangesAllowedResponseMaxFrameSize() await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -3923,7 +3923,7 @@ public async Task SETTINGS_Received_ClientMaxFrameSizeCannotExceedServerMaxFrame await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -4006,7 +4006,7 @@ public async Task PUSH_PROMISE_Received_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorPushPromiseReceived); - Assert.Equal(nameof(ConnectionErrorReason.ReceivedUnsupportedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.UnsupportedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -4047,7 +4047,7 @@ public async Task PING_Received_InterleavedWithHeaders_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.PING, streamId: 0, headersStreamId: 1)); - Assert.Equal(nameof(ConnectionErrorReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -4063,7 +4063,7 @@ public async Task PING_Received_StreamIdNotZero_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdNotZero(Http2FrameType.PING)); - Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Theory] @@ -4083,7 +4083,7 @@ public async Task PING_Received_LengthNotEight_ConnectionError(int length) expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.PING, expectedLength: 8)); - Assert.Equal(nameof(ConnectionErrorReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -4095,7 +4095,7 @@ public async Task GOAWAY_Received_ConnectionStops() await WaitForConnectionStopAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionErrorReason.ClientGoAway), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.ClientGoAway), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -4110,7 +4110,7 @@ public async Task GOAWAY_Received_ConnectionLifetimeNotification_Cancelled() Assert.True(lifetime.ConnectionClosedRequested.IsCancellationRequested); await WaitForConnectionStopAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionErrorReason.ClientGoAway), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.ClientGoAway), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -4157,7 +4157,7 @@ public async Task GOAWAY_Received_SetsConnectionStateToClosingAndWaitForAllStrea await WaitForConnectionStopAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); await _closedStateReached.Task.DefaultTimeout(); - Assert.Equal(nameof(ConnectionErrorReason.ClientGoAway), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.ClientGoAway), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -4262,7 +4262,7 @@ public async Task GOAWAY_Received_ContinuesAppsAwaitingConnectionOutputFlowContr Assert.Contains(3, _abortedStreamIds); Assert.Contains(5, _abortedStreamIds); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -4341,7 +4341,7 @@ async Task VerifyStreamBackpressure(int streamId, int headersLength) Assert.Contains(3, _abortedStreamIds); Assert.Contains(5, _abortedStreamIds); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -4357,7 +4357,7 @@ public async Task GOAWAY_Received_StreamIdNotZero_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdNotZero(Http2FrameType.GOAWAY)); - Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -4374,7 +4374,7 @@ public async Task GOAWAY_Received_InterleavedWithHeaders_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.GOAWAY, streamId: 0, headersStreamId: 1)); - Assert.Equal(nameof(ConnectionErrorReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -4390,7 +4390,7 @@ public async Task WINDOW_UPDATE_Received_StreamIdEven_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdEven(Http2FrameType.WINDOW_UPDATE, streamId: 2)); - Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -4407,7 +4407,7 @@ public async Task WINDOW_UPDATE_Received_InterleavedWithHeaders_ConnectionError( expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.WINDOW_UPDATE, streamId: 1, headersStreamId: 1)); - Assert.Equal(nameof(ConnectionErrorReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Theory] @@ -4427,7 +4427,7 @@ public async Task WINDOW_UPDATE_Received_LengthNotFour_ConnectionError(int strea expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.WINDOW_UPDATE, expectedLength: 4)); - Assert.Equal(nameof(ConnectionErrorReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -4443,7 +4443,7 @@ public async Task WINDOW_UPDATE_Received_OnConnection_SizeIncrementZero_Connecti expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorWindowUpdateIncrementZero); - Assert.Equal(nameof(ConnectionErrorReason.WindowUpdateSizeInvalid), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.WindowUpdateSizeInvalid), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -4460,7 +4460,7 @@ public async Task WINDOW_UPDATE_Received_OnStream_SizeIncrementZero_ConnectionEr expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorWindowUpdateIncrementZero); - Assert.Equal(nameof(ConnectionErrorReason.WindowUpdateSizeInvalid), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.WindowUpdateSizeInvalid), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -4476,7 +4476,7 @@ public async Task WINDOW_UPDATE_Received_StreamIdle_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdle(Http2FrameType.WINDOW_UPDATE, streamId: 1)); - Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -4495,7 +4495,7 @@ public async Task WINDOW_UPDATE_Received_OnConnection_IncreasesWindowAboveMaxVal expectedErrorCode: Http2ErrorCode.FLOW_CONTROL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorWindowUpdateSizeInvalid); - Assert.Equal(nameof(ConnectionErrorReason.WindowUpdateSizeInvalid), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.WindowUpdateSizeInvalid), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -4516,7 +4516,7 @@ public async Task WINDOW_UPDATE_Received_OnStream_IncreasesWindowAboveMaxValue_S await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -4609,7 +4609,7 @@ public async Task WINDOW_UPDATE_Received_OnConnection_Respected() await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -4798,7 +4798,7 @@ public async Task CONTINUATION_Received_StreamIdMismatch_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.CONTINUATION, streamId: 3, headersStreamId: 1)); - Assert.Equal(nameof(ConnectionErrorReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -4815,7 +4815,7 @@ public async Task CONTINUATION_Received_IncompleteHeaderBlock_ConnectionError() expectedErrorCode: Http2ErrorCode.COMPRESSION_ERROR, expectedErrorMessage: SR.net_http_hpack_incomplete_header_block); - Assert.Equal(nameof(ConnectionErrorReason.ErrorReadingHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.ErrorReadingHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Theory] @@ -4834,7 +4834,7 @@ public async Task CONTINUATION_Received_WithTrailers_ContainsIllegalTrailer_Conn expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: expectedErrorMessage); - Assert.Equal(nameof(ConnectionErrorReason.InvalidRequestHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidRequestHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Theory] @@ -4859,7 +4859,7 @@ public async Task CONTINUATION_Received_HeaderBlockDoesNotContainMandatoryPseudo expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1)); - Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Theory] @@ -4878,7 +4878,7 @@ public async Task CONTINUATION_Received_HeaderBlockDoesNotContainMandatoryPseudo await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -4920,7 +4920,7 @@ public async Task CONTINUATION_Sent_WhenHeadersLargerThanFrameLength() Assert.Equal(_4kHeaderValue, _decodedHeaders["g"]); Assert.Equal(_4kHeaderValue, _decodedHeaders["h"]); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -4939,7 +4939,7 @@ public async Task UnknownFrameType_Received_Ignored() await StopConnectionAsync(0, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -4956,7 +4956,7 @@ public async Task UnknownFrameType_Received_InterleavedWithHeaders_ConnectionErr expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(frameType: 42, streamId: 1, headersStreamId: 1)); - Assert.Equal(nameof(ConnectionErrorReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -4983,7 +4983,7 @@ public async Task ConnectionErrorAbortsAllStreams() Assert.Contains(3, _abortedStreamIds); Assert.Contains(5, _abortedStreamIds); - Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -4998,7 +4998,7 @@ public async Task ConnectionResetLoggedWithActiveStreams() await StopConnectionAsync(1, ignoreNonGoAwayFrames: false); Assert.Single(LogMessages, m => m.Exception is ConnectionResetException); - Assert.Equal(nameof(ConnectionErrorReason.ConnectionReset), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.ConnectionReset), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -5011,7 +5011,7 @@ public async Task ConnectionResetNotLoggedWithNoActiveStreams() await WaitForConnectionStopAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false); Assert.DoesNotContain(LogMessages, m => m.Exception is ConnectionResetException); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -5026,7 +5026,7 @@ public async Task OnInputOrOutputCompletedCompletesOutput() Assert.True(result.IsCompleted); Assert.True(result.Buffer.IsEmpty); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -5034,12 +5034,12 @@ public async Task AbortSendsFinalGOAWAY() { await InitializeConnectionAsync(_noopApplication); - _connection.Abort(new ConnectionAbortedException(), ConnectionErrorReason.AbortedByApplication); + _connection.Abort(new ConnectionAbortedException(), ConnectionEndReason.AbortedByApplication); await _closedStateReached.Task.DefaultTimeout(); VerifyGoAway(await ReceiveFrameAsync(), int.MaxValue, Http2ErrorCode.INTERNAL_ERROR); - Assert.Equal(nameof(ConnectionErrorReason.AbortedByApplication), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.AbortedByApplication), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -5053,7 +5053,7 @@ public async Task CompletionSendsFinalGOAWAY() VerifyGoAway(await ReceiveFrameAsync(), 0, Http2ErrorCode.NO_ERROR); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -5099,7 +5099,7 @@ public async Task StopProcessingNextRequestSendsGracefulGOAWAYAndWaitsForStreams result = await readTask; Assert.True(result.IsCompleted); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -5109,7 +5109,7 @@ public async Task StopProcessingNextRequestSendsGracefulGOAWAYThenFinalGOAWAYWhe await StartStreamAsync(1, _browserRequestHeaders, endStream: false); - _connection.StopProcessingNextRequest(ConnectionErrorReason.ApplicationShutdown); + _connection.StopProcessingNextRequest(ConnectionEndReason.ApplicationShutdown); await _closingStateReached.Task.DefaultTimeout(); VerifyGoAway(await ReceiveFrameAsync(), Int32.MaxValue, Http2ErrorCode.NO_ERROR); @@ -5132,7 +5132,7 @@ public async Task StopProcessingNextRequestSendsGracefulGOAWAYThenFinalGOAWAYWhe await _closedStateReached.Task.DefaultTimeout(); VerifyGoAway(await ReceiveFrameAsync(), 1, Http2ErrorCode.NO_ERROR); - Assert.Equal(nameof(ConnectionErrorReason.ApplicationShutdown), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.ApplicationShutdown), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -5143,7 +5143,7 @@ public async Task AcceptNewStreamsDuringClosingConnection() await StartStreamAsync(1, _browserRequestHeaders, endStream: false); - _connection.StopProcessingNextRequest(ConnectionErrorReason.ApplicationShutdown); + _connection.StopProcessingNextRequest(ConnectionEndReason.ApplicationShutdown); VerifyGoAway(await ReceiveFrameAsync(), Int32.MaxValue, Http2ErrorCode.NO_ERROR); await _closingStateReached.Task.DefaultTimeout(); @@ -5181,7 +5181,7 @@ public async Task AcceptNewStreamsDuringClosingConnection() await WaitForConnectionStopAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionErrorReason.ApplicationShutdown), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.ApplicationShutdown), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -5203,7 +5203,7 @@ public async Task IgnoreNewStreamsDuringClosedConnection() Assert.True(result.IsCompleted); Assert.True(result.Buffer.IsEmpty); - Assert.Equal(nameof(ConnectionErrorReason.ConnectionReset), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.ConnectionReset), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -5223,7 +5223,7 @@ public async Task IOExceptionDuringFrameProcessingIsNotLoggedHigherThanDebug() Assert.Equal("Connection id \"TestConnectionId\" request processing ended abnormally.", logMessage.Message); Assert.Same(ioException, logMessage.Exception); - Assert.Equal(nameof(ConnectionErrorReason.IOError), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.IOError), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -5242,7 +5242,7 @@ public async Task UnexpectedExceptionDuringFrameProcessingLoggedAWarning() Assert.Equal(CoreStrings.RequestProcessingEndError, logMessage.Message); Assert.Same(exception, logMessage.Exception); - Assert.Equal(nameof(ConnectionErrorReason.UnexpectedError), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.UnexpectedError), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Theory] @@ -5295,7 +5295,7 @@ public async Task AppDoesNotReadRequestBody_ResetsAndDrainsRequest(int intFinalF await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Theory] @@ -5341,7 +5341,7 @@ public async Task AbortedStream_ResetsAndDrainsRequest(int intFinalFrameType) await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Theory] @@ -5387,7 +5387,7 @@ public async Task ResetStream_ResetsAndDrainsRequest(int intFinalFrameType) await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Theory] @@ -5473,7 +5473,7 @@ async Task CompletePipeOnTaskCompletion() await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -5545,7 +5545,7 @@ async Task CompletePipeOnTaskCompletion() requestBlock.SetResult(); await StopConnectionAsync(expectedLastStreamId: 7, ignoreNonGoAwayFrames: true); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -5590,7 +5590,7 @@ public async Task FramesInBatchAreStillProcessedAfterStreamError_WithoutHeartbea Assert.Equal(streamPayload, streamResponse); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: true); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Theory] @@ -5629,7 +5629,7 @@ public async Task AbortedStream_ResetsAndDrainsRequest_RefusesFramesAfterEndOfSt CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.DATA, streamId: 1), CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.DATA, streamId: 1) }); - Assert.Equal(nameof(ConnectionErrorReason.ReceivedFrameUnknownStream), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.UnknownStream), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); break; case Http2FrameType.HEADERS: @@ -5646,7 +5646,7 @@ public async Task AbortedStream_ResetsAndDrainsRequest_RefusesFramesAfterEndOfSt CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1), CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.HEADERS, streamId: 1) }); - Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); break; case Http2FrameType.CONTINUATION: @@ -5664,7 +5664,7 @@ public async Task AbortedStream_ResetsAndDrainsRequest_RefusesFramesAfterEndOfSt CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1), CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.HEADERS, streamId: 1) }); - Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); break; default: throw new NotImplementedException(finalFrameType.ToString()); @@ -5720,11 +5720,11 @@ public async Task AbortedStream_ResetsAndDrainsRequest_RefusesFramesAfterClientR switch (finalFrameType) { case Http2FrameType.DATA: - Assert.Equal(nameof(ConnectionErrorReason.ReceivedFrameUnknownStream), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.UnknownStream), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); break; case Http2FrameType.HEADERS: - Assert.Equal(nameof(ConnectionErrorReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); break; default: @@ -5745,7 +5745,7 @@ public async Task StartConnection_SendPreface_ReturnSettings() withStreamId: 0); await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: true); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -5759,7 +5759,7 @@ public async Task StartConnection_SendHttp1xRequest_ReturnHttp11Status400() Assert.NotNull(Http2Connection.InvalidHttp1xErrorResponseBytes); Assert.Equal(Http2Connection.InvalidHttp1xErrorResponseBytes, data); - Assert.Equal(nameof(ConnectionErrorReason.InvalidHttpVersion), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidHttpVersion), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); Assert.Equal(Http2ErrorCode.PROTOCOL_ERROR, (Http2ErrorCode)_errorCodeFeature.Error); } @@ -5775,7 +5775,7 @@ public async Task StartConnection_SendHttp1xRequest_ExceedsRequestLineLimit_Prot expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorInvalidPreface); - Assert.Equal(nameof(ConnectionErrorReason.InvalidHandshake), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidHandshake), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -5792,7 +5792,7 @@ public async Task StartTlsConnection_SendHttp1xRequest_NoError() await SendAsync(Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\n")); await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } [Fact] @@ -5801,7 +5801,7 @@ public async Task StartConnection_SendNothing_NoError() InitializeConnectionWithoutPreface(_noopApplication); await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); } public static TheoryData UpperCaseHeaderNameData diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs index 29d6ff3fcdf7..1d0a1a660acc 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs @@ -38,7 +38,7 @@ public async Task HEADERS_Received_NewLineCharactersInValue_ConnectionError(stri await StartStreamAsync(1, headers, endStream: true); await WaitForConnectionErrorAsync(ignoreNonGoAwayFrames: false, 1, Http2ErrorCode.PROTOCOL_ERROR, "Malformed request: invalid headers."); - Assert.Equal(nameof(ConnectionErrorReason.InvalidRequestHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidRequestHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -2233,7 +2233,7 @@ public async Task ResponseHeaders_WithInvalidValuesAndCustomEncoder_AbortsConnec await StartStreamAsync(1, _browserRequestHeaders, endStream: true); await WaitForConnectionErrorAsync(ignoreNonGoAwayFrames: false, int.MaxValue, Http2ErrorCode.INTERNAL_ERROR); - Assert.Equal(nameof(ConnectionErrorReason.ErrorWritingHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.ErrorWritingHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -2584,7 +2584,7 @@ public async Task ResponseTrailers_WithInvalidValuesAndCustomEncoder_AbortsConne Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]); await WaitForConnectionErrorAsync(ignoreNonGoAwayFrames: false, int.MaxValue, Http2ErrorCode.INTERNAL_ERROR); - Assert.Equal(nameof(ConnectionErrorReason.ErrorWritingHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.ErrorWritingHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -3778,7 +3778,7 @@ public async Task ResponseHeader_OneMegaByte_SplitsHeaderToContinuationFrames() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: true); - Assert.Equal(nameof(ConnectionErrorReason.ErrorWritingHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.ErrorWritingHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -5812,7 +5812,7 @@ public async Task HEADERS_Received_Latin1_RejectedWhenLatin1OptionIsNotConfigure expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.BadRequest_MalformedRequestInvalidHeaders); - Assert.Equal(nameof(ConnectionErrorReason.InvalidRequestHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidRequestHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -5832,7 +5832,7 @@ public async Task HEADERS_Received_CustomEncoding_InvalidCharacters_AbortsConnec await WaitForConnectionErrorAsync(ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, Http2ErrorCode.PROTOCOL_ERROR, CoreStrings.BadRequest_MalformedRequestInvalidHeaders); - Assert.Equal(nameof(ConnectionErrorReason.InvalidRequestHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.InvalidRequestHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs index 56709c313f56..05db9044cb41 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs @@ -99,7 +99,7 @@ public async Task HEADERS_NotReceivedAfterFirstRequest_WithinKeepAliveTimeout_Cl _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.KeepAlive), Times.Once); await WaitForConnectionStopAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionErrorReason.KeepAliveTimeout), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.KeepAliveTimeout), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); _mockTimeoutHandler.VerifyNoOtherCalls(); } @@ -194,7 +194,7 @@ public async Task HEADERS_ReceivedWithoutAllCONTINUATIONs_WithinRequestHeadersTi expectedLastStreamId: int.MaxValue, Http2ErrorCode.INTERNAL_ERROR, CoreStrings.BadRequest_RequestHeadersTimeout); - Assert.Equal(nameof(ConnectionErrorReason.RequestHeadersTimeout), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.RequestHeadersTimeout), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.BadRequest_RequestHeadersTimeout)), Times.Once); @@ -213,7 +213,7 @@ public async Task ResponseDrain_SlowerThanMinimumDataRate_AbortsConnection() await SendGoAwayAsync(); await WaitForConnectionStopAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionErrorReason.ClientGoAway), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.ClientGoAway), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); AdvanceTime(TimeSpan.FromSeconds(_bytesReceived / limits.MinResponseDataRate.BytesPerSecond) + limits.MinResponseDataRate.GracePeriod + Heartbeat.Interval - TimeSpan.FromSeconds(.5)); @@ -304,11 +304,11 @@ async Task AdvanceClockAndSendFrames() switch (finalFrameType) { case Http2FrameType.DATA: - Assert.Equal(nameof(ConnectionErrorReason.ReceivedFrameUnknownStream), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.UnknownStream), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); break; case Http2FrameType.CONTINUATION: - Assert.Equal(nameof(ConnectionErrorReason.ReceivedFrameAfterStreamClose), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.FrameAfterStreamClose), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); break; default: @@ -388,7 +388,7 @@ public async Task DATA_Sent_TooSlowlyDueToSocketBackPressureOnSmallWrite_AbortsC withStreamId: 1); Assert.True((await _pair.Application.Input.ReadAsync().AsTask().DefaultTimeout()).IsCompleted); - Assert.Equal(nameof(ConnectionErrorReason.ResponseMininumDataRateNotSatisfied), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.MinResponseDataRate), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied)), Times.Once); @@ -443,7 +443,7 @@ public async Task DATA_Sent_TooSlowlyDueToSocketBackPressureOnLargeWrite_AbortsC withStreamId: 1); Assert.True((await _pair.Application.Input.ReadAsync().AsTask().DefaultTimeout()).IsCompleted); - Assert.Equal(nameof(ConnectionErrorReason.ResponseMininumDataRateNotSatisfied), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.MinResponseDataRate), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied)), Times.Once); @@ -495,7 +495,7 @@ public async Task DATA_Sent_TooSlowlyDueToFlowControlOnSmallWrite_AbortsConnecti expectedLastStreamId: int.MaxValue, Http2ErrorCode.INTERNAL_ERROR, null); - Assert.Equal(nameof(ConnectionErrorReason.ResponseMininumDataRateNotSatisfied), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.MinResponseDataRate), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied)), Times.Once); @@ -549,7 +549,7 @@ public async Task DATA_Sent_TooSlowlyDueToOutputFlowControlOnLargeWrite_AbortsCo expectedLastStreamId: int.MaxValue, Http2ErrorCode.INTERNAL_ERROR, null); - Assert.Equal(nameof(ConnectionErrorReason.ResponseMininumDataRateNotSatisfied), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.MinResponseDataRate), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied)), Times.Once); @@ -615,7 +615,7 @@ public async Task DATA_Sent_TooSlowlyDueToOutputFlowControlOnMultipleStreams_Abo expectedLastStreamId: int.MaxValue, Http2ErrorCode.INTERNAL_ERROR, null); - Assert.Equal(nameof(ConnectionErrorReason.ResponseMininumDataRateNotSatisfied), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.MinResponseDataRate), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied)), Times.Once); @@ -662,7 +662,7 @@ public async Task DATA_Received_TooSlowlyOnSmallRead_AbortsConnectionAfterGraceP expectedLastStreamId: int.MaxValue, Http2ErrorCode.INTERNAL_ERROR, null); - Assert.Equal(nameof(ConnectionErrorReason.RequestBodyTimeout), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.MinRequestBodyDataRate), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.BadRequest_RequestBodyTimeout)), Times.Once); @@ -713,7 +713,7 @@ public async Task DATA_Received_TooSlowlyOnLargeRead_AbortsConnectionAfterRateTi expectedLastStreamId: int.MaxValue, Http2ErrorCode.INTERNAL_ERROR, null); - Assert.Equal(nameof(ConnectionErrorReason.RequestBodyTimeout), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.MinRequestBodyDataRate), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.BadRequest_RequestBodyTimeout)), Times.Once); @@ -780,7 +780,7 @@ public async Task DATA_Received_TooSlowlyOnMultipleStreams_AbortsConnectionAfter expectedLastStreamId: int.MaxValue, Http2ErrorCode.INTERNAL_ERROR, null); - Assert.Equal(nameof(ConnectionErrorReason.RequestBodyTimeout), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.MinRequestBodyDataRate), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.BadRequest_RequestBodyTimeout)), Times.Once); @@ -848,7 +848,7 @@ public async Task DATA_Received_TooSlowlyOnSecondStream_AbortsConnectionAfterNon expectedLastStreamId: int.MaxValue, Http2ErrorCode.INTERNAL_ERROR, null); - Assert.Equal(nameof(ConnectionErrorReason.RequestBodyTimeout), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.MinRequestBodyDataRate), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.BadRequest_RequestBodyTimeout)), Times.Once); @@ -903,7 +903,7 @@ public async Task DATA_Received_SlowlyWhenRateLimitDisabledPerRequest_DoesNotAbo withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionErrorReason, ConnectionTags.Keys); + Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); _mockTimeoutHandler.VerifyNoOtherCalls(); _mockConnectionContext.VerifyNoOtherCalls(); @@ -989,7 +989,7 @@ public async Task DATA_Received_SlowlyDueToConnectionFlowControl_DoesNotAbortCon expectedLastStreamId: int.MaxValue, Http2ErrorCode.INTERNAL_ERROR, null); - Assert.Equal(nameof(ConnectionErrorReason.RequestBodyTimeout), ConnectionTags[KestrelMetrics.KestrelConnectionErrorReason]); + Assert.Equal(nameof(ConnectionEndReason.MinRequestBodyDataRate), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.BadRequest_RequestBodyTimeout)), Times.Once); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3ConnectionTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3ConnectionTests.cs index 2595f6dd76c3..a15ae2bf49f2 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3ConnectionTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3ConnectionTests.cs @@ -21,6 +21,7 @@ using Microsoft.Net.Http.Headers; using Xunit; using Http3SettingType = Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3.Http3SettingType; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests; @@ -190,6 +191,7 @@ public async Task GOAWAY_GracefulServerShutdown_SendsGoAway(int connectionReques Assert.Null(await Http3Api.MultiplexedConnectionContext.AcceptAsync().DefaultTimeout()); await Http3Api.WaitForConnectionStopAsync(expectedStreamId, false, expectedErrorCode: Http3ErrorCode.NoError); + Assert.Equal(nameof(ConnectionEndReason.ApplicationShutdown), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -219,6 +221,7 @@ public async Task GOAWAY_GracefulServerShutdownWithActiveRequest_SendsMultipleGo Http3Api.MultiplexedConnectionContext.Abort(); await Http3Api.WaitForConnectionStopAsync(4, false, expectedErrorCode: Http3ErrorCode.NoError); + Assert.Equal(nameof(ConnectionEndReason.ApplicationShutdown), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Theory] @@ -245,6 +248,7 @@ public async Task SETTINGS_ReservedSettingSent_ConnectionError(long settingIdent expectedErrorCode: Http3ErrorCode.SettingsError, matchExpectedErrorMessage: AssertExpectedErrorMessages, expectedErrorMessage: CoreStrings.FormatHttp3ErrorControlStreamReservedSetting($"0x{settingIdentifier.ToString("X", CultureInfo.InvariantCulture)}")); + Assert.Equal(nameof(ConnectionEndReason.InvalidSettings), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Theory] @@ -264,6 +268,7 @@ public async Task InboundStreams_CreateMultiple_ConnectionError(int streamId, st expectedErrorCode: Http3ErrorCode.StreamCreationError, matchExpectedErrorMessage: AssertExpectedErrorMessages, expectedErrorMessage: CoreStrings.FormatHttp3ControlStreamErrorMultipleInboundStreams(name)); + Assert.Equal(nameof(ConnectionEndReason.StreamCreationError), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Theory] @@ -285,6 +290,7 @@ public async Task ControlStream_ClientToServer_UnexpectedFrameType_ConnectionErr expectedErrorCode: Http3ErrorCode.UnexpectedFrame, matchExpectedErrorMessage: AssertExpectedErrorMessages, expectedErrorMessage: CoreStrings.FormatHttp3ErrorUnsupportedFrameOnControlStream(Http3Formatting.ToFormattedType(f))); + Assert.Equal(nameof(ConnectionEndReason.UnexpectedFrame), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -309,6 +315,7 @@ public async Task ControlStream_ClientToServer_Completes_ConnectionError() expectedErrorCode: Http3ErrorCode.ClosedCriticalStream, matchExpectedErrorMessage: AssertExpectedErrorMessages, expectedErrorMessage: CoreStrings.Http3ErrorControlStreamClosed); + Assert.Equal(nameof(ConnectionEndReason.ClosedCriticalStream), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -332,6 +339,7 @@ public async Task GOAWAY_TriggersLifetimeNotification_ConnectionClosedRequested( Http3Api.CloseServerGracefully(); await Http3Api.WaitForConnectionStopAsync(0, true, expectedErrorCode: Http3ErrorCode.NoError); + Assert.Equal(nameof(ConnectionEndReason.ClientGoAway), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -352,6 +360,7 @@ public async Task ControlStream_ServerToClient_ErrorInitializing_ConnectionError expectedErrorCode: Http3ErrorCode.ClosedCriticalStream, matchExpectedErrorMessage: AssertExpectedErrorMessages, expectedErrorMessage: CoreStrings.Http3ErrorControlStreamClosed); + Assert.Equal(nameof(ConnectionEndReason.ClosedCriticalStream), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs index d932f784d932..98e641b1b95b 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs @@ -1,24 +1,19 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; using System.Globalization; -using System.IO; -using System.Linq; using System.Net.Http; using System.Runtime.ExceptionServices; using System.Text; -using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Internal; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3; using Microsoft.AspNetCore.InternalTesting; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Net.Http.Headers; -using Xunit; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests; @@ -2032,6 +2027,13 @@ public async Task FrameAfterTrailers_UnexpectedFrameError() expectedErrorMessage: CoreStrings.FormatHttp3StreamErrorFrameReceivedAfterTrailers(Http3Formatting.ToFormattedType(Http3FrameType.Data))); tcs.SetResult(); + + await Http3Api.WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: null, + Http3ErrorCode.UnexpectedFrame, + null); + Assert.Equal(nameof(ConnectionEndReason.UnexpectedFrame), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -2107,6 +2109,7 @@ public async Task UnexpectedRequestFrame(string frameType, bool pendingStreamsEn expectedErrorCode: Http3ErrorCode.UnexpectedFrame, matchExpectedErrorMessage: AssertExpectedErrorMessages, expectedErrorMessage: CoreStrings.FormatHttp3ErrorUnsupportedFrameOnRequestStream(Http3Formatting.ToFormattedType(f))); + Assert.Equal(nameof(ConnectionEndReason.UnsupportedFrame), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Theory] @@ -2130,6 +2133,13 @@ public async Task UnexpectedServerFrame(string frameType) await requestStream.WaitForStreamErrorAsync( Http3ErrorCode.UnexpectedFrame, expectedErrorMessage: CoreStrings.FormatHttp3ErrorUnsupportedFrameOnServer(Http3Formatting.ToFormattedType(f))); + + await Http3Api.WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: null, + Http3ErrorCode.UnexpectedFrame, + null); + Assert.Equal(nameof(ConnectionEndReason.UnexpectedFrame), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TimeoutTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TimeoutTests.cs index 996bd97f24cc..4730a47ed8bb 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TimeoutTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TimeoutTests.cs @@ -28,6 +28,7 @@ public async Task KeepAliveTimeout_ControlStreamNotReceived_ConnectionClosed() Http3Api.AdvanceTime(limits.KeepAliveTimeout + TimeSpan.FromTicks(1)); await Http3Api.WaitForConnectionStopAsync(0, false, expectedErrorCode: Http3ErrorCode.NoError); + Assert.Equal(nameof(ConnectionEndReason.KeepAliveTimeout), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -44,6 +45,7 @@ public async Task KeepAliveTimeout_RequestNotReceived_ConnectionClosed() Http3Api.AdvanceTime(limits.KeepAliveTimeout + TimeSpan.FromTicks(1)); await Http3Api.WaitForConnectionStopAsync(0, false, expectedErrorCode: Http3ErrorCode.NoError); + Assert.Equal(nameof(ConnectionEndReason.KeepAliveTimeout), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -72,6 +74,7 @@ public async Task KeepAliveTimeout_AfterRequestComplete_ConnectionClosed() Http3Api.AdvanceTime(limits.KeepAliveTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1)); await Http3Api.WaitForConnectionStopAsync(4, false, expectedErrorCode: Http3ErrorCode.NoError); + Assert.Equal(nameof(ConnectionEndReason.KeepAliveTimeout), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -117,6 +120,7 @@ public async Task KeepAliveTimeout_LongRunningRequest_KeepsConnectionAlive() Http3Api.AdvanceTime(limits.KeepAliveTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1)); await Http3Api.WaitForConnectionStopAsync(4, false, expectedErrorCode: Http3ErrorCode.NoError); + Assert.Equal(nameof(ConnectionEndReason.KeepAliveTimeout), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -365,6 +369,7 @@ public async Task DATA_Received_TooSlowlyOnSmallRead_AbortsConnectionAfterGraceP expectedLastStreamId: 4, Http3ErrorCode.InternalError, null); + Assert.Equal(nameof(ConnectionEndReason.MinRequestBodyDataRate), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); _mockTimeoutHandler.VerifyNoOtherCalls(); } @@ -408,6 +413,7 @@ public async Task ResponseDrain_SlowerThanMinimumDataRate_AbortsConnection() Http3ErrorCode.InternalError, matchExpectedErrorMessage: AssertExpectedErrorMessages, expectedErrorMessage: CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied); + Assert.Equal(nameof(ConnectionEndReason.MinResponseDataRate), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); Assert.Contains(TestSink.Writes, w => w.EventId.Name == "ResponseMinimumDataRateNotSatisfied"); } @@ -561,6 +567,7 @@ public async Task DATA_Received_TooSlowlyOnLargeRead_AbortsConnectionAfterRateTi expectedLastStreamId: null, Http3ErrorCode.InternalError, null); + Assert.Equal(nameof(ConnectionEndReason.MinRequestBodyDataRate), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); _mockTimeoutHandler.VerifyNoOtherCalls(); } @@ -615,6 +622,7 @@ public async Task DATA_Received_TooSlowlyOnMultipleStreams_AbortsConnectionAfter expectedLastStreamId: null, Http3ErrorCode.InternalError, null); + Assert.Equal(nameof(ConnectionEndReason.MinRequestBodyDataRate), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); _mockTimeoutHandler.VerifyNoOtherCalls(); } @@ -670,6 +678,7 @@ public async Task DATA_Received_TooSlowlyOnSecondStream_AbortsConnectionAfterNon expectedLastStreamId: null, Http3ErrorCode.InternalError, null); + Assert.Equal(nameof(ConnectionEndReason.MinRequestBodyDataRate), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); _mockTimeoutHandler.VerifyNoOtherCalls(); } diff --git a/src/Shared/ServerInfrastructure/Http2/ConnectionErrorReason.cs b/src/Shared/ServerInfrastructure/Http2/ConnectionEndReason.cs similarity index 83% rename from src/Shared/ServerInfrastructure/Http2/ConnectionErrorReason.cs rename to src/Shared/ServerInfrastructure/Http2/ConnectionEndReason.cs index 0376c40660e2..3270239f62eb 100644 --- a/src/Shared/ServerInfrastructure/Http2/ConnectionErrorReason.cs +++ b/src/Shared/ServerInfrastructure/Http2/ConnectionEndReason.cs @@ -3,7 +3,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core; -internal enum ConnectionErrorReason +internal enum ConnectionEndReason { NoError, ConnectionReset, @@ -12,9 +12,9 @@ internal enum ConnectionErrorReason InsufficientTlsVersion, InvalidHandshake, InvalidStreamId, - ReceivedFrameAfterStreamClose, - ReceivedFrameUnknownStream, - ReceivedUnsupportedFrame, + FrameAfterStreamClose, + UnknownStream, + UnsupportedFrame, UnexpectedFrame, InvalidFrameLength, InvalidDataPadding, @@ -31,11 +31,11 @@ internal enum ConnectionErrorReason InputOrOutputCompleted, InvalidHttpVersion, RequestHeadersTimeout, - RequestBodyTimeout, + MinRequestBodyDataRate, + MinResponseDataRate, FlowControlQueueSizeExceeded, OutputQueueSizeExceeded, ClosedCriticalStream, - ResponseMininumDataRateNotSatisfied, AbortedByApplication, ServerTimeout, StreamCreationError, diff --git a/src/Shared/ServerInfrastructure/Http2/Http2ConnectionErrorException.cs b/src/Shared/ServerInfrastructure/Http2/Http2ConnectionErrorException.cs index 5e9ed3127fb8..bc44f084f797 100644 --- a/src/Shared/ServerInfrastructure/Http2/Http2ConnectionErrorException.cs +++ b/src/Shared/ServerInfrastructure/Http2/Http2ConnectionErrorException.cs @@ -7,7 +7,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; internal sealed class Http2ConnectionErrorException : Exception { - public Http2ConnectionErrorException(string message, Http2ErrorCode errorCode, ConnectionErrorReason errorReason) + public Http2ConnectionErrorException(string message, Http2ErrorCode errorCode, ConnectionEndReason errorReason) : base($"HTTP/2 connection error ({errorCode}): {message}") { ErrorCode = errorCode; @@ -15,5 +15,5 @@ public Http2ConnectionErrorException(string message, Http2ErrorCode errorCode, C } public Http2ErrorCode ErrorCode { get; } - public ConnectionErrorReason ErrorReason { get; } + public ConnectionEndReason ErrorReason { get; } } diff --git a/src/Shared/ServerInfrastructure/Http2/Http2FrameReader.cs b/src/Shared/ServerInfrastructure/Http2/Http2FrameReader.cs index 53a379a6411e..cb02d1a8686f 100644 --- a/src/Shared/ServerInfrastructure/Http2/Http2FrameReader.cs +++ b/src/Shared/ServerInfrastructure/Http2/Http2FrameReader.cs @@ -45,7 +45,7 @@ public static bool TryReadFrame(ref ReadOnlySequence buffer, Http2Frame fr var payloadLength = (int)Bitshifter.ReadUInt24BigEndian(header); if (payloadLength > maxFrameSize) { - throw new Http2ConnectionErrorException(SharedStrings.FormatHttp2ErrorFrameOverLimit(payloadLength, maxFrameSize), Http2ErrorCode.FRAME_SIZE_ERROR, ConnectionErrorReason.MaxFrameLengthExceeded); + throw new Http2ConnectionErrorException(SharedStrings.FormatHttp2ErrorFrameOverLimit(payloadLength, maxFrameSize), Http2ErrorCode.FRAME_SIZE_ERROR, ConnectionEndReason.MaxFrameLengthExceeded); } // Make sure the whole frame is buffered @@ -77,7 +77,7 @@ private static int ReadExtendedFields(Http2Frame frame, in ReadOnlySequence frame.PayloadLength) { throw new Http2ConnectionErrorException( - SharedStrings.FormatHttp2ErrorUnexpectedFrameLength(frame.Type, expectedLength: extendedHeaderLength), Http2ErrorCode.FRAME_SIZE_ERROR, ConnectionErrorReason.InvalidFrameLength); + SharedStrings.FormatHttp2ErrorUnexpectedFrameLength(frame.Type, expectedLength: extendedHeaderLength), Http2ErrorCode.FRAME_SIZE_ERROR, ConnectionEndReason.InvalidFrameLength); } var extendedHeaders = readableBuffer.Slice(HeaderLength, extendedHeaderLength).ToSpan(); From fe171bfe0f9e7b6e279861090cd7717cbda55253 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 9 May 2024 13:50:57 +0800 Subject: [PATCH 11/29] Fix tests --- .../Core/test/Http1/Http1OutputProducerTests.cs | 15 ++++++++++++--- .../Http3/Http3StreamTests.cs | 2 +- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/Servers/Kestrel/Core/test/Http1/Http1OutputProducerTests.cs b/src/Servers/Kestrel/Core/test/Http1/Http1OutputProducerTests.cs index 381a1ccd2483..c876e6c125f6 100644 --- a/src/Servers/Kestrel/Core/test/Http1/Http1OutputProducerTests.cs +++ b/src/Servers/Kestrel/Core/test/Http1/Http1OutputProducerTests.cs @@ -117,9 +117,10 @@ public async Task FlushAsync_OnSocketWithCanceledPendingFlush_ReturnsResultWithI [Fact] public void AbortsTransportEvenAfterDispose() { + var metricsTagsFeature = new TestConnectionMetricsTagsFeature(); var mockConnectionContext = new Mock(); - var outputProducer = CreateOutputProducer(connectionContext: mockConnectionContext.Object); + var outputProducer = CreateOutputProducer(connectionContext: mockConnectionContext.Object, metricsTagsFeature: metricsTagsFeature); outputProducer.Dispose(); @@ -132,6 +133,8 @@ public void AbortsTransportEvenAfterDispose() outputProducer.Abort(null, ConnectionEndReason.AbortedByApplication); mockConnectionContext.Verify(f => f.Abort(null), Times.Once()); + + Assert.Equal(nameof(ConnectionEndReason.AbortedByApplication), metricsTagsFeature.Tags.Single(t => t.Key == KestrelMetrics.KestrelConnectionEndReason).Value); } [Fact] @@ -219,7 +222,8 @@ public void ReusesFakeMemory() private TestHttpOutputProducer CreateOutputProducer( PipeOptions pipeOptions = null, - ConnectionContext connectionContext = null) + ConnectionContext connectionContext = null, + IConnectionMetricsTagsFeature metricsTagsFeature = null) { pipeOptions = pipeOptions ?? new PipeOptions(); connectionContext = connectionContext ?? Mock.Of(); @@ -234,12 +238,17 @@ public void ReusesFakeMemory() serviceContext.Log, Mock.Of(), Mock.Of(), - Mock.Of(), + metricsTagsFeature ?? new TestConnectionMetricsTagsFeature(), Mock.Of()); return socketOutput; } + private class TestConnectionMetricsTagsFeature : IConnectionMetricsTagsFeature + { + public ICollection> Tags { get; } = new List>(); + } + private class TestHttpOutputProducer : Http1OutputProducer { public TestHttpOutputProducer(Pipe pipe, string connectionId, ConnectionContext connectionContext, MemoryPool memoryPool, KestrelTrace log, ITimeoutControl timeoutControl, IHttpMinResponseDataRateFeature minResponseDataRateFeature, IConnectionMetricsTagsFeature metricsTagsFeature, IHttpOutputAborter outputAborter) diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs index 98e641b1b95b..ef93e8de5972 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs @@ -2139,7 +2139,7 @@ public async Task UnexpectedServerFrame(string frameType) expectedLastStreamId: null, Http3ErrorCode.UnexpectedFrame, null); - Assert.Equal(nameof(ConnectionEndReason.UnexpectedFrame), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(nameof(ConnectionEndReason.UnsupportedFrame), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] From 3771c1c046ff71f48dc3c98e04f7f72a1ead1dfb Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 9 May 2024 16:36:14 +0800 Subject: [PATCH 12/29] Clean up --- .../Core/src/Internal/Http/Http1Connection.cs | 2 +- .../src/Internal/Http/Http1OutputProducer.cs | 6 +++--- .../src/Internal/Http/IHttpOutputAborter.cs | 2 +- .../src/Internal/Http2/Http2Connection.cs | 14 ++++++------- .../src/Internal/Http2/Http2OutputProducer.cs | 2 +- .../src/Internal/Http3/Http3Connection.cs | 20 +++++++++---------- .../Http3/Http3ConnectionErrorException.cs | 6 +++--- .../src/Internal/Http3/Http3OutputProducer.cs | 2 +- .../Core/src/Internal/HttpConnection.cs | 4 ++-- .../Core/src/Internal/IRequestProcessor.cs | 2 +- .../Http2/Http2ConnectionErrorException.cs | 6 +++--- 11 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs index a45078076d1d..6f11886045c8 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs @@ -130,7 +130,7 @@ protected override void ApplicationAbort() /// Called on all active connections when the server wants to initiate a shutdown /// and after a keep-alive timeout. /// - public void StopProcessingNextRequest(ConnectionEndReason errorReason) + public void StopProcessingNextRequest(ConnectionEndReason reason) { _keepAlive = false; Input.CancelPendingRead(); diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs index 437f65436054..08c3232e607e 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs @@ -449,7 +449,7 @@ private void CompletePipe() } } - public void Abort(ConnectionAbortedException error, ConnectionEndReason errorReason) + public void Abort(ConnectionAbortedException error, ConnectionEndReason reason) { // Abort can be called after Dispose if there's a flush timeout. // It's important to still call _lifetimeFeature.Abort() in this case. @@ -460,9 +460,9 @@ public void Abort(ConnectionAbortedException error, ConnectionEndReason errorRea return; } - if (errorReason != ConnectionEndReason.NoError && _metricsTagsFeature != null) + if (reason != ConnectionEndReason.NoError && _metricsTagsFeature != null) { - _metricsTagsFeature.TryAddTag(KestrelMetrics.KestrelConnectionEndReason, errorReason.ToString()); + _metricsTagsFeature.TryAddTag(KestrelMetrics.KestrelConnectionEndReason, reason.ToString()); } _aborted = true; diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/IHttpOutputAborter.cs b/src/Servers/Kestrel/Core/src/Internal/Http/IHttpOutputAborter.cs index 08250a744e36..8fbc1396a307 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/IHttpOutputAborter.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/IHttpOutputAborter.cs @@ -7,6 +7,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; internal interface IHttpOutputAborter { - void Abort(ConnectionAbortedException abortReason, ConnectionEndReason errorReason); + void Abort(ConnectionAbortedException abortReason, ConnectionEndReason reason); void OnInputOrOutputCompleted(); } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs index b8cdcc97d4f5..d5199d941756 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs @@ -279,7 +279,7 @@ public void StopProcessingNextRequest(bool serverInitiated, ConnectionEndReason { Exception? error = null; var errorCode = Http2ErrorCode.NO_ERROR; - var errorReason = ConnectionEndReason.NoError; + var reason = ConnectionEndReason.NoError; try { @@ -383,7 +383,7 @@ public void StopProcessingNextRequest(bool serverInitiated, ConnectionEndReason if (_clientActiveStreamCount > 0) { Log.RequestProcessingError(ConnectionId, ex); - errorReason = ConnectionEndReason.ConnectionReset; + reason = ConnectionEndReason.ConnectionReset; } error = ex; @@ -392,7 +392,7 @@ public void StopProcessingNextRequest(bool serverInitiated, ConnectionEndReason { Log.RequestProcessingError(ConnectionId, ex); error = ex; - errorReason = ConnectionEndReason.IOError; + reason = ConnectionEndReason.IOError; } catch (ConnectionAbortedException ex) { @@ -404,7 +404,7 @@ public void StopProcessingNextRequest(bool serverInitiated, ConnectionEndReason Log.Http2ConnectionError(ConnectionId, ex); error = ex; errorCode = ex.ErrorCode; - errorReason = ex.ErrorReason; + reason = ex.Reason; } catch (HPackDecodingException ex) { @@ -413,14 +413,14 @@ public void StopProcessingNextRequest(bool serverInitiated, ConnectionEndReason Log.HPackDecodingError(ConnectionId, _currentHeadersStream.StreamId, ex); error = ex; errorCode = Http2ErrorCode.COMPRESSION_ERROR; - errorReason = ConnectionEndReason.ErrorReadingHeaders; + reason = ConnectionEndReason.ErrorReadingHeaders; } catch (Exception ex) { Log.LogWarning(0, ex, CoreStrings.RequestProcessingEndError); error = ex; errorCode = Http2ErrorCode.INTERNAL_ERROR; - errorReason = ConnectionEndReason.UnexpectedError; + reason = ConnectionEndReason.UnexpectedError; } finally { @@ -431,7 +431,7 @@ public void StopProcessingNextRequest(bool serverInitiated, ConnectionEndReason { if (TryClose()) { - SetConnectionErrorCode(errorReason, errorCode); + SetConnectionErrorCode(reason, errorCode); await _frameWriter.WriteGoAwayAsync(_highestOpenedStreamId, errorCode); } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2OutputProducer.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2OutputProducer.cs index 9380ce6711dd..4f7b8606590f 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2OutputProducer.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2OutputProducer.cs @@ -241,7 +241,7 @@ public void Complete() // This is called when a CancellationToken fires mid-write. In HTTP/1.x, this aborts the entire connection. // For HTTP/2 we abort the stream. - void IHttpOutputAborter.Abort(ConnectionAbortedException abortReason, ConnectionEndReason errorReason) + void IHttpOutputAborter.Abort(ConnectionAbortedException abortReason, ConnectionEndReason reason) { _stream.ResetAndAbort(abortReason, Http2ErrorCode.INTERNAL_ERROR); } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs index 03983e55823f..8e8105a4b77c 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs @@ -346,7 +346,7 @@ private void UpdateStreamTimeouts(long timestamp) Http3ControlStream? outboundControlStream = null; ValueTask outboundControlStreamTask = default; bool clientAbort = false; - ConnectionEndReason errorReason = ConnectionEndReason.NoError; + ConnectionEndReason reason = ConnectionEndReason.NoError; try { @@ -470,7 +470,7 @@ private void UpdateStreamTimeouts(long timestamp) if (_activeRequestCount > 0) { Log.RequestProcessingError(_context.ConnectionId, ex); - errorReason = ConnectionEndReason.ConnectionReset; + reason = ConnectionEndReason.ConnectionReset; } } error = ex; @@ -480,13 +480,13 @@ private void UpdateStreamTimeouts(long timestamp) { Log.RequestProcessingError(_context.ConnectionId, ex); error = ex; - errorReason = ConnectionEndReason.IOError; + reason = ConnectionEndReason.IOError; } catch (ConnectionAbortedException ex) { Log.RequestProcessingError(_context.ConnectionId, ex); error = ex; - errorReason = ConnectionEndReason.UnexpectedError; + reason = ConnectionEndReason.UnexpectedError; Debug.Fail("Figure out error reason"); } @@ -494,12 +494,12 @@ private void UpdateStreamTimeouts(long timestamp) { Log.Http3ConnectionError(_context.ConnectionId, ex); error = ex; - errorReason = ex.ErrorReason; + reason = ex.Reason; } catch (Exception ex) { error = ex; - errorReason = ConnectionEndReason.UnexpectedError; + reason = ConnectionEndReason.UnexpectedError; } finally { @@ -549,13 +549,13 @@ private void UpdateStreamTimeouts(long timestamp) } // Use graceful close reason if it has been set. - if (errorReason == ConnectionEndReason.NoError && _gracefulCloseReason != ConnectionEndReason.NoError) + if (reason == ConnectionEndReason.NoError && _gracefulCloseReason != ConnectionEndReason.NoError) { - errorReason = _gracefulCloseReason; + reason = _gracefulCloseReason; } // Complete - Abort(CreateConnectionAbortError(error, clientAbort), (Http3ErrorCode)_errorCodeFeature.Error, errorReason); + Abort(CreateConnectionAbortError(error, clientAbort), (Http3ErrorCode)_errorCodeFeature.Error, reason); // Wait for active requests to complete. while (_activeRequestCount > 0) @@ -865,7 +865,7 @@ void IHttp3StreamLifetimeHandler.OnStreamConnectionError(Http3ConnectionErrorExc private void OnStreamConnectionError(Http3ConnectionErrorException ex) { Log.Http3ConnectionError(ConnectionId, ex); - Abort(new ConnectionAbortedException(ex.Message, ex), ex.ErrorCode, ex.ErrorReason); + Abort(new ConnectionAbortedException(ex.Message, ex), ex.ErrorCode, ex.Reason); } void IHttp3StreamLifetimeHandler.OnInboundControlStreamSetting(Http3SettingType type, long value) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ConnectionErrorException.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ConnectionErrorException.cs index 2a000ff217e7..7140fe795b9a 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ConnectionErrorException.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ConnectionErrorException.cs @@ -7,13 +7,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3; internal sealed class Http3ConnectionErrorException : Exception { - public Http3ConnectionErrorException(string message, Http3ErrorCode errorCode, ConnectionEndReason errorReason) + public Http3ConnectionErrorException(string message, Http3ErrorCode errorCode, ConnectionEndReason reason) : base($"HTTP/3 connection error ({Http3Formatting.ToFormattedErrorCode(errorCode)}): {message}") { ErrorCode = errorCode; - ErrorReason = errorReason; + Reason = reason; } public Http3ErrorCode ErrorCode { get; } - public ConnectionEndReason ErrorReason { get; } + public ConnectionEndReason Reason { get; } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3OutputProducer.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3OutputProducer.cs index 8e0715bc9960..e8e69ec40e85 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3OutputProducer.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3OutputProducer.cs @@ -95,7 +95,7 @@ public void Dispose() } } - void IHttpOutputAborter.Abort(ConnectionAbortedException abortReason, ConnectionEndReason errorReason) + void IHttpOutputAborter.Abort(ConnectionAbortedException abortReason, ConnectionEndReason reason) { _stream.Abort(abortReason, Http3ErrorCode.InternalError); } diff --git a/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs b/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs index 922540557f97..8abfd8396762 100644 --- a/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs @@ -175,7 +175,7 @@ private void OnConnectionClosed() } } - private void Abort(ConnectionAbortedException ex, ConnectionEndReason errorReason) + private void Abort(ConnectionAbortedException ex, ConnectionEndReason reason) { ProtocolSelectionState previousState; @@ -190,7 +190,7 @@ private void Abort(ConnectionAbortedException ex, ConnectionEndReason errorReaso switch (previousState) { case ProtocolSelectionState.Selected: - _requestProcessor!.Abort(ex, errorReason); + _requestProcessor!.Abort(ex, reason); break; case ProtocolSelectionState.Aborted: break; diff --git a/src/Servers/Kestrel/Core/src/Internal/IRequestProcessor.cs b/src/Servers/Kestrel/Core/src/Internal/IRequestProcessor.cs index 124da112402f..8f314ffca1e5 100644 --- a/src/Servers/Kestrel/Core/src/Internal/IRequestProcessor.cs +++ b/src/Servers/Kestrel/Core/src/Internal/IRequestProcessor.cs @@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal; internal interface IRequestProcessor { Task ProcessRequestsAsync(IHttpApplication application) where TContext : notnull; - void StopProcessingNextRequest(ConnectionEndReason errorReason); + void StopProcessingNextRequest(ConnectionEndReason reason); void HandleRequestHeadersTimeout(); void HandleReadDataRateTimeout(); void OnInputOrOutputCompleted(); diff --git a/src/Shared/ServerInfrastructure/Http2/Http2ConnectionErrorException.cs b/src/Shared/ServerInfrastructure/Http2/Http2ConnectionErrorException.cs index bc44f084f797..3f4e78c8a5ba 100644 --- a/src/Shared/ServerInfrastructure/Http2/Http2ConnectionErrorException.cs +++ b/src/Shared/ServerInfrastructure/Http2/Http2ConnectionErrorException.cs @@ -7,13 +7,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; internal sealed class Http2ConnectionErrorException : Exception { - public Http2ConnectionErrorException(string message, Http2ErrorCode errorCode, ConnectionEndReason errorReason) + public Http2ConnectionErrorException(string message, Http2ErrorCode errorCode, ConnectionEndReason reason) : base($"HTTP/2 connection error ({errorCode}): {message}") { ErrorCode = errorCode; - ErrorReason = errorReason; + Reason = reason; } public Http2ErrorCode ErrorCode { get; } - public ConnectionEndReason ErrorReason { get; } + public ConnectionEndReason Reason { get; } } From 47b306987aaaf278a4cdf4d474755c16737e15a3 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 9 May 2024 21:32:46 +0800 Subject: [PATCH 13/29] Remove NoError and always tag reason tag --- .../Core/src/Internal/Http/Http1Connection.cs | 4 +- .../src/Internal/Http/Http1OutputProducer.cs | 2 +- .../src/Internal/Http2/Http2Connection.cs | 12 +- .../src/Internal/Http3/Http3Connection.cs | 8 +- .../test/FunctionalTests/RequestTests.cs | 2 +- .../test/FunctionalTests/ResponseTests.cs | 4 +- .../Http2/Http2ConnectionTests.cs | 140 +++++++++--------- .../Http2/Http2TimeoutTests.cs | 2 +- .../Http2/ConnectionEndReason.cs | 6 +- 9 files changed, 93 insertions(+), 87 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs index 6f11886045c8..69dc9975915d 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs @@ -99,13 +99,13 @@ protected override void OnRequestProcessingEnded() void IRequestProcessor.OnInputOrOutputCompleted() { // Closed gracefully. - _http1Output.Abort(ServerOptions.FinOnError ? new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient) : null!, ConnectionEndReason.NoError); + _http1Output.Abort(ServerOptions.FinOnError ? new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient) : null!, ConnectionEndReason.TransportCompleted); CancelRequestAbortedToken(); } void IHttpOutputAborter.OnInputOrOutputCompleted() { - _http1Output.Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient), ConnectionEndReason.InputOrOutputCompleted); + _http1Output.Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient), ConnectionEndReason.TransportCompleted); CancelRequestAbortedToken(); } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs index 08c3232e607e..4023600ba03d 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs @@ -460,7 +460,7 @@ public void Abort(ConnectionAbortedException error, ConnectionEndReason reason) return; } - if (reason != ConnectionEndReason.NoError && _metricsTagsFeature != null) + if (_metricsTagsFeature != null) { _metricsTagsFeature.TryAddTag(KestrelMetrics.KestrelConnectionEndReason, reason.ToString()); } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs index d5199d941756..9ca8b9877660 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs @@ -213,7 +213,7 @@ public void OnInputOrOutputCompleted() var hasActiveStreams = _clientActiveStreamCount != 0; if (TryClose()) { - SetConnectionErrorCode(hasActiveStreams ? ConnectionEndReason.ConnectionReset : ConnectionEndReason.NoError, Http2ErrorCode.NO_ERROR); + SetConnectionErrorCode(hasActiveStreams ? ConnectionEndReason.ConnectionReset : ConnectionEndReason.TransportCompleted, Http2ErrorCode.NO_ERROR); } var useException = _context.ServiceContext.ServerOptions.FinOnError || hasActiveStreams; _frameWriter.Abort(useException ? new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient) : null!); @@ -225,7 +225,7 @@ private void SetConnectionErrorCode(ConnectionEndReason reason, Http2ErrorCode e Debug.Assert(_errorCodeFeature.Error == -1, "Error code feature should only be set once."); _errorCodeFeature.Error = (long)errorCode; - if (_metricsTagsFeature != null && reason != ConnectionEndReason.NoError) + if (_metricsTagsFeature != null) { _metricsTagsFeature.TryAddTag(KestrelMetrics.KestrelConnectionEndReason, reason.ToString()); } @@ -279,7 +279,7 @@ public void StopProcessingNextRequest(bool serverInitiated, ConnectionEndReason { Exception? error = null; var errorCode = Http2ErrorCode.NO_ERROR; - var reason = ConnectionEndReason.NoError; + var reason = ConnectionEndReason.Unknown; try { @@ -290,6 +290,7 @@ public void StopProcessingNextRequest(bool serverInitiated, ConnectionEndReason if (!await TryReadPrefaceAsync()) { + reason = ConnectionEndReason.TransportCompleted; return; } @@ -349,6 +350,7 @@ public void StopProcessingNextRequest(bool serverInitiated, ConnectionEndReason if (result.IsCompleted) { + reason = ConnectionEndReason.TransportCompleted; return; } @@ -385,6 +387,10 @@ public void StopProcessingNextRequest(bool serverInitiated, ConnectionEndReason Log.RequestProcessingError(ConnectionId, ex); reason = ConnectionEndReason.ConnectionReset; } + else + { + reason = ConnectionEndReason.TransportCompleted; + } error = ex; } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs index 8e8105a4b77c..b31f92435d90 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs @@ -188,7 +188,7 @@ public void Abort(ConnectionAbortedException ex, Http3ErrorCode errorCode, Conne if (!previousState) { _errorCodeFeature.Error = (long)errorCode; - if (_metricsTagsFeature != null && reason != ConnectionEndReason.NoError) + if (_metricsTagsFeature != null) { _metricsTagsFeature.TryAddTag(KestrelMetrics.KestrelConnectionEndReason, reason.ToString()); } @@ -346,7 +346,7 @@ private void UpdateStreamTimeouts(long timestamp) Http3ControlStream? outboundControlStream = null; ValueTask outboundControlStreamTask = default; bool clientAbort = false; - ConnectionEndReason reason = ConnectionEndReason.NoError; + ConnectionEndReason reason = ConnectionEndReason.Unknown; try { @@ -549,7 +549,7 @@ private void UpdateStreamTimeouts(long timestamp) } // Use graceful close reason if it has been set. - if (reason == ConnectionEndReason.NoError && _gracefulCloseReason != ConnectionEndReason.NoError) + if (reason == ConnectionEndReason.Unknown && _gracefulCloseReason != ConnectionEndReason.Unknown) { reason = _gracefulCloseReason; } @@ -914,7 +914,7 @@ public void OnInputOrOutputCompleted() TryStopAcceptingStreams(); // Abort the connection using the error code the client used. For a graceful close, this should be H3_NO_ERROR. - Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient), (Http3ErrorCode)_errorCodeFeature.Error, ConnectionEndReason.InputOrOutputCompleted); + Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByClient), (Http3ErrorCode)_errorCodeFeature.Error, ConnectionEndReason.TransportCompleted); } internal WebTransportSession OpenNewWebTransportSession(Http3Stream http3Stream) diff --git a/src/Servers/Kestrel/test/FunctionalTests/RequestTests.cs b/src/Servers/Kestrel/test/FunctionalTests/RequestTests.cs index 8947b6a6d324..8af773bc7f87 100644 --- a/src/Servers/Kestrel/test/FunctionalTests/RequestTests.cs +++ b/src/Servers/Kestrel/test/FunctionalTests/RequestTests.cs @@ -626,7 +626,7 @@ public async Task RequestAbortedTokenFiredOnClientFIN() Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => { - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, m.Tags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), (string)m.Tags[KestrelMetrics.KestrelConnectionEndReason]); }); } diff --git a/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs b/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs index f1b0db9ed083..e54320a5e24b 100644 --- a/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs +++ b/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs @@ -263,7 +263,7 @@ public async Task ThrowsOnWriteWithRequestAbortedTokenAfterRequestIsAborted(List Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => { - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, m.Tags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), (string)m.Tags[KestrelMetrics.KestrelConnectionEndReason]); }); } @@ -471,7 +471,7 @@ await using (var server = new TestServer(context => Task.CompletedTask, testServ await connectionDuration.WaitForMeasurementsAsync(minCount: 1).DefaultTimeout(); var measurement = connectionDuration.GetMeasurementSnapshot().First(); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, measurement.Tags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), (string)measurement.Tags[KestrelMetrics.KestrelConnectionEndReason]); } [Theory] diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs index 2af67e1a7bdd..fa455d48b496 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs @@ -48,7 +48,7 @@ public async Task MaxConcurrentStreamsLogging_ReachLimit_MessageLogged() Assert.Equal(1, LogMessages.Count(m => m.EventId.Name == "Http2MaxConcurrentStreamsReached")); await StopConnectionAsync(expectedLastStreamId: 5, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -81,7 +81,7 @@ public async Task FlowControl_NoAvailability_ResponseHeadersStillFlushed() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -160,7 +160,7 @@ public async Task FlowControl_OneStream_CorrectlyAwaited() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -210,7 +210,7 @@ public async Task RequestHeaderStringReuse_MultipleStreams_KnownHeaderReused() Assert.Same(path1, path2); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -278,7 +278,7 @@ public async Task RequestHeaderStringReuse_MultipleStreams_KnownHeaderClearedIfN Assert.Equal(StringValues.Empty, contentTypeValue2); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } private class ResponseTrailersWrapper : IHeaderDictionary @@ -396,7 +396,7 @@ public async Task ResponseTrailers_MultipleStreams_Reset() Assert.NotSame(trailersFirst, trailersLast); await StopConnectionAsync(expectedLastStreamId: 5, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -430,7 +430,7 @@ public async Task StreamPool_SingleStream_ReturnedToPool() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -555,7 +555,7 @@ public async Task StreamPool_MultipleStreamsInSequence_PooledStreamReused() withStreamId: 3); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -623,7 +623,7 @@ public async Task StreamPool_EndedStreamErrorsOnStart_NotReturnedToPool() Assert.Equal(0, _connection.StreamPool.Count); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -653,7 +653,7 @@ public async Task StreamPool_UnendedStreamErrorsOnStart_NotReturnedToPool() Assert.Equal(0, _connection.StreamPool.Count); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -695,7 +695,7 @@ public async Task StreamPool_UnusedExpiredStream_RemovedFromPool() Assert.True(((Http2OutputProducer)pooledStream.Output)._disposed); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -746,7 +746,7 @@ public async Task ServerSettings_ChangesRequestMaxFrameSize() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -771,7 +771,7 @@ public async Task DATA_Received_ReadByStream() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); Assert.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.PayloadSequence.ToArray())); } @@ -799,7 +799,7 @@ public async Task DATA_Received_MaxSize_ReadByStream() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame.PayloadSequence.ToArray())); } @@ -894,7 +894,7 @@ public async Task DATA_Received_GreaterThanInitialWindowSize_ReadByStream() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); foreach (var frame in dataFrames) { @@ -934,7 +934,7 @@ public async Task DATA_Received_RightAtWindowLimit_DoesNotPausePipe() await SendDataAsync(1, new Memory(), endStream: true); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -965,7 +965,7 @@ public async Task DATA_Received_Multiple_ReadByStream() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); Assert.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.PayloadSequence.ToArray())); } @@ -1029,7 +1029,7 @@ public async Task DATA_Received_Multiplexed_ReadByStreams() withStreamId: 3); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); Assert.True(_helloBytes.AsSpan().SequenceEqual(stream1DataFrame1.PayloadSequence.ToArray())); Assert.True(_worldBytes.AsSpan().SequenceEqual(stream1DataFrame2.PayloadSequence.ToArray())); @@ -1142,7 +1142,7 @@ public async Task DATA_Received_Multiplexed_GreaterThanInitialWindowSize_ReadByS withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); foreach (var frame in dataFrames) { @@ -1221,7 +1221,7 @@ public async Task DATA_Received_Multiplexed_AppMustNotBlockOtherFrames() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Theory] @@ -1249,7 +1249,7 @@ public async Task DATA_Received_WithPadding_ReadByStream(byte padLength) withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); Assert.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.PayloadSequence.ToArray())); } @@ -1319,7 +1319,7 @@ public async Task DATA_Received_WithPadding_CountsTowardsInputFlowControl(byte p withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame3.PayloadSequence.ToArray())); @@ -1360,7 +1360,7 @@ public async Task DATA_Received_ButNotConsumedByApp_CountsTowardsInputFlowContro withStreamId: 0); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); var updateSize = ((framesConnectionInWindow / 2) + 1) * _maxData.Length; Assert.Equal(updateSize, connectionWindowUpdateFrame.WindowUpdateSizeIncrement); @@ -1452,7 +1452,7 @@ public async Task DATA_BufferRequestBodyLargerThanStreamSizeSmallerThanConnectio withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); foreach (var frame in dataFrames) { @@ -1650,7 +1650,7 @@ public async Task Frame_MultipleStreams_CanBeCreatedIfClientCountIsLessThanActua firstRequestBlock.SetResult(); await StopConnectionAsync(3, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -1663,7 +1663,7 @@ public async Task MaxTrackedStreams_SmallMaxConcurrentStreams_LowerLimitOf100Asy Assert.Equal((uint)100, _connection.MaxTrackedStreams); await StopConnectionAsync(0, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -1676,7 +1676,7 @@ public async Task MaxTrackedStreams_DefaultMaxConcurrentStreams_DoubleLimit() Assert.Equal((uint)200, _connection.MaxTrackedStreams); await StopConnectionAsync(0, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -1689,7 +1689,7 @@ public async Task MaxTrackedStreams_LargeMaxConcurrentStreams_DoubleLimit() Assert.Equal((uint)int.MaxValue * 2, _connection.MaxTrackedStreams); await StopConnectionAsync(0, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -1800,7 +1800,7 @@ private async Task RequestUntilEnhanceYourCalm(int maxStreamsPerConnection, int tcs.SetResult(); await StopConnectionAsync(streamId, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -1968,7 +1968,7 @@ public async Task DATA_Sent_DespiteConnectionOutputFlowControl_IfEmptyAndEndsStr await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); await WaitForAllStreamsAsync(); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -2054,7 +2054,7 @@ public async Task DATA_Sent_DespiteStreamOutputFlowControl_IfEmptyAndEndsStream( await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -2108,7 +2108,7 @@ public async Task StreamWindow_BiggerThan_ConnectionWindow() await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -2127,7 +2127,7 @@ public async Task HEADERS_Received_Decoded() await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Theory] @@ -2149,7 +2149,7 @@ public async Task HEADERS_Received_WithPadding_Decoded(byte padLength) await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -2168,7 +2168,7 @@ public async Task HEADERS_Received_WithPriority_Decoded() await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Theory] @@ -2190,7 +2190,7 @@ public async Task HEADERS_Received_WithPriorityAndPadding_Decoded(byte padLength await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Theory] @@ -2238,7 +2238,7 @@ public async Task HEADERS_Received_WithTrailers_Available(bool sendData) await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -2272,7 +2272,7 @@ public async Task HEADERS_Received_ContainsExpect100Continue_100ContinueSent() await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -2323,7 +2323,7 @@ public async Task HEADERS_Received_AppCannotBlockOtherFrames() await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -2359,7 +2359,7 @@ public async Task HEADERS_HeaderTableSizeLimitZero_Received_DynamicTableUpdate() await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -2845,7 +2845,7 @@ public async Task HEADERS_Received_HeaderBlockDoesNotContainMandatoryPseudoHeade await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Theory] @@ -3004,7 +3004,7 @@ public async Task HEADERS_Received_HeaderBlockContainsTEHeader_ValueIsTrailers_N await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -3024,7 +3024,7 @@ public async Task HEADERS_Received_RequestLineLength_StreamError() await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -3256,7 +3256,7 @@ public async Task RST_STREAM_Received_ContinuesAppsAwaitingConnectionOutputFlowC Assert.Contains(1, _abortedStreamIds); Assert.Contains(3, _abortedStreamIds); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -3342,7 +3342,7 @@ async Task VerifyStreamBackpressure(int streamId, int headersLength) Assert.Contains(3, _abortedStreamIds); Assert.Contains(5, _abortedStreamIds); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -3539,7 +3539,7 @@ public async Task RST_STREAM_IncompleteRequest_AdditionalResetFrame_IgnoreAdditi await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/53744")] @@ -3615,7 +3615,7 @@ public async Task SETTINGS_KestrelDefaults_Sent() await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -3676,7 +3676,7 @@ public async Task SETTINGS_Custom_Sent() await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -3882,7 +3882,7 @@ public async Task SETTINGS_Received_ChangesAllowedResponseMaxFrameSize() await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -3923,7 +3923,7 @@ public async Task SETTINGS_Received_ClientMaxFrameSizeCannotExceedServerMaxFrame await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -4262,7 +4262,7 @@ public async Task GOAWAY_Received_ContinuesAppsAwaitingConnectionOutputFlowContr Assert.Contains(3, _abortedStreamIds); Assert.Contains(5, _abortedStreamIds); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -4341,7 +4341,7 @@ async Task VerifyStreamBackpressure(int streamId, int headersLength) Assert.Contains(3, _abortedStreamIds); Assert.Contains(5, _abortedStreamIds); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -4516,7 +4516,7 @@ public async Task WINDOW_UPDATE_Received_OnStream_IncreasesWindowAboveMaxValue_S await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -4609,7 +4609,7 @@ public async Task WINDOW_UPDATE_Received_OnConnection_Respected() await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -4878,7 +4878,7 @@ public async Task CONTINUATION_Received_HeaderBlockDoesNotContainMandatoryPseudo await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -4920,7 +4920,7 @@ public async Task CONTINUATION_Sent_WhenHeadersLargerThanFrameLength() Assert.Equal(_4kHeaderValue, _decodedHeaders["g"]); Assert.Equal(_4kHeaderValue, _decodedHeaders["h"]); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -4939,7 +4939,7 @@ public async Task UnknownFrameType_Received_Ignored() await StopConnectionAsync(0, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -5011,7 +5011,7 @@ public async Task ConnectionResetNotLoggedWithNoActiveStreams() await WaitForConnectionStopAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false); Assert.DoesNotContain(LogMessages, m => m.Exception is ConnectionResetException); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -5026,7 +5026,7 @@ public async Task OnInputOrOutputCompletedCompletesOutput() Assert.True(result.IsCompleted); Assert.True(result.Buffer.IsEmpty); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -5053,7 +5053,7 @@ public async Task CompletionSendsFinalGOAWAY() VerifyGoAway(await ReceiveFrameAsync(), 0, Http2ErrorCode.NO_ERROR); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -5099,7 +5099,7 @@ public async Task StopProcessingNextRequestSendsGracefulGOAWAYAndWaitsForStreams result = await readTask; Assert.True(result.IsCompleted); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -5295,7 +5295,7 @@ public async Task AppDoesNotReadRequestBody_ResetsAndDrainsRequest(int intFinalF await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Theory] @@ -5341,7 +5341,7 @@ public async Task AbortedStream_ResetsAndDrainsRequest(int intFinalFrameType) await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Theory] @@ -5387,7 +5387,7 @@ public async Task ResetStream_ResetsAndDrainsRequest(int intFinalFrameType) await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Theory] @@ -5473,7 +5473,7 @@ async Task CompletePipeOnTaskCompletion() await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -5545,7 +5545,7 @@ async Task CompletePipeOnTaskCompletion() requestBlock.SetResult(); await StopConnectionAsync(expectedLastStreamId: 7, ignoreNonGoAwayFrames: true); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -5590,7 +5590,7 @@ public async Task FramesInBatchAreStillProcessedAfterStreamError_WithoutHeartbea Assert.Equal(streamPayload, streamResponse); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: true); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Theory] @@ -5745,7 +5745,7 @@ public async Task StartConnection_SendPreface_ReturnSettings() withStreamId: 0); await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: true); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -5792,7 +5792,7 @@ public async Task StartTlsConnection_SendHttp1xRequest_NoError() await SendAsync(Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\n")); await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] @@ -5801,7 +5801,7 @@ public async Task StartConnection_SendNothing_NoError() InitializeConnectionWithoutPreface(_noopApplication); await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } public static TheoryData UpperCaseHeaderNameData diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs index 05db9044cb41..abc6da2c628a 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs @@ -903,7 +903,7 @@ public async Task DATA_Received_SlowlyWhenRateLimitDisabledPerRequest_DoesNotAbo withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, ConnectionTags.Keys); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); _mockTimeoutHandler.VerifyNoOtherCalls(); _mockConnectionContext.VerifyNoOtherCalls(); diff --git a/src/Shared/ServerInfrastructure/Http2/ConnectionEndReason.cs b/src/Shared/ServerInfrastructure/Http2/ConnectionEndReason.cs index 3270239f62eb..214e80d0d9ee 100644 --- a/src/Shared/ServerInfrastructure/Http2/ConnectionEndReason.cs +++ b/src/Shared/ServerInfrastructure/Http2/ConnectionEndReason.cs @@ -5,7 +5,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core; internal enum ConnectionEndReason { - NoError, + Unknown, ConnectionReset, FlowControlWindowExceeded, KeepAliveTimeout, @@ -28,7 +28,6 @@ internal enum ConnectionEndReason ErrorReadingHeaders, ErrorWritingHeaders, UnexpectedError, - InputOrOutputCompleted, InvalidHttpVersion, RequestHeadersTimeout, MinRequestBodyDataRate, @@ -41,5 +40,6 @@ internal enum ConnectionEndReason StreamCreationError, IOError, ClientGoAway, - ApplicationShutdown + ApplicationShutdown, + TransportCompleted } From 49af29fd77a71456a045da55c769ad990b851034 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 9 May 2024 22:06:40 +0800 Subject: [PATCH 14/29] Fix build --- .../Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs | 5 +---- .../Kestrel/Core/src/Internal/Http2/Http2Connection.cs | 5 +---- .../Kestrel/Core/src/Internal/Http3/Http3Connection.cs | 5 +---- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs index 4023600ba03d..719575d40a3f 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs @@ -460,10 +460,7 @@ public void Abort(ConnectionAbortedException error, ConnectionEndReason reason) return; } - if (_metricsTagsFeature != null) - { - _metricsTagsFeature.TryAddTag(KestrelMetrics.KestrelConnectionEndReason, reason.ToString()); - } + _metricsTagsFeature?.TryAddTag(KestrelMetrics.KestrelConnectionEndReason, reason.ToString()); _aborted = true; _connectionContext.Abort(error); diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs index 9ca8b9877660..79062b9405c2 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs @@ -225,10 +225,7 @@ private void SetConnectionErrorCode(ConnectionEndReason reason, Http2ErrorCode e Debug.Assert(_errorCodeFeature.Error == -1, "Error code feature should only be set once."); _errorCodeFeature.Error = (long)errorCode; - if (_metricsTagsFeature != null) - { - _metricsTagsFeature.TryAddTag(KestrelMetrics.KestrelConnectionEndReason, reason.ToString()); - } + _metricsTagsFeature?.TryAddTag(KestrelMetrics.KestrelConnectionEndReason, reason.ToString()); } public void Abort(ConnectionAbortedException ex, ConnectionEndReason reason) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs index b31f92435d90..c3913b0fb18d 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs @@ -188,10 +188,7 @@ public void Abort(ConnectionAbortedException ex, Http3ErrorCode errorCode, Conne if (!previousState) { _errorCodeFeature.Error = (long)errorCode; - if (_metricsTagsFeature != null) - { - _metricsTagsFeature.TryAddTag(KestrelMetrics.KestrelConnectionEndReason, reason.ToString()); - } + _metricsTagsFeature?.TryAddTag(KestrelMetrics.KestrelConnectionEndReason, reason.ToString()); if (TryStopAcceptingStreams()) { From 449932750e491f876f482d625fb707ef21b730c8 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 10 May 2024 14:07:08 +0800 Subject: [PATCH 15/29] Report error from failed TLS handshake --- .../src/Internal/Http/Http1OutputProducer.cs | 2 +- .../src/Internal/Http2/Http2Connection.cs | 2 +- .../src/Internal/Http3/Http3Connection.cs | 2 +- .../Internal/Infrastructure/KestrelMetrics.cs | 27 +++++- .../Middleware/HttpsConnectionMiddleware.cs | 20 ++-- .../KestrelMetricsTests.cs | 93 ++++++++++++++++++- .../Http2/ConnectionEndReason.cs | 3 +- 7 files changed, 132 insertions(+), 17 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs index 719575d40a3f..41a437f89b43 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs @@ -460,7 +460,7 @@ public void Abort(ConnectionAbortedException error, ConnectionEndReason reason) return; } - _metricsTagsFeature?.TryAddTag(KestrelMetrics.KestrelConnectionEndReason, reason.ToString()); + KestrelMetrics.AddConnectionEndReason(_metricsTagsFeature, reason); _aborted = true; _connectionContext.Abort(error); diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs index 79062b9405c2..232da1920d75 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs @@ -225,7 +225,7 @@ private void SetConnectionErrorCode(ConnectionEndReason reason, Http2ErrorCode e Debug.Assert(_errorCodeFeature.Error == -1, "Error code feature should only be set once."); _errorCodeFeature.Error = (long)errorCode; - _metricsTagsFeature?.TryAddTag(KestrelMetrics.KestrelConnectionEndReason, reason.ToString()); + KestrelMetrics.AddConnectionEndReason(_metricsTagsFeature, reason); } public void Abort(ConnectionAbortedException ex, ConnectionEndReason reason) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs index c3913b0fb18d..3483c2e38222 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs @@ -188,7 +188,7 @@ public void Abort(ConnectionAbortedException ex, Http3ErrorCode errorCode, Conne if (!previousState) { _errorCodeFeature.Error = (long)errorCode; - _metricsTagsFeature?.TryAddTag(KestrelMetrics.KestrelConnectionEndReason, reason.ToString()); + KestrelMetrics.AddConnectionEndReason(_metricsTagsFeature, reason); if (TryStopAcceptingStreams()) { diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs index f94dc5f1fe92..e9191f302cd5 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs @@ -133,7 +133,7 @@ private void ConnectionStopCore(in ConnectionMetricsContext metricsContext, Exce if (exception != null) { - tags.Add("error.type", exception.GetType().FullName); + tags.TryAddTag("error.type", exception.GetType().FullName); } // Add custom tags for duration. @@ -315,7 +315,7 @@ private void TlsHandshakeStopCore(in ConnectionMetricsContext metricsContext, lo } if (exception != null) { - tags.Add("error.type", exception.GetType().FullName); + tags.TryAddTag("error.type", exception.GetType().FullName); } var duration = Stopwatch.GetElapsedTime(startTimestamp, currentTimestamp); @@ -412,4 +412,27 @@ public static bool TryGetHandshakeProtocol(SslProtocols protocols, [NotNullWhen( version = null; return false; } + + public static void AddConnectionEndReason(IConnectionMetricsTagsFeature? feature, ConnectionEndReason reason) + { + if (feature != null) + { + var s = reason.ToString(); + feature.TryAddTag(KestrelConnectionEndReason, s); + if (IsErrorReason(reason)) + { + feature.TryAddTag("error.type", s); + } + } + } + + private static bool IsErrorReason(ConnectionEndReason reason) + { + return reason switch + { + ConnectionEndReason.ClientGoAway => false, + ConnectionEndReason.TransportCompleted => false, + _ => true, + }; + } } diff --git a/src/Servers/Kestrel/Core/src/Middleware/HttpsConnectionMiddleware.cs b/src/Servers/Kestrel/Core/src/Middleware/HttpsConnectionMiddleware.cs index f986ee41c383..316123616d11 100644 --- a/src/Servers/Kestrel/Core/src/Middleware/HttpsConnectionMiddleware.cs +++ b/src/Servers/Kestrel/Core/src/Middleware/HttpsConnectionMiddleware.cs @@ -11,6 +11,7 @@ using System.Security.Cryptography.X509Certificates; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections.Features; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core; @@ -154,6 +155,7 @@ public async Task OnConnectionAsync(ConnectionContext context) context.Features.Set(feature); context.Features.Set(sslStream); // Anti-pattern, but retain for back compat + var metricsTagsFeature = context.Features.Get(); var metricsContext = context.Features.GetRequiredFeature().MetricsContext; var startTimestamp = Stopwatch.GetTimestamp(); try @@ -173,7 +175,7 @@ public async Task OnConnectionAsync(ConnectionContext context) } catch (OperationCanceledException ex) { - RecordHandshakeFailed(_metrics, startTimestamp, Stopwatch.GetTimestamp(), metricsContext, ex); + RecordHandshakeFailed(_metrics, startTimestamp, Stopwatch.GetTimestamp(), metricsContext, metricsTagsFeature, ex); _logger.AuthenticationTimedOut(); await sslStream.DisposeAsync(); @@ -181,7 +183,7 @@ public async Task OnConnectionAsync(ConnectionContext context) } catch (IOException ex) { - RecordHandshakeFailed(_metrics, startTimestamp, Stopwatch.GetTimestamp(), metricsContext, ex); + RecordHandshakeFailed(_metrics, startTimestamp, Stopwatch.GetTimestamp(), metricsContext, metricsTagsFeature, ex); _logger.AuthenticationFailed(ex); await sslStream.DisposeAsync(); @@ -189,10 +191,9 @@ public async Task OnConnectionAsync(ConnectionContext context) } catch (AuthenticationException ex) { - RecordHandshakeFailed(_metrics, startTimestamp, Stopwatch.GetTimestamp(), metricsContext, ex); + RecordHandshakeFailed(_metrics, startTimestamp, Stopwatch.GetTimestamp(), metricsContext, metricsTagsFeature, ex); _logger.AuthenticationFailed(ex); - await sslStream.DisposeAsync(); return; } @@ -202,15 +203,16 @@ public async Task OnConnectionAsync(ConnectionContext context) _logger.HttpsConnectionEstablished(context.ConnectionId, sslStream.SslProtocol); - if (context.Features.Get() is { } metricsTags) + if (metricsTagsFeature != null) { if (KestrelMetrics.TryGetHandshakeProtocol(sslStream.SslProtocol, out var protocolName, out var protocolVersion)) { + // "tls" is considered the default protocol name and isn't explicitly recorded. if (protocolName != "tls") { - metricsTags.Tags.Add(new KeyValuePair("tls.protocol.name", protocolName)); + metricsTagsFeature.Tags.Add(new KeyValuePair("tls.protocol.name", protocolName)); } - metricsTags.Tags.Add(new KeyValuePair("tls.protocol.version", protocolVersion)); + metricsTagsFeature.Tags.Add(new KeyValuePair("tls.protocol.version", protocolVersion)); } } @@ -235,10 +237,12 @@ await using (sslDuplexPipe) context.Transport = originalTransport; } - static void RecordHandshakeFailed(KestrelMetrics metrics, long startTimestamp, long currentTimestamp, ConnectionMetricsContext metricsContext, Exception ex) + static void RecordHandshakeFailed(KestrelMetrics metrics, long startTimestamp, long currentTimestamp, ConnectionMetricsContext metricsContext, IConnectionMetricsTagsFeature? metricsTagsFeature, Exception ex) { KestrelEventSource.Log.TlsHandshakeFailed(metricsContext.ConnectionContext.ConnectionId); KestrelEventSource.Log.TlsHandshakeStop(metricsContext.ConnectionContext, null); + + KestrelMetrics.AddConnectionEndReason(metricsTagsFeature, ConnectionEndReason.TlsHandshakeFailed); metrics.TlsHandshakeStop(metricsContext, startTimestamp, currentTimestamp, exception: ex); } } diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/KestrelMetricsTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/KestrelMetricsTests.cs index ba2679a09260..2a96a0681bf6 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/KestrelMetricsTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/KestrelMetricsTests.cs @@ -272,8 +272,7 @@ public async Task Http1Connection_Error() Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => { - AssertDuration(m, "127.0.0.1", localPort: 0, "tcp", "ipv4", httpVersion: null); - Assert.Equal("System.InvalidOperationException", (string)m.Tags["error.type"]); + AssertDuration(m, "127.0.0.1", localPort: 0, "tcp", "ipv4", httpVersion: null, error: "System.InvalidOperationException"); }); Assert.Collection(activeConnections.GetMeasurementSnapshot(), m => AssertCount(m, 1, "127.0.0.1", localPort: 0, "tcp", "ipv4"), m => AssertCount(m, -1, "127.0.0.1", localPort: 0, "tcp", "ipv4")); Assert.Collection(queuedConnections.GetMeasurementSnapshot(), m => AssertCount(m, 1, "127.0.0.1", localPort: 0, "tcp", "ipv4"), m => AssertCount(m, -1, "127.0.0.1", localPort: 0, "tcp", "ipv4")); @@ -405,6 +404,7 @@ public async Task Http2Connection() { Assert.True(m.Value > 0); Assert.Equal("1.2", (string)m.Tags["tls.protocol.version"]); + Assert.DoesNotContain("error.type", m.Tags.Keys); }); Assert.Collection(activeTlsHandshakes.GetMeasurementSnapshot(), m => Assert.Equal(1, m.Value), m => Assert.Equal(-1, m.Value)); @@ -416,6 +416,85 @@ static void AssertRequestCount(CollectedMeasurement measurement, long expe } } + [ConditionalFact] + [TlsAlpnSupported] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10)] + public async Task Http2Connection_TlsError() + { + string connectionId = null; + + //const int requestsToSend = 2; + var requestsReceived = 0; + + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + using var tlsHandshakeDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.tls_handshake.duration"); + + await using (var server = new TestServer(context => + { + connectionId = context.Features.Get().ConnectionId; + requestsReceived++; + return Task.CompletedTask; + }, + new TestServiceContext(LoggerFactory, metrics: new KestrelMetrics(testMeterFactory)), + listenOptions => + { + listenOptions.UseHttps(_x509Certificate2, options => + { + options.SslProtocols = SslProtocols.Tls12; + options.ClientCertificateMode = Https.ClientCertificateMode.RequireCertificate; + }); + listenOptions.Protocols = HttpProtocols.Http2; + })) + { + using var connection = server.CreateConnection(); + + using var socketsHandler = new SocketsHttpHandler() + { + ConnectCallback = (_, _) => + { + // This test should only require a single connection. + if (connectionId != null) + { + throw new InvalidOperationException(); + } + + return new ValueTask(connection.Stream); + }, + SslOptions = new SslClientAuthenticationOptions + { + RemoteCertificateValidationCallback = (_, _, _, _) => true + } + }; + + using var httpClient = new HttpClient(socketsHandler); + + //for (int i = 0; i < requestsToSend; i++) + { + using var httpRequestMessage = new HttpRequestMessage() + { + RequestUri = new Uri("https://localhost/"), + Version = new Version(2, 0), + VersionPolicy = HttpVersionPolicy.RequestVersionExact, + }; + + await Assert.ThrowsAsync(() => httpClient.SendAsync(httpRequestMessage)); + } + } + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => + { + AssertDuration(m, "127.0.0.1", localPort: 0, "tcp", "ipv4", httpVersion: null, tlsProtocolVersion: null, error: ConnectionEndReason.TlsHandshakeFailed.ToString()); + }); + + Assert.Collection(tlsHandshakeDuration.GetMeasurementSnapshot(), m => + { + Assert.True(m.Value > 0); + Assert.Equal(typeof(AuthenticationException).FullName, (string)m.Tags["error.type"]); + Assert.DoesNotContain("tls.protocol.version", m.Tags.Keys); + }); + } + private static async Task EchoApp(HttpContext httpContext) { var request = httpContext.Request; @@ -429,7 +508,7 @@ private static async Task EchoApp(HttpContext httpContext) } } - private static void AssertDuration(CollectedMeasurement measurement, string localAddress, int? localPort, string networkTransport, string networkType, string httpVersion, string tlsProtocolVersion = null) + private static void AssertDuration(CollectedMeasurement measurement, string localAddress, int? localPort, string networkTransport, string networkType, string httpVersion, string tlsProtocolVersion = null, string error = null) { Assert.True(measurement.Value > 0); Assert.Equal(networkTransport, (string)measurement.Tags["network.transport"]); @@ -468,6 +547,14 @@ private static void AssertDuration(CollectedMeasurement measurement, str { Assert.False(measurement.Tags.ContainsKey("tls.protocol.version")); } + if (error is not null) + { + Assert.Equal(error, (string)measurement.Tags["error.type"]); + } + else + { + Assert.False(measurement.Tags.ContainsKey("error.type")); + } } private static void AssertCount(CollectedMeasurement measurement, long expectedValue, string localAddress, int? localPort, string networkTransport, string networkType) diff --git a/src/Shared/ServerInfrastructure/Http2/ConnectionEndReason.cs b/src/Shared/ServerInfrastructure/Http2/ConnectionEndReason.cs index 214e80d0d9ee..2d21a4c282de 100644 --- a/src/Shared/ServerInfrastructure/Http2/ConnectionEndReason.cs +++ b/src/Shared/ServerInfrastructure/Http2/ConnectionEndReason.cs @@ -41,5 +41,6 @@ internal enum ConnectionEndReason IOError, ClientGoAway, ApplicationShutdown, - TransportCompleted + TransportCompleted, + TlsHandshakeFailed } From bee97ae48efdf1f30c214faaabec3021ee0c6ced Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 10 May 2024 14:26:21 +0800 Subject: [PATCH 16/29] Fix build --- .../Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs | 1 - src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs | 1 - src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs | 1 - .../Kestrel/Core/src/Middleware/HttpsConnectionMiddleware.cs | 1 - 4 files changed, 4 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs index 41a437f89b43..7f635e40a154 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs @@ -6,7 +6,6 @@ using System.IO.Pipelines; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections.Features; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs index 232da1920d75..c9969be87080 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs @@ -18,7 +18,6 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.Extensions.Logging; -using Microsoft.AspNetCore.Http; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs index 3483c2e38222..54fa95fbb7ca 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs @@ -8,7 +8,6 @@ using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Hosting.Server; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; diff --git a/src/Servers/Kestrel/Core/src/Middleware/HttpsConnectionMiddleware.cs b/src/Servers/Kestrel/Core/src/Middleware/HttpsConnectionMiddleware.cs index 316123616d11..4508204d7fe7 100644 --- a/src/Servers/Kestrel/Core/src/Middleware/HttpsConnectionMiddleware.cs +++ b/src/Servers/Kestrel/Core/src/Middleware/HttpsConnectionMiddleware.cs @@ -11,7 +11,6 @@ using System.Security.Cryptography.X509Certificates; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections.Features; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core; From 93ddbf64579c4430ce3ca39f63629e5d19b92ba3 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Mon, 10 Jun 2024 11:43:54 +0800 Subject: [PATCH 17/29] Fix merge --- .../test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs index 1d0a1a660acc..f2a80bde7749 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs @@ -11,6 +11,7 @@ using Microsoft.AspNetCore.InternalTesting; using Microsoft.AspNetCore.Server.Kestrel.Core.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.InternalTesting; using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; @@ -3778,7 +3779,7 @@ public async Task ResponseHeader_OneMegaByte_SplitsHeaderToContinuationFrames() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: true); - Assert.Equal(nameof(ConnectionEndReason.ErrorWritingHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); } [Fact] From 2ebfc8f9246a7b8a1b0630e1ac20e2181e27ac96 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Mon, 10 Jun 2024 13:52:45 +0800 Subject: [PATCH 18/29] Snake case tag value --- .../Core/src/Internal/Http/Http1Connection.cs | 2 +- .../src/Internal/Http/Http1MessageBody.cs | 4 +- .../src/Internal/Http3/Http3Connection.cs | 2 - .../Core/src/Internal/HttpConnection.cs | 2 +- .../Internal/Infrastructure/KestrelMetrics.cs | 64 +++- .../PipeWriterHelpers/TimingPipeFlusher.cs | 2 +- .../Core/test/Http1/Http1ConnectionTests.cs | 20 +- .../test/Http1/Http1OutputProducerTests.cs | 6 +- .../test/FunctionalTests/RequestTests.cs | 4 +- .../test/FunctionalTests/ResponseTests.cs | 10 +- .../Http2/Http2ConnectionTests.cs | 320 +++++++++--------- .../Http2/Http2StreamTests.cs | 12 +- .../Http2/Http2TestBase.cs | 5 + .../Http2/Http2TimeoutTests.cs | 32 +- .../Http3/Http3ConnectionTests.cs | 16 +- .../Http3/Http3StreamTests.cs | 6 +- .../Http3/Http3TimeoutTests.cs | 18 +- .../KestrelMetricsTests.cs | 12 +- .../Http2/ConnectionEndReason.cs | 4 +- 19 files changed, 299 insertions(+), 242 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs index 69dc9975915d..4708d22dd9f3 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs @@ -122,7 +122,7 @@ public void Abort(ConnectionAbortedException abortReason, ConnectionEndReason re protected override void ApplicationAbort() { Log.ApplicationAbortedConnection(ConnectionId, TraceIdentifier); - Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByApplication), ConnectionEndReason.AbortedByApplication); + Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByApplication), ConnectionEndReason.AbortedByApp); } /// diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs index f9adf4525c59..06abd7027aad 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs @@ -72,7 +72,7 @@ protected override Task OnConsumeAsync() _context.ReportApplicationError(connectionAbortedException); // Have to abort the connection because we can't finish draining the request - _context.StopProcessingNextRequest(ConnectionEndReason.AbortedByApplication); + _context.StopProcessingNextRequest(ConnectionEndReason.AbortedByApp); return Task.CompletedTask; } @@ -108,7 +108,7 @@ protected async Task OnConsumeAsyncAwaited() _context.ReportApplicationError(connectionAbortedException); // Have to abort the connection because we can't finish draining the request - _context.StopProcessingNextRequest(ConnectionEndReason.AbortedByApplication); + _context.StopProcessingNextRequest(ConnectionEndReason.AbortedByApp); } finally { diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs index 54fa95fbb7ca..0c2d8dd0a663 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs @@ -483,8 +483,6 @@ private void UpdateStreamTimeouts(long timestamp) Log.RequestProcessingError(_context.ConnectionId, ex); error = ex; reason = ConnectionEndReason.UnexpectedError; - - Debug.Fail("Figure out error reason"); } catch (Http3ConnectionErrorException ex) { diff --git a/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs b/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs index 8abfd8396762..3b657546ab80 100644 --- a/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs @@ -101,7 +101,7 @@ public HttpConnection(BaseHttpConnectionContext context) connectionHeartbeatFeature?.OnHeartbeat(state => ((HttpConnection)state).Tick(), this); // Register for graceful shutdown of the server - using var shutdownRegistration = connectionLifetimeNotificationFeature?.ConnectionClosedRequested.Register(state => ((HttpConnection)state!).StopProcessingNextRequest(ConnectionEndReason.ApplicationShutdown), this); + using var shutdownRegistration = connectionLifetimeNotificationFeature?.ConnectionClosedRequested.Register(state => ((HttpConnection)state!).StopProcessingNextRequest(ConnectionEndReason.AppShutdown), this); // Register for connection close using var closedRegistration = _context.ConnectionContext.ConnectionClosed.Register(state => ((HttpConnection)state!).OnConnectionClosed(), this); diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs index e9191f302cd5..21934b790045 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs @@ -19,7 +19,7 @@ internal sealed class KestrelMetrics // Note: Dot separated instead of dash. public const string MeterName = "Microsoft.AspNetCore.Server.Kestrel"; - public const string KestrelConnectionEndReason = "kestrel.connection.end_reason"; + public const string ErrorType = "error.type"; public const string Http11 = "1.1"; public const string Http2 = "2"; @@ -415,24 +415,68 @@ public static bool TryGetHandshakeProtocol(SslProtocols protocols, [NotNullWhen( public static void AddConnectionEndReason(IConnectionMetricsTagsFeature? feature, ConnectionEndReason reason) { + Debug.Assert(reason != ConnectionEndReason.Unknown); + if (feature != null) { - var s = reason.ToString(); - feature.TryAddTag(KestrelConnectionEndReason, s); - if (IsErrorReason(reason)) + if (TryGetErrorType(reason, out var errorTypeValue)) { - feature.TryAddTag("error.type", s); + feature.TryAddTag(ErrorType, errorTypeValue); } } } - private static bool IsErrorReason(ConnectionEndReason reason) + internal static string? GetErrorType(ConnectionEndReason reason) { - return reason switch + TryGetErrorType(reason, out var errorTypeValue); + return errorTypeValue; + } + + internal static bool TryGetErrorType(ConnectionEndReason reason, [NotNullWhen(true)]out string? errorTypeValue) + { + errorTypeValue = reason switch { - ConnectionEndReason.ClientGoAway => false, - ConnectionEndReason.TransportCompleted => false, - _ => true, + ConnectionEndReason.Unknown => null, // Not an error + ConnectionEndReason.ClientGoAway => null, // Not an error + ConnectionEndReason.TransportCompleted => null, // Not an error + ConnectionEndReason.ConnectionReset => "connection_reset", + ConnectionEndReason.FlowControlWindowExceeded => "flow_control_window_exceeded", + ConnectionEndReason.KeepAliveTimeout => "keep_alive_timeout", + ConnectionEndReason.InsufficientTlsVersion => "insufficient_tls_version", + ConnectionEndReason.InvalidHandshake => "invalid_handshake", + ConnectionEndReason.InvalidStreamId => "invalid_stream_id", + ConnectionEndReason.FrameAfterStreamClose => "frame_after_stream_close", + ConnectionEndReason.UnknownStream => "unknown_stream", + ConnectionEndReason.UnsupportedFrame => "unsupported_frame", + ConnectionEndReason.UnexpectedFrame => "unexpected_frame", + ConnectionEndReason.InvalidFrameLength => "invalid_frame_length", + ConnectionEndReason.InvalidDataPadding => "invalid_data_padding", + ConnectionEndReason.InvalidRequestHeaders => "invalid_request_headers", + ConnectionEndReason.StreamResetLimitExceeded => "stream_reset_limit_exceeded", + ConnectionEndReason.WindowUpdateSizeInvalid => "window_update_size_invalid", + ConnectionEndReason.StreamSelfDependency => "stream_self_dependency", + ConnectionEndReason.InvalidSettings => "invalid_settings", + ConnectionEndReason.MissingStreamEnd => "missing_stream_end", + ConnectionEndReason.MaxFrameLengthExceeded => "max_frame_length_exceeded", + ConnectionEndReason.ErrorReadingHeaders => "error_reading_headers", + ConnectionEndReason.ErrorWritingHeaders => "error_writing_headers", + ConnectionEndReason.UnexpectedError => "unexpected_error", + ConnectionEndReason.InvalidHttpVersion => "invalid_http_version", + ConnectionEndReason.RequestHeadersTimeout => "request_headers_timeout", + ConnectionEndReason.MinRequestBodyDataRate => "min_request_body_data_rate", + ConnectionEndReason.MinResponseDataRate => "min_response_data_rate", + ConnectionEndReason.FlowControlQueueSizeExceeded => "flow_control_queue_size_exceeded", + ConnectionEndReason.OutputQueueSizeExceeded => "output_queue_size_exceeded", + ConnectionEndReason.ClosedCriticalStream => "closed_critical_stream", + ConnectionEndReason.AbortedByApp => "aborted_by_app", + ConnectionEndReason.ServerTimeout => "server_timeout", + ConnectionEndReason.StreamCreationError => "stream_creation_error", + ConnectionEndReason.IOError => "io_error", + ConnectionEndReason.AppShutdown => "app_shutdown", + ConnectionEndReason.TlsHandshakeFailed => "tls_handshake_failed", + _ => throw new InvalidOperationException($"Unable to calculate whether {reason} resolves to error.type value.") }; + + return errorTypeValue != null; } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/TimingPipeFlusher.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/TimingPipeFlusher.cs index 288d7c557b08..c2a7b5352626 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/TimingPipeFlusher.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/TimingPipeFlusher.cs @@ -92,7 +92,7 @@ private async ValueTask TimeFlushAsyncAwaited(ValueTask cc.SetTimeout(expectedKeepAliveTimeout, TimeoutReason.KeepAlive)); - _http1Connection.StopProcessingNextRequest(ConnectionEndReason.ApplicationShutdown); + _http1Connection.StopProcessingNextRequest(ConnectionEndReason.AppShutdown); _application.Output.Complete(); await requestProcessingTask.DefaultTimeout(); @@ -657,7 +657,7 @@ public async Task RequestProcessingTaskIsUnwrapped() var data = Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\nHost:\r\n\r\n"); await _application.Output.WriteAsync(data); - _http1Connection.StopProcessingNextRequest(ConnectionEndReason.ApplicationShutdown); + _http1Connection.StopProcessingNextRequest(ConnectionEndReason.AppShutdown); Assert.IsNotType>(requestProcessingTask); await requestProcessingTask.DefaultTimeout(); @@ -680,7 +680,7 @@ public async Task RequestAbortedTokenIsResetBeforeLastWriteWithContentLength() await _http1Connection.WriteAsync(new ArraySegment(new[] { (byte)'d' })); Assert.NotEqual(original, _http1Connection.RequestAborted); - _http1Connection.Abort(new ConnectionAbortedException(), ConnectionEndReason.AbortedByApplication); + _http1Connection.Abort(new ConnectionAbortedException(), ConnectionEndReason.AbortedByApp); Assert.False(original.IsCancellationRequested); Assert.False(_http1Connection.RequestAborted.IsCancellationRequested); @@ -702,7 +702,7 @@ public async Task RequestAbortedTokenIsResetBeforeLastWriteAsyncWithContentLengt await _http1Connection.WriteAsync(new ArraySegment(new[] { (byte)'d' }), default(CancellationToken)); Assert.NotEqual(original, _http1Connection.RequestAborted); - _http1Connection.Abort(new ConnectionAbortedException(), ConnectionEndReason.AbortedByApplication); + _http1Connection.Abort(new ConnectionAbortedException(), ConnectionEndReason.AbortedByApp); Assert.False(original.IsCancellationRequested); Assert.False(_http1Connection.RequestAborted.IsCancellationRequested); @@ -717,7 +717,7 @@ public async void BodyWriter_OnAbortedConnection_ReturnsFlushResultWithIsComplet var successResult = await writer.WriteAsync(payload); Assert.False(successResult.IsCompleted); - _http1Connection.Abort(new ConnectionAbortedException(), ConnectionEndReason.AbortedByApplication); + _http1Connection.Abort(new ConnectionAbortedException(), ConnectionEndReason.AbortedByApp); var failResult = await _http1Connection.FlushPipeAsync(new CancellationToken()); Assert.True(failResult.IsCompleted); } @@ -762,7 +762,7 @@ public async Task RequestAbortedTokenIsResetBeforeLastWriteAsyncAwaitedWithConte await _http1Connection.WriteAsync(new ArraySegment(new[] { (byte)'d' }), default(CancellationToken)); Assert.NotEqual(original, _http1Connection.RequestAborted); - _http1Connection.Abort(new ConnectionAbortedException(), ConnectionEndReason.AbortedByApplication); + _http1Connection.Abort(new ConnectionAbortedException(), ConnectionEndReason.AbortedByApp); Assert.False(original.IsCancellationRequested); Assert.False(_http1Connection.RequestAborted.IsCancellationRequested); @@ -780,7 +780,7 @@ public async Task RequestAbortedTokenIsResetBeforeLastWriteWithChunkedEncoding() await _http1Connection.ProduceEndAsync(); Assert.NotEqual(original, _http1Connection.RequestAborted); - _http1Connection.Abort(new ConnectionAbortedException(), ConnectionEndReason.AbortedByApplication); + _http1Connection.Abort(new ConnectionAbortedException(), ConnectionEndReason.AbortedByApp); Assert.False(original.IsCancellationRequested); Assert.False(_http1Connection.RequestAborted.IsCancellationRequested); @@ -792,7 +792,7 @@ public void RequestAbortedTokenIsFullyUsableAfterCancellation() var originalToken = _http1Connection.RequestAborted; var originalRegistration = originalToken.Register(() => { }); - _http1Connection.Abort(new ConnectionAbortedException(), ConnectionEndReason.AbortedByApplication); + _http1Connection.Abort(new ConnectionAbortedException(), ConnectionEndReason.AbortedByApp); Assert.True(originalToken.WaitHandle.WaitOne(TestConstants.DefaultTimeout)); Assert.True(_http1Connection.RequestAborted.WaitHandle.WaitOne(TestConstants.DefaultTimeout)); @@ -806,7 +806,7 @@ public void RequestAbortedTokenIsUsableAfterCancellation() var originalToken = _http1Connection.RequestAborted; var originalRegistration = originalToken.Register(() => { }); - _http1Connection.Abort(new ConnectionAbortedException(), ConnectionEndReason.AbortedByApplication); + _http1Connection.Abort(new ConnectionAbortedException(), ConnectionEndReason.AbortedByApp); // The following line will throw an ODE because the original CTS backing the token has been diposed. // See https://github.com/dotnet/aspnetcore/pull/4447 for the history behind this test. diff --git a/src/Servers/Kestrel/Core/test/Http1/Http1OutputProducerTests.cs b/src/Servers/Kestrel/Core/test/Http1/Http1OutputProducerTests.cs index c876e6c125f6..28419ae1c3f5 100644 --- a/src/Servers/Kestrel/Core/test/Http1/Http1OutputProducerTests.cs +++ b/src/Servers/Kestrel/Core/test/Http1/Http1OutputProducerTests.cs @@ -126,15 +126,15 @@ public void AbortsTransportEvenAfterDispose() mockConnectionContext.Verify(f => f.Abort(It.IsAny()), Times.Never()); - outputProducer.Abort(null, ConnectionEndReason.AbortedByApplication); + outputProducer.Abort(null, ConnectionEndReason.AbortedByApp); mockConnectionContext.Verify(f => f.Abort(null), Times.Once()); - outputProducer.Abort(null, ConnectionEndReason.AbortedByApplication); + outputProducer.Abort(null, ConnectionEndReason.AbortedByApp); mockConnectionContext.Verify(f => f.Abort(null), Times.Once()); - Assert.Equal(nameof(ConnectionEndReason.AbortedByApplication), metricsTagsFeature.Tags.Single(t => t.Key == KestrelMetrics.KestrelConnectionEndReason).Value); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.AbortedByApp), metricsTagsFeature.Tags.Single(t => t.Key == KestrelMetrics.ErrorType).Value); } [Fact] diff --git a/src/Servers/Kestrel/test/FunctionalTests/RequestTests.cs b/src/Servers/Kestrel/test/FunctionalTests/RequestTests.cs index 8af773bc7f87..84e253b66400 100644 --- a/src/Servers/Kestrel/test/FunctionalTests/RequestTests.cs +++ b/src/Servers/Kestrel/test/FunctionalTests/RequestTests.cs @@ -626,7 +626,7 @@ public async Task RequestAbortedTokenFiredOnClientFIN() Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => { - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), (string)m.Tags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.DoesNotContain(KestrelMetrics.ErrorType, m.Tags.Keys); }); } @@ -729,7 +729,7 @@ public async Task AbortingTheConnection(bool fin) Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => { - Assert.Equal(nameof(ConnectionEndReason.AbortedByApplication), (string)m.Tags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.AbortedByApp), (string)m.Tags[KestrelMetrics.ErrorType]); }); } diff --git a/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs b/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs index e54320a5e24b..05cb39c46328 100644 --- a/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs +++ b/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs @@ -263,7 +263,7 @@ public async Task ThrowsOnWriteWithRequestAbortedTokenAfterRequestIsAborted(List Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => { - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), (string)m.Tags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.DoesNotContain(KestrelMetrics.ErrorType, m.Tags.Keys); }); } @@ -471,7 +471,7 @@ await using (var server = new TestServer(context => Task.CompletedTask, testServ await connectionDuration.WaitForMeasurementsAsync(minCount: 1).DefaultTimeout(); var measurement = connectionDuration.GetMeasurementSnapshot().First(); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), (string)measurement.Tags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.DoesNotContain(KestrelMetrics.ErrorType, measurement.Tags.Keys); } [Theory] @@ -609,7 +609,7 @@ async Task App(HttpContext context) Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => { - Assert.Equal(nameof(ConnectionEndReason.MinResponseDataRate), (string)m.Tags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MinResponseDataRate), (string)m.Tags[KestrelMetrics.ErrorType]); }); } @@ -860,7 +860,7 @@ async Task App(HttpContext context) Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => { - Assert.Equal(nameof(ConnectionEndReason.MinResponseDataRate), (string)m.Tags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MinResponseDataRate), (string)m.Tags[KestrelMetrics.ErrorType]); }); } @@ -1104,7 +1104,7 @@ await using (var server = new TestServer(App, testContext, listenOptions)) Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => { - Assert.DoesNotContain(KestrelMetrics.KestrelConnectionEndReason, m.Tags.Keys); + Assert.DoesNotContain(KestrelMetrics.ErrorType, m.Tags.Keys); }); } diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs index fa455d48b496..dc07a107e9c4 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs @@ -48,7 +48,7 @@ public async Task MaxConcurrentStreamsLogging_ReachLimit_MessageLogged() Assert.Equal(1, LogMessages.Count(m => m.EventId.Name == "Http2MaxConcurrentStreamsReached")); await StopConnectionAsync(expectedLastStreamId: 5, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -81,7 +81,7 @@ public async Task FlowControl_NoAvailability_ResponseHeadersStillFlushed() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -160,7 +160,7 @@ public async Task FlowControl_OneStream_CorrectlyAwaited() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -210,7 +210,7 @@ public async Task RequestHeaderStringReuse_MultipleStreams_KnownHeaderReused() Assert.Same(path1, path2); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -278,7 +278,7 @@ public async Task RequestHeaderStringReuse_MultipleStreams_KnownHeaderClearedIfN Assert.Equal(StringValues.Empty, contentTypeValue2); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } private class ResponseTrailersWrapper : IHeaderDictionary @@ -396,7 +396,7 @@ public async Task ResponseTrailers_MultipleStreams_Reset() Assert.NotSame(trailersFirst, trailersLast); await StopConnectionAsync(expectedLastStreamId: 5, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -430,7 +430,7 @@ public async Task StreamPool_SingleStream_ReturnedToPool() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -555,7 +555,7 @@ public async Task StreamPool_MultipleStreamsInSequence_PooledStreamReused() withStreamId: 3); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -623,7 +623,7 @@ public async Task StreamPool_EndedStreamErrorsOnStart_NotReturnedToPool() Assert.Equal(0, _connection.StreamPool.Count); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -653,7 +653,7 @@ public async Task StreamPool_UnendedStreamErrorsOnStart_NotReturnedToPool() Assert.Equal(0, _connection.StreamPool.Count); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -695,7 +695,7 @@ public async Task StreamPool_UnusedExpiredStream_RemovedFromPool() Assert.True(((Http2OutputProducer)pooledStream.Output)._disposed); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -713,7 +713,7 @@ public async Task Frame_Received_OverMaxSize_FrameError() expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorFrameOverLimit(length, Http2PeerSettings.MinAllowedMaxFrameSize)); - Assert.Equal(nameof(ConnectionEndReason.MaxFrameLengthExceeded), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MaxFrameLengthExceeded), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -746,7 +746,7 @@ public async Task ServerSettings_ChangesRequestMaxFrameSize() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -771,7 +771,7 @@ public async Task DATA_Received_ReadByStream() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); Assert.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.PayloadSequence.ToArray())); } @@ -799,7 +799,7 @@ public async Task DATA_Received_MaxSize_ReadByStream() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame.PayloadSequence.ToArray())); } @@ -894,7 +894,7 @@ public async Task DATA_Received_GreaterThanInitialWindowSize_ReadByStream() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); foreach (var frame in dataFrames) { @@ -934,7 +934,7 @@ public async Task DATA_Received_RightAtWindowLimit_DoesNotPausePipe() await SendDataAsync(1, new Memory(), endStream: true); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -965,7 +965,7 @@ public async Task DATA_Received_Multiple_ReadByStream() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); Assert.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.PayloadSequence.ToArray())); } @@ -1029,7 +1029,7 @@ public async Task DATA_Received_Multiplexed_ReadByStreams() withStreamId: 3); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); Assert.True(_helloBytes.AsSpan().SequenceEqual(stream1DataFrame1.PayloadSequence.ToArray())); Assert.True(_worldBytes.AsSpan().SequenceEqual(stream1DataFrame2.PayloadSequence.ToArray())); @@ -1142,7 +1142,7 @@ public async Task DATA_Received_Multiplexed_GreaterThanInitialWindowSize_ReadByS withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); foreach (var frame in dataFrames) { @@ -1221,7 +1221,7 @@ public async Task DATA_Received_Multiplexed_AppMustNotBlockOtherFrames() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Theory] @@ -1249,7 +1249,7 @@ public async Task DATA_Received_WithPadding_ReadByStream(byte padLength) withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); Assert.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.PayloadSequence.ToArray())); } @@ -1319,7 +1319,7 @@ public async Task DATA_Received_WithPadding_CountsTowardsInputFlowControl(byte p withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame3.PayloadSequence.ToArray())); @@ -1360,7 +1360,7 @@ public async Task DATA_Received_ButNotConsumedByApp_CountsTowardsInputFlowContro withStreamId: 0); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); var updateSize = ((framesConnectionInWindow / 2) + 1) * _maxData.Length; Assert.Equal(updateSize, connectionWindowUpdateFrame.WindowUpdateSizeIncrement); @@ -1452,7 +1452,7 @@ public async Task DATA_BufferRequestBodyLargerThanStreamSizeSmallerThanConnectio withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); foreach (var frame in dataFrames) { @@ -1477,7 +1477,7 @@ public async Task DATA_Received_StreamIdZero_ConnectionError() expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdZero(Http2FrameType.DATA)); - Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -1492,7 +1492,7 @@ public async Task DATA_Received_StreamIdEven_ConnectionError() expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdEven(Http2FrameType.DATA, streamId: 2)); - Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -1508,7 +1508,7 @@ public async Task DATA_Received_PaddingEqualToFramePayloadLength_ConnectionError expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorPaddingTooLong(Http2FrameType.DATA)); - Assert.Equal(nameof(ConnectionEndReason.InvalidDataPadding), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidDataPadding), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -1524,7 +1524,7 @@ public async Task DATA_Received_PaddingGreaterThanFramePayloadLength_ConnectionE expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorPaddingTooLong(Http2FrameType.DATA)); - Assert.Equal(nameof(ConnectionEndReason.InvalidDataPadding), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidDataPadding), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -1540,7 +1540,7 @@ public async Task DATA_Received_FrameLengthZeroPaddingZero_ConnectionError() expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.DATA, expectedLength: 1)); - Assert.Equal(nameof(ConnectionEndReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -1556,7 +1556,7 @@ public async Task DATA_Received_InterleavedWithHeaders_ConnectionError() expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.DATA, streamId: 1, headersStreamId: 1)); - Assert.Equal(nameof(ConnectionEndReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -1571,7 +1571,7 @@ public async Task DATA_Received_StreamIdle_ConnectionError() expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdle(Http2FrameType.DATA, streamId: 1)); - Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -1589,7 +1589,7 @@ public async Task DATA_Received_StreamHalfClosedRemote_ConnectionError() expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.DATA, streamId: 1)); - Assert.Equal(nameof(ConnectionEndReason.FrameAfterStreamClose), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.FrameAfterStreamClose), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -1615,7 +1615,7 @@ public async Task DATA_Received_StreamClosed_ConnectionError() CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.DATA, streamId: 1), CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.DATA, streamId: 1) }); - Assert.Equal(nameof(ConnectionEndReason.UnknownStream), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.UnknownStream), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -1650,7 +1650,7 @@ public async Task Frame_MultipleStreams_CanBeCreatedIfClientCountIsLessThanActua firstRequestBlock.SetResult(); await StopConnectionAsync(3, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -1663,7 +1663,7 @@ public async Task MaxTrackedStreams_SmallMaxConcurrentStreams_LowerLimitOf100Asy Assert.Equal((uint)100, _connection.MaxTrackedStreams); await StopConnectionAsync(0, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -1676,7 +1676,7 @@ public async Task MaxTrackedStreams_DefaultMaxConcurrentStreams_DoubleLimit() Assert.Equal((uint)200, _connection.MaxTrackedStreams); await StopConnectionAsync(0, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -1689,7 +1689,7 @@ public async Task MaxTrackedStreams_LargeMaxConcurrentStreams_DoubleLimit() Assert.Equal((uint)int.MaxValue * 2, _connection.MaxTrackedStreams); await StopConnectionAsync(0, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -1699,8 +1699,8 @@ public void MinimumMaxTrackedStreams() CreateConnection(); // Kestrel always tracks at least 100 streams Assert.Equal(100u, _connection.MaxTrackedStreams); - _connection.Abort(new ConnectionAbortedException(), ConnectionEndReason.AbortedByApplication); - Assert.Equal(nameof(ConnectionEndReason.AbortedByApplication), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + _connection.Abort(new ConnectionAbortedException(), ConnectionEndReason.AbortedByApp); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.AbortedByApp), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -1770,7 +1770,7 @@ public async Task AbortConnectionAfterTooManyEnhanceYourCalms() expectedErrorCode: Http2ErrorCode.ENHANCE_YOUR_CALM, expectedErrorMessage: CoreStrings.Http2ConnectionFaulted); - Assert.Equal(nameof(ConnectionEndReason.StreamResetLimitExceeded), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.StreamResetLimitExceeded), ConnectionTags[KestrelMetrics.ErrorType]); } private async Task RequestUntilEnhanceYourCalm(int maxStreamsPerConnection, int sentStreams) @@ -1800,7 +1800,7 @@ private async Task RequestUntilEnhanceYourCalm(int maxStreamsPerConnection, int tcs.SetResult(); await StopConnectionAsync(streamId, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -1830,7 +1830,7 @@ public async Task DATA_Received_StreamClosedImplicitly_ConnectionError() expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.DATA, streamId: 1)); - Assert.Equal(nameof(ConnectionEndReason.UnknownStream), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.UnknownStream), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -1854,7 +1854,7 @@ public async Task DATA_Received_NoStreamWindowSpace_ConnectionError() expectedErrorCode: Http2ErrorCode.FLOW_CONTROL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorFlowControlWindowExceeded); - Assert.Equal(nameof(ConnectionEndReason.FlowControlWindowExceeded), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.FlowControlWindowExceeded), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -1885,7 +1885,7 @@ public async Task DATA_Received_NoConnectionWindowSpace_ConnectionError() expectedErrorCode: Http2ErrorCode.FLOW_CONTROL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorFlowControlWindowExceeded); - Assert.Equal(nameof(ConnectionEndReason.FlowControlWindowExceeded), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.FlowControlWindowExceeded), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -1968,7 +1968,7 @@ public async Task DATA_Sent_DespiteConnectionOutputFlowControl_IfEmptyAndEndsStr await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); await WaitForAllStreamsAsync(); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -2054,7 +2054,7 @@ public async Task DATA_Sent_DespiteStreamOutputFlowControl_IfEmptyAndEndsStream( await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -2108,7 +2108,7 @@ public async Task StreamWindow_BiggerThan_ConnectionWindow() await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -2127,7 +2127,7 @@ public async Task HEADERS_Received_Decoded() await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Theory] @@ -2149,7 +2149,7 @@ public async Task HEADERS_Received_WithPadding_Decoded(byte padLength) await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -2168,7 +2168,7 @@ public async Task HEADERS_Received_WithPriority_Decoded() await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Theory] @@ -2190,7 +2190,7 @@ public async Task HEADERS_Received_WithPriorityAndPadding_Decoded(byte padLength await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Theory] @@ -2238,7 +2238,7 @@ public async Task HEADERS_Received_WithTrailers_Available(bool sendData) await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -2272,7 +2272,7 @@ public async Task HEADERS_Received_ContainsExpect100Continue_100ContinueSent() await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -2323,7 +2323,7 @@ public async Task HEADERS_Received_AppCannotBlockOtherFrames() await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -2359,7 +2359,7 @@ public async Task HEADERS_HeaderTableSizeLimitZero_Received_DynamicTableUpdate() await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -2519,7 +2519,7 @@ public async Task HEADERS_Received_StreamIdZero_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdZero(Http2FrameType.HEADERS)); - Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -2535,7 +2535,7 @@ public async Task HEADERS_Received_StreamIdEven_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdEven(Http2FrameType.HEADERS, streamId: 2)); - Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -2563,7 +2563,7 @@ public async Task HEADERS_Received_StreamClosed_ConnectionError() CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.HEADERS, streamId: 1) }); - Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -2582,7 +2582,7 @@ public async Task HEADERS_Received_StreamHalfClosedRemote_ConnectionError() expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.HEADERS, streamId: 1)); - Assert.Equal(nameof(ConnectionEndReason.FrameAfterStreamClose), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.FrameAfterStreamClose), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -2606,7 +2606,7 @@ public async Task HEADERS_Received_StreamClosedImplicitly_ConnectionError() expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1)); - Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); } [Theory] @@ -2625,7 +2625,7 @@ public async Task HEADERS_Received_PaddingEqualToFramePayloadLength_ConnectionEr expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorPaddingTooLong(Http2FrameType.HEADERS)); - Assert.Equal(nameof(ConnectionEndReason.InvalidDataPadding), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidDataPadding), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -2641,7 +2641,7 @@ public async Task HEADERS_Received_PaddingFieldMissing_ConnectionError() expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.HEADERS, expectedLength: 1)); - Assert.Equal(nameof(ConnectionEndReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.ErrorType]); } [Theory] @@ -2659,7 +2659,7 @@ public async Task HEADERS_Received_PaddingGreaterThanFramePayloadLength_Connecti expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorPaddingTooLong(Http2FrameType.HEADERS)); - Assert.Equal(nameof(ConnectionEndReason.InvalidDataPadding), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidDataPadding), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -2676,7 +2676,7 @@ public async Task HEADERS_Received_InterleavedWithHeaders_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.HEADERS, streamId: 3, headersStreamId: 1)); - Assert.Equal(nameof(ConnectionEndReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -2692,7 +2692,7 @@ public async Task HEADERS_Received_WithPriority_StreamDependencyOnSelf_Connectio expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamSelfDependency(Http2FrameType.HEADERS, streamId: 1)); - Assert.Equal(nameof(ConnectionEndReason.StreamSelfDependency), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.StreamSelfDependency), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -2708,7 +2708,7 @@ public async Task HEADERS_Received_IncompleteHeaderBlock_ConnectionError() expectedErrorCode: Http2ErrorCode.COMPRESSION_ERROR, expectedErrorMessage: SR.net_http_hpack_incomplete_header_block); - Assert.Equal(nameof(ConnectionEndReason.ErrorReadingHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.ErrorReadingHeaders), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -2738,7 +2738,7 @@ public async Task HEADERS_Received_IntegerOverLimit_ConnectionError() expectedErrorCode: Http2ErrorCode.COMPRESSION_ERROR, expectedErrorMessage: SR.net_http_hpack_bad_integer); - Assert.Equal(nameof(ConnectionEndReason.ErrorReadingHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.ErrorReadingHeaders), ConnectionTags[KestrelMetrics.ErrorType]); } [Theory] @@ -2756,7 +2756,7 @@ public async Task HEADERS_Received_WithTrailers_ContainsIllegalTrailer_Connectio expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: expectedErrorMessage); - Assert.Equal(nameof(ConnectionEndReason.InvalidRequestHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidRequestHeaders), ConnectionTags[KestrelMetrics.ErrorType]); } [Theory] @@ -2776,7 +2776,7 @@ public async Task HEADERS_Received_WithTrailers_EndStreamNotSet_ConnectionError( expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorHeadersWithTrailersNoEndStream); - Assert.Equal(nameof(ConnectionEndReason.MissingStreamEnd), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MissingStreamEnd), ConnectionTags[KestrelMetrics.ErrorType]); } [Theory] @@ -2792,7 +2792,7 @@ public async Task HEADERS_Received_HeaderNameContainsUpperCaseCharacter_Connecti expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.HttpErrorHeaderNameUppercase); - Assert.Equal(nameof(ConnectionEndReason.InvalidRequestHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidRequestHeaders), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -2845,7 +2845,7 @@ public async Task HEADERS_Received_HeaderBlockDoesNotContainMandatoryPseudoHeade await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Theory] @@ -2865,7 +2865,7 @@ private async Task HEADERS_Received_InvalidHeaderFields_ConnectionError(IEnumera expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: expectedErrorMessage); - Assert.Equal(nameof(ConnectionEndReason.InvalidRequestHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidRequestHeaders), ConnectionTags[KestrelMetrics.ErrorType]); } [Theory] @@ -2888,7 +2888,7 @@ public async Task HEADERS_Received_HeaderBlockDoesNotContainMandatoryPseudoHeade expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1)); - Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -3004,7 +3004,7 @@ public async Task HEADERS_Received_HeaderBlockContainsTEHeader_ValueIsTrailers_N await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -3024,7 +3024,7 @@ public async Task HEADERS_Received_RequestLineLength_StreamError() await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -3067,7 +3067,7 @@ public async Task PRIORITY_Received_StreamIdZero_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdZero(Http2FrameType.PRIORITY)); - Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -3083,7 +3083,7 @@ public async Task PRIORITY_Received_StreamIdEven_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdEven(Http2FrameType.PRIORITY, streamId: 2)); - Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); } [Theory] @@ -3101,7 +3101,7 @@ public async Task PRIORITY_Received_LengthNotFive_ConnectionError(int length) expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.PRIORITY, expectedLength: 5)); - Assert.Equal(nameof(ConnectionEndReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -3118,7 +3118,7 @@ public async Task PRIORITY_Received_InterleavedWithHeaders_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.PRIORITY, streamId: 1, headersStreamId: 1)); - Assert.Equal(nameof(ConnectionEndReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -3134,7 +3134,7 @@ public async Task PRIORITY_Received_StreamDependencyOnSelf_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamSelfDependency(Http2FrameType.PRIORITY, 1)); - Assert.Equal(nameof(ConnectionEndReason.StreamSelfDependency), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.StreamSelfDependency), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -3256,7 +3256,7 @@ public async Task RST_STREAM_Received_ContinuesAppsAwaitingConnectionOutputFlowC Assert.Contains(1, _abortedStreamIds); Assert.Contains(3, _abortedStreamIds); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -3342,7 +3342,7 @@ async Task VerifyStreamBackpressure(int streamId, int headersLength) Assert.Contains(3, _abortedStreamIds); Assert.Contains(5, _abortedStreamIds); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -3392,7 +3392,7 @@ public async Task RST_STREAM_Received_StreamIdZero_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdZero(Http2FrameType.RST_STREAM)); - Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -3408,7 +3408,7 @@ public async Task RST_STREAM_Received_StreamIdEven_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdEven(Http2FrameType.RST_STREAM, streamId: 2)); - Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -3424,7 +3424,7 @@ public async Task RST_STREAM_Received_StreamIdle_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdle(Http2FrameType.RST_STREAM, streamId: 1)); - Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); } [Theory] @@ -3445,7 +3445,7 @@ public async Task RST_STREAM_Received_LengthNotFour_ConnectionError(int length) expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.RST_STREAM, expectedLength: 4)); - Assert.Equal(nameof(ConnectionEndReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -3462,7 +3462,7 @@ public async Task RST_STREAM_Received_InterleavedWithHeaders_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.RST_STREAM, streamId: 1, headersStreamId: 1)); - Assert.Equal(nameof(ConnectionEndReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.ErrorType]); } // Compare to h2spec http2/5.1/8 @@ -3489,7 +3489,7 @@ public async Task RST_STREAM_IncompleteRequest_AdditionalDataFrames_ConnectionAb await WaitForConnectionErrorAsync(ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, Http2ErrorCode.STREAM_CLOSED, CoreStrings.FormatHttp2ErrorStreamAborted(Http2FrameType.DATA, 1)); - Assert.Equal(nameof(ConnectionEndReason.FrameAfterStreamClose), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.FrameAfterStreamClose), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -3515,7 +3515,7 @@ public async Task RST_STREAM_IncompleteRequest_AdditionalTrailerFrames_Connectio await WaitForConnectionErrorAsync(ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, Http2ErrorCode.STREAM_CLOSED, CoreStrings.FormatHttp2ErrorStreamAborted(Http2FrameType.HEADERS, 1)); - Assert.Equal(nameof(ConnectionEndReason.FrameAfterStreamClose), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.FrameAfterStreamClose), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -3539,7 +3539,7 @@ public async Task RST_STREAM_IncompleteRequest_AdditionalResetFrame_IgnoreAdditi await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/53744")] @@ -3615,7 +3615,7 @@ public async Task SETTINGS_KestrelDefaults_Sent() await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -3676,7 +3676,7 @@ public async Task SETTINGS_Custom_Sent() await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -3713,7 +3713,7 @@ public async Task SETTINGS_Received_StreamIdNotZero_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdNotZero(Http2FrameType.SETTINGS)); - Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); } [Theory] @@ -3741,7 +3741,7 @@ public async Task SETTINGS_Received_InvalidParameterValue_ConnectionError(int in expectedErrorCode: expectedErrorCode, expectedErrorMessage: CoreStrings.FormatHttp2ErrorSettingsParameterOutOfRange(parameter)); - Assert.Equal(nameof(ConnectionEndReason.InvalidSettings), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidSettings), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -3758,7 +3758,7 @@ public async Task SETTINGS_Received_InterleavedWithHeaders_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.SETTINGS, streamId: 0, headersStreamId: 1)); - Assert.Equal(nameof(ConnectionEndReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.ErrorType]); } [Theory] @@ -3776,7 +3776,7 @@ public async Task SETTINGS_Received_WithACK_LengthNotZero_ConnectionError(int le expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorSettingsAckLengthNotZero); - Assert.Equal(nameof(ConnectionEndReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.ErrorType]); } [Theory] @@ -3797,7 +3797,7 @@ public async Task SETTINGS_Received_LengthNotMultipleOfSix_ConnectionError(int l expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorSettingsLengthNotMultipleOfSix); - Assert.Equal(nameof(ConnectionEndReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -3823,7 +3823,7 @@ public async Task SETTINGS_Received_WithInitialWindowSizePushingStreamWindowOver expectedErrorCode: Http2ErrorCode.FLOW_CONTROL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorInitialWindowSizeInvalid); - Assert.Equal(nameof(ConnectionEndReason.InvalidSettings), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidSettings), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -3882,7 +3882,7 @@ public async Task SETTINGS_Received_ChangesAllowedResponseMaxFrameSize() await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -3923,7 +3923,7 @@ public async Task SETTINGS_Received_ClientMaxFrameSizeCannotExceedServerMaxFrame await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -4006,7 +4006,7 @@ public async Task PUSH_PROMISE_Received_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorPushPromiseReceived); - Assert.Equal(nameof(ConnectionEndReason.UnsupportedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.UnsupportedFrame), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -4047,7 +4047,7 @@ public async Task PING_Received_InterleavedWithHeaders_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.PING, streamId: 0, headersStreamId: 1)); - Assert.Equal(nameof(ConnectionEndReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -4063,7 +4063,7 @@ public async Task PING_Received_StreamIdNotZero_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdNotZero(Http2FrameType.PING)); - Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); } [Theory] @@ -4083,7 +4083,7 @@ public async Task PING_Received_LengthNotEight_ConnectionError(int length) expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.PING, expectedLength: 8)); - Assert.Equal(nameof(ConnectionEndReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -4095,7 +4095,7 @@ public async Task GOAWAY_Received_ConnectionStops() await WaitForConnectionStopAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.ClientGoAway), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -4110,7 +4110,7 @@ public async Task GOAWAY_Received_ConnectionLifetimeNotification_Cancelled() Assert.True(lifetime.ConnectionClosedRequested.IsCancellationRequested); await WaitForConnectionStopAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.ClientGoAway), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -4157,7 +4157,7 @@ public async Task GOAWAY_Received_SetsConnectionStateToClosingAndWaitForAllStrea await WaitForConnectionStopAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); await _closedStateReached.Task.DefaultTimeout(); - Assert.Equal(nameof(ConnectionEndReason.ClientGoAway), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -4262,7 +4262,7 @@ public async Task GOAWAY_Received_ContinuesAppsAwaitingConnectionOutputFlowContr Assert.Contains(3, _abortedStreamIds); Assert.Contains(5, _abortedStreamIds); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -4341,7 +4341,7 @@ async Task VerifyStreamBackpressure(int streamId, int headersLength) Assert.Contains(3, _abortedStreamIds); Assert.Contains(5, _abortedStreamIds); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -4357,7 +4357,7 @@ public async Task GOAWAY_Received_StreamIdNotZero_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdNotZero(Http2FrameType.GOAWAY)); - Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -4374,7 +4374,7 @@ public async Task GOAWAY_Received_InterleavedWithHeaders_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.GOAWAY, streamId: 0, headersStreamId: 1)); - Assert.Equal(nameof(ConnectionEndReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -4390,7 +4390,7 @@ public async Task WINDOW_UPDATE_Received_StreamIdEven_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdEven(Http2FrameType.WINDOW_UPDATE, streamId: 2)); - Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -4407,7 +4407,7 @@ public async Task WINDOW_UPDATE_Received_InterleavedWithHeaders_ConnectionError( expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.WINDOW_UPDATE, streamId: 1, headersStreamId: 1)); - Assert.Equal(nameof(ConnectionEndReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.ErrorType]); } [Theory] @@ -4427,7 +4427,7 @@ public async Task WINDOW_UPDATE_Received_LengthNotFour_ConnectionError(int strea expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.WINDOW_UPDATE, expectedLength: 4)); - Assert.Equal(nameof(ConnectionEndReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -4443,7 +4443,7 @@ public async Task WINDOW_UPDATE_Received_OnConnection_SizeIncrementZero_Connecti expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorWindowUpdateIncrementZero); - Assert.Equal(nameof(ConnectionEndReason.WindowUpdateSizeInvalid), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.WindowUpdateSizeInvalid), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -4460,7 +4460,7 @@ public async Task WINDOW_UPDATE_Received_OnStream_SizeIncrementZero_ConnectionEr expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorWindowUpdateIncrementZero); - Assert.Equal(nameof(ConnectionEndReason.WindowUpdateSizeInvalid), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.WindowUpdateSizeInvalid), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -4476,7 +4476,7 @@ public async Task WINDOW_UPDATE_Received_StreamIdle_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdle(Http2FrameType.WINDOW_UPDATE, streamId: 1)); - Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -4495,7 +4495,7 @@ public async Task WINDOW_UPDATE_Received_OnConnection_IncreasesWindowAboveMaxVal expectedErrorCode: Http2ErrorCode.FLOW_CONTROL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorWindowUpdateSizeInvalid); - Assert.Equal(nameof(ConnectionEndReason.WindowUpdateSizeInvalid), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.WindowUpdateSizeInvalid), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -4516,7 +4516,7 @@ public async Task WINDOW_UPDATE_Received_OnStream_IncreasesWindowAboveMaxValue_S await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -4609,7 +4609,7 @@ public async Task WINDOW_UPDATE_Received_OnConnection_Respected() await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -4798,7 +4798,7 @@ public async Task CONTINUATION_Received_StreamIdMismatch_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.CONTINUATION, streamId: 3, headersStreamId: 1)); - Assert.Equal(nameof(ConnectionEndReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -4815,7 +4815,7 @@ public async Task CONTINUATION_Received_IncompleteHeaderBlock_ConnectionError() expectedErrorCode: Http2ErrorCode.COMPRESSION_ERROR, expectedErrorMessage: SR.net_http_hpack_incomplete_header_block); - Assert.Equal(nameof(ConnectionEndReason.ErrorReadingHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.ErrorReadingHeaders), ConnectionTags[KestrelMetrics.ErrorType]); } [Theory] @@ -4834,7 +4834,7 @@ public async Task CONTINUATION_Received_WithTrailers_ContainsIllegalTrailer_Conn expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: expectedErrorMessage); - Assert.Equal(nameof(ConnectionEndReason.InvalidRequestHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidRequestHeaders), ConnectionTags[KestrelMetrics.ErrorType]); } [Theory] @@ -4859,7 +4859,7 @@ public async Task CONTINUATION_Received_HeaderBlockDoesNotContainMandatoryPseudo expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1)); - Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); } [Theory] @@ -4878,7 +4878,7 @@ public async Task CONTINUATION_Received_HeaderBlockDoesNotContainMandatoryPseudo await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -4920,7 +4920,7 @@ public async Task CONTINUATION_Sent_WhenHeadersLargerThanFrameLength() Assert.Equal(_4kHeaderValue, _decodedHeaders["g"]); Assert.Equal(_4kHeaderValue, _decodedHeaders["h"]); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -4939,7 +4939,7 @@ public async Task UnknownFrameType_Received_Ignored() await StopConnectionAsync(0, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -4956,7 +4956,7 @@ public async Task UnknownFrameType_Received_InterleavedWithHeaders_ConnectionErr expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(frameType: 42, streamId: 1, headersStreamId: 1)); - Assert.Equal(nameof(ConnectionEndReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -4983,7 +4983,7 @@ public async Task ConnectionErrorAbortsAllStreams() Assert.Contains(3, _abortedStreamIds); Assert.Contains(5, _abortedStreamIds); - Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -4998,7 +4998,7 @@ public async Task ConnectionResetLoggedWithActiveStreams() await StopConnectionAsync(1, ignoreNonGoAwayFrames: false); Assert.Single(LogMessages, m => m.Exception is ConnectionResetException); - Assert.Equal(nameof(ConnectionEndReason.ConnectionReset), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.ConnectionReset), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -5011,7 +5011,7 @@ public async Task ConnectionResetNotLoggedWithNoActiveStreams() await WaitForConnectionStopAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false); Assert.DoesNotContain(LogMessages, m => m.Exception is ConnectionResetException); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -5026,7 +5026,7 @@ public async Task OnInputOrOutputCompletedCompletesOutput() Assert.True(result.IsCompleted); Assert.True(result.Buffer.IsEmpty); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -5034,12 +5034,12 @@ public async Task AbortSendsFinalGOAWAY() { await InitializeConnectionAsync(_noopApplication); - _connection.Abort(new ConnectionAbortedException(), ConnectionEndReason.AbortedByApplication); + _connection.Abort(new ConnectionAbortedException(), ConnectionEndReason.AbortedByApp); await _closedStateReached.Task.DefaultTimeout(); VerifyGoAway(await ReceiveFrameAsync(), int.MaxValue, Http2ErrorCode.INTERNAL_ERROR); - Assert.Equal(nameof(ConnectionEndReason.AbortedByApplication), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.AbortedByApp), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -5053,7 +5053,7 @@ public async Task CompletionSendsFinalGOAWAY() VerifyGoAway(await ReceiveFrameAsync(), 0, Http2ErrorCode.NO_ERROR); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -5099,7 +5099,7 @@ public async Task StopProcessingNextRequestSendsGracefulGOAWAYAndWaitsForStreams result = await readTask; Assert.True(result.IsCompleted); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -5109,7 +5109,7 @@ public async Task StopProcessingNextRequestSendsGracefulGOAWAYThenFinalGOAWAYWhe await StartStreamAsync(1, _browserRequestHeaders, endStream: false); - _connection.StopProcessingNextRequest(ConnectionEndReason.ApplicationShutdown); + _connection.StopProcessingNextRequest(ConnectionEndReason.AppShutdown); await _closingStateReached.Task.DefaultTimeout(); VerifyGoAway(await ReceiveFrameAsync(), Int32.MaxValue, Http2ErrorCode.NO_ERROR); @@ -5132,7 +5132,7 @@ public async Task StopProcessingNextRequestSendsGracefulGOAWAYThenFinalGOAWAYWhe await _closedStateReached.Task.DefaultTimeout(); VerifyGoAway(await ReceiveFrameAsync(), 1, Http2ErrorCode.NO_ERROR); - Assert.Equal(nameof(ConnectionEndReason.ApplicationShutdown), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.AppShutdown), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -5143,7 +5143,7 @@ public async Task AcceptNewStreamsDuringClosingConnection() await StartStreamAsync(1, _browserRequestHeaders, endStream: false); - _connection.StopProcessingNextRequest(ConnectionEndReason.ApplicationShutdown); + _connection.StopProcessingNextRequest(ConnectionEndReason.AppShutdown); VerifyGoAway(await ReceiveFrameAsync(), Int32.MaxValue, Http2ErrorCode.NO_ERROR); await _closingStateReached.Task.DefaultTimeout(); @@ -5181,7 +5181,7 @@ public async Task AcceptNewStreamsDuringClosingConnection() await WaitForConnectionStopAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.ApplicationShutdown), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.AppShutdown), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -5203,7 +5203,7 @@ public async Task IgnoreNewStreamsDuringClosedConnection() Assert.True(result.IsCompleted); Assert.True(result.Buffer.IsEmpty); - Assert.Equal(nameof(ConnectionEndReason.ConnectionReset), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.ConnectionReset), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -5223,7 +5223,7 @@ public async Task IOExceptionDuringFrameProcessingIsNotLoggedHigherThanDebug() Assert.Equal("Connection id \"TestConnectionId\" request processing ended abnormally.", logMessage.Message); Assert.Same(ioException, logMessage.Exception); - Assert.Equal(nameof(ConnectionEndReason.IOError), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.IOError), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -5242,7 +5242,7 @@ public async Task UnexpectedExceptionDuringFrameProcessingLoggedAWarning() Assert.Equal(CoreStrings.RequestProcessingEndError, logMessage.Message); Assert.Same(exception, logMessage.Exception); - Assert.Equal(nameof(ConnectionEndReason.UnexpectedError), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.UnexpectedError), ConnectionTags[KestrelMetrics.ErrorType]); } [Theory] @@ -5295,7 +5295,7 @@ public async Task AppDoesNotReadRequestBody_ResetsAndDrainsRequest(int intFinalF await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Theory] @@ -5341,7 +5341,7 @@ public async Task AbortedStream_ResetsAndDrainsRequest(int intFinalFrameType) await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Theory] @@ -5387,7 +5387,7 @@ public async Task ResetStream_ResetsAndDrainsRequest(int intFinalFrameType) await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Theory] @@ -5473,7 +5473,7 @@ async Task CompletePipeOnTaskCompletion() await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -5545,7 +5545,7 @@ async Task CompletePipeOnTaskCompletion() requestBlock.SetResult(); await StopConnectionAsync(expectedLastStreamId: 7, ignoreNonGoAwayFrames: true); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -5590,7 +5590,7 @@ public async Task FramesInBatchAreStillProcessedAfterStreamError_WithoutHeartbea Assert.Equal(streamPayload, streamResponse); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: true); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Theory] @@ -5629,7 +5629,7 @@ public async Task AbortedStream_ResetsAndDrainsRequest_RefusesFramesAfterEndOfSt CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.DATA, streamId: 1), CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.DATA, streamId: 1) }); - Assert.Equal(nameof(ConnectionEndReason.UnknownStream), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.UnknownStream), ConnectionTags[KestrelMetrics.ErrorType]); break; case Http2FrameType.HEADERS: @@ -5646,7 +5646,7 @@ public async Task AbortedStream_ResetsAndDrainsRequest_RefusesFramesAfterEndOfSt CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1), CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.HEADERS, streamId: 1) }); - Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); break; case Http2FrameType.CONTINUATION: @@ -5664,7 +5664,7 @@ public async Task AbortedStream_ResetsAndDrainsRequest_RefusesFramesAfterEndOfSt CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1), CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.HEADERS, streamId: 1) }); - Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); break; default: throw new NotImplementedException(finalFrameType.ToString()); @@ -5720,11 +5720,11 @@ public async Task AbortedStream_ResetsAndDrainsRequest_RefusesFramesAfterClientR switch (finalFrameType) { case Http2FrameType.DATA: - Assert.Equal(nameof(ConnectionEndReason.UnknownStream), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.UnknownStream), ConnectionTags[KestrelMetrics.ErrorType]); break; case Http2FrameType.HEADERS: - Assert.Equal(nameof(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); break; default: @@ -5745,7 +5745,7 @@ public async Task StartConnection_SendPreface_ReturnSettings() withStreamId: 0); await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: true); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -5759,7 +5759,7 @@ public async Task StartConnection_SendHttp1xRequest_ReturnHttp11Status400() Assert.NotNull(Http2Connection.InvalidHttp1xErrorResponseBytes); Assert.Equal(Http2Connection.InvalidHttp1xErrorResponseBytes, data); - Assert.Equal(nameof(ConnectionEndReason.InvalidHttpVersion), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidHttpVersion), ConnectionTags[KestrelMetrics.ErrorType]); Assert.Equal(Http2ErrorCode.PROTOCOL_ERROR, (Http2ErrorCode)_errorCodeFeature.Error); } @@ -5775,7 +5775,7 @@ public async Task StartConnection_SendHttp1xRequest_ExceedsRequestLineLimit_Prot expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorInvalidPreface); - Assert.Equal(nameof(ConnectionEndReason.InvalidHandshake), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidHandshake), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -5792,7 +5792,7 @@ public async Task StartTlsConnection_SendHttp1xRequest_NoError() await SendAsync(Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\n")); await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } [Fact] @@ -5801,7 +5801,7 @@ public async Task StartConnection_SendNothing_NoError() InitializeConnectionWithoutPreface(_noopApplication); await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); } public static TheoryData UpperCaseHeaderNameData diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs index f2a80bde7749..624052e1bba4 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs @@ -39,7 +39,7 @@ public async Task HEADERS_Received_NewLineCharactersInValue_ConnectionError(stri await StartStreamAsync(1, headers, endStream: true); await WaitForConnectionErrorAsync(ignoreNonGoAwayFrames: false, 1, Http2ErrorCode.PROTOCOL_ERROR, "Malformed request: invalid headers."); - Assert.Equal(nameof(ConnectionEndReason.InvalidRequestHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidRequestHeaders), ConnectionTags["error.type"]); } [Fact] @@ -2234,7 +2234,7 @@ public async Task ResponseHeaders_WithInvalidValuesAndCustomEncoder_AbortsConnec await StartStreamAsync(1, _browserRequestHeaders, endStream: true); await WaitForConnectionErrorAsync(ignoreNonGoAwayFrames: false, int.MaxValue, Http2ErrorCode.INTERNAL_ERROR); - Assert.Equal(nameof(ConnectionEndReason.ErrorWritingHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.ErrorWritingHeaders), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -2585,7 +2585,7 @@ public async Task ResponseTrailers_WithInvalidValuesAndCustomEncoder_AbortsConne Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]); await WaitForConnectionErrorAsync(ignoreNonGoAwayFrames: false, int.MaxValue, Http2ErrorCode.INTERNAL_ERROR); - Assert.Equal(nameof(ConnectionEndReason.ErrorWritingHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.ErrorWritingHeaders), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -3779,7 +3779,7 @@ public async Task ResponseHeader_OneMegaByte_SplitsHeaderToContinuationFrames() withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: true); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.False(ConnectionTags.ContainsKey(KestrelMetrics.ErrorType), "Non-error reason shouldn't be added to error.type"); } [Fact] @@ -5813,7 +5813,7 @@ public async Task HEADERS_Received_Latin1_RejectedWhenLatin1OptionIsNotConfigure expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.BadRequest_MalformedRequestInvalidHeaders); - Assert.Equal(nameof(ConnectionEndReason.InvalidRequestHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidRequestHeaders), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -5833,7 +5833,7 @@ public async Task HEADERS_Received_CustomEncoding_InvalidCharacters_AbortsConnec await WaitForConnectionErrorAsync(ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, Http2ErrorCode.PROTOCOL_ERROR, CoreStrings.BadRequest_MalformedRequestInvalidHeaders); - Assert.Equal(nameof(ConnectionEndReason.InvalidRequestHeaders), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidRequestHeaders), ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs index 181b90ca0129..ef8b201f5322 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs @@ -424,6 +424,11 @@ public override void Dispose() base.Dispose(); } + protected void AssertConnectionNoError() + { + Assert.DoesNotContain(KestrelMetrics.ErrorType, ConnectionTags.Keys); + } + void IHttpStreamHeadersHandler.OnHeader(ReadOnlySpan name, ReadOnlySpan value) { var nameStr = name.GetHeaderName(); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs index abc6da2c628a..e4e6b2878077 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs @@ -99,7 +99,7 @@ public async Task HEADERS_NotReceivedAfterFirstRequest_WithinKeepAliveTimeout_Cl _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.KeepAlive), Times.Once); await WaitForConnectionStopAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.KeepAliveTimeout), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.KeepAliveTimeout), ConnectionTags[KestrelMetrics.ErrorType]); _mockTimeoutHandler.VerifyNoOtherCalls(); } @@ -194,7 +194,7 @@ public async Task HEADERS_ReceivedWithoutAllCONTINUATIONs_WithinRequestHeadersTi expectedLastStreamId: int.MaxValue, Http2ErrorCode.INTERNAL_ERROR, CoreStrings.BadRequest_RequestHeadersTimeout); - Assert.Equal(nameof(ConnectionEndReason.RequestHeadersTimeout), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.RequestHeadersTimeout), ConnectionTags[KestrelMetrics.ErrorType]); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.BadRequest_RequestHeadersTimeout)), Times.Once); @@ -213,7 +213,7 @@ public async Task ResponseDrain_SlowerThanMinimumDataRate_AbortsConnection() await SendGoAwayAsync(); await WaitForConnectionStopAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.ClientGoAway), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); AdvanceTime(TimeSpan.FromSeconds(_bytesReceived / limits.MinResponseDataRate.BytesPerSecond) + limits.MinResponseDataRate.GracePeriod + Heartbeat.Interval - TimeSpan.FromSeconds(.5)); @@ -304,11 +304,11 @@ async Task AdvanceClockAndSendFrames() switch (finalFrameType) { case Http2FrameType.DATA: - Assert.Equal(nameof(ConnectionEndReason.UnknownStream), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.UnknownStream), ConnectionTags[KestrelMetrics.ErrorType]); break; case Http2FrameType.CONTINUATION: - Assert.Equal(nameof(ConnectionEndReason.FrameAfterStreamClose), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.FrameAfterStreamClose), ConnectionTags[KestrelMetrics.ErrorType]); break; default: @@ -388,7 +388,7 @@ public async Task DATA_Sent_TooSlowlyDueToSocketBackPressureOnSmallWrite_AbortsC withStreamId: 1); Assert.True((await _pair.Application.Input.ReadAsync().AsTask().DefaultTimeout()).IsCompleted); - Assert.Equal(nameof(ConnectionEndReason.MinResponseDataRate), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MinResponseDataRate), ConnectionTags[KestrelMetrics.ErrorType]); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied)), Times.Once); @@ -443,7 +443,7 @@ public async Task DATA_Sent_TooSlowlyDueToSocketBackPressureOnLargeWrite_AbortsC withStreamId: 1); Assert.True((await _pair.Application.Input.ReadAsync().AsTask().DefaultTimeout()).IsCompleted); - Assert.Equal(nameof(ConnectionEndReason.MinResponseDataRate), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MinResponseDataRate), ConnectionTags[KestrelMetrics.ErrorType]); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied)), Times.Once); @@ -495,7 +495,7 @@ public async Task DATA_Sent_TooSlowlyDueToFlowControlOnSmallWrite_AbortsConnecti expectedLastStreamId: int.MaxValue, Http2ErrorCode.INTERNAL_ERROR, null); - Assert.Equal(nameof(ConnectionEndReason.MinResponseDataRate), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MinResponseDataRate), ConnectionTags[KestrelMetrics.ErrorType]); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied)), Times.Once); @@ -549,7 +549,7 @@ public async Task DATA_Sent_TooSlowlyDueToOutputFlowControlOnLargeWrite_AbortsCo expectedLastStreamId: int.MaxValue, Http2ErrorCode.INTERNAL_ERROR, null); - Assert.Equal(nameof(ConnectionEndReason.MinResponseDataRate), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MinResponseDataRate), ConnectionTags[KestrelMetrics.ErrorType]); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied)), Times.Once); @@ -615,7 +615,7 @@ public async Task DATA_Sent_TooSlowlyDueToOutputFlowControlOnMultipleStreams_Abo expectedLastStreamId: int.MaxValue, Http2ErrorCode.INTERNAL_ERROR, null); - Assert.Equal(nameof(ConnectionEndReason.MinResponseDataRate), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MinResponseDataRate), ConnectionTags[KestrelMetrics.ErrorType]); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied)), Times.Once); @@ -662,7 +662,7 @@ public async Task DATA_Received_TooSlowlyOnSmallRead_AbortsConnectionAfterGraceP expectedLastStreamId: int.MaxValue, Http2ErrorCode.INTERNAL_ERROR, null); - Assert.Equal(nameof(ConnectionEndReason.MinRequestBodyDataRate), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MinRequestBodyDataRate), ConnectionTags[KestrelMetrics.ErrorType]); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.BadRequest_RequestBodyTimeout)), Times.Once); @@ -713,7 +713,7 @@ public async Task DATA_Received_TooSlowlyOnLargeRead_AbortsConnectionAfterRateTi expectedLastStreamId: int.MaxValue, Http2ErrorCode.INTERNAL_ERROR, null); - Assert.Equal(nameof(ConnectionEndReason.MinRequestBodyDataRate), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MinRequestBodyDataRate), ConnectionTags[KestrelMetrics.ErrorType]); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.BadRequest_RequestBodyTimeout)), Times.Once); @@ -780,7 +780,7 @@ public async Task DATA_Received_TooSlowlyOnMultipleStreams_AbortsConnectionAfter expectedLastStreamId: int.MaxValue, Http2ErrorCode.INTERNAL_ERROR, null); - Assert.Equal(nameof(ConnectionEndReason.MinRequestBodyDataRate), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MinRequestBodyDataRate), ConnectionTags[KestrelMetrics.ErrorType]); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.BadRequest_RequestBodyTimeout)), Times.Once); @@ -848,7 +848,7 @@ public async Task DATA_Received_TooSlowlyOnSecondStream_AbortsConnectionAfterNon expectedLastStreamId: int.MaxValue, Http2ErrorCode.INTERNAL_ERROR, null); - Assert.Equal(nameof(ConnectionEndReason.MinRequestBodyDataRate), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MinRequestBodyDataRate), ConnectionTags[KestrelMetrics.ErrorType]); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.BadRequest_RequestBodyTimeout)), Times.Once); @@ -903,7 +903,7 @@ public async Task DATA_Received_SlowlyWhenRateLimitDisabledPerRequest_DoesNotAbo withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.Equal(nameof(ConnectionEndReason.TransportCompleted), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + AssertConnectionNoError(); _mockTimeoutHandler.VerifyNoOtherCalls(); _mockConnectionContext.VerifyNoOtherCalls(); @@ -989,7 +989,7 @@ public async Task DATA_Received_SlowlyDueToConnectionFlowControl_DoesNotAbortCon expectedLastStreamId: int.MaxValue, Http2ErrorCode.INTERNAL_ERROR, null); - Assert.Equal(nameof(ConnectionEndReason.MinRequestBodyDataRate), ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MinRequestBodyDataRate), ConnectionTags[KestrelMetrics.ErrorType]); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.BadRequest_RequestBodyTimeout)), Times.Once); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3ConnectionTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3ConnectionTests.cs index a15ae2bf49f2..6e3d2160d05b 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3ConnectionTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3ConnectionTests.cs @@ -191,7 +191,7 @@ public async Task GOAWAY_GracefulServerShutdown_SendsGoAway(int connectionReques Assert.Null(await Http3Api.MultiplexedConnectionContext.AcceptAsync().DefaultTimeout()); await Http3Api.WaitForConnectionStopAsync(expectedStreamId, false, expectedErrorCode: Http3ErrorCode.NoError); - Assert.Equal(nameof(ConnectionEndReason.ApplicationShutdown), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.AppShutdown), Http3Api.ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -221,7 +221,7 @@ public async Task GOAWAY_GracefulServerShutdownWithActiveRequest_SendsMultipleGo Http3Api.MultiplexedConnectionContext.Abort(); await Http3Api.WaitForConnectionStopAsync(4, false, expectedErrorCode: Http3ErrorCode.NoError); - Assert.Equal(nameof(ConnectionEndReason.ApplicationShutdown), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.AppShutdown), Http3Api.ConnectionTags[KestrelMetrics.ErrorType]); } [Theory] @@ -248,7 +248,7 @@ public async Task SETTINGS_ReservedSettingSent_ConnectionError(long settingIdent expectedErrorCode: Http3ErrorCode.SettingsError, matchExpectedErrorMessage: AssertExpectedErrorMessages, expectedErrorMessage: CoreStrings.FormatHttp3ErrorControlStreamReservedSetting($"0x{settingIdentifier.ToString("X", CultureInfo.InvariantCulture)}")); - Assert.Equal(nameof(ConnectionEndReason.InvalidSettings), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidSettings), Http3Api.ConnectionTags[KestrelMetrics.ErrorType]); } [Theory] @@ -268,7 +268,7 @@ public async Task InboundStreams_CreateMultiple_ConnectionError(int streamId, st expectedErrorCode: Http3ErrorCode.StreamCreationError, matchExpectedErrorMessage: AssertExpectedErrorMessages, expectedErrorMessage: CoreStrings.FormatHttp3ControlStreamErrorMultipleInboundStreams(name)); - Assert.Equal(nameof(ConnectionEndReason.StreamCreationError), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.StreamCreationError), Http3Api.ConnectionTags[KestrelMetrics.ErrorType]); } [Theory] @@ -290,7 +290,7 @@ public async Task ControlStream_ClientToServer_UnexpectedFrameType_ConnectionErr expectedErrorCode: Http3ErrorCode.UnexpectedFrame, matchExpectedErrorMessage: AssertExpectedErrorMessages, expectedErrorMessage: CoreStrings.FormatHttp3ErrorUnsupportedFrameOnControlStream(Http3Formatting.ToFormattedType(f))); - Assert.Equal(nameof(ConnectionEndReason.UnexpectedFrame), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.UnexpectedFrame), Http3Api.ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -315,7 +315,7 @@ public async Task ControlStream_ClientToServer_Completes_ConnectionError() expectedErrorCode: Http3ErrorCode.ClosedCriticalStream, matchExpectedErrorMessage: AssertExpectedErrorMessages, expectedErrorMessage: CoreStrings.Http3ErrorControlStreamClosed); - Assert.Equal(nameof(ConnectionEndReason.ClosedCriticalStream), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.ClosedCriticalStream), Http3Api.ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -339,7 +339,7 @@ public async Task GOAWAY_TriggersLifetimeNotification_ConnectionClosedRequested( Http3Api.CloseServerGracefully(); await Http3Api.WaitForConnectionStopAsync(0, true, expectedErrorCode: Http3ErrorCode.NoError); - Assert.Equal(nameof(ConnectionEndReason.ClientGoAway), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.DoesNotContain(KestrelMetrics.ErrorType, Http3Api.ConnectionTags.Keys); } [Fact] @@ -360,7 +360,7 @@ public async Task ControlStream_ServerToClient_ErrorInitializing_ConnectionError expectedErrorCode: Http3ErrorCode.ClosedCriticalStream, matchExpectedErrorMessage: AssertExpectedErrorMessages, expectedErrorMessage: CoreStrings.Http3ErrorControlStreamClosed); - Assert.Equal(nameof(ConnectionEndReason.ClosedCriticalStream), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.ClosedCriticalStream), Http3Api.ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs index ef93e8de5972..69fc112bddfc 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs @@ -2033,7 +2033,7 @@ public async Task FrameAfterTrailers_UnexpectedFrameError() expectedLastStreamId: null, Http3ErrorCode.UnexpectedFrame, null); - Assert.Equal(nameof(ConnectionEndReason.UnexpectedFrame), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.UnexpectedFrame), Http3Api.ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -2109,7 +2109,7 @@ public async Task UnexpectedRequestFrame(string frameType, bool pendingStreamsEn expectedErrorCode: Http3ErrorCode.UnexpectedFrame, matchExpectedErrorMessage: AssertExpectedErrorMessages, expectedErrorMessage: CoreStrings.FormatHttp3ErrorUnsupportedFrameOnRequestStream(Http3Formatting.ToFormattedType(f))); - Assert.Equal(nameof(ConnectionEndReason.UnsupportedFrame), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.UnsupportedFrame), Http3Api.ConnectionTags[KestrelMetrics.ErrorType]); } [Theory] @@ -2139,7 +2139,7 @@ public async Task UnexpectedServerFrame(string frameType) expectedLastStreamId: null, Http3ErrorCode.UnexpectedFrame, null); - Assert.Equal(nameof(ConnectionEndReason.UnsupportedFrame), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.UnsupportedFrame), Http3Api.ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TimeoutTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TimeoutTests.cs index 4730a47ed8bb..b5d98a588581 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TimeoutTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TimeoutTests.cs @@ -28,7 +28,7 @@ public async Task KeepAliveTimeout_ControlStreamNotReceived_ConnectionClosed() Http3Api.AdvanceTime(limits.KeepAliveTimeout + TimeSpan.FromTicks(1)); await Http3Api.WaitForConnectionStopAsync(0, false, expectedErrorCode: Http3ErrorCode.NoError); - Assert.Equal(nameof(ConnectionEndReason.KeepAliveTimeout), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.KeepAliveTimeout), Http3Api.ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -45,7 +45,7 @@ public async Task KeepAliveTimeout_RequestNotReceived_ConnectionClosed() Http3Api.AdvanceTime(limits.KeepAliveTimeout + TimeSpan.FromTicks(1)); await Http3Api.WaitForConnectionStopAsync(0, false, expectedErrorCode: Http3ErrorCode.NoError); - Assert.Equal(nameof(ConnectionEndReason.KeepAliveTimeout), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.KeepAliveTimeout), Http3Api.ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -74,7 +74,7 @@ public async Task KeepAliveTimeout_AfterRequestComplete_ConnectionClosed() Http3Api.AdvanceTime(limits.KeepAliveTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1)); await Http3Api.WaitForConnectionStopAsync(4, false, expectedErrorCode: Http3ErrorCode.NoError); - Assert.Equal(nameof(ConnectionEndReason.KeepAliveTimeout), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.KeepAliveTimeout), Http3Api.ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -120,7 +120,7 @@ public async Task KeepAliveTimeout_LongRunningRequest_KeepsConnectionAlive() Http3Api.AdvanceTime(limits.KeepAliveTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1)); await Http3Api.WaitForConnectionStopAsync(4, false, expectedErrorCode: Http3ErrorCode.NoError); - Assert.Equal(nameof(ConnectionEndReason.KeepAliveTimeout), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.KeepAliveTimeout), Http3Api.ConnectionTags[KestrelMetrics.ErrorType]); } [Fact] @@ -369,7 +369,7 @@ public async Task DATA_Received_TooSlowlyOnSmallRead_AbortsConnectionAfterGraceP expectedLastStreamId: 4, Http3ErrorCode.InternalError, null); - Assert.Equal(nameof(ConnectionEndReason.MinRequestBodyDataRate), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MinRequestBodyDataRate), Http3Api.ConnectionTags[KestrelMetrics.ErrorType]); _mockTimeoutHandler.VerifyNoOtherCalls(); } @@ -413,7 +413,7 @@ public async Task ResponseDrain_SlowerThanMinimumDataRate_AbortsConnection() Http3ErrorCode.InternalError, matchExpectedErrorMessage: AssertExpectedErrorMessages, expectedErrorMessage: CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied); - Assert.Equal(nameof(ConnectionEndReason.MinResponseDataRate), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MinResponseDataRate), Http3Api.ConnectionTags[KestrelMetrics.ErrorType]); Assert.Contains(TestSink.Writes, w => w.EventId.Name == "ResponseMinimumDataRateNotSatisfied"); } @@ -567,7 +567,7 @@ public async Task DATA_Received_TooSlowlyOnLargeRead_AbortsConnectionAfterRateTi expectedLastStreamId: null, Http3ErrorCode.InternalError, null); - Assert.Equal(nameof(ConnectionEndReason.MinRequestBodyDataRate), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MinRequestBodyDataRate), Http3Api.ConnectionTags[KestrelMetrics.ErrorType]); _mockTimeoutHandler.VerifyNoOtherCalls(); } @@ -622,7 +622,7 @@ public async Task DATA_Received_TooSlowlyOnMultipleStreams_AbortsConnectionAfter expectedLastStreamId: null, Http3ErrorCode.InternalError, null); - Assert.Equal(nameof(ConnectionEndReason.MinRequestBodyDataRate), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MinRequestBodyDataRate), Http3Api.ConnectionTags[KestrelMetrics.ErrorType]); _mockTimeoutHandler.VerifyNoOtherCalls(); } @@ -678,7 +678,7 @@ public async Task DATA_Received_TooSlowlyOnSecondStream_AbortsConnectionAfterNon expectedLastStreamId: null, Http3ErrorCode.InternalError, null); - Assert.Equal(nameof(ConnectionEndReason.MinRequestBodyDataRate), Http3Api.ConnectionTags[KestrelMetrics.KestrelConnectionEndReason]); + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MinRequestBodyDataRate), Http3Api.ConnectionTags[KestrelMetrics.ErrorType]); _mockTimeoutHandler.VerifyNoOtherCalls(); } diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/KestrelMetricsTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/KestrelMetricsTests.cs index 2a96a0681bf6..8909945ca615 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/KestrelMetricsTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/KestrelMetricsTests.cs @@ -24,6 +24,16 @@ public class KestrelMetricsTests : TestApplicationErrorLoggerLoggedTest { private static readonly X509Certificate2 _x509Certificate2 = TestResources.GetTestCertificate(); + [Fact] + public void ConnectionEndReasonMappings() + { + foreach (var reason in Enum.GetValues()) + { + var hasValue = KestrelMetrics.TryGetErrorType(reason, out var value); + Assert.True(hasValue || value == null, $"ConnectionEndReason '{reason}' doesn't have a mapping."); + } + } + [Fact] public async Task Http1Connection() { @@ -484,7 +494,7 @@ public async Task Http2Connection_TlsError() Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => { - AssertDuration(m, "127.0.0.1", localPort: 0, "tcp", "ipv4", httpVersion: null, tlsProtocolVersion: null, error: ConnectionEndReason.TlsHandshakeFailed.ToString()); + AssertDuration(m, "127.0.0.1", localPort: 0, "tcp", "ipv4", httpVersion: null, tlsProtocolVersion: null, error: KestrelMetrics.GetErrorType(ConnectionEndReason.TlsHandshakeFailed)); }); Assert.Collection(tlsHandshakeDuration.GetMeasurementSnapshot(), m => diff --git a/src/Shared/ServerInfrastructure/Http2/ConnectionEndReason.cs b/src/Shared/ServerInfrastructure/Http2/ConnectionEndReason.cs index 2d21a4c282de..9123016707ef 100644 --- a/src/Shared/ServerInfrastructure/Http2/ConnectionEndReason.cs +++ b/src/Shared/ServerInfrastructure/Http2/ConnectionEndReason.cs @@ -35,12 +35,12 @@ internal enum ConnectionEndReason FlowControlQueueSizeExceeded, OutputQueueSizeExceeded, ClosedCriticalStream, - AbortedByApplication, + AbortedByApp, ServerTimeout, StreamCreationError, IOError, ClientGoAway, - ApplicationShutdown, + AppShutdown, TransportCompleted, TlsHandshakeFailed } From ddf1bce74811994c23341dff1f477f9bb1d29738 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Mon, 10 Jun 2024 14:15:40 +0800 Subject: [PATCH 19/29] Fix build --- .../test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs index 624052e1bba4..cf04cf221c4b 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs @@ -12,7 +12,6 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; -using Microsoft.AspNetCore.InternalTesting; using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; From 22872ca0c3c7a2e217ab0137f5a2a73415851043 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Mon, 10 Jun 2024 15:54:25 +0800 Subject: [PATCH 20/29] Update --- .../Kestrel/Core/src/Internal/Http2/Http2Connection.cs | 2 +- .../Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs | 9 ++++++--- .../Kestrel/Core/src/Internal/Http3/Http3Connection.cs | 4 ++-- .../Core/src/Internal/Infrastructure/KestrelMetrics.cs | 4 ++-- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs index c9969be87080..ca717902a540 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs @@ -275,7 +275,7 @@ public void StopProcessingNextRequest(bool serverInitiated, ConnectionEndReason { Exception? error = null; var errorCode = Http2ErrorCode.NO_ERROR; - var reason = ConnectionEndReason.Unknown; + var reason = ConnectionEndReason.Unset; try { diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs index 0b38d7dedd87..2854a6aa7b68 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs @@ -323,14 +323,17 @@ static bool HasStateFlag(Http2OutputProducer.State state, Http2OutputProducer.St private async Task HandleFlowControlErrorAsync() { - var connectionError = new Http2ConnectionErrorException(CoreStrings.Http2ErrorWindowUpdateSizeInvalid, Http2ErrorCode.FLOW_CONTROL_ERROR, ConnectionEndReason.WindowUpdateSizeInvalid); + const ConnectionEndReason reason = ConnectionEndReason.WindowUpdateSizeInvalid; + const Http2ErrorCode http2ErrorCode = Http2ErrorCode.FLOW_CONTROL_ERROR; + + var connectionError = new Http2ConnectionErrorException(CoreStrings.Http2ErrorWindowUpdateSizeInvalid, http2ErrorCode, reason); _log.Http2ConnectionError(_connectionId, connectionError); - await WriteGoAwayAsync(int.MaxValue, Http2ErrorCode.FLOW_CONTROL_ERROR); + await WriteGoAwayAsync(int.MaxValue, http2ErrorCode); // Prevent Abort() from writing an INTERNAL_ERROR GOAWAY frame after our FLOW_CONTROL_ERROR. Complete(); // Stop processing any more requests and immediately close the connection. - _http2Connection.Abort(new ConnectionAbortedException(CoreStrings.Http2ErrorWindowUpdateSizeInvalid, connectionError), Http2ErrorCode.FLOW_CONTROL_ERROR, ConnectionEndReason.WindowUpdateSizeInvalid); + _http2Connection.Abort(new ConnectionAbortedException(CoreStrings.Http2ErrorWindowUpdateSizeInvalid, connectionError), http2ErrorCode, reason); } private bool TryQueueProducerForConnectionWindowUpdate(long actual, Http2OutputProducer producer) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs index 0c2d8dd0a663..4d16041fcfa1 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs @@ -342,7 +342,7 @@ private void UpdateStreamTimeouts(long timestamp) Http3ControlStream? outboundControlStream = null; ValueTask outboundControlStreamTask = default; bool clientAbort = false; - ConnectionEndReason reason = ConnectionEndReason.Unknown; + ConnectionEndReason reason = ConnectionEndReason.Unset; try { @@ -543,7 +543,7 @@ private void UpdateStreamTimeouts(long timestamp) } // Use graceful close reason if it has been set. - if (reason == ConnectionEndReason.Unknown && _gracefulCloseReason != ConnectionEndReason.Unknown) + if (reason == ConnectionEndReason.Unset && _gracefulCloseReason != ConnectionEndReason.Unset) { reason = _gracefulCloseReason; } diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs index 21934b790045..7bced1821f59 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs @@ -415,7 +415,7 @@ public static bool TryGetHandshakeProtocol(SslProtocols protocols, [NotNullWhen( public static void AddConnectionEndReason(IConnectionMetricsTagsFeature? feature, ConnectionEndReason reason) { - Debug.Assert(reason != ConnectionEndReason.Unknown); + Debug.Assert(reason != ConnectionEndReason.Unset); if (feature != null) { @@ -436,7 +436,7 @@ internal static bool TryGetErrorType(ConnectionEndReason reason, [NotNullWhen(tr { errorTypeValue = reason switch { - ConnectionEndReason.Unknown => null, // Not an error + ConnectionEndReason.Unset => null, // Not an error ConnectionEndReason.ClientGoAway => null, // Not an error ConnectionEndReason.TransportCompleted => null, // Not an error ConnectionEndReason.ConnectionReset => "connection_reset", From 8f7fcb842b701ae93053a16f2203feee5b209702 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Mon, 10 Jun 2024 15:54:40 +0800 Subject: [PATCH 21/29] Update --- src/Shared/ServerInfrastructure/Http2/ConnectionEndReason.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Shared/ServerInfrastructure/Http2/ConnectionEndReason.cs b/src/Shared/ServerInfrastructure/Http2/ConnectionEndReason.cs index 9123016707ef..d15ced3d1130 100644 --- a/src/Shared/ServerInfrastructure/Http2/ConnectionEndReason.cs +++ b/src/Shared/ServerInfrastructure/Http2/ConnectionEndReason.cs @@ -5,7 +5,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core; internal enum ConnectionEndReason { - Unknown, + Unset, ConnectionReset, FlowControlWindowExceeded, KeepAliveTimeout, From e9acb0f196fe869e1a56324d13b03d59044f82df Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 11 Jun 2024 22:29:56 +0800 Subject: [PATCH 22/29] Tests, graceful shutdown vs abort shutdown, add reason for invalid bodywriter state --- .../Core/src/Internal/Http/Http1Connection.cs | 6 +- .../src/Internal/Http/Http1MessageBody.cs | 5 +- .../Core/src/Internal/HttpConnection.cs | 2 +- .../Infrastructure/KestrelConnectionOfT.cs | 1 + .../Internal/Infrastructure/KestrelMetrics.cs | 13 +- .../Infrastructure/KestrelTrace.General.cs | 8 + .../TransportConnectionManager.cs | 5 + .../Kestrel/shared/test/TestServiceContext.cs | 3 + .../Http3/Http3ConnectionTests.cs | 4 +- .../KestrelMetricsTests.cs | 315 +++++++++++++++++- .../InMemory.FunctionalTests/RequestTests.cs | 2 +- .../TestTransport/TestServer.cs | 2 +- src/Shared/Metrics/MetricsExtensions.cs | 37 ++ .../Http2/ConnectionEndReason.cs | 2 + 14 files changed, 386 insertions(+), 19 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs index 4708d22dd9f3..256b1f0c490a 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs @@ -62,7 +62,7 @@ public Http1Connection(HttpConnectionContext context) _context.ServiceContext.Log, _context.TimeoutControl, minResponseDataRateFeature: this, - _context.ConnectionFeatures.Get(), + ConnectionMetricsTagsFeature, outputAborter: this); Input = _context.Transport.Input; @@ -70,6 +70,8 @@ public Http1Connection(HttpConnectionContext context) MemoryPool = _context.MemoryPool; } + private IConnectionMetricsTagsFeature? ConnectionMetricsTagsFeature => _context.ConnectionFeatures.Get(); + public PipeReader Input { get; } public bool RequestTimedOut => _requestTimedOut; @@ -132,6 +134,8 @@ protected override void ApplicationAbort() /// public void StopProcessingNextRequest(ConnectionEndReason reason) { + KestrelMetrics.AddConnectionEndReason(ConnectionMetricsTagsFeature, reason); + _keepAlive = false; Input.CancelPendingRead(); } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs index 06abd7027aad..7c2de425afa9 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs @@ -104,11 +104,10 @@ protected async Task OnConsumeAsyncAwaited() } catch (InvalidOperationException ex) { - var connectionAbortedException = new ConnectionAbortedException(CoreStrings.ConnectionAbortedByApplication, ex); - _context.ReportApplicationError(connectionAbortedException); + Log.RequestBodyDrainBodyReaderInvalidState(_context.ConnectionIdFeature, _context.TraceIdentifier, ex); // Have to abort the connection because we can't finish draining the request - _context.StopProcessingNextRequest(ConnectionEndReason.AbortedByApp); + _context.StopProcessingNextRequest(ConnectionEndReason.BodyReaderInvalidState); } finally { diff --git a/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs b/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs index 3b657546ab80..96d5d39c92a8 100644 --- a/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs @@ -101,7 +101,7 @@ public HttpConnection(BaseHttpConnectionContext context) connectionHeartbeatFeature?.OnHeartbeat(state => ((HttpConnection)state).Tick(), this); // Register for graceful shutdown of the server - using var shutdownRegistration = connectionLifetimeNotificationFeature?.ConnectionClosedRequested.Register(state => ((HttpConnection)state!).StopProcessingNextRequest(ConnectionEndReason.AppShutdown), this); + using var shutdownRegistration = connectionLifetimeNotificationFeature?.ConnectionClosedRequested.Register(state => ((HttpConnection)state!).StopProcessingNextRequest(ConnectionEndReason.GracefulAppShutdown), this); // Register for connection close using var closedRegistration = _context.ConnectionContext.ConnectionClosed.Register(state => ((HttpConnection)state!).OnConnectionClosed(), this); diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnectionOfT.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnectionOfT.cs index 900219d4c7de..b7a353da5255 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnectionOfT.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnectionOfT.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections; using System.Diagnostics; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections.Features; diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs index 7bced1821f59..5f00bddbcf12 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs @@ -413,7 +413,7 @@ public static bool TryGetHandshakeProtocol(SslProtocols protocols, [NotNullWhen( return false; } - public static void AddConnectionEndReason(IConnectionMetricsTagsFeature? feature, ConnectionEndReason reason) + public static void AddConnectionEndReason(IConnectionMetricsTagsFeature? feature, ConnectionEndReason reason, bool overwrite = false) { Debug.Assert(reason != ConnectionEndReason.Unset); @@ -421,7 +421,14 @@ public static void AddConnectionEndReason(IConnectionMetricsTagsFeature? feature { if (TryGetErrorType(reason, out var errorTypeValue)) { - feature.TryAddTag(ErrorType, errorTypeValue); + if (overwrite) + { + feature.SetTag(ErrorType, errorTypeValue); + } + else + { + feature.TryAddTag(ErrorType, errorTypeValue); + } } } } @@ -439,6 +446,7 @@ internal static bool TryGetErrorType(ConnectionEndReason reason, [NotNullWhen(tr ConnectionEndReason.Unset => null, // Not an error ConnectionEndReason.ClientGoAway => null, // Not an error ConnectionEndReason.TransportCompleted => null, // Not an error + ConnectionEndReason.GracefulAppShutdown => null, // Not an error ConnectionEndReason.ConnectionReset => "connection_reset", ConnectionEndReason.FlowControlWindowExceeded => "flow_control_window_exceeded", ConnectionEndReason.KeepAliveTimeout => "keep_alive_timeout", @@ -469,6 +477,7 @@ internal static bool TryGetErrorType(ConnectionEndReason reason, [NotNullWhen(tr ConnectionEndReason.OutputQueueSizeExceeded => "output_queue_size_exceeded", ConnectionEndReason.ClosedCriticalStream => "closed_critical_stream", ConnectionEndReason.AbortedByApp => "aborted_by_app", + ConnectionEndReason.BodyReaderInvalidState => "body_reader_invalid_state", ConnectionEndReason.ServerTimeout => "server_timeout", ConnectionEndReason.StreamCreationError => "stream_creation_error", ConnectionEndReason.IOError => "io_error", diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelTrace.General.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelTrace.General.cs index e37583755531..25428d10bccd 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelTrace.General.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelTrace.General.cs @@ -69,6 +69,11 @@ public void RequestAborted(string connectionId, string traceIdentifier) GeneralLog.RequestAbortedException(_generalLogger, connectionId, traceIdentifier); } + public void RequestBodyDrainBodyReaderInvalidState(string connectionId, string traceIdentifier, Exception ex) + { + GeneralLog.RequestBodyDrainBodyReaderInvalidState(_generalLogger, connectionId, traceIdentifier, ex); + } + private static partial class GeneralLog { [LoggerMessage(13, LogLevel.Error, @"Connection id ""{ConnectionId}"", Request id ""{TraceIdentifier}"": An unhandled exception was thrown by the application.", EventName = "ApplicationError")] @@ -107,6 +112,9 @@ private static partial class GeneralLog [LoggerMessage(66, LogLevel.Debug, @"Connection id ""{ConnectionId}"", Request id ""{TraceIdentifier}"": The request was aborted by the client.", EventName = "RequestAborted")] public static partial void RequestAbortedException(ILogger logger, string connectionId, string traceIdentifier); + [LoggerMessage(67, LogLevel.Information, @"Connection id ""{ConnectionId}"", Request id ""{TraceIdentifier}"": automatic draining of the request body failed because the body reader is in an invalid state.", EventName = "RequestBodyDrainBodyReaderInvalidState")] + public static partial void RequestBodyDrainBodyReaderInvalidState(ILogger logger, string connectionId, string traceIdentifier, Exception ex); + // IDs prior to 64 are reserved for back compat (the various KestrelTrace loggers used to share a single sequence) } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TransportConnectionManager.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TransportConnectionManager.cs index 3ff915a8909e..2c3ffbb7d8ce 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TransportConnectionManager.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TransportConnectionManager.cs @@ -5,6 +5,7 @@ using System.Collections.Concurrent; using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Connections.Features; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; @@ -77,6 +78,10 @@ public async Task AbortAllConnectionsAsync() { if (kvp.Value.TryGetConnection(out var connection)) { + KestrelMetrics.AddConnectionEndReason( + connection.TransportConnection.Features.Get(), + ConnectionEndReason.AppShutdown, overwrite: true); + connection.TransportConnection.Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedDuringServerShutdown)); abortTasks.Add(connection.ExecutionTask); } diff --git a/src/Servers/Kestrel/shared/test/TestServiceContext.cs b/src/Servers/Kestrel/shared/test/TestServiceContext.cs index b8681b784a52..85692f1fe341 100644 --- a/src/Servers/Kestrel/shared/test/TestServiceContext.cs +++ b/src/Servers/Kestrel/shared/test/TestServiceContext.cs @@ -64,8 +64,11 @@ private void Initialize(ILoggerFactory loggerFactory, KestrelTrace kestrelTrace, DateHeaderValueManager.OnHeartbeat(); Metrics = metrics; + ShutdownTimeout = TestConstants.DefaultTimeout; } + public TimeSpan ShutdownTimeout { get; set; } + public ILoggerFactory LoggerFactory { get; set; } public FakeTimeProvider FakeTimeProvider { get; set; } diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3ConnectionTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3ConnectionTests.cs index 6e3d2160d05b..5b3fe8d95f9f 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3ConnectionTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3ConnectionTests.cs @@ -191,7 +191,7 @@ public async Task GOAWAY_GracefulServerShutdown_SendsGoAway(int connectionReques Assert.Null(await Http3Api.MultiplexedConnectionContext.AcceptAsync().DefaultTimeout()); await Http3Api.WaitForConnectionStopAsync(expectedStreamId, false, expectedErrorCode: Http3ErrorCode.NoError); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.AppShutdown), Http3Api.ConnectionTags[KestrelMetrics.ErrorType]); + Assert.DoesNotContain(KestrelMetrics.ErrorType, Http3Api.ConnectionTags.Keys); } [Fact] @@ -221,7 +221,7 @@ public async Task GOAWAY_GracefulServerShutdownWithActiveRequest_SendsMultipleGo Http3Api.MultiplexedConnectionContext.Abort(); await Http3Api.WaitForConnectionStopAsync(4, false, expectedErrorCode: Http3ErrorCode.NoError); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.AppShutdown), Http3Api.ConnectionTags[KestrelMetrics.ErrorType]); + Assert.DoesNotContain(KestrelMetrics.ErrorType, Http3Api.ConnectionTags.Keys); } [Theory] diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/KestrelMetricsTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/KestrelMetricsTests.cs index 8909945ca615..d1700d835806 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/KestrelMetricsTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/KestrelMetricsTests.cs @@ -17,6 +17,10 @@ using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport; using Microsoft.AspNetCore.InternalTesting; using Microsoft.Extensions.Diagnostics.Metrics.Testing; +using System.Buffers; +using System.Text; +using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests; @@ -154,6 +158,157 @@ public async Task Http1Connection_BeginListeningAfterConnectionStarted() } } + [Fact] + public async Task Http1Connection_RequestEndsWithIncompleteReadAsync() + { + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + + var serviceContext = new TestServiceContext(LoggerFactory, metrics: new KestrelMetrics(testMeterFactory)); + + var sendString = "POST / HTTP/1.0\r\nContent-Length: 12\r\n\r\nHello World?"; + + await using var server = new TestServer(async context => + { + var result = await context.Request.BodyReader.ReadAsync(); + await context.Response.BodyWriter.WriteAsync(result.Buffer.ToArray()); + // No BodyReader.Advance. Connection will fail when attempting to complete body. + }, serviceContext); + + using (var connection = server.CreateConnection()) + { + await connection.Send(sendString); + + await connection.ReceiveEnd( + "HTTP/1.1 200 OK", + "Connection: close", + $"Date: {serviceContext.DateHeaderValue}", + "", + "Hello World?"); + + await connection.WaitForConnectionClose(); + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => + { + AssertDuration(m, "127.0.0.1", localPort: 0, "tcp", "ipv4", KestrelMetrics.Http11, error: KestrelMetrics.GetErrorType(ConnectionEndReason.AbortedByApp)); + }); + } + } + + [Fact] + public async Task Http1Connection_ServerShutdown_Graceful() + { + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + + var serviceContext = new TestServiceContext(LoggerFactory, metrics: new KestrelMetrics(testMeterFactory)) + { + ShutdownTimeout = TimeSpan.FromSeconds(60) + }; + + var sendString = "POST / HTTP/1.0\r\nContent-Length: 12\r\n\r\nHello World?"; + + var getNotificationFeatureTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var server = new TestServer(async c => + { + getNotificationFeatureTcs.TrySetResult(c.Features.Get()); + await EchoApp(c); + }, serviceContext); + using var connection = server.CreateConnection(); + + try + { + await connection.Send(sendString); + } + finally + { + Logger.LogInformation("Waiting for notification feature"); + var notificationFeature = await getNotificationFeatureTcs.Task.DefaultTimeout(); + + // Dispose while the connection is in-progress. + var shutdownTask = server.DisposeAsync(); + + var waitForConnectionCloseRequest = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + notificationFeature.ConnectionClosedRequested.Register(() => + { + Logger.LogInformation("ConnectionClosedRequested"); + waitForConnectionCloseRequest.TrySetResult(); + }); + + Logger.LogInformation("Waiting for connection close request."); + await waitForConnectionCloseRequest.Task.DefaultTimeout(); + + Logger.LogInformation("Receiving data and closing connection."); + await connection.ReceiveEnd( + "HTTP/1.1 200 OK", + "Connection: close", + $"Date: {serviceContext.DateHeaderValue}", + "", + "Hello World?"); + await connection.WaitForConnectionClose(); + connection.Dispose(); + + Logger.LogInformation("Finishing shutting down."); + await shutdownTask; + } + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => + { + AssertDuration(m, "127.0.0.1", localPort: 0, "tcp", "ipv4", KestrelMetrics.Http11); + }); + } + + [Fact] + public async Task Http1Connection_ServerShutdown_Abort() + { + ThrowOnUngracefulShutdown = false; + + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + + var serviceContext = new TestServiceContext(LoggerFactory, metrics: new KestrelMetrics(testMeterFactory)) + { + MemoryPoolFactory = PinnedBlockMemoryPoolFactory.CreatePinnedBlockMemoryPool, + ShutdownTimeout = TimeSpan.Zero + }; + + var sendString = "POST / HTTP/1.0\r\nContent-Length: 12\r\n\r\nHello World?"; + var connectionCloseTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var requestReceivedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var server = new TestServer(async c => + { + requestReceivedTcs.TrySetResult(); + await c.Response.BodyWriter.WriteAsync(Encoding.UTF8.GetBytes("Hello world")); + await c.Response.BodyWriter.FlushAsync(); + await connectionCloseTcs.Task; + Logger.LogInformation("Server request delegate finishing."); + }, serviceContext); + + using var connection = server.CreateConnection(); + connection.TransportConnection.ConnectionClosed.Register(() => + { + Logger.LogInformation("Connection closed raised."); + connectionCloseTcs.TrySetResult(); + }); + + try + { + await connection.Send(sendString); + await requestReceivedTcs.Task.DefaultTimeout(); + } + finally + { + // Dispose while the connection is in-progress. + Logger.LogInformation("Shutting down server."); + await server.DisposeAsync(); + } + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => + { + AssertDuration(m, "127.0.0.1", localPort: 0, "tcp", "ipv4", KestrelMetrics.Http11, error: KestrelMetrics.GetErrorType(ConnectionEndReason.AppShutdown)); + }); + } + [Fact] public async Task Http1Connection_IHttpConnectionTagsFeatureIgnoreFeatureSetOnTransport() { @@ -327,6 +482,143 @@ static async Task UpgradeApp(HttpContext context) } } + [Fact] + public async Task Http2Connection_ServerShutdown_Graceful() + { + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var getNotificationFeatureTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var server = new TestServer(async context => + { + getNotificationFeatureTcs.TrySetResult(context.Features.Get()); + await context.Response.BodyWriter.FlushAsync(); + await tcs.Task; + }, + new TestServiceContext(LoggerFactory, metrics: new KestrelMetrics(testMeterFactory)) + { + ShutdownTimeout = TimeSpan.FromSeconds(200) + }, + listenOptions => + { + listenOptions.Protocols = HttpProtocols.Http2; + }); + + HttpResponseMessage responseMessage = null; + Stream responseStream = null; + using var connection = server.CreateConnection(); + using var socketsHandler = new SocketsHttpHandler() + { + ConnectCallback = (_, _) => + { + return new ValueTask(connection.Stream); + } + }; + using var httpClient = new HttpClient(socketsHandler); + + try + { + + var httpRequestMessage = new HttpRequestMessage() + { + RequestUri = new Uri("http://localhost/"), + Version = new Version(2, 0), + VersionPolicy = HttpVersionPolicy.RequestVersionExact, + }; + + responseMessage = await httpClient.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseHeadersRead).DefaultTimeout(); + responseMessage.EnsureSuccessStatusCode(); + responseStream = await responseMessage.Content.ReadAsStreamAsync(); + } + finally + { + var notificationFeature = await getNotificationFeatureTcs.Task.DefaultTimeout(); + + var shutdownTask = server.DisposeAsync(); + + var waitForConnectionCloseRequest = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + notificationFeature.ConnectionClosedRequested.Register(() => + { + waitForConnectionCloseRequest.TrySetResult(); + }); + + await waitForConnectionCloseRequest.Task.DefaultTimeout(); + tcs.TrySetResult(); + + await responseStream.ReadUntilEndAsync().DefaultTimeout(); + responseMessage.Dispose(); + + connection.Dispose(); + + await shutdownTask; + } + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => AssertDuration(m, "127.0.0.1", localPort: 0, "tcp", "ipv4", KestrelMetrics.Http2)); + } + + [Fact] + public async Task Http2Connection_ServerShutdown_Abort() + { + ThrowOnUngracefulShutdown = false; + + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + + var serviceContext = new TestServiceContext(LoggerFactory, metrics: new KestrelMetrics(testMeterFactory)) + { + ShutdownTimeout = TimeSpan.Zero, + MemoryPoolFactory = PinnedBlockMemoryPoolFactory.CreatePinnedBlockMemoryPool + }; + + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var server = new TestServer(async context => + { + await context.Response.BodyWriter.FlushAsync(); + await tcs.Task; + }, + serviceContext, + listenOptions => + { + listenOptions.Protocols = HttpProtocols.Http2; + }); + + HttpResponseMessage responseMessage = null; + using var connection = server.CreateConnection(); + connection.TransportConnection.ConnectionClosed.Register(() => tcs.TrySetResult()); + + using var socketsHandler = new SocketsHttpHandler() + { + ConnectCallback = (_, _) => + { + return new ValueTask(connection.Stream); + } + }; + + using var httpClient = new HttpClient(socketsHandler); + + try + { + var httpRequestMessage = new HttpRequestMessage() + { + RequestUri = new Uri("http://localhost/"), + Version = new Version(2, 0), + VersionPolicy = HttpVersionPolicy.RequestVersionExact, + }; + + responseMessage = await httpClient.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseHeadersRead).DefaultTimeout(); + responseMessage.EnsureSuccessStatusCode(); + } + finally + { + var shutdownTask = server.DisposeAsync().DefaultTimeout(); + + await shutdownTask; + } + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => AssertDuration(m, "127.0.0.1", localPort: 0, "tcp", "ipv4", KestrelMetrics.Http2, error: KestrelMetrics.GetErrorType(ConnectionEndReason.AppShutdown))); + } + [ConditionalFact] [TlsAlpnSupported] [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10)] @@ -529,7 +821,7 @@ private static void AssertDuration(CollectedMeasurement measurement, str } else { - Assert.False(measurement.Tags.ContainsKey("server.port")); + Assert.DoesNotContain("server.port", measurement.Tags.Keys); } if (networkType is not null) { @@ -537,7 +829,7 @@ private static void AssertDuration(CollectedMeasurement measurement, str } else { - Assert.False(measurement.Tags.ContainsKey("network.type")); + Assert.DoesNotContain("network.type", measurement.Tags.Keys); } if (httpVersion is not null) { @@ -546,8 +838,8 @@ private static void AssertDuration(CollectedMeasurement measurement, str } else { - Assert.False(measurement.Tags.ContainsKey("network.protocol.name")); - Assert.False(measurement.Tags.ContainsKey("network.protocol.version")); + Assert.DoesNotContain("network.protocol.name", measurement.Tags.Keys); + Assert.DoesNotContain("network.protocol.version", measurement.Tags.Keys); } if (tlsProtocolVersion is not null) { @@ -555,7 +847,7 @@ private static void AssertDuration(CollectedMeasurement measurement, str } else { - Assert.False(measurement.Tags.ContainsKey("tls.protocol.version")); + Assert.DoesNotContain("tls.protocol.version", measurement.Tags.Keys); } if (error is not null) { @@ -563,7 +855,14 @@ private static void AssertDuration(CollectedMeasurement measurement, str } else { - Assert.False(measurement.Tags.ContainsKey("error.type")); + try + { + Assert.DoesNotContain("error.type", measurement.Tags.Keys); + } + catch (Exception ex) + { + throw new Exception($"Connection has unexpected error.type value: {measurement.Tags["error.type"]}", ex); + } } } @@ -578,7 +877,7 @@ private static void AssertCount(CollectedMeasurement measurement, long exp } else { - Assert.False(measurement.Tags.ContainsKey("server.port")); + Assert.DoesNotContain("server.port", measurement.Tags.Keys); } if (networkType is not null) { @@ -586,7 +885,7 @@ private static void AssertCount(CollectedMeasurement measurement, long exp } else { - Assert.False(measurement.Tags.ContainsKey("network.type")); + Assert.DoesNotContain("network.type", measurement.Tags.Keys); } } } diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs index a983dc03cc03..4464fecefa87 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs @@ -1051,7 +1051,7 @@ public async Task ContentLengthReadAsyncPipeReaderBufferRequestBody() httpContext.Request.BodyReader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End); readResult = await httpContext.Request.BodyReader.ReadAsync(); Assert.Equal(5, readResult.Buffer.Length); - + httpContext.Request.BodyReader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End); }, testContext)) { using (var connection = server.CreateConnection()) diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/TestServer.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/TestServer.cs index 9d0b543a7520..0ee3a41d48ec 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/TestServer.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/TestServer.cs @@ -78,7 +78,7 @@ public TestServer(RequestDelegate app, TestServiceContext context, Action { webHostBuilder - .UseSetting(WebHostDefaults.ShutdownTimeoutKey, TestConstants.DefaultTimeout.TotalSeconds.ToString(CultureInfo.InvariantCulture)) + .UseSetting(WebHostDefaults.ShutdownTimeoutKey, context.ShutdownTimeout.TotalSeconds.ToString(CultureInfo.InvariantCulture)) .Configure(app => { app.Run(_app); }); }) .ConfigureServices(services => diff --git a/src/Shared/Metrics/MetricsExtensions.cs b/src/Shared/Metrics/MetricsExtensions.cs index 456de672a49d..afe9f419a2c7 100644 --- a/src/Shared/Metrics/MetricsExtensions.cs +++ b/src/Shared/Metrics/MetricsExtensions.cs @@ -23,6 +23,43 @@ public static bool TryAddTag(this IConnectionMetricsTagsFeature feature, string return TryAddTagCore(name, value, tags); } + public static void SetTag(this IConnectionMetricsTagsFeature feature, string name, object? value) + { + var tags = feature.Tags; + + SetTagCore(name, value, tags); + } + + private static void SetTagCore(string name, object? value, ICollection> tags) + { + // Tags is internally represented as a List. + // Prefer looping through the list to avoid allocating an enumerator. + if (tags is List> list) + { + for (var i = 0; i < list.Count; i++) + { + if (list[i].Key == name) + { + list.RemoveAt(i); + break; + } + } + } + else + { + foreach (var tag in tags) + { + if (tag.Key == name) + { + tags.Remove(tag); + break; + } + } + } + + tags.Add(new KeyValuePair(name, value)); + } + private static bool TryAddTagCore(string name, object? value, ICollection> tags) { // Tags is internally represented as a List. diff --git a/src/Shared/ServerInfrastructure/Http2/ConnectionEndReason.cs b/src/Shared/ServerInfrastructure/Http2/ConnectionEndReason.cs index d15ced3d1130..27604423f24c 100644 --- a/src/Shared/ServerInfrastructure/Http2/ConnectionEndReason.cs +++ b/src/Shared/ServerInfrastructure/Http2/ConnectionEndReason.cs @@ -36,11 +36,13 @@ internal enum ConnectionEndReason OutputQueueSizeExceeded, ClosedCriticalStream, AbortedByApp, + BodyReaderInvalidState, ServerTimeout, StreamCreationError, IOError, ClientGoAway, AppShutdown, + GracefulAppShutdown, TransportCompleted, TlsHandshakeFailed } From c0042fd612f242269425efc53f81e444d02801d6 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Sat, 15 Jun 2024 12:05:17 +0800 Subject: [PATCH 23/29] WIP --- .../src/Internal/BaseHttpConnectionContext.cs | 5 +- .../Core/src/Internal/Http/Http1Connection.cs | 6 +- .../src/Internal/Http/Http1OutputProducer.cs | 9 +- .../src/Internal/Http2/Http2Connection.cs | 4 +- .../src/Internal/Http3/Http3Connection.cs | 7 +- .../Core/src/Internal/HttpConnection.cs | 11 ++ .../src/Internal/HttpConnectionContext.cs | 4 +- .../HttpMultiplexedConnectionContext.cs | 3 +- .../ConnectionMetricsContext.cs | 31 ++-- .../IConnectionMetricsContextFeature.cs | 2 +- .../Infrastructure/KestrelConnectionOfT.cs | 1 - .../Internal/Infrastructure/KestrelMetrics.cs | 36 ++-- .../TransportConnectionManager.cs | 4 +- .../HttpMultiplexedConnectionMiddleware.cs | 5 +- .../test/Http1/Http1ConnectionTestsBase.cs | 7 +- ...Http1HttpProtocolFeatureCollectionTests.cs | 13 +- .../test/Http1/Http1OutputProducerTests.cs | 18 +- .../Core/test/HttpResponseHeadersTests.cs | 13 +- .../Core/test/TestHelpers/TestInput.cs | 9 +- .../shared/test/Http3/Http3InMemory.cs | 7 +- .../TestConnectionMetricsContextFeature.cs | 11 ++ .../Kestrel/shared/test/TestContextFactory.cs | 22 ++- .../Http2/Http2ConnectionTests.cs | 166 +++++++++--------- .../Http2/Http2StreamTests.cs | 10 +- .../Http2/Http2TestBase.cs | 19 +- .../Http2/Http2TimeoutTests.cs | 28 +-- .../Http3/Http3StreamTests.cs | 2 +- .../Http3/Http3RequestTests.cs | 6 +- 28 files changed, 266 insertions(+), 193 deletions(-) create mode 100644 src/Servers/Kestrel/shared/test/TestConnectionMetricsContextFeature.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/BaseHttpConnectionContext.cs b/src/Servers/Kestrel/Core/src/Internal/BaseHttpConnectionContext.cs index 24ec4a560d72..5a99fb872410 100644 --- a/src/Servers/Kestrel/Core/src/Internal/BaseHttpConnectionContext.cs +++ b/src/Servers/Kestrel/Core/src/Internal/BaseHttpConnectionContext.cs @@ -20,7 +20,8 @@ internal class BaseHttpConnectionContext IFeatureCollection connectionFeatures, MemoryPool memoryPool, IPEndPoint? localEndPoint, - IPEndPoint? remoteEndPoint) + IPEndPoint? remoteEndPoint, + ConnectionMetricsContext metricsContext) { ConnectionId = connectionId; Protocols = protocols; @@ -31,6 +32,7 @@ internal class BaseHttpConnectionContext MemoryPool = memoryPool; LocalEndPoint = localEndPoint; RemoteEndPoint = remoteEndPoint; + MetricsContext = metricsContext; } public string ConnectionId { get; set; } @@ -42,6 +44,7 @@ internal class BaseHttpConnectionContext public MemoryPool MemoryPool { get; } public IPEndPoint? LocalEndPoint { get; } public IPEndPoint? RemoteEndPoint { get; } + public ConnectionMetricsContext MetricsContext { get; } public ITimeoutControl TimeoutControl { get; set; } = default!; // Always set by HttpConnection public ExecutionContext? InitialExecutionContext { get; set; } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs index 256b1f0c490a..c9bbc62a7ebf 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs @@ -62,7 +62,7 @@ public Http1Connection(HttpConnectionContext context) _context.ServiceContext.Log, _context.TimeoutControl, minResponseDataRateFeature: this, - ConnectionMetricsTagsFeature, + ConnectionMetricsContext, outputAborter: this); Input = _context.Transport.Input; @@ -70,7 +70,7 @@ public Http1Connection(HttpConnectionContext context) MemoryPool = _context.MemoryPool; } - private IConnectionMetricsTagsFeature? ConnectionMetricsTagsFeature => _context.ConnectionFeatures.Get(); + private ConnectionMetricsContext ConnectionMetricsContext => _context.MetricsContext; public PipeReader Input { get; } @@ -134,7 +134,7 @@ protected override void ApplicationAbort() /// public void StopProcessingNextRequest(ConnectionEndReason reason) { - KestrelMetrics.AddConnectionEndReason(ConnectionMetricsTagsFeature, reason); + KestrelMetrics.AddConnectionEndReason(ConnectionMetricsContext, reason); _keepAlive = false; Input.CancelPendingRead(); diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs index 7f635e40a154..47d74bdaa6c6 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs @@ -5,7 +5,6 @@ using System.Diagnostics; using System.IO.Pipelines; using Microsoft.AspNetCore.Connections; -using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; @@ -30,7 +29,7 @@ internal class Http1OutputProducer : IHttpOutputProducer, IDisposable private readonly MemoryPool _memoryPool; private readonly KestrelTrace _log; private readonly IHttpMinResponseDataRateFeature _minResponseDataRateFeature; - private readonly IConnectionMetricsTagsFeature? _metricsTagsFeature; + private readonly ConnectionMetricsContext _connectionMetricsContext; private readonly IHttpOutputAborter _outputAborter; private readonly TimingPipeFlusher _flusher; @@ -76,7 +75,7 @@ internal class Http1OutputProducer : IHttpOutputProducer, IDisposable KestrelTrace log, ITimeoutControl timeoutControl, IHttpMinResponseDataRateFeature minResponseDataRateFeature, - IConnectionMetricsTagsFeature? metricsTagsFeature, + ConnectionMetricsContext connectionMetricsContext, IHttpOutputAborter outputAborter) { // Allow appending more data to the PipeWriter when a flush is pending. @@ -86,7 +85,7 @@ internal class Http1OutputProducer : IHttpOutputProducer, IDisposable _memoryPool = memoryPool; _log = log; _minResponseDataRateFeature = minResponseDataRateFeature; - _metricsTagsFeature = metricsTagsFeature; + _connectionMetricsContext = connectionMetricsContext; _outputAborter = outputAborter; _flusher = new TimingPipeFlusher(timeoutControl, log); @@ -459,7 +458,7 @@ public void Abort(ConnectionAbortedException error, ConnectionEndReason reason) return; } - KestrelMetrics.AddConnectionEndReason(_metricsTagsFeature, reason); + KestrelMetrics.AddConnectionEndReason(_connectionMetricsContext, reason); _aborted = true; _connectionContext.Abort(error); diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs index ca717902a540..6f4975fa840f 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs @@ -141,7 +141,7 @@ public Http2Connection(HttpConnectionContext context) _context = context; _streamLifetimeHandler = this; - _metricsContext = context.ConnectionFeatures.GetRequiredFeature().MetricsContext; + _metricsContext = context.MetricsContext; _errorCodeFeature = context.ConnectionFeatures.GetRequiredFeature(); _metricsTagsFeature = context.ConnectionFeatures.Get(); @@ -224,7 +224,7 @@ private void SetConnectionErrorCode(ConnectionEndReason reason, Http2ErrorCode e Debug.Assert(_errorCodeFeature.Error == -1, "Error code feature should only be set once."); _errorCodeFeature.Error = (long)errorCode; - KestrelMetrics.AddConnectionEndReason(_metricsTagsFeature, reason); + KestrelMetrics.AddConnectionEndReason(_metricsContext, reason); } public void Abort(ConnectionAbortedException ex, ConnectionEndReason reason) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs index 4d16041fcfa1..41663f60aa9f 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs @@ -40,7 +40,6 @@ internal sealed class Http3Connection : IHttp3StreamLifetimeHandler, IRequestPro private readonly object _protocolSelectionLock = new(); private readonly StreamCloseAwaitable _streamCompletionAwaitable = new(); private readonly IProtocolErrorCodeFeature _errorCodeFeature; - private readonly IConnectionMetricsTagsFeature? _metricsTagsFeature; private readonly Dictionary? _webtransportSessions; private long _highestOpenedRequestStreamId = DefaultHighestOpenedRequestStreamId; @@ -57,10 +56,8 @@ public Http3Connection(HttpMultiplexedConnectionContext context) _multiplexedContext = (MultiplexedConnectionContext)context.ConnectionContext; _context = context; _streamLifetimeHandler = this; - MetricsContext = context.ConnectionFeatures.GetRequiredFeature().MetricsContext; - + MetricsContext = context.MetricsContext; _errorCodeFeature = context.ConnectionFeatures.GetRequiredFeature(); - _metricsTagsFeature = context.ConnectionFeatures.Get(); var httpLimits = context.ServiceContext.ServerOptions.Limits; @@ -187,7 +184,7 @@ public void Abort(ConnectionAbortedException ex, Http3ErrorCode errorCode, Conne if (!previousState) { _errorCodeFeature.Error = (long)errorCode; - KestrelMetrics.AddConnectionEndReason(_metricsTagsFeature, reason); + KestrelMetrics.AddConnectionEndReason(MetricsContext, reason); if (TryStopAcceptingStreams()) { diff --git a/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs b/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs index 96d5d39c92a8..d65c39876450 100644 --- a/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs @@ -45,8 +45,12 @@ public HttpConnection(BaseHttpConnectionContext context) public async Task ProcessRequestsAsync(IHttpApplication httpApplication) where TContext : notnull { + IConnectionMetricsTagsFeature? connectionMetricsTagsFeature = null; + try { + connectionMetricsTagsFeature = _context.ConnectionFeatures.Get(); + // Ensure TimeoutControl._lastTimestamp is initialized before anything that could set timeouts runs. _timeoutControl.Initialize(); @@ -113,6 +117,13 @@ public HttpConnection(BaseHttpConnectionContext context) { Log.LogCritical(0, ex, $"Unexpected exception in {nameof(HttpConnection)}.{nameof(ProcessRequestsAsync)}."); } + finally + { + if (_context.MetricsContext.ConnectionEndReason is { } connectionEndReason) + { + KestrelMetrics.AddConnectionEndReason(connectionMetricsTagsFeature, connectionEndReason); + } + } } private sealed class ProtocolErrorCodeFeature : IProtocolErrorCodeFeature diff --git a/src/Servers/Kestrel/Core/src/Internal/HttpConnectionContext.cs b/src/Servers/Kestrel/Core/src/Internal/HttpConnectionContext.cs index 92c6ad1a0583..98afcead381a 100644 --- a/src/Servers/Kestrel/Core/src/Internal/HttpConnectionContext.cs +++ b/src/Servers/Kestrel/Core/src/Internal/HttpConnectionContext.cs @@ -22,11 +22,9 @@ internal class HttpConnectionContext : BaseHttpConnectionContext MemoryPool memoryPool, IPEndPoint? localEndPoint, IPEndPoint? remoteEndPoint, - ConnectionMetricsContext metricsContext) : base(connectionId, protocols, altSvcHeader, connectionContext, serviceContext, connectionFeatures, memoryPool, localEndPoint, remoteEndPoint) + ConnectionMetricsContext metricsContext) : base(connectionId, protocols, altSvcHeader, connectionContext, serviceContext, connectionFeatures, memoryPool, localEndPoint, remoteEndPoint, metricsContext) { - MetricsContext = metricsContext; } public IDuplexPipe Transport { get; set; } = default!; - public ConnectionMetricsContext MetricsContext { get; } } diff --git a/src/Servers/Kestrel/Core/src/Internal/HttpMultiplexedConnectionContext.cs b/src/Servers/Kestrel/Core/src/Internal/HttpMultiplexedConnectionContext.cs index a579e0e8e3e1..9bb3df1850a6 100644 --- a/src/Servers/Kestrel/Core/src/Internal/HttpMultiplexedConnectionContext.cs +++ b/src/Servers/Kestrel/Core/src/Internal/HttpMultiplexedConnectionContext.cs @@ -20,7 +20,8 @@ internal sealed class HttpMultiplexedConnectionContext : BaseHttpConnectionConte IFeatureCollection connectionFeatures, MemoryPool memoryPool, IPEndPoint? localEndPoint, - IPEndPoint? remoteEndPoint) : base(connectionId, protocols, altSvcHeader, connectionContext, serviceContext, connectionFeatures, memoryPool, localEndPoint, remoteEndPoint) + IPEndPoint? remoteEndPoint, + ConnectionMetricsContext metricsContext) : base(connectionId, protocols, altSvcHeader, connectionContext, serviceContext, connectionFeatures, memoryPool, localEndPoint, remoteEndPoint, metricsContext) { } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionMetricsContext.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionMetricsContext.cs index f561bda2437e..e5f6e534ffc3 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionMetricsContext.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionMetricsContext.cs @@ -1,30 +1,19 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.AspNetCore.Connections; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; -internal readonly struct ConnectionMetricsContext +internal sealed class ConnectionMetricsContext { - public BaseConnectionContext ConnectionContext { get; } - public bool CurrentConnectionsCounterEnabled { get; } - public bool ConnectionDurationEnabled { get; } - public bool QueuedConnectionsCounterEnabled { get; } - public bool QueuedRequestsCounterEnabled { get; } - public bool CurrentUpgradedRequestsCounterEnabled { get; } - public bool CurrentTlsHandshakesCounterEnabled { get; } + public required BaseConnectionContext ConnectionContext { get; init; } + public bool CurrentConnectionsCounterEnabled { get; init; } + public bool ConnectionDurationEnabled { get; init; } + public bool QueuedConnectionsCounterEnabled { get; init; } + public bool QueuedRequestsCounterEnabled { get; init; } + public bool CurrentUpgradedRequestsCounterEnabled { get; init; } + public bool CurrentTlsHandshakesCounterEnabled { get; init; } - public ConnectionMetricsContext(BaseConnectionContext connectionContext, bool currentConnectionsCounterEnabled, - bool connectionDurationEnabled, bool queuedConnectionsCounterEnabled, bool queuedRequestsCounterEnabled, - bool currentUpgradedRequestsCounterEnabled, bool currentTlsHandshakesCounterEnabled) - { - ConnectionContext = connectionContext; - CurrentConnectionsCounterEnabled = currentConnectionsCounterEnabled; - ConnectionDurationEnabled = connectionDurationEnabled; - QueuedConnectionsCounterEnabled = queuedConnectionsCounterEnabled; - QueuedRequestsCounterEnabled = queuedRequestsCounterEnabled; - CurrentUpgradedRequestsCounterEnabled = currentUpgradedRequestsCounterEnabled; - CurrentTlsHandshakesCounterEnabled = currentTlsHandshakesCounterEnabled; - } + public ConnectionEndReason? ConnectionEndReason { get; set; } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/IConnectionMetricsContextFeature.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/IConnectionMetricsContextFeature.cs index a1266549de3c..f9dc25b5f193 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/IConnectionMetricsContextFeature.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/IConnectionMetricsContextFeature.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnectionOfT.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnectionOfT.cs index b7a353da5255..900219d4c7de 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnectionOfT.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelConnectionOfT.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Collections; using System.Diagnostics; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections.Features; diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs index 5f00bddbcf12..640996cb024e 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs @@ -366,9 +366,16 @@ private static void InitializeConnectionTags(ref TagList tags, in ConnectionMetr public ConnectionMetricsContext CreateContext(BaseConnectionContext connection) { // Cache the state at the start of the connection so we produce consistent start/stop events. - return new ConnectionMetricsContext(connection, - _activeConnectionsCounter.Enabled, _connectionDuration.Enabled, _queuedConnectionsCounter.Enabled, - _queuedRequestsCounter.Enabled, _currentUpgradedRequestsCounter.Enabled, _activeTlsHandshakesCounter.Enabled); + return new ConnectionMetricsContext + { + ConnectionContext = connection, + CurrentConnectionsCounterEnabled = _activeConnectionsCounter.Enabled, + ConnectionDurationEnabled = _connectionDuration.Enabled, + QueuedConnectionsCounterEnabled = _queuedConnectionsCounter.Enabled, + QueuedRequestsCounterEnabled = _queuedRequestsCounter.Enabled, + CurrentUpgradedRequestsCounterEnabled = _currentUpgradedRequestsCounter.Enabled, + CurrentTlsHandshakesCounterEnabled = _activeTlsHandshakesCounter.Enabled + }; } public static bool TryGetHandshakeProtocol(SslProtocols protocols, [NotNullWhen(true)] out string? name, [NotNullWhen(true)] out string? version) @@ -413,7 +420,7 @@ public static bool TryGetHandshakeProtocol(SslProtocols protocols, [NotNullWhen( return false; } - public static void AddConnectionEndReason(IConnectionMetricsTagsFeature? feature, ConnectionEndReason reason, bool overwrite = false) + public static void AddConnectionEndReason(IConnectionMetricsTagsFeature? feature, ConnectionEndReason reason) { Debug.Assert(reason != ConnectionEndReason.Unset); @@ -421,13 +428,22 @@ public static void AddConnectionEndReason(IConnectionMetricsTagsFeature? feature { if (TryGetErrorType(reason, out var errorTypeValue)) { - if (overwrite) - { - feature.SetTag(ErrorType, errorTypeValue); - } - else + feature.TryAddTag(ErrorType, errorTypeValue); + } + } + } + + public static void AddConnectionEndReason(ConnectionMetricsContext? context, ConnectionEndReason reason, bool overwrite = false) + { + Debug.Assert(reason != ConnectionEndReason.Unset); + + if (context != null) + { + if (TryGetErrorType(reason, out _)) + { + if (context.ConnectionEndReason == null || overwrite) { - feature.TryAddTag(ErrorType, errorTypeValue); + context.ConnectionEndReason = reason; } } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TransportConnectionManager.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TransportConnectionManager.cs index 2c3ffbb7d8ce..69f3196c45f0 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TransportConnectionManager.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TransportConnectionManager.cs @@ -5,7 +5,6 @@ using System.Collections.Concurrent; using Microsoft.AspNetCore.Connections; -using Microsoft.AspNetCore.Connections.Features; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; @@ -78,8 +77,9 @@ public async Task AbortAllConnectionsAsync() { if (kvp.Value.TryGetConnection(out var connection)) { + // Connection didn't shutdown in allowed time. Force close the connection and set the end reason. KestrelMetrics.AddConnectionEndReason( - connection.TransportConnection.Features.Get(), + connection.TransportConnection.Features.Get()?.MetricsContext, ConnectionEndReason.AppShutdown, overwrite: true); connection.TransportConnection.Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedDuringServerShutdown)); diff --git a/src/Servers/Kestrel/Core/src/Middleware/HttpMultiplexedConnectionMiddleware.cs b/src/Servers/Kestrel/Core/src/Middleware/HttpMultiplexedConnectionMiddleware.cs index 28649eeb95af..e365c99a9398 100644 --- a/src/Servers/Kestrel/Core/src/Middleware/HttpMultiplexedConnectionMiddleware.cs +++ b/src/Servers/Kestrel/Core/src/Middleware/HttpMultiplexedConnectionMiddleware.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal; @@ -29,6 +30,7 @@ public Task OnConnectionAsync(MultiplexedConnectionContext connectionContext) var memoryPoolFeature = connectionContext.Features.Get(); var localEndPoint = connectionContext.LocalEndPoint as IPEndPoint; var altSvcHeader = _addAltSvcHeader && localEndPoint != null ? HttpUtilities.GetEndpointAltSvc(localEndPoint, _protocols) : null; + var metricContext = connectionContext.Features.GetRequiredFeature().MetricsContext; var httpConnectionContext = new HttpMultiplexedConnectionContext( connectionContext.ConnectionId, @@ -39,7 +41,8 @@ public Task OnConnectionAsync(MultiplexedConnectionContext connectionContext) connectionContext.Features, memoryPoolFeature?.MemoryPool ?? System.Buffers.MemoryPool.Shared, localEndPoint, - connectionContext.RemoteEndPoint as IPEndPoint); + connectionContext.RemoteEndPoint as IPEndPoint, + metricContext); if (connectionContext.Features.Get() is { } metricsTags) { diff --git a/src/Servers/Kestrel/Core/test/Http1/Http1ConnectionTestsBase.cs b/src/Servers/Kestrel/Core/test/Http1/Http1ConnectionTestsBase.cs index ba86cff87fb6..5134fd9d0ddd 100644 --- a/src/Servers/Kestrel/Core/test/Http1/Http1ConnectionTestsBase.cs +++ b/src/Servers/Kestrel/Core/test/Http1/Http1ConnectionTestsBase.cs @@ -38,8 +38,12 @@ public override void Initialize(TestContext context, MethodInfo methodInfo, obje _transport = pair.Transport; _application = pair.Application; + var connectionContext = Mock.Of(); + var metricsContext = TestContextFactory.CreateMetricsContext(connectionContext); + var connectionFeatures = new FeatureCollection(); connectionFeatures.Set(Mock.Of()); + connectionFeatures.Set(new TestConnectionMetricsContextFeature { MetricsContext = metricsContext }); _serviceContext = new TestServiceContext(LoggerFactory) { @@ -53,7 +57,8 @@ public override void Initialize(TestContext context, MethodInfo methodInfo, obje transport: pair.Transport, timeoutControl: _timeoutControl.Object, memoryPool: _pipelineFactory, - connectionFeatures: connectionFeatures); + connectionFeatures: connectionFeatures, + metricsContext: metricsContext); _http1Connection = new TestHttp1Connection(_http1ConnectionContext); } diff --git a/src/Servers/Kestrel/Core/test/Http1/Http1HttpProtocolFeatureCollectionTests.cs b/src/Servers/Kestrel/Core/test/Http1/Http1HttpProtocolFeatureCollectionTests.cs index 33200c697196..af82ee077f53 100644 --- a/src/Servers/Kestrel/Core/test/Http1/Http1HttpProtocolFeatureCollectionTests.cs +++ b/src/Servers/Kestrel/Core/test/Http1/Http1HttpProtocolFeatureCollectionTests.cs @@ -26,12 +26,19 @@ public class Http1HttpProtocolFeatureCollectionTests public Http1HttpProtocolFeatureCollectionTests() { + var connectionContext = Mock.Of(); + var metricsContext = TestContextFactory.CreateMetricsContext(connectionContext); + + var connectionFeatures = new FeatureCollection(); + connectionFeatures.Set(new TestConnectionMetricsContextFeature { MetricsContext = metricsContext }); + var context = TestContextFactory.CreateHttpConnectionContext( - connectionContext: Mock.Of(), + connectionContext: connectionContext, serviceContext: new TestServiceContext(), transport: Mock.Of(), - connectionFeatures: new FeatureCollection(), - timeoutControl: Mock.Of()); + connectionFeatures: connectionFeatures, + timeoutControl: Mock.Of(), + metricsContext: metricsContext); _httpConnectionContext = context; _http1Connection = new TestHttp1Connection(context); diff --git a/src/Servers/Kestrel/Core/test/Http1/Http1OutputProducerTests.cs b/src/Servers/Kestrel/Core/test/Http1/Http1OutputProducerTests.cs index 28419ae1c3f5..b927baefa60f 100644 --- a/src/Servers/Kestrel/Core/test/Http1/Http1OutputProducerTests.cs +++ b/src/Servers/Kestrel/Core/test/Http1/Http1OutputProducerTests.cs @@ -117,10 +117,10 @@ public async Task FlushAsync_OnSocketWithCanceledPendingFlush_ReturnsResultWithI [Fact] public void AbortsTransportEvenAfterDispose() { - var metricsTagsFeature = new TestConnectionMetricsTagsFeature(); var mockConnectionContext = new Mock(); + var metricsContext = new ConnectionMetricsContext { ConnectionContext = mockConnectionContext.Object }; - var outputProducer = CreateOutputProducer(connectionContext: mockConnectionContext.Object, metricsTagsFeature: metricsTagsFeature); + var outputProducer = CreateOutputProducer(connectionContext: mockConnectionContext.Object, metricsContext: metricsContext); outputProducer.Dispose(); @@ -134,7 +134,7 @@ public void AbortsTransportEvenAfterDispose() mockConnectionContext.Verify(f => f.Abort(null), Times.Once()); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.AbortedByApp), metricsTagsFeature.Tags.Single(t => t.Key == KestrelMetrics.ErrorType).Value); + Assert.Equal(ConnectionEndReason.AbortedByApp, metricsContext.ConnectionEndReason); } [Fact] @@ -223,7 +223,7 @@ public void ReusesFakeMemory() private TestHttpOutputProducer CreateOutputProducer( PipeOptions pipeOptions = null, ConnectionContext connectionContext = null, - IConnectionMetricsTagsFeature metricsTagsFeature = null) + ConnectionMetricsContext metricsContext = null) { pipeOptions = pipeOptions ?? new PipeOptions(); connectionContext = connectionContext ?? Mock.Of(); @@ -238,21 +238,21 @@ public void ReusesFakeMemory() serviceContext.Log, Mock.Of(), Mock.Of(), - metricsTagsFeature ?? new TestConnectionMetricsTagsFeature(), + metricsContext ?? new ConnectionMetricsContext { ConnectionContext = connectionContext }, Mock.Of()); return socketOutput; } - private class TestConnectionMetricsTagsFeature : IConnectionMetricsTagsFeature + private sealed class TestConnectionMetricsContextFeature : IConnectionMetricsContextFeature { - public ICollection> Tags { get; } = new List>(); + public ConnectionMetricsContext MetricsContext { get; } } private class TestHttpOutputProducer : Http1OutputProducer { - public TestHttpOutputProducer(Pipe pipe, string connectionId, ConnectionContext connectionContext, MemoryPool memoryPool, KestrelTrace log, ITimeoutControl timeoutControl, IHttpMinResponseDataRateFeature minResponseDataRateFeature, IConnectionMetricsTagsFeature metricsTagsFeature, IHttpOutputAborter outputAborter) - : base(pipe.Writer, connectionId, connectionContext, memoryPool, log, timeoutControl, minResponseDataRateFeature, metricsTagsFeature, outputAborter) + public TestHttpOutputProducer(Pipe pipe, string connectionId, ConnectionContext connectionContext, MemoryPool memoryPool, KestrelTrace log, ITimeoutControl timeoutControl, IHttpMinResponseDataRateFeature minResponseDataRateFeature, ConnectionMetricsContext metricsContext, IHttpOutputAborter outputAborter) + : base(pipe.Writer, connectionId, connectionContext, memoryPool, log, timeoutControl, minResponseDataRateFeature, metricsContext, outputAborter) { Pipe = pipe; } diff --git a/src/Servers/Kestrel/Core/test/HttpResponseHeadersTests.cs b/src/Servers/Kestrel/Core/test/HttpResponseHeadersTests.cs index 5a85823a88cf..4862831210c7 100644 --- a/src/Servers/Kestrel/Core/test/HttpResponseHeadersTests.cs +++ b/src/Servers/Kestrel/Core/test/HttpResponseHeadersTests.cs @@ -16,6 +16,7 @@ using Microsoft.Extensions.Primitives; using Moq; using Xunit; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests; @@ -28,12 +29,20 @@ public void InitialDictionaryIsEmpty() { var options = new PipeOptions(memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false); var pair = DuplexPipe.CreateConnectionPair(options, options); + + var connectionContext = Mock.Of(); + var metricsContext = TestContextFactory.CreateMetricsContext(connectionContext); + + var connectionFeatures = new FeatureCollection(); + connectionFeatures.Set(new TestConnectionMetricsContextFeature { MetricsContext = metricsContext }); + var http1ConnectionContext = TestContextFactory.CreateHttpConnectionContext( serviceContext: new TestServiceContext(), - connectionContext: Mock.Of(), + connectionContext: connectionContext, transport: pair.Transport, memoryPool: memoryPool, - connectionFeatures: new FeatureCollection()); + connectionFeatures: connectionFeatures, + metricsContext: metricsContext); var http1Connection = new Http1Connection(http1ConnectionContext); diff --git a/src/Servers/Kestrel/Core/test/TestHelpers/TestInput.cs b/src/Servers/Kestrel/Core/test/TestHelpers/TestInput.cs index 1bb8795d1395..914fa9bb5f58 100644 --- a/src/Servers/Kestrel/Core/test/TestHelpers/TestInput.cs +++ b/src/Servers/Kestrel/Core/test/TestHelpers/TestInput.cs @@ -30,19 +30,24 @@ public TestInput(KestrelTrace log = null, ITimeoutControl timeoutControl = null) Transport = pair.Transport; Application = pair.Application; + var connectionContext = Mock.Of(); + var metricsContext = TestContextFactory.CreateMetricsContext(connectionContext); + var connectionFeatures = new FeatureCollection(); connectionFeatures.Set(Mock.Of()); + connectionFeatures.Set(new TestConnectionMetricsContextFeature { MetricsContext = metricsContext }); Http1ConnectionContext = TestContextFactory.CreateHttpConnectionContext( serviceContext: new TestServiceContext { Log = log ?? new KestrelTrace(NullLoggerFactory.Instance) }, - connectionContext: Mock.Of(), + connectionContext: connectionContext, transport: Transport, timeoutControl: timeoutControl ?? Mock.Of(), memoryPool: _memoryPool, - connectionFeatures: connectionFeatures); + connectionFeatures: connectionFeatures, + metricsContext: metricsContext); Http1Connection = new Http1Connection(Http1ConnectionContext); Http1Connection.HttpResponseControl = Mock.Of(); diff --git a/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs b/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs index 8aae0764d2e7..e387534249d1 100644 --- a/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs +++ b/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs @@ -230,6 +230,8 @@ public async Task InitializeConnectionAsync(RequestDelegate application) ConnectionId = "TEST" }; + var metricsContext = MultiplexedConnectionContext.Features.GetRequiredFeature().MetricsContext; + var httpConnectionContext = new HttpMultiplexedConnectionContext( connectionId: MultiplexedConnectionContext.ConnectionId, HttpProtocols.Http3, @@ -239,7 +241,8 @@ public async Task InitializeConnectionAsync(RequestDelegate application) serviceContext: _serviceContext, memoryPool: _memoryPool, localEndPoint: null, - remoteEndPoint: null); + remoteEndPoint: null, + metricsContext: metricsContext); httpConnectionContext.TimeoutControl = _timeoutControl; _httpConnection = new HttpConnection(httpConnectionContext); @@ -1010,6 +1013,8 @@ public TestMultiplexedConnectionContext(Http3InMemory testBase) Features.Set(this); Features.Set(this); ConnectionClosedRequested = ConnectionClosingCts.Token; + + MetricsContext = TestContextFactory.CreateMetricsContext(this); } public override string ConnectionId { get; set; } diff --git a/src/Servers/Kestrel/shared/test/TestConnectionMetricsContextFeature.cs b/src/Servers/Kestrel/shared/test/TestConnectionMetricsContextFeature.cs new file mode 100644 index 000000000000..1a328bd2d85c --- /dev/null +++ b/src/Servers/Kestrel/shared/test/TestConnectionMetricsContextFeature.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; + +namespace Microsoft.AspNetCore.InternalTesting; + +internal sealed class TestConnectionMetricsContextFeature : IConnectionMetricsContextFeature +{ + public ConnectionMetricsContext MetricsContext { get; init; } +} diff --git a/src/Servers/Kestrel/shared/test/TestContextFactory.cs b/src/Servers/Kestrel/shared/test/TestContextFactory.cs index 1bb026b776af..3baf69b6348a 100644 --- a/src/Servers/Kestrel/shared/test/TestContextFactory.cs +++ b/src/Servers/Kestrel/shared/test/TestContextFactory.cs @@ -53,7 +53,8 @@ internal static class TestContextFactory MemoryPool memoryPool = null, IPEndPoint localEndPoint = null, IPEndPoint remoteEndPoint = null, - ITimeoutControl timeoutControl = null) + ITimeoutControl timeoutControl = null, + ConnectionMetricsContext metricsContext = null) { var context = new HttpConnectionContext( "TestConnectionId", @@ -65,7 +66,7 @@ internal static class TestContextFactory memoryPool ?? MemoryPool.Shared, localEndPoint, remoteEndPoint, - CreateMetricsContext(connectionContext)); + metricsContext ?? CreateMetricsContext(connectionContext)); context.TimeoutControl = timeoutControl; context.Transport = transport; @@ -79,18 +80,23 @@ internal static class TestContextFactory MemoryPool memoryPool = null, IPEndPoint localEndPoint = null, IPEndPoint remoteEndPoint = null, - ITimeoutControl timeoutControl = null) + ITimeoutControl timeoutControl = null, + ConnectionMetricsContext metricsContext = null) { + connectionContext ??= new TestMultiplexedConnectionContext { ConnectionId = "TEST" }; + metricsContext ??= new ConnectionMetricsContext { ConnectionContext = connectionContext }; + var http3ConnectionContext = new HttpMultiplexedConnectionContext( "TEST", HttpProtocols.Http3, altSvcHeader: null, - connectionContext ?? new TestMultiplexedConnectionContext { ConnectionId = "TEST" }, + connectionContext, serviceContext ?? CreateServiceContext(new KestrelServerOptions()), connectionFeatures ?? new FeatureCollection(), memoryPool ?? PinnedBlockMemoryPoolFactory.Create(), localEndPoint, - remoteEndPoint) + remoteEndPoint, + metricsContext) { TimeoutControl = timeoutControl }; @@ -214,11 +220,9 @@ internal static class TestContextFactory }; } - public static ConnectionMetricsContext CreateMetricsContext(ConnectionContext connectionContext) + public static ConnectionMetricsContext CreateMetricsContext(BaseConnectionContext connectionContext) { - return new ConnectionMetricsContext(connectionContext, - currentConnectionsCounterEnabled: false, connectionDurationEnabled: false, queuedConnectionsCounterEnabled: false, - queuedRequestsCounterEnabled: false, currentUpgradedRequestsCounterEnabled: false, currentTlsHandshakesCounterEnabled: false); + return new ConnectionMetricsContext { ConnectionContext = connectionContext }; } private class TestHttp2StreamLifetimeHandler : IHttp2StreamLifetimeHandler diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs index dc07a107e9c4..134220b65870 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs @@ -713,7 +713,7 @@ public async Task Frame_Received_OverMaxSize_FrameError() expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorFrameOverLimit(length, Http2PeerSettings.MinAllowedMaxFrameSize)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MaxFrameLengthExceeded), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.MaxFrameLengthExceeded); } [Fact] @@ -1477,7 +1477,7 @@ public async Task DATA_Received_StreamIdZero_ConnectionError() expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdZero(Http2FrameType.DATA)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId); } [Fact] @@ -1492,7 +1492,7 @@ public async Task DATA_Received_StreamIdEven_ConnectionError() expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdEven(Http2FrameType.DATA, streamId: 2)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId); } [Fact] @@ -1508,7 +1508,7 @@ public async Task DATA_Received_PaddingEqualToFramePayloadLength_ConnectionError expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorPaddingTooLong(Http2FrameType.DATA)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidDataPadding), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidDataPadding); } [Fact] @@ -1524,7 +1524,7 @@ public async Task DATA_Received_PaddingGreaterThanFramePayloadLength_ConnectionE expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorPaddingTooLong(Http2FrameType.DATA)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidDataPadding), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidDataPadding); } [Fact] @@ -1540,7 +1540,7 @@ public async Task DATA_Received_FrameLengthZeroPaddingZero_ConnectionError() expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.DATA, expectedLength: 1)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidFrameLength); } [Fact] @@ -1556,7 +1556,7 @@ public async Task DATA_Received_InterleavedWithHeaders_ConnectionError() expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.DATA, streamId: 1, headersStreamId: 1)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.UnexpectedFrame); } [Fact] @@ -1571,7 +1571,7 @@ public async Task DATA_Received_StreamIdle_ConnectionError() expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdle(Http2FrameType.DATA, streamId: 1)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId); } [Fact] @@ -1589,7 +1589,7 @@ public async Task DATA_Received_StreamHalfClosedRemote_ConnectionError() expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.DATA, streamId: 1)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.FrameAfterStreamClose), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.FrameAfterStreamClose); } [Fact] @@ -1615,7 +1615,7 @@ public async Task DATA_Received_StreamClosed_ConnectionError() CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.DATA, streamId: 1), CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.DATA, streamId: 1) }); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.UnknownStream), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.UnknownStream); } [Fact] @@ -1700,7 +1700,7 @@ public void MinimumMaxTrackedStreams() // Kestrel always tracks at least 100 streams Assert.Equal(100u, _connection.MaxTrackedStreams); _connection.Abort(new ConnectionAbortedException(), ConnectionEndReason.AbortedByApp); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.AbortedByApp), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.AbortedByApp); } [Fact] @@ -1770,7 +1770,7 @@ public async Task AbortConnectionAfterTooManyEnhanceYourCalms() expectedErrorCode: Http2ErrorCode.ENHANCE_YOUR_CALM, expectedErrorMessage: CoreStrings.Http2ConnectionFaulted); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.StreamResetLimitExceeded), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.StreamResetLimitExceeded); } private async Task RequestUntilEnhanceYourCalm(int maxStreamsPerConnection, int sentStreams) @@ -1830,7 +1830,7 @@ public async Task DATA_Received_StreamClosedImplicitly_ConnectionError() expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.DATA, streamId: 1)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.UnknownStream), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.UnknownStream); } [Fact] @@ -1854,7 +1854,7 @@ public async Task DATA_Received_NoStreamWindowSpace_ConnectionError() expectedErrorCode: Http2ErrorCode.FLOW_CONTROL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorFlowControlWindowExceeded); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.FlowControlWindowExceeded), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.FlowControlWindowExceeded); } [Fact] @@ -1885,7 +1885,7 @@ public async Task DATA_Received_NoConnectionWindowSpace_ConnectionError() expectedErrorCode: Http2ErrorCode.FLOW_CONTROL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorFlowControlWindowExceeded); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.FlowControlWindowExceeded), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.FlowControlWindowExceeded); } [Fact] @@ -2519,7 +2519,7 @@ public async Task HEADERS_Received_StreamIdZero_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdZero(Http2FrameType.HEADERS)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId); } [Fact] @@ -2535,7 +2535,7 @@ public async Task HEADERS_Received_StreamIdEven_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdEven(Http2FrameType.HEADERS, streamId: 2)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId); } [Fact] @@ -2563,7 +2563,7 @@ public async Task HEADERS_Received_StreamClosed_ConnectionError() CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.HEADERS, streamId: 1) }); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId); } [Fact] @@ -2582,7 +2582,7 @@ public async Task HEADERS_Received_StreamHalfClosedRemote_ConnectionError() expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.HEADERS, streamId: 1)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.FrameAfterStreamClose), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.FrameAfterStreamClose); } [Fact] @@ -2606,7 +2606,7 @@ public async Task HEADERS_Received_StreamClosedImplicitly_ConnectionError() expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId); } [Theory] @@ -2625,7 +2625,7 @@ public async Task HEADERS_Received_PaddingEqualToFramePayloadLength_ConnectionEr expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorPaddingTooLong(Http2FrameType.HEADERS)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidDataPadding), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidDataPadding); } [Fact] @@ -2641,7 +2641,7 @@ public async Task HEADERS_Received_PaddingFieldMissing_ConnectionError() expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.HEADERS, expectedLength: 1)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidFrameLength); } [Theory] @@ -2659,7 +2659,7 @@ public async Task HEADERS_Received_PaddingGreaterThanFramePayloadLength_Connecti expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorPaddingTooLong(Http2FrameType.HEADERS)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidDataPadding), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidDataPadding); } [Fact] @@ -2676,7 +2676,7 @@ public async Task HEADERS_Received_InterleavedWithHeaders_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.HEADERS, streamId: 3, headersStreamId: 1)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.UnexpectedFrame); } [Fact] @@ -2692,7 +2692,7 @@ public async Task HEADERS_Received_WithPriority_StreamDependencyOnSelf_Connectio expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamSelfDependency(Http2FrameType.HEADERS, streamId: 1)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.StreamSelfDependency), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.StreamSelfDependency); } [Fact] @@ -2708,7 +2708,7 @@ public async Task HEADERS_Received_IncompleteHeaderBlock_ConnectionError() expectedErrorCode: Http2ErrorCode.COMPRESSION_ERROR, expectedErrorMessage: SR.net_http_hpack_incomplete_header_block); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.ErrorReadingHeaders), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.ErrorReadingHeaders); } [Fact] @@ -2738,7 +2738,7 @@ public async Task HEADERS_Received_IntegerOverLimit_ConnectionError() expectedErrorCode: Http2ErrorCode.COMPRESSION_ERROR, expectedErrorMessage: SR.net_http_hpack_bad_integer); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.ErrorReadingHeaders), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.ErrorReadingHeaders); } [Theory] @@ -2756,7 +2756,7 @@ public async Task HEADERS_Received_WithTrailers_ContainsIllegalTrailer_Connectio expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: expectedErrorMessage); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidRequestHeaders), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidRequestHeaders); } [Theory] @@ -2776,7 +2776,7 @@ public async Task HEADERS_Received_WithTrailers_EndStreamNotSet_ConnectionError( expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorHeadersWithTrailersNoEndStream); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MissingStreamEnd), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.MissingStreamEnd); } [Theory] @@ -2792,7 +2792,7 @@ public async Task HEADERS_Received_HeaderNameContainsUpperCaseCharacter_Connecti expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.HttpErrorHeaderNameUppercase); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidRequestHeaders), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidRequestHeaders); } [Fact] @@ -2865,7 +2865,7 @@ private async Task HEADERS_Received_InvalidHeaderFields_ConnectionError(IEnumera expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: expectedErrorMessage); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidRequestHeaders), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidRequestHeaders); } [Theory] @@ -2888,7 +2888,7 @@ public async Task HEADERS_Received_HeaderBlockDoesNotContainMandatoryPseudoHeade expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId); } [Fact] @@ -3067,7 +3067,7 @@ public async Task PRIORITY_Received_StreamIdZero_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdZero(Http2FrameType.PRIORITY)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId); } [Fact] @@ -3083,7 +3083,7 @@ public async Task PRIORITY_Received_StreamIdEven_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdEven(Http2FrameType.PRIORITY, streamId: 2)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId); } [Theory] @@ -3101,7 +3101,7 @@ public async Task PRIORITY_Received_LengthNotFive_ConnectionError(int length) expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.PRIORITY, expectedLength: 5)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidFrameLength); } [Fact] @@ -3118,7 +3118,7 @@ public async Task PRIORITY_Received_InterleavedWithHeaders_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.PRIORITY, streamId: 1, headersStreamId: 1)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.UnexpectedFrame); } [Fact] @@ -3134,7 +3134,7 @@ public async Task PRIORITY_Received_StreamDependencyOnSelf_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamSelfDependency(Http2FrameType.PRIORITY, 1)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.StreamSelfDependency), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.StreamSelfDependency); } [Fact] @@ -3392,7 +3392,7 @@ public async Task RST_STREAM_Received_StreamIdZero_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdZero(Http2FrameType.RST_STREAM)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId); } [Fact] @@ -3408,7 +3408,7 @@ public async Task RST_STREAM_Received_StreamIdEven_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdEven(Http2FrameType.RST_STREAM, streamId: 2)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId); } [Fact] @@ -3424,7 +3424,7 @@ public async Task RST_STREAM_Received_StreamIdle_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdle(Http2FrameType.RST_STREAM, streamId: 1)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId); } [Theory] @@ -3445,7 +3445,7 @@ public async Task RST_STREAM_Received_LengthNotFour_ConnectionError(int length) expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.RST_STREAM, expectedLength: 4)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidFrameLength); } [Fact] @@ -3462,7 +3462,7 @@ public async Task RST_STREAM_Received_InterleavedWithHeaders_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.RST_STREAM, streamId: 1, headersStreamId: 1)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.UnexpectedFrame); } // Compare to h2spec http2/5.1/8 @@ -3489,7 +3489,7 @@ public async Task RST_STREAM_IncompleteRequest_AdditionalDataFrames_ConnectionAb await WaitForConnectionErrorAsync(ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, Http2ErrorCode.STREAM_CLOSED, CoreStrings.FormatHttp2ErrorStreamAborted(Http2FrameType.DATA, 1)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.FrameAfterStreamClose), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.FrameAfterStreamClose); } [Fact] @@ -3515,7 +3515,7 @@ public async Task RST_STREAM_IncompleteRequest_AdditionalTrailerFrames_Connectio await WaitForConnectionErrorAsync(ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, Http2ErrorCode.STREAM_CLOSED, CoreStrings.FormatHttp2ErrorStreamAborted(Http2FrameType.HEADERS, 1)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.FrameAfterStreamClose), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.FrameAfterStreamClose); } [Fact] @@ -3713,7 +3713,7 @@ public async Task SETTINGS_Received_StreamIdNotZero_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdNotZero(Http2FrameType.SETTINGS)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId); } [Theory] @@ -3741,7 +3741,7 @@ public async Task SETTINGS_Received_InvalidParameterValue_ConnectionError(int in expectedErrorCode: expectedErrorCode, expectedErrorMessage: CoreStrings.FormatHttp2ErrorSettingsParameterOutOfRange(parameter)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidSettings), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidSettings); } [Fact] @@ -3758,7 +3758,7 @@ public async Task SETTINGS_Received_InterleavedWithHeaders_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.SETTINGS, streamId: 0, headersStreamId: 1)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.UnexpectedFrame); } [Theory] @@ -3776,7 +3776,7 @@ public async Task SETTINGS_Received_WithACK_LengthNotZero_ConnectionError(int le expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorSettingsAckLengthNotZero); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidFrameLength); } [Theory] @@ -3797,7 +3797,7 @@ public async Task SETTINGS_Received_LengthNotMultipleOfSix_ConnectionError(int l expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorSettingsLengthNotMultipleOfSix); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidFrameLength); } [Fact] @@ -3823,7 +3823,7 @@ public async Task SETTINGS_Received_WithInitialWindowSizePushingStreamWindowOver expectedErrorCode: Http2ErrorCode.FLOW_CONTROL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorInitialWindowSizeInvalid); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidSettings), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidSettings); } [Fact] @@ -4006,7 +4006,7 @@ public async Task PUSH_PROMISE_Received_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorPushPromiseReceived); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.UnsupportedFrame), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.UnsupportedFrame); } [Fact] @@ -4047,7 +4047,7 @@ public async Task PING_Received_InterleavedWithHeaders_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.PING, streamId: 0, headersStreamId: 1)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.UnexpectedFrame); } [Fact] @@ -4063,7 +4063,7 @@ public async Task PING_Received_StreamIdNotZero_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdNotZero(Http2FrameType.PING)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId); } [Theory] @@ -4083,7 +4083,7 @@ public async Task PING_Received_LengthNotEight_ConnectionError(int length) expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.PING, expectedLength: 8)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidFrameLength); } [Fact] @@ -4357,7 +4357,7 @@ public async Task GOAWAY_Received_StreamIdNotZero_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdNotZero(Http2FrameType.GOAWAY)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId); } [Fact] @@ -4374,7 +4374,7 @@ public async Task GOAWAY_Received_InterleavedWithHeaders_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.GOAWAY, streamId: 0, headersStreamId: 1)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.UnexpectedFrame); } [Fact] @@ -4390,7 +4390,7 @@ public async Task WINDOW_UPDATE_Received_StreamIdEven_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdEven(Http2FrameType.WINDOW_UPDATE, streamId: 2)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId); } [Fact] @@ -4407,7 +4407,7 @@ public async Task WINDOW_UPDATE_Received_InterleavedWithHeaders_ConnectionError( expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.WINDOW_UPDATE, streamId: 1, headersStreamId: 1)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.UnexpectedFrame); } [Theory] @@ -4427,7 +4427,7 @@ public async Task WINDOW_UPDATE_Received_LengthNotFour_ConnectionError(int strea expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.WINDOW_UPDATE, expectedLength: 4)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidFrameLength), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidFrameLength); } [Fact] @@ -4443,7 +4443,7 @@ public async Task WINDOW_UPDATE_Received_OnConnection_SizeIncrementZero_Connecti expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorWindowUpdateIncrementZero); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.WindowUpdateSizeInvalid), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.WindowUpdateSizeInvalid); } [Fact] @@ -4460,7 +4460,7 @@ public async Task WINDOW_UPDATE_Received_OnStream_SizeIncrementZero_ConnectionEr expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorWindowUpdateIncrementZero); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.WindowUpdateSizeInvalid), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.WindowUpdateSizeInvalid); } [Fact] @@ -4476,7 +4476,7 @@ public async Task WINDOW_UPDATE_Received_StreamIdle_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdle(Http2FrameType.WINDOW_UPDATE, streamId: 1)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId); } [Fact] @@ -4495,7 +4495,7 @@ public async Task WINDOW_UPDATE_Received_OnConnection_IncreasesWindowAboveMaxVal expectedErrorCode: Http2ErrorCode.FLOW_CONTROL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorWindowUpdateSizeInvalid); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.WindowUpdateSizeInvalid), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.WindowUpdateSizeInvalid); } [Fact] @@ -4798,7 +4798,7 @@ public async Task CONTINUATION_Received_StreamIdMismatch_ConnectionError() expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.CONTINUATION, streamId: 3, headersStreamId: 1)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.UnexpectedFrame); } [Fact] @@ -4815,7 +4815,7 @@ public async Task CONTINUATION_Received_IncompleteHeaderBlock_ConnectionError() expectedErrorCode: Http2ErrorCode.COMPRESSION_ERROR, expectedErrorMessage: SR.net_http_hpack_incomplete_header_block); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.ErrorReadingHeaders), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.ErrorReadingHeaders); } [Theory] @@ -4834,7 +4834,7 @@ public async Task CONTINUATION_Received_WithTrailers_ContainsIllegalTrailer_Conn expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: expectedErrorMessage); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidRequestHeaders), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidRequestHeaders); } [Theory] @@ -4859,7 +4859,7 @@ public async Task CONTINUATION_Received_HeaderBlockDoesNotContainMandatoryPseudo expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId); } [Theory] @@ -4956,7 +4956,7 @@ public async Task UnknownFrameType_Received_InterleavedWithHeaders_ConnectionErr expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(frameType: 42, streamId: 1, headersStreamId: 1)); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.UnexpectedFrame), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.UnexpectedFrame); } [Fact] @@ -4983,7 +4983,7 @@ public async Task ConnectionErrorAbortsAllStreams() Assert.Contains(3, _abortedStreamIds); Assert.Contains(5, _abortedStreamIds); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId); } [Fact] @@ -4998,7 +4998,7 @@ public async Task ConnectionResetLoggedWithActiveStreams() await StopConnectionAsync(1, ignoreNonGoAwayFrames: false); Assert.Single(LogMessages, m => m.Exception is ConnectionResetException); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.ConnectionReset), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.ConnectionReset); } [Fact] @@ -5039,7 +5039,7 @@ public async Task AbortSendsFinalGOAWAY() VerifyGoAway(await ReceiveFrameAsync(), int.MaxValue, Http2ErrorCode.INTERNAL_ERROR); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.AbortedByApp), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.AbortedByApp); } [Fact] @@ -5132,7 +5132,7 @@ public async Task StopProcessingNextRequestSendsGracefulGOAWAYThenFinalGOAWAYWhe await _closedStateReached.Task.DefaultTimeout(); VerifyGoAway(await ReceiveFrameAsync(), 1, Http2ErrorCode.NO_ERROR); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.AppShutdown), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.AppShutdown); } [Fact] @@ -5181,7 +5181,7 @@ public async Task AcceptNewStreamsDuringClosingConnection() await WaitForConnectionStopAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.AppShutdown), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.AppShutdown); } [Fact] @@ -5203,7 +5203,7 @@ public async Task IgnoreNewStreamsDuringClosedConnection() Assert.True(result.IsCompleted); Assert.True(result.Buffer.IsEmpty); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.ConnectionReset), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.ConnectionReset); } [Fact] @@ -5223,7 +5223,7 @@ public async Task IOExceptionDuringFrameProcessingIsNotLoggedHigherThanDebug() Assert.Equal("Connection id \"TestConnectionId\" request processing ended abnormally.", logMessage.Message); Assert.Same(ioException, logMessage.Exception); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.IOError), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.IOError); } [Fact] @@ -5242,7 +5242,7 @@ public async Task UnexpectedExceptionDuringFrameProcessingLoggedAWarning() Assert.Equal(CoreStrings.RequestProcessingEndError, logMessage.Message); Assert.Same(exception, logMessage.Exception); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.UnexpectedError), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.UnexpectedError); } [Theory] @@ -5629,7 +5629,7 @@ public async Task AbortedStream_ResetsAndDrainsRequest_RefusesFramesAfterEndOfSt CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.DATA, streamId: 1), CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.DATA, streamId: 1) }); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.UnknownStream), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.UnknownStream); break; case Http2FrameType.HEADERS: @@ -5646,7 +5646,7 @@ public async Task AbortedStream_ResetsAndDrainsRequest_RefusesFramesAfterEndOfSt CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1), CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.HEADERS, streamId: 1) }); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId); break; case Http2FrameType.CONTINUATION: @@ -5664,7 +5664,7 @@ public async Task AbortedStream_ResetsAndDrainsRequest_RefusesFramesAfterEndOfSt CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1), CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.HEADERS, streamId: 1) }); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId); break; default: throw new NotImplementedException(finalFrameType.ToString()); @@ -5720,11 +5720,11 @@ public async Task AbortedStream_ResetsAndDrainsRequest_RefusesFramesAfterClientR switch (finalFrameType) { case Http2FrameType.DATA: - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.UnknownStream), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.UnknownStream); break; case Http2FrameType.HEADERS: - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidStreamId), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId); break; default: @@ -5759,7 +5759,7 @@ public async Task StartConnection_SendHttp1xRequest_ReturnHttp11Status400() Assert.NotNull(Http2Connection.InvalidHttp1xErrorResponseBytes); Assert.Equal(Http2Connection.InvalidHttp1xErrorResponseBytes, data); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidHttpVersion), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidHttpVersion); Assert.Equal(Http2ErrorCode.PROTOCOL_ERROR, (Http2ErrorCode)_errorCodeFeature.Error); } @@ -5775,7 +5775,7 @@ public async Task StartConnection_SendHttp1xRequest_ExceedsRequestLineLimit_Prot expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorInvalidPreface); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidHandshake), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidHandshake); } [Fact] diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs index cf04cf221c4b..0429af4e6924 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs @@ -38,7 +38,7 @@ public async Task HEADERS_Received_NewLineCharactersInValue_ConnectionError(stri await StartStreamAsync(1, headers, endStream: true); await WaitForConnectionErrorAsync(ignoreNonGoAwayFrames: false, 1, Http2ErrorCode.PROTOCOL_ERROR, "Malformed request: invalid headers."); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidRequestHeaders), ConnectionTags["error.type"]); + AssertConnectionEndReason(ConnectionEndReason.InvalidRequestHeaders); } [Fact] @@ -2233,7 +2233,7 @@ public async Task ResponseHeaders_WithInvalidValuesAndCustomEncoder_AbortsConnec await StartStreamAsync(1, _browserRequestHeaders, endStream: true); await WaitForConnectionErrorAsync(ignoreNonGoAwayFrames: false, int.MaxValue, Http2ErrorCode.INTERNAL_ERROR); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.ErrorWritingHeaders), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.ErrorWritingHeaders); } [Fact] @@ -2584,7 +2584,7 @@ public async Task ResponseTrailers_WithInvalidValuesAndCustomEncoder_AbortsConne Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]); await WaitForConnectionErrorAsync(ignoreNonGoAwayFrames: false, int.MaxValue, Http2ErrorCode.INTERNAL_ERROR); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.ErrorWritingHeaders), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.ErrorWritingHeaders); } [Fact] @@ -5812,7 +5812,7 @@ public async Task HEADERS_Received_Latin1_RejectedWhenLatin1OptionIsNotConfigure expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.BadRequest_MalformedRequestInvalidHeaders); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidRequestHeaders), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidRequestHeaders); } [Fact] @@ -5832,7 +5832,7 @@ public async Task HEADERS_Received_CustomEncoding_InvalidCharacters_AbortsConnec await WaitForConnectionErrorAsync(ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, Http2ErrorCode.PROTOCOL_ERROR, CoreStrings.BadRequest_MalformedRequestInvalidHeaders); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidRequestHeaders), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.InvalidRequestHeaders); } [Fact] diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs index ef8b201f5322..bb50cd5e4ff3 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs @@ -156,12 +156,14 @@ public class Http2TestBase : TestApplicationErrorLoggerLoggedTest, IDisposable, internal DuplexPipe.DuplexPipePair _pair; internal IConnectionMetricsTagsFeature _metricsTagsFeature; + internal IConnectionMetricsContextFeature _metricsContextFeature; internal IProtocolErrorCodeFeature _errorCodeFeature; internal Http2Connection _connection; protected Task _connectionTask; protected long _bytesReceived; internal IDictionary ConnectionTags => _metricsTagsFeature.Tags.ToDictionary(t => t.Key, t => t.Value); + internal ConnectionMetricsContext MetricsContext => _metricsContextFeature.MetricsContext; public Http2TestBase() { @@ -424,11 +426,16 @@ public override void Dispose() base.Dispose(); } - protected void AssertConnectionNoError() + internal void AssertConnectionNoError() { Assert.DoesNotContain(KestrelMetrics.ErrorType, ConnectionTags.Keys); } + internal void AssertConnectionEndReason(ConnectionEndReason expectedEndReason) + { + Assert.Equal(expectedEndReason, MetricsContext.ConnectionEndReason); + } + void IHttpStreamHeadersHandler.OnHeader(ReadOnlySpan name, ReadOnlySpan value) { var nameStr = name.GetHeaderName(); @@ -470,8 +477,11 @@ protected void CreateConnection() _metricsTagsFeature = new TestConnectionMetricsTagsFeature(); _errorCodeFeature = new TestProtocolErrorCodeFeature(); + var metricsContext = TestContextFactory.CreateMetricsContext(_mockConnectionContext.Object); + _metricsContextFeature = new TestConnectionMetricsContextFeature() { MetricsContext = metricsContext }; + var features = new FeatureCollection(); - features.Set(new TestConnectionMetricsContextFeature()); + features.Set(_metricsContextFeature); features.Set(_metricsTagsFeature); features.Set(_errorCodeFeature); _mockConnectionContext.Setup(x => x.Features).Returns(features); @@ -481,7 +491,8 @@ protected void CreateConnection() transport: _pair.Transport, memoryPool: _memoryPool, connectionFeatures: features, - timeoutControl: _mockTimeoutControl.Object); + timeoutControl: _mockTimeoutControl.Object, + metricsContext: metricsContext); _connection = new Http2Connection(httpConnectionContext); _connection._streamLifetimeHandler = new LifetimeHandlerInterceptor(_connection._streamLifetimeHandler, this); @@ -501,7 +512,7 @@ private sealed class TestConnectionMetricsTagsFeature : IConnectionMetricsTagsFe private class TestConnectionMetricsContextFeature : IConnectionMetricsContextFeature { - public ConnectionMetricsContext MetricsContext { get; } + public ConnectionMetricsContext MetricsContext { get; init; } } private class TestProtocolErrorCodeFeature : IProtocolErrorCodeFeature diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs index e4e6b2878077..0e10567e68b9 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs @@ -99,7 +99,7 @@ public async Task HEADERS_NotReceivedAfterFirstRequest_WithinKeepAliveTimeout_Cl _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.KeepAlive), Times.Once); await WaitForConnectionStopAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.KeepAliveTimeout), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.KeepAliveTimeout); _mockTimeoutHandler.VerifyNoOtherCalls(); } @@ -194,7 +194,7 @@ public async Task HEADERS_ReceivedWithoutAllCONTINUATIONs_WithinRequestHeadersTi expectedLastStreamId: int.MaxValue, Http2ErrorCode.INTERNAL_ERROR, CoreStrings.BadRequest_RequestHeadersTimeout); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.RequestHeadersTimeout), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.RequestHeadersTimeout); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.BadRequest_RequestHeadersTimeout)), Times.Once); @@ -304,11 +304,11 @@ async Task AdvanceClockAndSendFrames() switch (finalFrameType) { case Http2FrameType.DATA: - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.UnknownStream), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.UnknownStream); break; case Http2FrameType.CONTINUATION: - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.FrameAfterStreamClose), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.FrameAfterStreamClose); break; default: @@ -388,7 +388,7 @@ public async Task DATA_Sent_TooSlowlyDueToSocketBackPressureOnSmallWrite_AbortsC withStreamId: 1); Assert.True((await _pair.Application.Input.ReadAsync().AsTask().DefaultTimeout()).IsCompleted); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MinResponseDataRate), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.MinResponseDataRate); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied)), Times.Once); @@ -443,7 +443,7 @@ public async Task DATA_Sent_TooSlowlyDueToSocketBackPressureOnLargeWrite_AbortsC withStreamId: 1); Assert.True((await _pair.Application.Input.ReadAsync().AsTask().DefaultTimeout()).IsCompleted); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MinResponseDataRate), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.MinResponseDataRate); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied)), Times.Once); @@ -495,7 +495,7 @@ public async Task DATA_Sent_TooSlowlyDueToFlowControlOnSmallWrite_AbortsConnecti expectedLastStreamId: int.MaxValue, Http2ErrorCode.INTERNAL_ERROR, null); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MinResponseDataRate), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.MinResponseDataRate); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied)), Times.Once); @@ -549,7 +549,7 @@ public async Task DATA_Sent_TooSlowlyDueToOutputFlowControlOnLargeWrite_AbortsCo expectedLastStreamId: int.MaxValue, Http2ErrorCode.INTERNAL_ERROR, null); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MinResponseDataRate), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.MinResponseDataRate); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied)), Times.Once); @@ -615,7 +615,7 @@ public async Task DATA_Sent_TooSlowlyDueToOutputFlowControlOnMultipleStreams_Abo expectedLastStreamId: int.MaxValue, Http2ErrorCode.INTERNAL_ERROR, null); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MinResponseDataRate), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.MinResponseDataRate); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied)), Times.Once); @@ -662,7 +662,7 @@ public async Task DATA_Received_TooSlowlyOnSmallRead_AbortsConnectionAfterGraceP expectedLastStreamId: int.MaxValue, Http2ErrorCode.INTERNAL_ERROR, null); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MinRequestBodyDataRate), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.MinRequestBodyDataRate); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.BadRequest_RequestBodyTimeout)), Times.Once); @@ -713,7 +713,7 @@ public async Task DATA_Received_TooSlowlyOnLargeRead_AbortsConnectionAfterRateTi expectedLastStreamId: int.MaxValue, Http2ErrorCode.INTERNAL_ERROR, null); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MinRequestBodyDataRate), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.MinRequestBodyDataRate); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.BadRequest_RequestBodyTimeout)), Times.Once); @@ -780,7 +780,7 @@ public async Task DATA_Received_TooSlowlyOnMultipleStreams_AbortsConnectionAfter expectedLastStreamId: int.MaxValue, Http2ErrorCode.INTERNAL_ERROR, null); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MinRequestBodyDataRate), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.MinRequestBodyDataRate); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.BadRequest_RequestBodyTimeout)), Times.Once); @@ -848,7 +848,7 @@ public async Task DATA_Received_TooSlowlyOnSecondStream_AbortsConnectionAfterNon expectedLastStreamId: int.MaxValue, Http2ErrorCode.INTERNAL_ERROR, null); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MinRequestBodyDataRate), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.MinRequestBodyDataRate); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.BadRequest_RequestBodyTimeout)), Times.Once); @@ -989,7 +989,7 @@ public async Task DATA_Received_SlowlyDueToConnectionFlowControl_DoesNotAbortCon expectedLastStreamId: int.MaxValue, Http2ErrorCode.INTERNAL_ERROR, null); - Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MinRequestBodyDataRate), ConnectionTags[KestrelMetrics.ErrorType]); + AssertConnectionEndReason(ConnectionEndReason.MinRequestBodyDataRate); _mockConnectionContext.Verify(c => c.Abort(It.Is(e => e.Message == CoreStrings.BadRequest_RequestBodyTimeout)), Times.Once); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs index 69fc112bddfc..6a34b984d5d2 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs @@ -777,7 +777,7 @@ public async Task FlushPipeAsync_OnStoppedHttp3Stream_ReturnsFlushResultWithIsCo }, requestHeaders, endStream: true); await requestStream.ExpectReceiveEndOfStream(); - await appTcs.Task; + await appTcs.Task.DefaultTimeout(); } [Fact] diff --git a/src/Servers/Kestrel/test/Interop.FunctionalTests/Http3/Http3RequestTests.cs b/src/Servers/Kestrel/test/Interop.FunctionalTests/Http3/Http3RequestTests.cs index e16404299f90..37a4ad25fe12 100644 --- a/src/Servers/Kestrel/test/Interop.FunctionalTests/Http3/Http3RequestTests.cs +++ b/src/Servers/Kestrel/test/Interop.FunctionalTests/Http3/Http3RequestTests.cs @@ -1744,7 +1744,7 @@ public async Task GET_ServerAbortTransport_ConnectionAbortRaised() using (var host = builder.Build()) using (var client = HttpHelpers.CreateClient()) { - await host.StartAsync(); + await host.StartAsync().DefaultTimeout(); var port = host.GetPort(); @@ -1759,7 +1759,7 @@ public async Task GET_ServerAbortTransport_ConnectionAbortRaised() var connection = await connectionStartedTcs.Task.DefaultTimeout(); // Request in progress. - await syncPoint.WaitForSyncPoint(); + await syncPoint.WaitForSyncPoint().DefaultTimeout(); // Server connection middleware triggers close. // Note that this aborts the transport, not the HTTP/3 connection. @@ -1774,7 +1774,7 @@ public async Task GET_ServerAbortTransport_ConnectionAbortRaised() syncPoint.Continue(); - await host.StopAsync(); + await host.StopAsync().DefaultTimeout(); } } From afbf95ca6695b0c71c05194722bdebb53a669579 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 18 Jun 2024 11:54:24 +0800 Subject: [PATCH 24/29] HTTP/1.1 end reason work --- .../Core/src/Internal/Http/Http1Connection.cs | 78 ++++++++++++++--- .../src/Internal/Http/Http1MessageBody.cs | 5 +- .../src/Internal/Http2/Http2Connection.cs | 2 +- .../src/Internal/Http3/Http3Connection.cs | 6 +- .../Internal/Infrastructure/KestrelMetrics.cs | 13 ++- .../PipeWriterHelpers/TimingPipeFlusher.cs | 2 +- .../test/FunctionalTests/ResponseTests.cs | 12 +-- .../BadHttpRequestTests.cs | 11 ++- .../Http2/Http2ConnectionTests.cs | 2 +- .../Http2/Http2KeepAliveTests.cs | 13 +++ .../KestrelMetricsTests.cs | 83 ++++++++++++++++++- .../MaxRequestLineSizeTests.cs | 29 +++++-- .../RequestBodyTimeoutTests.cs | 34 +++++++- .../RequestHeaderLimitsTests.cs | 47 ++++++++--- .../RequestHeadersTimeoutTests.cs | 41 +++++++-- .../InMemory.FunctionalTests/ResponseTests.cs | 11 ++- .../Http3/Http3RequestTests.cs | 1 - .../Http2/ConnectionEndReason.cs | 4 +- 18 files changed, 330 insertions(+), 64 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs index c9bbc62a7ebf..b6fc8dc9d5ca 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs @@ -62,7 +62,7 @@ public Http1Connection(HttpConnectionContext context) _context.ServiceContext.Log, _context.TimeoutControl, minResponseDataRateFeature: this, - ConnectionMetricsContext, + MetricsContext, outputAborter: this); Input = _context.Transport.Input; @@ -70,7 +70,7 @@ public Http1Connection(HttpConnectionContext context) MemoryPool = _context.MemoryPool; } - private ConnectionMetricsContext ConnectionMetricsContext => _context.MetricsContext; + public ConnectionMetricsContext MetricsContext => _context.MetricsContext; public PipeReader Input { get; } @@ -85,7 +85,7 @@ protected override void OnRequestProcessingEnded() if (IsUpgraded) { KestrelEventSource.Log.RequestUpgradedStop(this); - ServiceContext.Metrics.RequestUpgradedStop(_context.MetricsContext); + ServiceContext.Metrics.RequestUpgradedStop(MetricsContext); ServiceContext.ConnectionManager.UpgradedConnectionCount.ReleaseOne(); } @@ -134,7 +134,7 @@ protected override void ApplicationAbort() /// public void StopProcessingNextRequest(ConnectionEndReason reason) { - KestrelMetrics.AddConnectionEndReason(ConnectionMetricsContext, reason); + KestrelMetrics.AddConnectionEndReason(MetricsContext, reason); _keepAlive = false; Input.CancelPendingRead(); @@ -147,12 +147,16 @@ public void SendTimeoutResponse() } public void HandleRequestHeadersTimeout() - => SendTimeoutResponse(); + { + KestrelMetrics.AddConnectionEndReason(MetricsContext, ConnectionEndReason.RequestHeadersTimeout); + SendTimeoutResponse(); + } public void HandleReadDataRateTimeout() { Debug.Assert(MinRequestBodyDataRate != null); + KestrelMetrics.AddConnectionEndReason(MetricsContext, ConnectionEndReason.MinRequestBodyDataRate); Log.RequestBodyMinimumDataRateNotSatisfied(ConnectionId, TraceIdentifier, MinRequestBodyDataRate.BytesPerSecond); SendTimeoutResponse(); } @@ -706,17 +710,22 @@ protected override bool TryParseRequest(ReadResult result, out bool endConnectio } catch (InvalidOperationException) when (_requestProcessingStatus == RequestProcessingStatus.ParsingHeaders) { + KestrelMetrics.AddConnectionEndReason(MetricsContext, ConnectionEndReason.InvalidRequestHeaders); KestrelBadHttpRequestException.Throw(RequestRejectionReason.MalformedRequestInvalidHeaders); throw; } #pragma warning disable CS0618 // Type or member is obsolete catch (BadHttpRequestException ex) { - DetectHttp2Preface(result.Buffer, ex); - + OnBaseRequest(result.Buffer, ex); throw; } #pragma warning restore CS0618 // Type or member is obsolete + catch (Exception) + { + KestrelMetrics.AddConnectionEndReason(MetricsContext, ConnectionEndReason.OtherError); + throw; + } finally { Input.AdvanceTo(reader.Position, isConsumed ? reader.Position : result.Buffer.End); @@ -730,9 +739,11 @@ protected override bool TryParseRequest(ReadResult result, out bool endConnectio endConnection = true; return true; case RequestProcessingStatus.ParsingRequestLine: + KestrelMetrics.AddConnectionEndReason(MetricsContext, ConnectionEndReason.InvalidRequestHeaders); KestrelBadHttpRequestException.Throw(RequestRejectionReason.InvalidRequestLine); break; case RequestProcessingStatus.ParsingHeaders: + KestrelMetrics.AddConnectionEndReason(MetricsContext, ConnectionEndReason.InvalidRequestHeaders); KestrelBadHttpRequestException.Throw(RequestRejectionReason.MalformedRequestInvalidHeaders); break; } @@ -748,6 +759,7 @@ protected override bool TryParseRequest(ReadResult result, out bool endConnectio { // In this case, there is an ongoing request but the start line/header parsing has timed out, so send // a 408 response. + KestrelMetrics.AddConnectionEndReason(MetricsContext, ConnectionEndReason.RequestHeadersTimeout); KestrelBadHttpRequestException.Throw(RequestRejectionReason.RequestHeadersTimeout); } @@ -764,8 +776,55 @@ protected override bool TryParseRequest(ReadResult result, out bool endConnectio } #pragma warning disable CS0618 // Type or member is obsolete - private void DetectHttp2Preface(ReadOnlySequence requestData, BadHttpRequestException ex) + private void OnBaseRequest(ReadOnlySequence requestData, BadHttpRequestException ex) #pragma warning restore CS0618 // Type or member is obsolete + { + var reason = ex.Reason; + + switch (reason) + { + case RequestRejectionReason.UnrecognizedHTTPVersion: + KestrelMetrics.AddConnectionEndReason(MetricsContext, ConnectionEndReason.InvalidHttpVersion); + DetectHttp2Preface(requestData); + break; + case RequestRejectionReason.InvalidRequestLine: + case RequestRejectionReason.RequestLineTooLong: + KestrelMetrics.AddConnectionEndReason(MetricsContext, ConnectionEndReason.InvalidRequestLine); + break; + case RequestRejectionReason.InvalidRequestHeadersNoCRLF: + case RequestRejectionReason.InvalidRequestHeader: + case RequestRejectionReason.InvalidContentLength: + case RequestRejectionReason.HeadersExceedMaxTotalSize: + case RequestRejectionReason.TooManyHeaders: + case RequestRejectionReason.MultipleContentLengths: + case RequestRejectionReason.MalformedRequestInvalidHeaders: + case RequestRejectionReason.InvalidRequestTarget: + case RequestRejectionReason.InvalidCharactersInHeaderName: + case RequestRejectionReason.LengthRequiredHttp10: + case RequestRejectionReason.OptionsMethodRequired: + case RequestRejectionReason.ConnectMethodRequired: + case RequestRejectionReason.MissingHostHeader: + case RequestRejectionReason.MultipleHostHeaders: + case RequestRejectionReason.InvalidHostHeader: + KestrelMetrics.AddConnectionEndReason(MetricsContext, ConnectionEndReason.InvalidRequestHeaders); + break; + case RequestRejectionReason.TlsOverHttpError: + case RequestRejectionReason.UnexpectedEndOfRequestContent: + case RequestRejectionReason.BadChunkSuffix: + case RequestRejectionReason.BadChunkSizeData: + case RequestRejectionReason.ChunkedRequestIncomplete: + case RequestRejectionReason.RequestBodyTooLarge: + case RequestRejectionReason.RequestHeadersTimeout: + case RequestRejectionReason.RequestBodyTimeout: + case RequestRejectionReason.FinalTransferCodingNotChunked: + case RequestRejectionReason.RequestBodyExceedsContentLength: + default: + KestrelMetrics.AddConnectionEndReason(MetricsContext, ConnectionEndReason.OtherError); + break; + } + } + + private void DetectHttp2Preface(ReadOnlySequence requestData) { const int PrefaceLineLength = 16; @@ -775,8 +834,7 @@ private void DetectHttp2Preface(ReadOnlySequence requestData, BadHttpReque { // If there is an unrecognized HTTP version, it is the first request on the connection, and the request line // bytes matches the HTTP/2 preface request line bytes then log and return a HTTP/2 GOAWAY frame. - if (ex.Reason == RequestRejectionReason.UnrecognizedHTTPVersion - && _requestCount == 1 + if (_requestCount == 1 && requestData.Length >= PrefaceLineLength) { var clientPrefaceRequestLine = Http2.Http2Connection.ClientPreface.Slice(0, PrefaceLineLength); diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs index 7c2de425afa9..92e0a5a04630 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs @@ -68,11 +68,10 @@ protected override Task OnConsumeAsync() } catch (InvalidOperationException ex) { - var connectionAbortedException = new ConnectionAbortedException(CoreStrings.ConnectionAbortedByApplication, ex); - _context.ReportApplicationError(connectionAbortedException); + Log.RequestBodyDrainBodyReaderInvalidState(_context.ConnectionIdFeature, _context.TraceIdentifier, ex); // Have to abort the connection because we can't finish draining the request - _context.StopProcessingNextRequest(ConnectionEndReason.AbortedByApp); + _context.StopProcessingNextRequest(ConnectionEndReason.BodyReaderInvalidState); return Task.CompletedTask; } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs index 6f4975fa840f..9c6c380a8e98 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs @@ -422,7 +422,7 @@ public void StopProcessingNextRequest(bool serverInitiated, ConnectionEndReason Log.LogWarning(0, ex, CoreStrings.RequestProcessingEndError); error = ex; errorCode = Http2ErrorCode.INTERNAL_ERROR; - reason = ConnectionEndReason.UnexpectedError; + reason = ConnectionEndReason.OtherError; } finally { diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs index 41663f60aa9f..e0f300839104 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs @@ -479,7 +479,7 @@ private void UpdateStreamTimeouts(long timestamp) { Log.RequestProcessingError(_context.ConnectionId, ex); error = ex; - reason = ConnectionEndReason.UnexpectedError; + reason = ConnectionEndReason.OtherError; } catch (Http3ConnectionErrorException ex) { @@ -490,7 +490,7 @@ private void UpdateStreamTimeouts(long timestamp) catch (Exception ex) { error = ex; - reason = ConnectionEndReason.UnexpectedError; + reason = ConnectionEndReason.OtherError; } finally { @@ -558,7 +558,7 @@ private void UpdateStreamTimeouts(long timestamp) } catch { - Abort(CreateConnectionAbortError(error, clientAbort), Http3ErrorCode.InternalError, ConnectionEndReason.UnexpectedError); + Abort(CreateConnectionAbortError(error, clientAbort), Http3ErrorCode.InternalError, ConnectionEndReason.OtherError); throw; } finally diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs index 640996cb024e..de277606c0ad 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs @@ -131,9 +131,14 @@ private void ConnectionStopCore(in ConnectionMetricsContext metricsContext, Exce tags.Add("http.connection.protocol_code", protocolErrorCode); } - if (exception != null) + // Check if there is an end reason on the context. For example, the connection could have been aborted by shutdown. + if (metricsContext.ConnectionEndReason is { } reason && TryGetErrorType(reason, out var errorValue)) { - tags.TryAddTag("error.type", exception.GetType().FullName); + tags.TryAddTag(ErrorType, errorValue); + } + else if (exception != null) + { + tags.TryAddTag(ErrorType, exception.GetType().FullName); } // Add custom tags for duration. @@ -484,7 +489,7 @@ internal static bool TryGetErrorType(ConnectionEndReason reason, [NotNullWhen(tr ConnectionEndReason.MaxFrameLengthExceeded => "max_frame_length_exceeded", ConnectionEndReason.ErrorReadingHeaders => "error_reading_headers", ConnectionEndReason.ErrorWritingHeaders => "error_writing_headers", - ConnectionEndReason.UnexpectedError => "unexpected_error", + ConnectionEndReason.OtherError => "other_error", ConnectionEndReason.InvalidHttpVersion => "invalid_http_version", ConnectionEndReason.RequestHeadersTimeout => "request_headers_timeout", ConnectionEndReason.MinRequestBodyDataRate => "min_request_body_data_rate", @@ -493,12 +498,14 @@ internal static bool TryGetErrorType(ConnectionEndReason reason, [NotNullWhen(tr ConnectionEndReason.OutputQueueSizeExceeded => "output_queue_size_exceeded", ConnectionEndReason.ClosedCriticalStream => "closed_critical_stream", ConnectionEndReason.AbortedByApp => "aborted_by_app", + ConnectionEndReason.WriteCanceled => "write_canceled", ConnectionEndReason.BodyReaderInvalidState => "body_reader_invalid_state", ConnectionEndReason.ServerTimeout => "server_timeout", ConnectionEndReason.StreamCreationError => "stream_creation_error", ConnectionEndReason.IOError => "io_error", ConnectionEndReason.AppShutdown => "app_shutdown", ConnectionEndReason.TlsHandshakeFailed => "tls_handshake_failed", + ConnectionEndReason.InvalidRequestLine => "invalid_request_line", _ => throw new InvalidOperationException($"Unable to calculate whether {reason} resolves to error.type value.") }; diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/TimingPipeFlusher.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/TimingPipeFlusher.cs index c2a7b5352626..9a4d4eaa4930 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/TimingPipeFlusher.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/TimingPipeFlusher.cs @@ -92,7 +92,7 @@ private async ValueTask TimeFlushAsyncAwaited(ValueTask Task.CompletedTask, new Test [Fact] public async Task BadRequestForHttp2() { - await using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory))) + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + + await using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory, metrics: new KestrelMetrics(testMeterFactory)))) { using (var client = server.CreateConnection()) { @@ -238,6 +242,11 @@ await using (var server = new TestServer(context => Task.CompletedTask, new Test Assert.Empty(await client.Stream.ReadUntilEndAsync().DefaultTimeout()); } } + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => + { + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidHttpVersion), m.Tags[KestrelMetrics.ErrorType]); + }); } [Fact] diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs index 134220b65870..21a1c785aef5 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs @@ -5242,7 +5242,7 @@ public async Task UnexpectedExceptionDuringFrameProcessingLoggedAWarning() Assert.Equal(CoreStrings.RequestProcessingEndError, logMessage.Message); Assert.Same(exception, logMessage.Exception); - AssertConnectionEndReason(ConnectionEndReason.UnexpectedError); + AssertConnectionEndReason(ConnectionEndReason.OtherError); } [Theory] diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2KeepAliveTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2KeepAliveTests.cs index ff6863aa325f..b71387129a10 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2KeepAliveTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2KeepAliveTests.cs @@ -18,6 +18,7 @@ public async Task KeepAlivePingDelay_InfiniteTimeSpan_KeepAliveNotEnabled() Assert.Null(_connection._keepAlive); await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false).DefaultTimeout(); + AssertConnectionNoError(); } [Fact] @@ -47,6 +48,7 @@ public async Task KeepAlivePingTimeout_InfiniteTimeSpan_NoGoAway() Assert.Equal(KeepAliveState.PingSent, _connection._keepAlive._state); await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false).DefaultTimeout(); + AssertConnectionNoError(); } [Fact] @@ -68,6 +70,7 @@ public async Task IntervalExceeded_WithoutActivity_PingSent() withStreamId: 0).DefaultTimeout(); await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false).DefaultTimeout(); + AssertConnectionNoError(); } [Fact] @@ -90,6 +93,7 @@ public async Task IntervalExceeded_WithActivity_NoPingSent() TriggerTick(TimeSpan.FromSeconds(1.1)); await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false).DefaultTimeout(); + AssertConnectionNoError(); } [Fact] @@ -106,6 +110,7 @@ public async Task IntervalNotExceeded_NoPingSent() TriggerTick(TimeSpan.FromSeconds(1.1)); await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false).DefaultTimeout(); + AssertConnectionNoError(); } [Fact] @@ -128,6 +133,7 @@ public async Task IntervalExceeded_MultipleTimes_PingsNotSentWhileAwaitingOnAck( TriggerTick(TimeSpan.FromSeconds(1.1)); await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false).DefaultTimeout(); + AssertConnectionNoError(); } [Fact] @@ -165,6 +171,7 @@ public async Task IntervalExceeded_MultipleTimes_PingSentAfterAck() withStreamId: 0).DefaultTimeout(); await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false).DefaultTimeout(); + AssertConnectionNoError(); } [Fact] @@ -195,6 +202,8 @@ public async Task TimeoutExceeded_NoAck_GoAway() Assert.Equal(KeepAliveState.Timeout, _connection._keepAlive._state); VerifyGoAway(await ReceiveFrameAsync().DefaultTimeout(), 0, Http2ErrorCode.INTERNAL_ERROR); + + AssertConnectionEndReason(ConnectionEndReason.KeepAliveTimeout); } [Fact] @@ -226,6 +235,7 @@ public async Task TimeoutExceeded_NonPingActivity_NoGoAway() withStreamId: 1).DefaultTimeout(); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false).DefaultTimeout(); + AssertConnectionNoError(); } [Fact] @@ -249,6 +259,7 @@ public async Task IntervalExceeded_StreamStarted_NoPingSent() TriggerTick(TimeSpan.FromSeconds(1.1)); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false).DefaultTimeout(); + AssertConnectionNoError(); } [Fact] @@ -302,6 +313,7 @@ public async Task IntervalExceeded_ConnectionFlowControlUsedUpThenPings_NoPingSe // Server could send RST_STREAM await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: true).DefaultTimeout(); + AssertConnectionNoError(); } [Fact] @@ -359,5 +371,6 @@ public async Task TimeoutExceeded_ConnectionFlowControlUsedUpThenPings_NoGoAway( // Server could send RST_STREAM await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: true).DefaultTimeout(); + AssertConnectionNoError(); } } diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/KestrelMetricsTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/KestrelMetricsTests.cs index d1700d835806..27e93a47d69e 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/KestrelMetricsTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/KestrelMetricsTests.cs @@ -190,7 +190,7 @@ public async Task Http1Connection_RequestEndsWithIncompleteReadAsync() Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => { - AssertDuration(m, "127.0.0.1", localPort: 0, "tcp", "ipv4", KestrelMetrics.Http11, error: KestrelMetrics.GetErrorType(ConnectionEndReason.AbortedByApp)); + AssertDuration(m, "127.0.0.1", localPort: 0, "tcp", "ipv4", KestrelMetrics.Http11, error: KestrelMetrics.GetErrorType(ConnectionEndReason.BodyReaderInvalidState)); }); } } @@ -384,6 +384,37 @@ public async Task Http1Connection_IHttpConnectionTagsFeatureIgnoreFeatureSetOnTr Assert.Collection(queuedConnections.GetMeasurementSnapshot(), m => AssertCount(m, 1, "127.0.0.1", localPort: 0, "tcp", "ipv4"), m => AssertCount(m, -1, "127.0.0.1", localPort: 0, "tcp", "ipv4")); } + [Fact] + public async Task Http1Connection_ServerAbort_HasErrorType() + { + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + + var serviceContext = new TestServiceContext(LoggerFactory, metrics: new KestrelMetrics(testMeterFactory)); + + var sendString = "POST / HTTP/1.0\r\nContent-Length: 12\r\n\r\nHello World?"; + + await using var server = new TestServer(c => + { + c.Abort(); + return Task.CompletedTask; + }, serviceContext); + + using (var connection = server.CreateConnection()) + { + await connection.Send(sendString).DefaultTimeout(); + + await connection.ReceiveEnd().DefaultTimeout(); + + await connection.WaitForConnectionClose().DefaultTimeout(); + } + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => + { + AssertDuration(m, "127.0.0.1", localPort: 0, "tcp", "ipv4", KestrelMetrics.Http11, error: KestrelMetrics.GetErrorType(ConnectionEndReason.AbortedByApp)); + }); + } + private sealed class TestConnectionMetricsTagsFeature : IConnectionMetricsTagsFeature { public ICollection> Tags { get; } = new List>(); @@ -718,6 +749,56 @@ static void AssertRequestCount(CollectedMeasurement measurement, long expe } } + [Fact] + public async Task Http2Connection_ServerAbort_NoErrorType() + { + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + + await using (var server = new TestServer(context => + { + context.Response.WriteAsync("Hello world"); + context.Abort(); + return Task.CompletedTask; + }, + new TestServiceContext(LoggerFactory, metrics: new KestrelMetrics(testMeterFactory)), + listenOptions => + { + listenOptions.Protocols = HttpProtocols.Http2; + })) + { + using var connection = server.CreateConnection(); + + using var socketsHandler = new SocketsHttpHandler() + { + ConnectCallback = (_, _) => + { + return new ValueTask(connection.Stream); + }, + SslOptions = new SslClientAuthenticationOptions + { + RemoteCertificateValidationCallback = (_, _, _, _) => true + } + }; + + using var httpClient = new HttpClient(socketsHandler); + + using var httpRequestMessage = new HttpRequestMessage() + { + RequestUri = new Uri("http://localhost/"), + Version = new Version(2, 0), + VersionPolicy = HttpVersionPolicy.RequestVersionExact, + }; + + using var responseMessage = await httpClient.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseHeadersRead); + responseMessage.EnsureSuccessStatusCode(); + + await connection.WaitForConnectionClose(); + } + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => AssertDuration(m, "127.0.0.1", localPort: 0, "tcp", "ipv4", KestrelMetrics.Http2)); + } + [ConditionalFact] [TlsAlpnSupported] [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10)] diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/MaxRequestLineSizeTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/MaxRequestLineSizeTests.cs index 3425eb9a2ee7..f3a8e45c1e5b 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/MaxRequestLineSizeTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/MaxRequestLineSizeTests.cs @@ -1,13 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Threading.Tasks; +using System.Diagnostics.Metrics; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.InternalTesting; using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport; -using Microsoft.AspNetCore.InternalTesting; -using Microsoft.Extensions.Logging.Testing; -using Xunit; +using Microsoft.Extensions.Diagnostics.Metrics.Testing; namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests; @@ -28,7 +28,10 @@ public class MaxRequestLineSizeTests : LoggedTest [InlineData("DELETE /a%20b%20c/d%20e?f=ghi HTTP/1.1\r\nHost:\r\n\r\n", 1027)] public async Task ServerAcceptsRequestLineWithinLimit(string request, int limit) { - await using (var server = CreateServer(limit)) + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + + await using (var server = CreateServer(limit, testMeterFactory)) { using (var connection = server.CreateConnection()) { @@ -45,6 +48,8 @@ await using (var server = CreateServer(limit)) ""); } } + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => Assert.DoesNotContain(KestrelMetrics.ErrorType, m.Tags.Keys)); } [Theory] @@ -54,7 +59,10 @@ await using (var server = CreateServer(limit)) [InlineData("DELETE /a%20b%20c/d%20e?f=ghi HTTP/1.1\r\n")] public async Task ServerRejectsRequestLineExceedingLimit(string requestLine) { - await using (var server = CreateServer(requestLine.Length - 1)) + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + + await using (var server = CreateServer(requestLine.Length - 1, testMeterFactory)) { using (var connection = server.CreateConnection()) { @@ -68,11 +76,16 @@ await using (var server = CreateServer(requestLine.Length - 1)) ""); } } + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => + { + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidRequestLine), m.Tags[KestrelMetrics.ErrorType]); + }); } - private TestServer CreateServer(int maxRequestLineSize) + private TestServer CreateServer(int maxRequestLineSize, IMeterFactory meterFactory) { - return new TestServer(async httpContext => await httpContext.Response.WriteAsync("hello, world"), new TestServiceContext(LoggerFactory) + return new TestServer(async httpContext => await httpContext.Response.WriteAsync("hello, world"), new TestServiceContext(LoggerFactory, metrics: new KestrelMetrics(meterFactory)) { ServerOptions = new KestrelServerOptions { diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestBodyTimeoutTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestBodyTimeoutTests.cs index 864492a67d6f..55edab87bda7 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestBodyTimeoutTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestBodyTimeoutTests.cs @@ -2,11 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.InternalTesting; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport; -using Microsoft.AspNetCore.InternalTesting; +using Microsoft.Extensions.Diagnostics.Metrics.Testing; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Time.Testing; @@ -17,8 +19,11 @@ public class RequestBodyTimeoutTests : LoggedTest [Fact] public async Task RequestTimesOutWhenRequestBodyNotReceivedAtSpecifiedMinimumRate() { + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + var gracePeriod = TimeSpan.FromSeconds(5); - var serviceContext = new TestServiceContext(LoggerFactory); + var serviceContext = new TestServiceContext(LoggerFactory, metrics: new KestrelMetrics(testMeterFactory)); var appRunningEvent = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -84,13 +89,21 @@ public async Task RequestTimesOutWhenRequestBodyNotReceivedAtSpecifiedMinimumRat ""); } } + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => + { + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MinRequestBodyDataRate), m.Tags[KestrelMetrics.ErrorType]); + }); } [Fact] public async Task RequestTimesOutWhenNotDrainedWithinDrainTimeoutPeriod() { + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + + var serviceContext = new TestServiceContext(LoggerFactory, metrics: new KestrelMetrics(testMeterFactory)); // This test requires a real clock since we can't control when the drain timeout is set - var serviceContext = new TestServiceContext(LoggerFactory); serviceContext.InitializeHeartbeat(); // Ensure there's still a constant date header value. @@ -132,13 +145,21 @@ public async Task RequestTimesOutWhenNotDrainedWithinDrainTimeoutPeriod() Assert.Contains(TestSink.Writes, w => w.EventId.Id == 32 && w.LogLevel == LogLevel.Information); Assert.Contains(TestSink.Writes, w => w.EventId.Id == 33 && w.LogLevel == LogLevel.Information); + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => + { + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.ServerTimeout), m.Tags[KestrelMetrics.ErrorType]); + }); } [Fact] public async Task ConnectionClosedEvenIfAppSwallowsException() { + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + var gracePeriod = TimeSpan.FromSeconds(5); - var serviceContext = new TestServiceContext(LoggerFactory); + var serviceContext = new TestServiceContext(LoggerFactory, metrics: new KestrelMetrics(testMeterFactory)); var appRunningTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var exceptionSwallowedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -201,5 +222,10 @@ public async Task ConnectionClosedEvenIfAppSwallowsException() "hello, world"); } } + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => + { + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MinRequestBodyDataRate), m.Tags[KestrelMetrics.ErrorType]); + }); } } diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestHeaderLimitsTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestHeaderLimitsTests.cs index a11eff246a0f..80cd1a3c242e 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestHeaderLimitsTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestHeaderLimitsTests.cs @@ -1,13 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Linq; -using System.Threading.Tasks; +using System.Diagnostics.Metrics; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.InternalTesting; using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport; -using Microsoft.AspNetCore.InternalTesting; -using Xunit; +using Microsoft.Extensions.Diagnostics.Metrics.Testing; namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests; @@ -24,9 +24,12 @@ public class RequestHeaderLimitsTests : LoggedTest [InlineData(5, 1337)] public async Task ServerAcceptsRequestWithHeaderTotalSizeWithinLimit(int headerCount, int extraLimit) { + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + var headers = MakeHeaders(headerCount); - await using (var server = CreateServer(maxRequestHeadersTotalSize: headers.Length + extraLimit)) + await using (var server = CreateServer(maxRequestHeadersTotalSize: headers.Length + extraLimit, meterFactory: testMeterFactory)) { using (var connection = server.CreateConnection()) { @@ -43,6 +46,8 @@ await using (var server = CreateServer(maxRequestHeadersTotalSize: headers.Lengt ""); } } + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => Assert.DoesNotContain(KestrelMetrics.ErrorType, m.Tags.Keys)); } [Theory] @@ -56,9 +61,12 @@ await using (var server = CreateServer(maxRequestHeadersTotalSize: headers.Lengt [InlineData(5, 1337)] public async Task ServerAcceptsRequestWithHeaderCountWithinLimit(int headerCount, int maxHeaderCount) { + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + var headers = MakeHeaders(headerCount); - await using (var server = CreateServer(maxRequestHeaderCount: maxHeaderCount)) + await using (var server = CreateServer(maxRequestHeaderCount: maxHeaderCount, meterFactory: testMeterFactory)) { using (var connection = server.CreateConnection()) { @@ -75,6 +83,8 @@ await using (var server = CreateServer(maxRequestHeaderCount: maxHeaderCount)) ""); } } + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => Assert.DoesNotContain(KestrelMetrics.ErrorType, m.Tags.Keys)); } [Theory] @@ -82,9 +92,12 @@ await using (var server = CreateServer(maxRequestHeaderCount: maxHeaderCount)) [InlineData(5)] public async Task ServerRejectsRequestWithHeaderTotalSizeOverLimit(int headerCount) { + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + var headers = MakeHeaders(headerCount); - await using (var server = CreateServer(maxRequestHeadersTotalSize: headers.Length - 1)) + await using (var server = CreateServer(maxRequestHeadersTotalSize: headers.Length - 1, meterFactory: testMeterFactory)) { using (var connection = server.CreateConnection()) { @@ -98,6 +111,11 @@ await using (var server = CreateServer(maxRequestHeadersTotalSize: headers.Lengt ""); } } + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => + { + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidRequestHeaders), m.Tags[KestrelMetrics.ErrorType]); + }); } [Theory] @@ -106,9 +124,12 @@ await using (var server = CreateServer(maxRequestHeadersTotalSize: headers.Lengt [InlineData(5, 4)] public async Task ServerRejectsRequestWithHeaderCountOverLimit(int headerCount, int maxHeaderCount) { + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + var headers = MakeHeaders(headerCount); - await using (var server = CreateServer(maxRequestHeaderCount: maxHeaderCount)) + await using (var server = CreateServer(maxRequestHeaderCount: maxHeaderCount, meterFactory: testMeterFactory)) { using (var connection = server.CreateConnection()) { @@ -122,6 +143,11 @@ await using (var server = CreateServer(maxRequestHeaderCount: maxHeaderCount)) ""); } } + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => + { + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InvalidRequestHeaders), m.Tags[KestrelMetrics.ErrorType]); + }); } private static string MakeHeaders(int count) @@ -138,7 +164,7 @@ private static string MakeHeaders(int count) .Select(i => $"Header-{i}: value{i}\r\n"))); } - private TestServer CreateServer(int? maxRequestHeaderCount = null, int? maxRequestHeadersTotalSize = null) + private TestServer CreateServer(int? maxRequestHeaderCount = null, int? maxRequestHeadersTotalSize = null, IMeterFactory meterFactory = null) { var options = new KestrelServerOptions { AddServerHeader = false }; @@ -152,7 +178,8 @@ private TestServer CreateServer(int? maxRequestHeaderCount = null, int? maxReque options.Limits.MaxRequestHeadersTotalSize = maxRequestHeadersTotalSize.Value; } - return new TestServer(async httpContext => await httpContext.Response.WriteAsync("hello, world"), new TestServiceContext(LoggerFactory) + var kestrelMetrics = meterFactory != null ? new KestrelMetrics(meterFactory) : null; + return new TestServer(async httpContext => await httpContext.Response.WriteAsync("hello, world"), new TestServiceContext(LoggerFactory, metrics: kestrelMetrics) { ServerOptions = options }); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestHeadersTimeoutTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestHeadersTimeoutTests.cs index 965a110ee078..8b0cb40b507d 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestHeadersTimeoutTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestHeadersTimeoutTests.cs @@ -3,9 +3,11 @@ using System.IO.Pipelines; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.InternalTesting; +using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport; -using Microsoft.AspNetCore.InternalTesting; +using Microsoft.Extensions.Diagnostics.Metrics.Testing; namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests; @@ -21,7 +23,10 @@ public class RequestHeadersTimeoutTests : LoggedTest [InlineData("Host:\r\nContent-Length: 1\r\n\r")] public async Task ConnectionAbortedWhenRequestHeadersNotReceivedInTime(string headers) { - var testContext = new TestServiceContext(LoggerFactory); + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + + var testContext = new TestServiceContext(LoggerFactory, metrics: new KestrelMetrics(testMeterFactory)); await using (var server = CreateServer(testContext)) { @@ -40,12 +45,20 @@ await using (var server = CreateServer(testContext)) await ReceiveTimeoutResponse(connection, testContext); } } + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => + { + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.RequestHeadersTimeout), m.Tags[KestrelMetrics.ErrorType]); + }); } [Fact] public async Task RequestHeadersTimeoutCanceledAfterHeadersReceived() { - var testContext = new TestServiceContext(LoggerFactory); + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + + var testContext = new TestServiceContext(LoggerFactory, metrics: new KestrelMetrics(testMeterFactory)); await using (var server = CreateServer(testContext)) { @@ -70,6 +83,8 @@ await using (var server = CreateServer(testContext)) await ReceiveResponse(connection, testContext); } } + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => Assert.DoesNotContain(KestrelMetrics.ErrorType, m.Tags.Keys)); } [Theory] @@ -77,7 +92,10 @@ await using (var server = CreateServer(testContext)) [InlineData("POST / HTTP/1.1\r")] public async Task ConnectionAbortedWhenRequestLineNotReceivedInTime(string requestLine) { - var testContext = new TestServiceContext(LoggerFactory); + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + + var testContext = new TestServiceContext(LoggerFactory, metrics: new KestrelMetrics(testMeterFactory)); await using (var server = CreateServer(testContext)) { @@ -94,12 +112,20 @@ await using (var server = CreateServer(testContext)) await ReceiveTimeoutResponse(connection, testContext); } } + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => + { + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.RequestHeadersTimeout), m.Tags[KestrelMetrics.ErrorType]); + }); } [Fact] public async Task TimeoutNotResetOnEachRequestLineCharacterReceived() { - var testContext = new TestServiceContext(LoggerFactory); + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + + var testContext = new TestServiceContext(LoggerFactory, metrics: new KestrelMetrics(testMeterFactory)); // Disable response rate, so we can finish the send loop without timing out the response. testContext.ServerOptions.Limits.MinResponseDataRate = null; @@ -123,6 +149,11 @@ await using (var server = CreateServer(testContext)) await connection.WaitForConnectionClose(); } } + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => + { + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.RequestHeadersTimeout), m.Tags[KestrelMetrics.ErrorType]); + }); } private TestServer CreateServer(TestServiceContext context) diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseTests.cs index 0909df237f49..915d656b41e1 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseTests.cs @@ -26,6 +26,7 @@ using Moq; using Xunit; using BadHttpRequestException = Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException; +using Microsoft.Extensions.Diagnostics.Metrics.Testing; namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests; @@ -140,7 +141,10 @@ public async Task OnStartingThrowsWhenSetAfterStartAsyncIsCalled() [Fact] public async Task ResponseBodyWriteAsyncCanBeCancelled() { - var serviceContext = new TestServiceContext(LoggerFactory); + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + + var serviceContext = new TestServiceContext(LoggerFactory, metrics: new KestrelMetrics(testMeterFactory)); var cts = new CancellationTokenSource(); var appTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var writeBlockedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -202,6 +206,11 @@ public async Task ResponseBodyWriteAsyncCanBeCancelled() await Assert.ThrowsAsync(() => appTcs.Task).DefaultTimeout(); } } + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => + { + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.WriteCanceled), m.Tags[KestrelMetrics.ErrorType]); + }); } [Fact] diff --git a/src/Servers/Kestrel/test/Interop.FunctionalTests/Http3/Http3RequestTests.cs b/src/Servers/Kestrel/test/Interop.FunctionalTests/Http3/Http3RequestTests.cs index 37a4ad25fe12..1b7ae1ff0132 100644 --- a/src/Servers/Kestrel/test/Interop.FunctionalTests/Http3/Http3RequestTests.cs +++ b/src/Servers/Kestrel/test/Interop.FunctionalTests/Http3/Http3RequestTests.cs @@ -1137,7 +1137,6 @@ public async Task POST_Bidirectional_LargeData_Cancellation_Error(HttpProtocols var badLogWrite = TestSink.Writes.FirstOrDefault(w => w.LogLevel >= LogLevel.Critical); if (badLogWrite != null) { - Debugger.Launch(); Assert.True(false, "Bad log write: " + badLogWrite + Environment.NewLine + badLogWrite.Exception); } diff --git a/src/Shared/ServerInfrastructure/Http2/ConnectionEndReason.cs b/src/Shared/ServerInfrastructure/Http2/ConnectionEndReason.cs index 27604423f24c..04d75460a241 100644 --- a/src/Shared/ServerInfrastructure/Http2/ConnectionEndReason.cs +++ b/src/Shared/ServerInfrastructure/Http2/ConnectionEndReason.cs @@ -19,6 +19,7 @@ internal enum ConnectionEndReason InvalidFrameLength, InvalidDataPadding, InvalidRequestHeaders, + InvalidRequestLine, StreamResetLimitExceeded, WindowUpdateSizeInvalid, StreamSelfDependency, @@ -27,7 +28,7 @@ internal enum ConnectionEndReason MaxFrameLengthExceeded, ErrorReadingHeaders, ErrorWritingHeaders, - UnexpectedError, + OtherError, InvalidHttpVersion, RequestHeadersTimeout, MinRequestBodyDataRate, @@ -36,6 +37,7 @@ internal enum ConnectionEndReason OutputQueueSizeExceeded, ClosedCriticalStream, AbortedByApp, + WriteCanceled, BodyReaderInvalidState, ServerTimeout, StreamCreationError, From 2c2c9bc3dd42affe4459baeb01a4c35e2ee0f994 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 18 Jun 2024 12:23:29 +0800 Subject: [PATCH 25/29] Comment --- .../Kestrel/Core/src/Internal/Http/Http1Connection.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs index b6fc8dc9d5ca..0db83e4f6ca2 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs @@ -717,7 +717,7 @@ protected override bool TryParseRequest(ReadResult result, out bool endConnectio #pragma warning disable CS0618 // Type or member is obsolete catch (BadHttpRequestException ex) { - OnBaseRequest(result.Buffer, ex); + OnBadRequest(result.Buffer, ex); throw; } #pragma warning restore CS0618 // Type or member is obsolete @@ -776,11 +776,14 @@ protected override bool TryParseRequest(ReadResult result, out bool endConnectio } #pragma warning disable CS0618 // Type or member is obsolete - private void OnBaseRequest(ReadOnlySequence requestData, BadHttpRequestException ex) + private void OnBadRequest(ReadOnlySequence requestData, BadHttpRequestException ex) #pragma warning restore CS0618 // Type or member is obsolete { var reason = ex.Reason; + // Some code shared between HTTP versions throws errors. For example, HttpRequestHeaders collection + // throws when an invalid content length is set. + // Only want to set a reasons for HTTP/1.1 connection, so set end reason by catching the exception here. switch (reason) { case RequestRejectionReason.UnrecognizedHTTPVersion: From b5d34fc9d1037ce4c4e03f3ccc0fb5b2f7e06bd8 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 28 Jun 2024 22:04:37 +0800 Subject: [PATCH 26/29] Add reasons for some HTTP/1.1 request body errors --- .../Core/src/Internal/Http/Http1Connection.cs | 13 ++---- .../Http/Http1ContentLengthMessageBody.cs | 3 ++ .../src/Internal/Http/Http1MessageBody.cs | 9 ++++ .../Core/src/Internal/Http/MessageBody.cs | 8 +++- .../Internal/Http/RequestRejectionReason.cs | 3 +- .../Internal/Infrastructure/KestrelMetrics.cs | 3 ++ .../ChunkedRequestTests.cs | 21 +++++---- .../MaxRequestBodySizeTests.cs | 22 +++++++++- .../InMemory.FunctionalTests/RequestTests.cs | 44 ++++++++++++++++++- .../Http2/ConnectionEndReason.cs | 5 ++- 10 files changed, 106 insertions(+), 25 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs index 0db83e4f6ca2..58cf63bee2e5 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs @@ -812,16 +812,11 @@ private void OnBadRequest(ReadOnlySequence requestData, BadHttpRequestExce KestrelMetrics.AddConnectionEndReason(MetricsContext, ConnectionEndReason.InvalidRequestHeaders); break; case RequestRejectionReason.TlsOverHttpError: - case RequestRejectionReason.UnexpectedEndOfRequestContent: - case RequestRejectionReason.BadChunkSuffix: - case RequestRejectionReason.BadChunkSizeData: - case RequestRejectionReason.ChunkedRequestIncomplete: - case RequestRejectionReason.RequestBodyTooLarge: - case RequestRejectionReason.RequestHeadersTimeout: - case RequestRejectionReason.RequestBodyTimeout: - case RequestRejectionReason.FinalTransferCodingNotChunked: - case RequestRejectionReason.RequestBodyExceedsContentLength: + KestrelMetrics.AddConnectionEndReason(MetricsContext, ConnectionEndReason.TlsOverHttp); + break; default: + // In some scenarios the end reason might already be set to a more specific error + // and attempting to set the reason again has no impact. KestrelMetrics.AddConnectionEndReason(MetricsContext, ConnectionEndReason.OtherError); break; } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1ContentLengthMessageBody.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1ContentLengthMessageBody.cs index 69a7c2a5d8ca..adfc36ab4cb8 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1ContentLengthMessageBody.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1ContentLengthMessageBody.cs @@ -7,6 +7,7 @@ using System.IO.Pipelines; using System.Runtime.CompilerServices; using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; @@ -247,6 +248,7 @@ protected override void OnReadStarting() if (_contentLength > maxRequestBodySize) { _context.DisableHttp1KeepAlive(); + KestrelMetrics.AddConnectionEndReason(_context.MetricsContext, ConnectionEndReason.MaxRequestBodySizeExceeded); KestrelBadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTooLarge, maxRequestBodySize.GetValueOrDefault().ToString(CultureInfo.InvariantCulture)); } } @@ -269,6 +271,7 @@ private void VerifyIsNotReading() { if (_readResult.IsCompleted) { + KestrelMetrics.AddConnectionEndReason(_context.MetricsContext, ConnectionEndReason.UnexpectedEndOfRequestContent); KestrelBadHttpRequestException.Throw(RequestRejectionReason.UnexpectedEndOfRequestContent); } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs index 92e0a5a04630..d6adf36bdd3c 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Globalization; using System.IO.Pipelines; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Http; @@ -114,6 +115,13 @@ protected async Task OnConsumeAsyncAwaited() } } + protected override void OnOnbservedBytesExceedMaxRequestBodySize(long? maxRequestBodySize) + { + _context.DisableHttp1KeepAlive(); + KestrelMetrics.AddConnectionEndReason(_context.MetricsContext, ConnectionEndReason.MaxRequestBodySizeExceeded); + KestrelBadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTooLarge, maxRequestBodySize.GetValueOrDefault().ToString(CultureInfo.InvariantCulture)); + } + public static MessageBody For( HttpVersion httpVersion, HttpRequestHeaders headers, @@ -226,6 +234,7 @@ protected void ThrowUnexpectedEndOfRequestContent() // closing the connection without a response as expected. ((IHttpOutputAborter)_context).OnInputOrOutputCompleted(); + KestrelMetrics.AddConnectionEndReason(_context.MetricsContext, ConnectionEndReason.UnexpectedEndOfRequestContent); KestrelBadHttpRequestException.Throw(RequestRejectionReason.UnexpectedEndOfRequestContent); } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/MessageBody.cs b/src/Servers/Kestrel/Core/src/Internal/Http/MessageBody.cs index 9f0980cd4cda..f9e80c8fb169 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/MessageBody.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/MessageBody.cs @@ -191,11 +191,15 @@ protected void AddAndCheckObservedBytes(long observedBytes) var maxRequestBodySize = _context.MaxRequestBodySize; if (_observedBytes > maxRequestBodySize) { - _context.DisableHttp1KeepAlive(); - KestrelBadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTooLarge, maxRequestBodySize.GetValueOrDefault().ToString(CultureInfo.InvariantCulture)); + OnOnbservedBytesExceedMaxRequestBodySize(maxRequestBodySize); } } + protected virtual void OnOnbservedBytesExceedMaxRequestBodySize(long? maxRequestBodySize) + { + KestrelBadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTooLarge, maxRequestBodySize.GetValueOrDefault().ToString(CultureInfo.InvariantCulture)); + } + protected ValueTask StartTimingReadAsync(ValueTask readAwaitable, CancellationToken cancellationToken) { if (!readAwaitable.IsCompleted) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/RequestRejectionReason.cs b/src/Servers/Kestrel/Core/src/Internal/Http/RequestRejectionReason.cs index 0194f09f16d6..827192823023 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/RequestRejectionReason.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/RequestRejectionReason.cs @@ -31,6 +31,5 @@ internal enum RequestRejectionReason ConnectMethodRequired, MissingHostHeader, MultipleHostHeaders, - InvalidHostHeader, - RequestBodyExceedsContentLength + InvalidHostHeader } diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs index de277606c0ad..78fcbec05ba2 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs @@ -506,6 +506,9 @@ internal static bool TryGetErrorType(ConnectionEndReason reason, [NotNullWhen(tr ConnectionEndReason.AppShutdown => "app_shutdown", ConnectionEndReason.TlsHandshakeFailed => "tls_handshake_failed", ConnectionEndReason.InvalidRequestLine => "invalid_request_line", + ConnectionEndReason.TlsOverHttp => "tls_over_http", + ConnectionEndReason.MaxRequestBodySizeExceeded => "max_request_body_size_exceeded", + ConnectionEndReason.UnexpectedEndOfRequestContent => "unexpected_end_of_request_content", _ => throw new InvalidOperationException($"Unable to calculate whether {reason} resolves to error.type value.") }; diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ChunkedRequestTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ChunkedRequestTests.cs index 1642d31d7a82..576a4058ae41 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ChunkedRequestTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ChunkedRequestTests.cs @@ -1,20 +1,17 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Buffers; -using System.Collections.Generic; using System.Globalization; -using System.IO; -using System.Linq; using System.Text; -using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.InternalTesting; +using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport; -using Microsoft.AspNetCore.InternalTesting; +using Microsoft.Extensions.Diagnostics.Metrics.Testing; using Microsoft.Extensions.Logging; -using Xunit; using BadHttpRequestException = Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException; namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests; @@ -852,7 +849,10 @@ public async Task ChunkedNotFinalTransferCodingResultsIn400() [Fact] public async Task ClosingConnectionMidChunkPrefixThrows() { - var testContext = new TestServiceContext(LoggerFactory); + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + + var testContext = new TestServiceContext(LoggerFactory, metrics: new KestrelMetrics(testMeterFactory)); var readStartedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); #pragma warning disable CS0618 // Type or member is obsolete var exTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -898,6 +898,11 @@ public async Task ClosingConnectionMidChunkPrefixThrows() Assert.Equal(RequestRejectionReason.UnexpectedEndOfRequestContent, badReqEx.Reason); } } + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => + { + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.UnexpectedEndOfRequestContent), m.Tags[KestrelMetrics.ErrorType]); + }); } [Fact] diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/MaxRequestBodySizeTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/MaxRequestBodySizeTests.cs index 2d95a3a20991..09c746201450 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/MaxRequestBodySizeTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/MaxRequestBodySizeTests.cs @@ -12,6 +12,8 @@ using Microsoft.AspNetCore.InternalTesting; using Xunit; using BadHttpRequestException = Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException; +using Microsoft.Extensions.Diagnostics.Metrics.Testing; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests; @@ -107,6 +109,9 @@ public async Task RejectsRequestWithBodySizeExceedingPerRequestLimitAndException [Fact] public async Task RejectsRequestWithChunckedBodySizeExceedingPerRequestLimitAndExceptionWasCaughtByApplication() { + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + var maxRequestBodySize = 3; var customApplicationResponse = "custom"; var chunkedPayload = $"5;random chunk extension\r\nHello\r\n6\r\n World\r\n0\r\n"; @@ -127,7 +132,7 @@ public async Task RejectsRequestWithChunckedBodySizeExceedingPerRequestLimitAndE await context.Response.WriteAsync(customApplicationResponse); throw requestRejectedEx; }, - new TestServiceContext(LoggerFactory) { ServerOptions = { Limits = { MaxRequestBodySize = maxRequestBodySize } } })) + new TestServiceContext(LoggerFactory, metrics: new KestrelMetrics(testMeterFactory)) { ServerOptions = { Limits = { MaxRequestBodySize = maxRequestBodySize } } })) { using var connection = server.CreateConnection(); await connection.Send( @@ -146,6 +151,11 @@ public async Task RejectsRequestWithChunckedBodySizeExceedingPerRequestLimitAndE customApplicationResponse, ""); } + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => + { + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MaxRequestBodySizeExceeded), m.Tags[KestrelMetrics.ErrorType]); + }); } [Fact] @@ -355,6 +365,9 @@ public async Task SettingMaxRequestBodySizeAfterUpgradingRequestThrows() [Fact] public async Task EveryReadFailsWhenContentLengthHeaderExceedsGlobalLimit() { + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + #pragma warning disable CS0618 // Type or member is obsolete BadHttpRequestException requestRejectedEx1 = null; BadHttpRequestException requestRejectedEx2 = null; @@ -371,7 +384,7 @@ public async Task EveryReadFailsWhenContentLengthHeaderExceedsGlobalLimit() #pragma warning restore CS0618 // Type or member is obsolete throw requestRejectedEx2; }, - new TestServiceContext(LoggerFactory) { ServerOptions = { Limits = { MaxRequestBodySize = 0 } } })) + new TestServiceContext(LoggerFactory, metrics: new KestrelMetrics(testMeterFactory)) { ServerOptions = { Limits = { MaxRequestBodySize = 0 } } })) { using (var connection = server.CreateConnection()) { @@ -395,6 +408,11 @@ public async Task EveryReadFailsWhenContentLengthHeaderExceedsGlobalLimit() Assert.NotNull(requestRejectedEx2); Assert.Equal(CoreStrings.FormatBadRequest_RequestBodyTooLarge(0), requestRejectedEx1.Message); Assert.Equal(CoreStrings.FormatBadRequest_RequestBodyTooLarge(0), requestRejectedEx2.Message); + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => + { + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MaxRequestBodySizeExceeded), m.Tags[KestrelMetrics.ErrorType]); + }); } [Fact] diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs index 4464fecefa87..876a7b4523b5 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs @@ -15,6 +15,7 @@ using Microsoft.AspNetCore.InternalTesting; using Microsoft.Extensions.Logging; using Moq; +using Microsoft.Extensions.Diagnostics.Metrics.Testing; namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests; @@ -1152,7 +1153,10 @@ public async Task ContentLengthReadAsyncPipeReaderReadsCompletedBody() [Fact] public async Task ContentLengthReadAsyncSingleBytesAtATime() { - var testContext = new TestServiceContext(LoggerFactory); + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + + var testContext = new TestServiceContext(LoggerFactory, metrics: new KestrelMetrics(testMeterFactory)); var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var tcs2 = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -1221,6 +1225,11 @@ static async Task ReadAtLeastAsync(PipeReader reader, int numBytes) ""); } } + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => + { + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.UnexpectedEndOfRequestContent), m.Tags[KestrelMetrics.ErrorType]); + }); } [Fact] @@ -2246,6 +2255,39 @@ await using (var server = new TestServer(_ => Task.CompletedTask, testContext)) } } + [Fact] + public async Task TlsOverHttp() + { + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + + var testContext = new TestServiceContext(LoggerFactory, metrics: new KestrelMetrics(testMeterFactory)); + + await using (var server = new TestServer(context => + { + return Task.CompletedTask; + }, testContext)) + { + using (var connection = server.CreateConnection()) + { + await connection.Stream.WriteAsync(new byte[] { 0x16, 0x03, 0x01, 0x02, 0x00, 0x01, 0x00, 0xfc, 0x03, 0x03, 0x03, 0xca, 0xe0, 0xfd, 0x0a }).DefaultTimeout(); + + await connection.ReceiveEnd( + "HTTP/1.1 400 Bad Request", + "Content-Length: 0", + "Connection: close", + $"Date: {testContext.DateHeaderValue}", + "", + ""); + } + } + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => + { + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.TlsOverHttp), m.Tags[KestrelMetrics.ErrorType]); + }); + } + [Fact] public async Task CustomRequestHeaderEncodingSelectorCanBeConfigured() { diff --git a/src/Shared/ServerInfrastructure/Http2/ConnectionEndReason.cs b/src/Shared/ServerInfrastructure/Http2/ConnectionEndReason.cs index 04d75460a241..41bb848a8083 100644 --- a/src/Shared/ServerInfrastructure/Http2/ConnectionEndReason.cs +++ b/src/Shared/ServerInfrastructure/Http2/ConnectionEndReason.cs @@ -46,5 +46,8 @@ internal enum ConnectionEndReason AppShutdown, GracefulAppShutdown, TransportCompleted, - TlsHandshakeFailed + TlsHandshakeFailed, + TlsOverHttp, + MaxRequestBodySizeExceeded, + UnexpectedEndOfRequestContent } From 22f30c6d8f2ce9df9e56a1ade199077c13122ee5 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Mon, 1 Jul 2024 07:58:26 +0800 Subject: [PATCH 27/29] More tests --- .../Http2/TlsTests.cs | 12 +++- .../KeepAliveTimeoutTests.cs | 62 +++++++++++++++++-- 2 files changed, 67 insertions(+), 7 deletions(-) diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/TlsTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/TlsTests.cs index 1dee2193a9d7..c6cd7f77b2c6 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/TlsTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/TlsTests.cs @@ -17,6 +17,8 @@ using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport; using Microsoft.AspNetCore.InternalTesting; using Xunit; +using Microsoft.Extensions.Diagnostics.Metrics.Testing; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.Http2; @@ -33,6 +35,9 @@ public class TlsTests : LoggedTest SkipReason = "Windows versions newer than 20H2 do not enable TLS 1.1: https://github.com/dotnet/aspnetcore/issues/37761")] public async Task TlsHandshakeRejectsTlsLessThan12() { + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + await using (var server = new TestServer(context => { var tlsFeature = context.Features.Get(); @@ -41,7 +46,7 @@ public async Task TlsHandshakeRejectsTlsLessThan12() return context.Response.WriteAsync("hello world " + context.Request.Protocol); }, - new TestServiceContext(LoggerFactory), + new TestServiceContext(LoggerFactory, metrics: new KestrelMetrics(testMeterFactory)), listenOptions => { listenOptions.Protocols = HttpProtocols.Http2; @@ -71,6 +76,11 @@ public async Task TlsHandshakeRejectsTlsLessThan12() reader.Complete(); } } + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => + { + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.InsufficientTlsVersion), m.Tags[KestrelMetrics.ErrorType]); + }); } private async Task WaitForConnectionErrorAsync(PipeReader reader, bool ignoreNonGoAwayFrames, int expectedLastStreamId, Http2ErrorCode expectedErrorCode) diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/KeepAliveTimeoutTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/KeepAliveTimeoutTests.cs index aecd2637f4d3..fd799942e29b 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/KeepAliveTimeoutTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/KeepAliveTimeoutTests.cs @@ -8,6 +8,8 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport; using Microsoft.AspNetCore.InternalTesting; +using Microsoft.Extensions.Diagnostics.Metrics.Testing; +using Microsoft.AspNetCore.Server.Kestrel.Core; namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests; @@ -22,7 +24,10 @@ public class KeepAliveTimeoutTests : LoggedTest [Fact] public async Task ConnectionClosedWhenKeepAliveTimeoutExpires() { - var testContext = new TestServiceContext(LoggerFactory); + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + + var testContext = new TestServiceContext(LoggerFactory, metrics: new KestrelMetrics(testMeterFactory)); await using (var server = CreateServer(testContext)) { @@ -44,12 +49,20 @@ await using (var server = CreateServer(testContext)) await connection.WaitForConnectionClose(); } } + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => + { + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.KeepAliveTimeout), m.Tags[KestrelMetrics.ErrorType]); + }); } [Fact] public async Task ConnectionKeptAliveBetweenRequests() { - var testContext = new TestServiceContext(LoggerFactory); + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + + var testContext = new TestServiceContext(LoggerFactory, metrics: new KestrelMetrics(testMeterFactory)); await using (var server = CreateServer(testContext)) { @@ -72,12 +85,20 @@ await using (var server = CreateServer(testContext)) } } } + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => + { + Assert.DoesNotContain(KestrelMetrics.ErrorType, m.Tags.Keys); + }); } [Fact] public async Task ConnectionNotTimedOutWhileRequestBeingSent() { - var testContext = new TestServiceContext(LoggerFactory); + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + + var testContext = new TestServiceContext(LoggerFactory, metrics: new KestrelMetrics(testMeterFactory)); await using (var server = CreateServer(testContext)) { @@ -112,12 +133,20 @@ await using (var server = CreateServer(testContext)) await ReceiveResponse(connection, testContext); } } + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => + { + Assert.DoesNotContain(KestrelMetrics.ErrorType, m.Tags.Keys); + }); } [Fact] private async Task ConnectionNotTimedOutWhileAppIsRunning() { - var testContext = new TestServiceContext(LoggerFactory); + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + + var testContext = new TestServiceContext(LoggerFactory, metrics: new KestrelMetrics(testMeterFactory)); var cts = new CancellationTokenSource(); await using (var server = CreateServer(testContext, longRunningCt: cts.Token)) @@ -152,12 +181,20 @@ await using (var server = CreateServer(testContext, longRunningCt: cts.Token)) await ReceiveResponse(connection, testContext); } } + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => + { + Assert.DoesNotContain(KestrelMetrics.ErrorType, m.Tags.Keys); + }); } [Fact] private async Task ConnectionTimesOutWhenOpenedButNoRequestSent() { - var testContext = new TestServiceContext(LoggerFactory); + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + + var testContext = new TestServiceContext(LoggerFactory, metrics: new KestrelMetrics(testMeterFactory)); await using (var server = CreateServer(testContext)) { @@ -172,12 +209,20 @@ await using (var server = CreateServer(testContext)) await connection.WaitForConnectionClose(); } } + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => + { + Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.KeepAliveTimeout), m.Tags[KestrelMetrics.ErrorType]); + }); } [Fact] private async Task KeepAliveTimeoutDoesNotApplyToUpgradedConnections() { - var testContext = new TestServiceContext(LoggerFactory); + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + + var testContext = new TestServiceContext(LoggerFactory, metrics: new KestrelMetrics(testMeterFactory)); var cts = new CancellationTokenSource(); await using (var server = CreateServer(testContext, upgradeCt: cts.Token)) @@ -210,6 +255,11 @@ await using (var server = CreateServer(testContext, upgradeCt: cts.Token)) await connection.Receive("hello, world"); } } + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => + { + Assert.DoesNotContain(KestrelMetrics.ErrorType, m.Tags.Keys); + }); } private TestServer CreateServer(TestServiceContext context, CancellationToken longRunningCt = default, CancellationToken upgradeCt = default) From be9f89e65c6d4c15290430a6752989404ac07474 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Mon, 1 Jul 2024 18:19:01 +0800 Subject: [PATCH 28/29] Add exceeded max concurrent connections reason --- .../Internal/Infrastructure/KestrelMetrics.cs | 3 +++ .../ConnectionMiddlewareTests.cs | 8 +----- .../ConnectionLimitTests.cs | 25 +++++++++++++++++++ .../ConnectionMiddlewareTests.cs | 12 ++++++++- .../Http2/ConnectionEndReason.cs | 3 ++- 5 files changed, 42 insertions(+), 9 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs index 78fcbec05ba2..46c6da2f26cf 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs @@ -157,6 +157,8 @@ private void ConnectionStopCore(in ConnectionMetricsContext metricsContext, Exce public void ConnectionRejected(in ConnectionMetricsContext metricsContext) { + AddConnectionEndReason(metricsContext, ConnectionEndReason.MaxConcurrentConnectionsExceeded); + // Check live rather than cached state because this is just a counter, it's not a start/stop event like the other metrics. if (_rejectedConnectionsCounter.Enabled) { @@ -509,6 +511,7 @@ internal static bool TryGetErrorType(ConnectionEndReason reason, [NotNullWhen(tr ConnectionEndReason.TlsOverHttp => "tls_over_http", ConnectionEndReason.MaxRequestBodySizeExceeded => "max_request_body_size_exceeded", ConnectionEndReason.UnexpectedEndOfRequestContent => "unexpected_end_of_request_content", + ConnectionEndReason.MaxConcurrentConnectionsExceeded => "max_concurrent_connections_exceeded", _ => throw new InvalidOperationException($"Unable to calculate whether {reason} resolves to error.type value.") }; diff --git a/src/Servers/Kestrel/test/FunctionalTests/ConnectionMiddlewareTests.cs b/src/Servers/Kestrel/test/FunctionalTests/ConnectionMiddlewareTests.cs index f4b0af365b16..ccc023b7be00 100644 --- a/src/Servers/Kestrel/test/FunctionalTests/ConnectionMiddlewareTests.cs +++ b/src/Servers/Kestrel/test/FunctionalTests/ConnectionMiddlewareTests.cs @@ -1,17 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Concurrent; -using System.IO; using System.IO.Pipelines; using System.Net; -using System.Threading; -using System.Threading.Tasks; +using Microsoft.AspNetCore.InternalTesting; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.FunctionalTests; -using Microsoft.AspNetCore.InternalTesting; -using Xunit; #if SOCKETS namespace Microsoft.AspNetCore.Server.Kestrel.Sockets.FunctionalTests; diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ConnectionLimitTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ConnectionLimitTests.cs index 859ecad6373a..2c4e17ffa7cf 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ConnectionLimitTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ConnectionLimitTests.cs @@ -104,6 +104,7 @@ public async Task RejectsConnectionsWhenLimitReached() { var testMeterFactory = new TestMeterFactory(); using var rejectedConnections = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.rejected_connections"); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); const int max = 10; var requestTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -149,6 +150,30 @@ public async Task RejectsConnectionsWhenLimitReached() } } + var measurements = connectionDuration.GetMeasurementSnapshot(); + + Assert.Collection(measurements, + m => Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MaxConcurrentConnectionsExceeded), m.Tags[KestrelMetrics.ErrorType]), + m => Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MaxConcurrentConnectionsExceeded), m.Tags[KestrelMetrics.ErrorType]), + m => Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MaxConcurrentConnectionsExceeded), m.Tags[KestrelMetrics.ErrorType]), + m => Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MaxConcurrentConnectionsExceeded), m.Tags[KestrelMetrics.ErrorType]), + m => Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MaxConcurrentConnectionsExceeded), m.Tags[KestrelMetrics.ErrorType]), + m => Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MaxConcurrentConnectionsExceeded), m.Tags[KestrelMetrics.ErrorType]), + m => Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MaxConcurrentConnectionsExceeded), m.Tags[KestrelMetrics.ErrorType]), + m => Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MaxConcurrentConnectionsExceeded), m.Tags[KestrelMetrics.ErrorType]), + m => Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MaxConcurrentConnectionsExceeded), m.Tags[KestrelMetrics.ErrorType]), + m => Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MaxConcurrentConnectionsExceeded), m.Tags[KestrelMetrics.ErrorType]), + m => Assert.DoesNotContain(KestrelMetrics.ErrorType, m.Tags.Keys), + m => Assert.DoesNotContain(KestrelMetrics.ErrorType, m.Tags.Keys), + m => Assert.DoesNotContain(KestrelMetrics.ErrorType, m.Tags.Keys), + m => Assert.DoesNotContain(KestrelMetrics.ErrorType, m.Tags.Keys), + m => Assert.DoesNotContain(KestrelMetrics.ErrorType, m.Tags.Keys), + m => Assert.DoesNotContain(KestrelMetrics.ErrorType, m.Tags.Keys), + m => Assert.DoesNotContain(KestrelMetrics.ErrorType, m.Tags.Keys), + m => Assert.DoesNotContain(KestrelMetrics.ErrorType, m.Tags.Keys), + m => Assert.DoesNotContain(KestrelMetrics.ErrorType, m.Tags.Keys), + m => Assert.DoesNotContain(KestrelMetrics.ErrorType, m.Tags.Keys)); + static void AssertCounter(CollectedMeasurement measurement) => Assert.Equal(1, measurement.Value); } diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ConnectionMiddlewareTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ConnectionMiddlewareTests.cs index 07d6502a7ff4..2f56f8294c64 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ConnectionMiddlewareTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ConnectionMiddlewareTests.cs @@ -16,6 +16,8 @@ using Microsoft.AspNetCore.InternalTesting; using Microsoft.Extensions.Logging.Testing; using Xunit; +using Microsoft.Extensions.Diagnostics.Metrics.Testing; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests; @@ -150,10 +152,13 @@ await using (var server = new TestServer(requestDelegate, serviceContext, listen [MemberData(nameof(EchoAppRequestDelegates))] public async Task ImmediateFinAfterThrowingClosesGracefully(RequestDelegate requestDelegate) { + var testMeterFactory = new TestMeterFactory(); + using var connectionDuration = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"); + var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); listenOptions.Use(next => context => throw new InvalidOperationException()); - var serviceContext = new TestServiceContext(LoggerFactory); + var serviceContext = new TestServiceContext(LoggerFactory, metrics: new KestrelMetrics(testMeterFactory)); await using (var server = new TestServer(requestDelegate, serviceContext, listenOptions)) { @@ -164,6 +169,11 @@ await using (var server = new TestServer(requestDelegate, serviceContext, listen await connection.WaitForConnectionClose(); } } + + Assert.Collection(connectionDuration.GetMeasurementSnapshot(), m => + { + Assert.Equal(typeof(InvalidOperationException).FullName, m.Tags[KestrelMetrics.ErrorType]); + }); } [Theory] diff --git a/src/Shared/ServerInfrastructure/Http2/ConnectionEndReason.cs b/src/Shared/ServerInfrastructure/Http2/ConnectionEndReason.cs index 41bb848a8083..2c7858893bc3 100644 --- a/src/Shared/ServerInfrastructure/Http2/ConnectionEndReason.cs +++ b/src/Shared/ServerInfrastructure/Http2/ConnectionEndReason.cs @@ -49,5 +49,6 @@ internal enum ConnectionEndReason TlsHandshakeFailed, TlsOverHttp, MaxRequestBodySizeExceeded, - UnexpectedEndOfRequestContent + UnexpectedEndOfRequestContent, + MaxConcurrentConnectionsExceeded } From 4b4839f9d52fe19a21d1674783cf18477f716e8d Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Mon, 1 Jul 2024 21:59:30 +0800 Subject: [PATCH 29/29] Flaky test --- .../ConnectionLimitTests.cs | 33 +++++++------------ 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ConnectionLimitTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ConnectionLimitTests.cs index 2c4e17ffa7cf..296a001132ca 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ConnectionLimitTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ConnectionLimitTests.cs @@ -152,27 +152,18 @@ public async Task RejectsConnectionsWhenLimitReached() var measurements = connectionDuration.GetMeasurementSnapshot(); - Assert.Collection(measurements, - m => Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MaxConcurrentConnectionsExceeded), m.Tags[KestrelMetrics.ErrorType]), - m => Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MaxConcurrentConnectionsExceeded), m.Tags[KestrelMetrics.ErrorType]), - m => Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MaxConcurrentConnectionsExceeded), m.Tags[KestrelMetrics.ErrorType]), - m => Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MaxConcurrentConnectionsExceeded), m.Tags[KestrelMetrics.ErrorType]), - m => Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MaxConcurrentConnectionsExceeded), m.Tags[KestrelMetrics.ErrorType]), - m => Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MaxConcurrentConnectionsExceeded), m.Tags[KestrelMetrics.ErrorType]), - m => Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MaxConcurrentConnectionsExceeded), m.Tags[KestrelMetrics.ErrorType]), - m => Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MaxConcurrentConnectionsExceeded), m.Tags[KestrelMetrics.ErrorType]), - m => Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MaxConcurrentConnectionsExceeded), m.Tags[KestrelMetrics.ErrorType]), - m => Assert.Equal(KestrelMetrics.GetErrorType(ConnectionEndReason.MaxConcurrentConnectionsExceeded), m.Tags[KestrelMetrics.ErrorType]), - m => Assert.DoesNotContain(KestrelMetrics.ErrorType, m.Tags.Keys), - m => Assert.DoesNotContain(KestrelMetrics.ErrorType, m.Tags.Keys), - m => Assert.DoesNotContain(KestrelMetrics.ErrorType, m.Tags.Keys), - m => Assert.DoesNotContain(KestrelMetrics.ErrorType, m.Tags.Keys), - m => Assert.DoesNotContain(KestrelMetrics.ErrorType, m.Tags.Keys), - m => Assert.DoesNotContain(KestrelMetrics.ErrorType, m.Tags.Keys), - m => Assert.DoesNotContain(KestrelMetrics.ErrorType, m.Tags.Keys), - m => Assert.DoesNotContain(KestrelMetrics.ErrorType, m.Tags.Keys), - m => Assert.DoesNotContain(KestrelMetrics.ErrorType, m.Tags.Keys), - m => Assert.DoesNotContain(KestrelMetrics.ErrorType, m.Tags.Keys)); + var connectionErrors = measurements + .GroupBy(m => + { + m.Tags.TryGetValue(KestrelMetrics.ErrorType, out var value); + return value as string; + }) + .ToList(); + + // 10 successful connections. + Assert.Equal(10, connectionErrors.Single(e => e.Key == null).Count()); + // 10 rejected connecitons. + Assert.Equal(10, connectionErrors.Single(e => e.Key == KestrelMetrics.GetErrorType(ConnectionEndReason.MaxConcurrentConnectionsExceeded)).Count()); static void AssertCounter(CollectedMeasurement measurement) => Assert.Equal(1, measurement.Value); }