@@ -547,6 +547,8 @@ public async Task ProcessRequestsAsync<TContext>(IHttpApplication<TContext> appl
547
547
{
548
548
try
549
549
{
550
+ // We run the request processing loop in a seperate async method so per connection
551
+ // exception handling doesn't complicate the generated asm for the loop.
550
552
await ProcessRequests ( application ) ;
551
553
}
552
554
catch ( BadHttpRequestException ex )
@@ -624,103 +626,111 @@ private async Task ProcessRequests<TContext>(IHttpApplication<TContext> applicat
624
626
625
627
InitializeBodyControl ( messageBody ) ;
626
628
627
- var context = application . CreateContext ( this ) ;
629
+ // We run user controlled request processing in a seperate async method
630
+ // so any changes made to ExecutionContext are undone when it returns and
631
+ // each request starts with a fresh ExecutionContext state.
632
+ await ProcessRequest ( application ) ;
628
633
629
- try
630
- {
631
- KestrelEventSource . Log . RequestStart ( this ) ;
632
-
633
- // Run the application code for this request
634
- await application . ProcessRequestAsync ( context ) ;
635
-
636
- // Trigger OnStarting if it hasn't been called yet and the app hasn't
637
- // already failed. If an OnStarting callback throws we can go through
638
- // our normal error handling in ProduceEnd.
639
- // https://github.com/aspnet/KestrelHttpServer/issues/43
640
- if ( ! HasResponseStarted && _applicationException == null && _onStarting ? . Count > 0 )
641
- {
642
- await FireOnStarting ( ) ;
643
- }
644
-
645
- if ( ! _connectionAborted && ! VerifyResponseContentLength ( out var lengthException ) )
646
- {
647
- ReportApplicationError ( lengthException ) ;
648
- }
649
- }
650
- catch ( BadHttpRequestException ex )
634
+ // Even for non-keep-alive requests, try to consume the entire body to avoid RSTs.
635
+ if ( ! _connectionAborted && _requestRejectedException == null && ! messageBody . IsEmpty )
651
636
{
652
- // Capture BadHttpRequestException for further processing
653
- // This has to be caught here so StatusCode is set properly before disposing the HttpContext
654
- // (DisposeContext logs StatusCode).
655
- SetBadRequestState ( ex ) ;
656
- ReportApplicationError ( ex ) ;
637
+ await messageBody . ConsumeAsync ( ) ;
657
638
}
658
- catch ( Exception ex )
639
+
640
+ if ( HasStartedConsumingRequestBody )
659
641
{
660
- ReportApplicationError ( ex ) ;
642
+ await messageBody . StopAsync ( ) ;
661
643
}
644
+ }
645
+ }
662
646
663
- KestrelEventSource . Log . RequestStop ( this ) ;
647
+ private async ValueTask ProcessRequest < TContext > ( IHttpApplication < TContext > application )
648
+ {
649
+ var context = application . CreateContext ( this ) ;
664
650
665
- // At this point all user code that needs use to the request or response streams has completed.
666
- // Using these streams in the OnCompleted callback is not allowed.
667
- try
668
- {
669
- await _bodyControl . StopAsync ( ) ;
670
- }
671
- catch ( Exception ex )
672
- {
673
- // BodyControl.StopAsync() can throw if the PipeWriter was completed prior to the application writing
674
- // enough bytes to satisfy the specified Content-Length. This risks double-logging the exception,
675
- // but this scenario generally indicates an app bug, so I don't want to risk not logging it.
676
- ReportApplicationError ( ex ) ;
677
- }
651
+ try
652
+ {
653
+ KestrelEventSource . Log . RequestStart ( this ) ;
678
654
679
- // 4XX responses are written by TryProduceInvalidRequestResponse during connection tear down.
680
- if ( _requestRejectedException == null )
655
+ // Run the application code for this request
656
+ await application . ProcessRequestAsync ( context ) ;
657
+
658
+ // Trigger OnStarting if it hasn't been called yet and the app hasn't
659
+ // already failed. If an OnStarting callback throws we can go through
660
+ // our normal error handling in ProduceEnd.
661
+ // https://github.com/aspnet/KestrelHttpServer/issues/43
662
+ if ( ! HasResponseStarted && _applicationException == null && _onStarting ? . Count > 0 )
681
663
{
682
- if ( ! _connectionAborted )
683
- {
684
- // Call ProduceEnd() before consuming the rest of the request body to prevent
685
- // delaying clients waiting for the chunk terminator:
686
- //
687
- // https://github.com/dotnet/corefx/issues/17330#issuecomment-288248663
688
- //
689
- // This also prevents the 100 Continue response from being sent if the app
690
- // never tried to read the body.
691
- // https://github.com/aspnet/KestrelHttpServer/issues/2102
692
- //
693
- // ProduceEnd() must be called before _application.DisposeContext(), to ensure
694
- // HttpContext.Response.StatusCode is correctly set when
695
- // IHttpContextFactory.Dispose(HttpContext) is called.
696
- await ProduceEnd ( ) ;
697
- }
698
- else if ( ! HasResponseStarted )
699
- {
700
- // If the request was aborted and no response was sent, there's no
701
- // meaningful status code to log.
702
- StatusCode = 0 ;
703
- }
664
+ await FireOnStarting ( ) ;
704
665
}
705
666
706
- if ( _onCompleted ? . Count > 0 )
667
+ if ( ! _connectionAborted && ! VerifyResponseContentLength ( out var lengthException ) )
707
668
{
708
- await FireOnCompleted ( ) ;
669
+ ReportApplicationError ( lengthException ) ;
709
670
}
671
+ }
672
+ catch ( BadHttpRequestException ex )
673
+ {
674
+ // Capture BadHttpRequestException for further processing
675
+ // This has to be caught here so StatusCode is set properly before disposing the HttpContext
676
+ // (DisposeContext logs StatusCode).
677
+ SetBadRequestState ( ex ) ;
678
+ ReportApplicationError ( ex ) ;
679
+ }
680
+ catch ( Exception ex )
681
+ {
682
+ ReportApplicationError ( ex ) ;
683
+ }
710
684
711
- application . DisposeContext ( context , _applicationException ) ;
685
+ KestrelEventSource . Log . RequestStop ( this ) ;
712
686
713
- // Even for non-keep-alive requests, try to consume the entire body to avoid RSTs.
714
- if ( ! _connectionAborted && _requestRejectedException == null && ! messageBody . IsEmpty )
687
+ // At this point all user code that needs use to the request or response streams has completed.
688
+ // Using these streams in the OnCompleted callback is not allowed.
689
+ try
690
+ {
691
+ await _bodyControl . StopAsync ( ) ;
692
+ }
693
+ catch ( Exception ex )
694
+ {
695
+ // BodyControl.StopAsync() can throw if the PipeWriter was completed prior to the application writing
696
+ // enough bytes to satisfy the specified Content-Length. This risks double-logging the exception,
697
+ // but this scenario generally indicates an app bug, so I don't want to risk not logging it.
698
+ ReportApplicationError ( ex ) ;
699
+ }
700
+
701
+ // 4XX responses are written by TryProduceInvalidRequestResponse during connection tear down.
702
+ if ( _requestRejectedException == null )
703
+ {
704
+ if ( ! _connectionAborted )
715
705
{
716
- await messageBody . ConsumeAsync ( ) ;
706
+ // Call ProduceEnd() before consuming the rest of the request body to prevent
707
+ // delaying clients waiting for the chunk terminator:
708
+ //
709
+ // https://github.com/dotnet/corefx/issues/17330#issuecomment-288248663
710
+ //
711
+ // This also prevents the 100 Continue response from being sent if the app
712
+ // never tried to read the body.
713
+ // https://github.com/aspnet/KestrelHttpServer/issues/2102
714
+ //
715
+ // ProduceEnd() must be called before _application.DisposeContext(), to ensure
716
+ // HttpContext.Response.StatusCode is correctly set when
717
+ // IHttpContextFactory.Dispose(HttpContext) is called.
718
+ await ProduceEnd ( ) ;
717
719
}
718
-
719
- if ( HasStartedConsumingRequestBody )
720
+ else if ( ! HasResponseStarted )
720
721
{
721
- await messageBody . StopAsync ( ) ;
722
+ // If the request was aborted and no response was sent, there's no
723
+ // meaningful status code to log.
724
+ StatusCode = 0 ;
722
725
}
723
726
}
727
+
728
+ if ( _onCompleted ? . Count > 0 )
729
+ {
730
+ await FireOnCompleted ( ) ;
731
+ }
732
+
733
+ application . DisposeContext ( context , _applicationException ) ;
724
734
}
725
735
726
736
public void OnStarting ( Func < object , Task > callback , object state )
@@ -749,108 +759,55 @@ public void OnCompleted(Func<object, Task> callback, object state)
749
759
protected Task FireOnStarting ( )
750
760
{
751
761
var onStarting = _onStarting ;
752
-
753
- if ( onStarting == null || onStarting . Count == 0 )
754
- {
755
- return Task . CompletedTask ;
756
- }
757
- else
762
+ if ( onStarting ? . Count > 0 )
758
763
{
759
- return FireOnStartingMayAwait ( onStarting ) ;
764
+ return ProcessEvents ( this , onStarting ) ;
760
765
}
761
- }
762
766
763
- private Task FireOnStartingMayAwait ( Stack < KeyValuePair < Func < object , Task > , object > > onStarting )
764
- {
765
- try
767
+ return Task . CompletedTask ;
768
+
769
+ static async Task ProcessEvents ( HttpProtocol protocol , Stack < KeyValuePair < Func < object , Task > , object > > events )
766
770
{
767
- while ( onStarting . TryPop ( out var entry ) )
771
+ // Try/Catch is outside the loop as any error that occurs is before the request starts.
772
+ // So we want to report it as an ApplicationError to fail the request and not process more events.
773
+ try
768
774
{
769
- var task = entry . Key . Invoke ( entry . Value ) ;
770
- if ( ! task . IsCompletedSuccessfully )
775
+ while ( events . TryPop ( out var entry ) )
771
776
{
772
- return FireOnStartingAwaited ( task , onStarting ) ;
777
+ await entry . Key . Invoke ( entry . Value ) ;
773
778
}
774
779
}
775
- }
776
- catch ( Exception ex )
777
- {
778
- ReportApplicationError ( ex ) ;
779
- }
780
-
781
- return Task . CompletedTask ;
782
- }
783
-
784
- private async Task FireOnStartingAwaited ( Task currentTask , Stack < KeyValuePair < Func < object , Task > , object > > onStarting )
785
- {
786
- try
787
- {
788
- await currentTask ;
789
-
790
- while ( onStarting . TryPop ( out var entry ) )
780
+ catch ( Exception ex )
791
781
{
792
- await entry . Key . Invoke ( entry . Value ) ;
782
+ protocol . ReportApplicationError ( ex ) ;
793
783
}
794
784
}
795
- catch ( Exception ex )
796
- {
797
- ReportApplicationError ( ex ) ;
798
- }
799
785
}
800
786
801
787
protected Task FireOnCompleted ( )
802
788
{
803
789
var onCompleted = _onCompleted ;
804
-
805
- if ( onCompleted == null || onCompleted . Count == 0 )
790
+ if ( onCompleted ? . Count > 0 )
806
791
{
807
- return Task . CompletedTask ;
792
+ return ProcessEvents ( this , onCompleted ) ;
808
793
}
809
794
810
- return FireOnCompletedMayAwait ( onCompleted ) ;
811
- }
795
+ return Task . CompletedTask ;
812
796
813
- private Task FireOnCompletedMayAwait ( Stack < KeyValuePair < Func < object , Task > , object > > onCompleted )
814
- {
815
- while ( onCompleted . TryPop ( out var entry ) )
797
+ static async Task ProcessEvents ( HttpProtocol protocol , Stack < KeyValuePair < Func < object , Task > , object > > events )
816
798
{
817
- try
799
+ // Try/Catch is inside the loop as any error that occurs is after the request has finished.
800
+ // So we will just log it and keep processing the events, as the completion has already happened.
801
+ while ( events . TryPop ( out var entry ) )
818
802
{
819
- var task = entry . Key . Invoke ( entry . Value ) ;
820
- if ( ! task . IsCompletedSuccessfully )
803
+ try
821
804
{
822
- return FireOnCompletedAwaited ( task , onCompleted ) ;
805
+ await entry . Key . Invoke ( entry . Value ) ;
806
+ }
807
+ catch ( Exception ex )
808
+ {
809
+ protocol . Log . ApplicationError ( protocol . ConnectionId , protocol . TraceIdentifier , ex ) ;
823
810
}
824
- }
825
- catch ( Exception ex )
826
- {
827
- ReportApplicationError ( ex ) ;
828
- }
829
- }
830
-
831
- return Task . CompletedTask ;
832
- }
833
-
834
- private async Task FireOnCompletedAwaited ( Task currentTask , Stack < KeyValuePair < Func < object , Task > , object > > onCompleted )
835
- {
836
- try
837
- {
838
- await currentTask ;
839
- }
840
- catch ( Exception ex )
841
- {
842
- Log . ApplicationError ( ConnectionId , TraceIdentifier , ex ) ;
843
- }
844
-
845
- while ( onCompleted . TryPop ( out var entry ) )
846
- {
847
- try
848
- {
849
- await entry . Key . Invoke ( entry . Value ) ;
850
- }
851
- catch ( Exception ex )
852
- {
853
- Log . ApplicationError ( ConnectionId , TraceIdentifier , ex ) ;
854
811
}
855
812
}
856
813
}
0 commit comments