Lazy create ExceptionHandlerMiddleware statemachine (#9549)

This commit is contained in:
Ben Adams 2019-05-06 23:15:48 +01:00 committed by Chris Ross
parent e436fd9d08
commit bbf7ed2907
3 changed files with 83 additions and 47 deletions

View File

@ -71,8 +71,7 @@ namespace Microsoft.AspNetCore.Diagnostics
} }
public partial class ExceptionHandlerMiddleware public partial class ExceptionHandlerMiddleware
{ {
public ExceptionHandlerMiddleware(Microsoft.AspNetCore.Http.RequestDelegate next, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Builder.ExceptionHandlerOptions> options, System.Diagnostics.DiagnosticSource diagnosticSource) { } public ExceptionHandlerMiddleware(Microsoft.AspNetCore.Http.RequestDelegate next, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Builder.ExceptionHandlerOptions> options, System.Diagnostics.DiagnosticListener diagnosticListener) { }
[System.Diagnostics.DebuggerStepThroughAttribute]
public System.Threading.Tasks.Task Invoke(Microsoft.AspNetCore.Http.HttpContext context) { throw null; } public System.Threading.Tasks.Task Invoke(Microsoft.AspNetCore.Http.HttpContext context) { throw null; }
} }
public partial class StatusCodeContext public partial class StatusCodeContext

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.ExceptionServices;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.Internal; using Microsoft.AspNetCore.Diagnostics.Internal;
@ -19,19 +20,19 @@ namespace Microsoft.AspNetCore.Diagnostics
private readonly ExceptionHandlerOptions _options; private readonly ExceptionHandlerOptions _options;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly Func<object, Task> _clearCacheHeadersDelegate; private readonly Func<object, Task> _clearCacheHeadersDelegate;
private readonly DiagnosticSource _diagnosticSource; private readonly DiagnosticListener _diagnosticListener;
public ExceptionHandlerMiddleware( public ExceptionHandlerMiddleware(
RequestDelegate next, RequestDelegate next,
ILoggerFactory loggerFactory, ILoggerFactory loggerFactory,
IOptions<ExceptionHandlerOptions> options, IOptions<ExceptionHandlerOptions> options,
DiagnosticSource diagnosticSource) DiagnosticListener diagnosticListener)
{ {
_next = next; _next = next;
_options = options.Value; _options = options.Value;
_logger = loggerFactory.CreateLogger<ExceptionHandlerMiddleware>(); _logger = loggerFactory.CreateLogger<ExceptionHandlerMiddleware>();
_clearCacheHeadersDelegate = ClearCacheHeaders; _clearCacheHeadersDelegate = ClearCacheHeaders;
_diagnosticSource = diagnosticSource; _diagnosticListener = diagnosticListener;
if (_options.ExceptionHandler == null) if (_options.ExceptionHandler == null)
{ {
if (_options.ExceptionHandlingPath == null) if (_options.ExceptionHandlingPath == null)
@ -45,64 +46,99 @@ namespace Microsoft.AspNetCore.Diagnostics
} }
} }
public async Task Invoke(HttpContext context) public Task Invoke(HttpContext context)
{ {
ExceptionDispatchInfo edi;
try try
{ {
await _next(context); var task = _next(context);
} if (!task.IsCompletedSuccessfully)
catch (Exception ex)
{
_logger.UnhandledException(ex);
// We can't do anything if the response has already started, just abort.
if (context.Response.HasStarted)
{ {
_logger.ResponseStartedErrorHandler(); return Awaited(this, context, task);
throw;
} }
PathString originalPath = context.Request.Path; return Task.CompletedTask;
if (_options.ExceptionHandlingPath.HasValue) }
{ catch (Exception exception)
context.Request.Path = _options.ExceptionHandlingPath; {
} // Get the Exception, but don't continue processing in the catch block as its bad for stack usage.
edi = ExceptionDispatchInfo.Capture(exception);
}
return HandleException(context, edi);
static async Task Awaited(ExceptionHandlerMiddleware middleware, HttpContext context, Task task)
{
ExceptionDispatchInfo edi = null;
try try
{ {
context.Response.Clear(); await task;
var exceptionHandlerFeature = new ExceptionHandlerFeature()
{
Error = ex,
Path = originalPath.Value,
};
context.Features.Set<IExceptionHandlerFeature>(exceptionHandlerFeature);
context.Features.Set<IExceptionHandlerPathFeature>(exceptionHandlerFeature);
context.Response.StatusCode = 500;
context.Response.OnStarting(_clearCacheHeadersDelegate, context.Response);
await _options.ExceptionHandler(context);
if (_diagnosticSource.IsEnabled("Microsoft.AspNetCore.Diagnostics.HandledException"))
{
_diagnosticSource.Write("Microsoft.AspNetCore.Diagnostics.HandledException", new { httpContext = context, exception = ex });
}
// TODO: Optional re-throw? We'll re-throw the original exception by default if the error handler throws.
return;
} }
catch (Exception ex2) catch (Exception exception)
{ {
// Suppress secondary exceptions, re-throw the original. // Get the Exception, but don't continue processing in the catch block as its bad for stack usage.
_logger.ErrorHandlerException(ex2); edi = ExceptionDispatchInfo.Capture(exception);
} }
finally
if (edi != null)
{ {
context.Request.Path = originalPath; await middleware.HandleException(context, edi);
} }
throw; // Re-throw the original if we couldn't handle it
} }
} }
private Task ClearCacheHeaders(object state) private async Task HandleException(HttpContext context, ExceptionDispatchInfo edi)
{
_logger.UnhandledException(edi.SourceException);
// We can't do anything if the response has already started, just abort.
if (context.Response.HasStarted)
{
_logger.ResponseStartedErrorHandler();
edi.Throw();
}
PathString originalPath = context.Request.Path;
if (_options.ExceptionHandlingPath.HasValue)
{
context.Request.Path = _options.ExceptionHandlingPath;
}
try
{
context.Response.Clear();
var exceptionHandlerFeature = new ExceptionHandlerFeature()
{
Error = edi.SourceException,
Path = originalPath.Value,
};
context.Features.Set<IExceptionHandlerFeature>(exceptionHandlerFeature);
context.Features.Set<IExceptionHandlerPathFeature>(exceptionHandlerFeature);
context.Response.StatusCode = 500;
context.Response.OnStarting(_clearCacheHeadersDelegate, context.Response);
await _options.ExceptionHandler(context);
if (_diagnosticListener.IsEnabled() && _diagnosticListener.IsEnabled("Microsoft.AspNetCore.Diagnostics.HandledException"))
{
_diagnosticListener.Write("Microsoft.AspNetCore.Diagnostics.HandledException", new { httpContext = context, exception = edi.SourceException });
}
// TODO: Optional re-throw? We'll re-throw the original exception by default if the error handler throws.
return;
}
catch (Exception ex2)
{
// Suppress secondary exceptions, re-throw the original.
_logger.ErrorHandlerException(ex2);
}
finally
{
context.Request.Path = originalPath;
}
edi.Throw(); // Re-throw the original if we couldn't handle it
}
private static Task ClearCacheHeaders(object state)
{ {
var headers = ((HttpResponse)state).Headers; var headers = ((HttpResponse)state).Headers;
headers[HeaderNames.CacheControl] = "no-cache"; headers[HeaderNames.CacheControl] = "no-cache";

View File

@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace EchoApp namespace EchoApp