@@ -62,13 +62,16 @@ public Http1Connection(HttpConnectionContext context)
62
62
_context . ServiceContext . Log ,
63
63
_context . TimeoutControl ,
64
64
minResponseDataRateFeature : this ,
65
+ MetricsContext ,
65
66
outputAborter : this ) ;
66
67
67
68
Input = _context . Transport . Input ;
68
69
Output = _http1Output ;
69
70
MemoryPool = _context . MemoryPool ;
70
71
}
71
72
73
+ public ConnectionMetricsContext MetricsContext => _context . MetricsContext ;
74
+
72
75
public PipeReader Input { get ; }
73
76
74
77
public bool RequestTimedOut => _requestTimedOut ;
@@ -82,7 +85,7 @@ protected override void OnRequestProcessingEnded()
82
85
if ( IsUpgraded )
83
86
{
84
87
KestrelEventSource . Log . RequestUpgradedStop ( this ) ;
85
- ServiceContext . Metrics . RequestUpgradedStop ( _context . MetricsContext ) ;
88
+ ServiceContext . Metrics . RequestUpgradedStop ( MetricsContext ) ;
86
89
87
90
ServiceContext . ConnectionManager . UpgradedConnectionCount . ReleaseOne ( ) ;
88
91
}
@@ -98,56 +101,66 @@ protected override void OnRequestProcessingEnded()
98
101
void IRequestProcessor . OnInputOrOutputCompleted ( )
99
102
{
100
103
// Closed gracefully.
101
- _http1Output . Abort ( ServerOptions . FinOnError ? new ConnectionAbortedException ( CoreStrings . ConnectionAbortedByClient ) : null ! ) ;
104
+ _http1Output . Abort ( ServerOptions . FinOnError ? new ConnectionAbortedException ( CoreStrings . ConnectionAbortedByClient ) : null ! , ConnectionEndReason . TransportCompleted ) ;
102
105
CancelRequestAbortedToken ( ) ;
103
106
}
104
107
105
108
void IHttpOutputAborter . OnInputOrOutputCompleted ( )
106
109
{
107
- _http1Output . Abort ( new ConnectionAbortedException ( CoreStrings . ConnectionAbortedByClient ) ) ;
110
+ _http1Output . Abort ( new ConnectionAbortedException ( CoreStrings . ConnectionAbortedByClient ) , ConnectionEndReason . TransportCompleted ) ;
108
111
CancelRequestAbortedToken ( ) ;
109
112
}
110
113
111
114
/// <summary>
112
115
/// Immediately kill the connection and poison the request body stream with an error.
113
116
/// </summary>
114
- public void Abort ( ConnectionAbortedException abortReason )
117
+ public void Abort ( ConnectionAbortedException abortReason , ConnectionEndReason reason )
115
118
{
116
- _http1Output . Abort ( abortReason ) ;
119
+ _http1Output . Abort ( abortReason , reason ) ;
117
120
CancelRequestAbortedToken ( ) ;
118
121
PoisonBody ( abortReason ) ;
119
122
}
120
123
121
124
protected override void ApplicationAbort ( )
122
125
{
123
126
Log . ApplicationAbortedConnection ( ConnectionId , TraceIdentifier ) ;
124
- Abort ( new ConnectionAbortedException ( CoreStrings . ConnectionAbortedByApplication ) ) ;
127
+ Abort ( new ConnectionAbortedException ( CoreStrings . ConnectionAbortedByApplication ) , ConnectionEndReason . AbortedByApp ) ;
125
128
}
126
129
127
130
/// <summary>
128
131
/// Stops the request processing loop between requests.
129
132
/// Called on all active connections when the server wants to initiate a shutdown
130
133
/// and after a keep-alive timeout.
131
134
/// </summary>
132
- public void StopProcessingNextRequest ( )
135
+ public void StopProcessingNextRequest ( ConnectionEndReason reason )
133
136
{
134
- _keepAlive = false ;
137
+ DisableKeepAlive ( reason ) ;
135
138
Input . CancelPendingRead ( ) ;
136
139
}
137
140
141
+ internal override void DisableKeepAlive ( ConnectionEndReason reason )
142
+ {
143
+ KestrelMetrics . AddConnectionEndReason ( MetricsContext , reason ) ;
144
+ _keepAlive = false ;
145
+ }
146
+
138
147
public void SendTimeoutResponse ( )
139
148
{
140
149
_requestTimedOut = true ;
141
150
Input . CancelPendingRead ( ) ;
142
151
}
143
152
144
153
public void HandleRequestHeadersTimeout ( )
145
- => SendTimeoutResponse ( ) ;
154
+ {
155
+ KestrelMetrics . AddConnectionEndReason ( MetricsContext , ConnectionEndReason . RequestHeadersTimeout ) ;
156
+ SendTimeoutResponse ( ) ;
157
+ }
146
158
147
159
public void HandleReadDataRateTimeout ( )
148
160
{
149
161
Debug . Assert ( MinRequestBodyDataRate != null ) ;
150
162
163
+ KestrelMetrics . AddConnectionEndReason ( MetricsContext , ConnectionEndReason . MinRequestBodyDataRate ) ;
151
164
Log . RequestBodyMinimumDataRateNotSatisfied ( ConnectionId , TraceIdentifier , MinRequestBodyDataRate . BytesPerSecond ) ;
152
165
SendTimeoutResponse ( ) ;
153
166
}
@@ -606,6 +619,7 @@ internal void EnsureHostHeaderExists()
606
619
}
607
620
else if ( ! HttpUtilities . IsHostHeaderValid ( hostText ) )
608
621
{
622
+ KestrelMetrics . AddConnectionEndReason ( MetricsContext , ConnectionEndReason . InvalidRequestHeaders ) ;
609
623
KestrelBadHttpRequestException . Throw ( RequestRejectionReason . InvalidHostHeader , hostText ) ;
610
624
}
611
625
}
@@ -616,6 +630,7 @@ private void ValidateNonOriginHostHeader(string hostText)
616
630
{
617
631
if ( hostText != RawTarget )
618
632
{
633
+ KestrelMetrics . AddConnectionEndReason ( MetricsContext , ConnectionEndReason . InvalidRequestHeaders ) ;
619
634
KestrelBadHttpRequestException . Throw ( RequestRejectionReason . InvalidHostHeader , hostText ) ;
620
635
}
621
636
}
@@ -640,6 +655,7 @@ private void ValidateNonOriginHostHeader(string hostText)
640
655
}
641
656
else
642
657
{
658
+ KestrelMetrics . AddConnectionEndReason ( MetricsContext , ConnectionEndReason . InvalidRequestHeaders ) ;
643
659
KestrelBadHttpRequestException . Throw ( RequestRejectionReason . InvalidHostHeader , hostText ) ;
644
660
}
645
661
}
@@ -648,6 +664,7 @@ private void ValidateNonOriginHostHeader(string hostText)
648
664
649
665
if ( ! HttpUtilities . IsHostHeaderValid ( hostText ) )
650
666
{
667
+ KestrelMetrics . AddConnectionEndReason ( MetricsContext , ConnectionEndReason . InvalidRequestHeaders ) ;
651
668
KestrelBadHttpRequestException . Throw ( RequestRejectionReason . InvalidHostHeader , hostText ) ;
652
669
}
653
670
}
@@ -707,11 +724,15 @@ protected override bool TryParseRequest(ReadResult result, out bool endConnectio
707
724
#pragma warning disable CS0618 // Type or member is obsolete
708
725
catch ( BadHttpRequestException ex )
709
726
{
710
- DetectHttp2Preface ( result . Buffer , ex ) ;
711
-
727
+ OnBadRequest ( result . Buffer , ex ) ;
712
728
throw ;
713
729
}
714
730
#pragma warning restore CS0618 // Type or member is obsolete
731
+ catch ( Exception )
732
+ {
733
+ KestrelMetrics . AddConnectionEndReason ( MetricsContext , ConnectionEndReason . OtherError ) ;
734
+ throw ;
735
+ }
715
736
finally
716
737
{
717
738
Input . AdvanceTo ( reader . Position , isConsumed ? reader . Position : result . Buffer . End ) ;
@@ -758,9 +779,65 @@ protected override bool TryParseRequest(ReadResult result, out bool endConnectio
758
779
}
759
780
}
760
781
782
+ internal static ConnectionEndReason GetConnectionEndReason ( Microsoft . AspNetCore . Http . BadHttpRequestException ex )
783
+ {
784
+ #pragma warning disable CS0618 // Type or member is obsolete
785
+ var kestrelEx = ex as BadHttpRequestException ;
786
+ #pragma warning restore CS0618 // Type or member is obsolete
787
+
788
+ switch ( kestrelEx ? . Reason )
789
+ {
790
+ case RequestRejectionReason . UnrecognizedHTTPVersion :
791
+ return ConnectionEndReason . InvalidHttpVersion ;
792
+ case RequestRejectionReason . InvalidRequestLine :
793
+ case RequestRejectionReason . RequestLineTooLong :
794
+ case RequestRejectionReason . InvalidRequestTarget :
795
+ return ConnectionEndReason . InvalidRequestLine ;
796
+ case RequestRejectionReason . InvalidRequestHeadersNoCRLF :
797
+ case RequestRejectionReason . InvalidRequestHeader :
798
+ case RequestRejectionReason . InvalidContentLength :
799
+ case RequestRejectionReason . MultipleContentLengths :
800
+ case RequestRejectionReason . MalformedRequestInvalidHeaders :
801
+ case RequestRejectionReason . InvalidCharactersInHeaderName :
802
+ case RequestRejectionReason . LengthRequiredHttp10 :
803
+ case RequestRejectionReason . OptionsMethodRequired :
804
+ case RequestRejectionReason . ConnectMethodRequired :
805
+ case RequestRejectionReason . MissingHostHeader :
806
+ case RequestRejectionReason . MultipleHostHeaders :
807
+ case RequestRejectionReason . InvalidHostHeader :
808
+ return ConnectionEndReason . InvalidRequestHeaders ;
809
+ case RequestRejectionReason . HeadersExceedMaxTotalSize :
810
+ return ConnectionEndReason . MaxRequestHeadersTotalSizeExceeded ;
811
+ case RequestRejectionReason . TooManyHeaders :
812
+ return ConnectionEndReason . MaxRequestHeaderCountExceeded ;
813
+ case RequestRejectionReason . TlsOverHttpError :
814
+ return ConnectionEndReason . TlsNotSupported ;
815
+ case RequestRejectionReason . UnexpectedEndOfRequestContent :
816
+ return ConnectionEndReason . UnexpectedEndOfRequestContent ;
817
+ default :
818
+ // In some scenarios the end reason might already be set to a more specific error
819
+ // and attempting to set the reason again has no impact.
820
+ return ConnectionEndReason . OtherError ;
821
+ }
822
+ }
823
+
761
824
#pragma warning disable CS0618 // Type or member is obsolete
762
- private void DetectHttp2Preface ( ReadOnlySequence < byte > requestData , BadHttpRequestException ex )
825
+ private void OnBadRequest ( ReadOnlySequence < byte > requestData , BadHttpRequestException ex )
763
826
#pragma warning restore CS0618 // Type or member is obsolete
827
+ {
828
+ // Some code shared between HTTP versions throws errors. For example, HttpRequestHeaders collection
829
+ // throws when an invalid content length is set.
830
+ // Only want to set a reasons for HTTP/1.1 connection, so set end reason by catching the exception here.
831
+ var reason = GetConnectionEndReason ( ex ) ;
832
+ KestrelMetrics . AddConnectionEndReason ( MetricsContext , reason ) ;
833
+
834
+ if ( ex . Reason == RequestRejectionReason . UnrecognizedHTTPVersion )
835
+ {
836
+ DetectHttp2Preface ( requestData ) ;
837
+ }
838
+ }
839
+
840
+ private void DetectHttp2Preface ( ReadOnlySequence < byte > requestData )
764
841
{
765
842
const int PrefaceLineLength = 16 ;
766
843
@@ -770,8 +847,7 @@ private void DetectHttp2Preface(ReadOnlySequence<byte> requestData, BadHttpReque
770
847
{
771
848
// If there is an unrecognized HTTP version, it is the first request on the connection, and the request line
772
849
// bytes matches the HTTP/2 preface request line bytes then log and return a HTTP/2 GOAWAY frame.
773
- if ( ex . Reason == RequestRejectionReason . UnrecognizedHTTPVersion
774
- && _requestCount == 1
850
+ if ( _requestCount == 1
775
851
&& requestData . Length >= PrefaceLineLength )
776
852
{
777
853
var clientPrefaceRequestLine = Http2 . Http2Connection . ClientPreface . Slice ( 0 , PrefaceLineLength ) ;
0 commit comments