Lazy create ExceptionHandlerMiddleware statemachine (#9549)
This commit is contained in:
parent
e436fd9d08
commit
bbf7ed2907
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue