Reset endpoint and route values during exception handling.
- We initially did this change as part of EndpointRouting but the impact of that change resulted in a variety of performance regressions. To mitigate the impact of resetting state for a request we now only reset the state when an exception has occurred in a way that does not require any additional state machines to be allocated. - Added a test to validate that http context state gets reset on exception handling. #12897
This commit is contained in:
parent
4f6022323b
commit
1f0641f5c0
|
|
@ -7,6 +7,7 @@ using System.Runtime.ExceptionServices;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Microsoft.Net.Http.Headers;
|
using Microsoft.Net.Http.Headers;
|
||||||
|
|
@ -103,7 +104,8 @@ namespace Microsoft.AspNetCore.Diagnostics
|
||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
context.Response.Clear();
|
ClearHttpContext(context);
|
||||||
|
|
||||||
var exceptionHandlerFeature = new ExceptionHandlerFeature()
|
var exceptionHandlerFeature = new ExceptionHandlerFeature()
|
||||||
{
|
{
|
||||||
Error = edi.SourceException,
|
Error = edi.SourceException,
|
||||||
|
|
@ -137,6 +139,17 @@ namespace Microsoft.AspNetCore.Diagnostics
|
||||||
edi.Throw(); // Re-throw the original if we couldn't handle it
|
edi.Throw(); // Re-throw the original if we couldn't handle it
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void ClearHttpContext(HttpContext context)
|
||||||
|
{
|
||||||
|
context.Response.Clear();
|
||||||
|
|
||||||
|
// An endpoint may have already been set. Since we're going to re-invoke the middleware pipeline we need to reset
|
||||||
|
// the endpoint and route values to ensure things are re-calculated.
|
||||||
|
context.SetEndpoint(endpoint: null);
|
||||||
|
var routeValuesFeature = context.Features.Get<IRouteValuesFeature>();
|
||||||
|
routeValuesFeature?.RouteValues?.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
private static Task ClearCacheHeaders(object state)
|
private static Task ClearCacheHeaders(object state)
|
||||||
{
|
{
|
||||||
var headers = ((HttpResponse)state).Headers;
|
var headers = ((HttpResponse)state).Headers;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Diagnostics
|
||||||
|
{
|
||||||
|
public class ExceptionHandlerMiddlewareTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task Invoke_ExceptionThrownResultsInClearedRouteValuesAndEndpoint()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var httpContext = CreateHttpContext();
|
||||||
|
httpContext.SetEndpoint(new Endpoint((_) => Task.CompletedTask, new EndpointMetadataCollection(), "Test"));
|
||||||
|
httpContext.Request.RouteValues["John"] = "Doe";
|
||||||
|
|
||||||
|
var optionsAccessor = CreateOptionsAccessor(
|
||||||
|
exceptionHandler: context =>
|
||||||
|
{
|
||||||
|
Assert.Empty(context.Request.RouteValues);
|
||||||
|
Assert.Null(context.GetEndpoint());
|
||||||
|
return Task.CompletedTask;
|
||||||
|
});
|
||||||
|
var middleware = CreateMiddleware(_ => throw new InvalidOperationException(), optionsAccessor);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
await middleware.Invoke(httpContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpContext CreateHttpContext()
|
||||||
|
{
|
||||||
|
var httpContext = new DefaultHttpContext
|
||||||
|
{
|
||||||
|
RequestServices = new TestServiceProvider()
|
||||||
|
};
|
||||||
|
|
||||||
|
return httpContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IOptions<ExceptionHandlerOptions> CreateOptionsAccessor(
|
||||||
|
RequestDelegate exceptionHandler = null,
|
||||||
|
string exceptionHandlingPath = null)
|
||||||
|
{
|
||||||
|
exceptionHandler ??= c => Task.CompletedTask;
|
||||||
|
var options = new ExceptionHandlerOptions()
|
||||||
|
{
|
||||||
|
ExceptionHandler = exceptionHandler,
|
||||||
|
ExceptionHandlingPath = exceptionHandlingPath,
|
||||||
|
};
|
||||||
|
var optionsAccessor = Mock.Of<IOptions<ExceptionHandlerOptions>>(o => o.Value == options);
|
||||||
|
return optionsAccessor;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExceptionHandlerMiddleware CreateMiddleware(
|
||||||
|
RequestDelegate next,
|
||||||
|
IOptions<ExceptionHandlerOptions> options)
|
||||||
|
{
|
||||||
|
next ??= c => Task.CompletedTask;
|
||||||
|
var listener = new DiagnosticListener("Microsoft.AspNetCore");
|
||||||
|
|
||||||
|
var middleware = new ExceptionHandlerMiddleware(
|
||||||
|
next,
|
||||||
|
NullLoggerFactory.Instance,
|
||||||
|
options,
|
||||||
|
listener);
|
||||||
|
|
||||||
|
return middleware;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestServiceProvider : IServiceProvider
|
||||||
|
{
|
||||||
|
public object GetService(Type serviceType)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue