ASP.NET Core错误处理中间件[3]: 异常处理器
DeveloperExceptionPageMiddleware中间件错误页面可以呈现抛出的异常和当前请求上下文的详细信息,以辅助开发人员更好地进行纠错诊断工作。ExceptionHandlerMiddleware中间件则主要面向最终用户,我们可以利用它来显示一个友好的定制化错误页面。更多关于ASP.NET Core的文章请点这里]
一、ExceptionHandlerMiddleware
由于ExceptionHandlerMiddleware中间件可以使用指定的RequestDelegate对象来作为异常处理器,所以我们可以将它视为一个“万能”的异常处理方案。按照惯例,下面先介绍ExceptionHandlerMiddleware类型的定义。
public class ExceptionHandlerMiddleware
{
public ExceptionHandlerMiddleware(RequestDelegate next, ILoggerFactory loggerFactory, IOptions<ExceptionHandlerOptions> options, DiagnosticListener diagnosticListener);
public Task Invoke(HttpContext context);
} public class ExceptionHandlerOptions
{
public RequestDelegate ExceptionHandler { get; set; }
public PathString ExceptionHandlingPath { get; set; }
}
与DeveloperExceptionPageMiddleware类似,在创建一个ExceptionHandlerMiddleware对象时同样需要提供一个携带配置选项的对象,从上面的代码片段可以看出,配置选项由一个ExceptionHandlerOptions对象承载。一个ExceptionHandlerOptions对象通过其ExceptionHandler属性提供了一个作为异常处理器的RequestDelegate对象。如果希望应用在发生异常后自动重定向到某个指定的路径,该路径就可以利用ExceptionHandlingPath属性来指定。我们一般调用IApplicationBuilder接口的UseExceptionHandler扩展方法来注册ExceptionHandlerMiddleware中间件,这些重载的UseExceptionHandler扩展方法会采用如下方式完成中间件的注册工作。
public static class ExceptionHandlerExtensions
{
public static IApplicationBuilder UseExceptionHandler(this IApplicationBuilder app)
=> app.UseMiddleware<ExceptionHandlerMiddleware>(); public static IApplicationBuilder UseExceptionHandler(this IApplicationBuilder app, ExceptionHandlerOptions options)
=> app.UseMiddleware<ExceptionHandlerMiddleware>(Options.Create(options)); public static IApplicationBuilder UseExceptionHandler(this IApplicationBuilder app, string errorHandlingPath)
=>app.UseExceptionHandler(new ExceptionHandlerOptions
{
ExceptionHandlingPath = new PathString(errorHandlingPath)
}); public static IApplicationBuilder UseExceptionHandler(this IApplicationBuilder app, Action<IApplicationBuilder> configure)
{
IApplicationBuilder newBuilder = app.New();
configure(newBuilder); return app.UseExceptionHandler(new ExceptionHandlerOptions
{
ExceptionHandler = newBuilder.Build()
});
}
}
ExceptionHandlerMiddleware中间件处理请求的本质如下:在后续请求处理过程中出现异常的情况下,采用注册的异常处理器来处理当前请求,这个异常处理器就是RequestDelegate对象。该中间件采用的请求处理逻辑大体上可以通过如下所示的代码片段来体现。
public class ExceptionHandlerMiddleware
{
private RequestDelegate _next;
private ExceptionHandlerOptions _options; public ExceptionHandlerMiddleware(RequestDelegate next, IOptions<ExceptionHandlerOptions> options,...)
{
_next = next;
_options = options.Value;
...
} public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch
{
context.Response.StatusCode = 500;
context.Response.Clear();
if (_options.ExceptionHandlingPath.HasValue)
{
context.Request.Path = _options.ExceptionHandlingPath;
}
var handler = _options.ExceptionHandler ?? _next;
await handler(context);
}
}
}
如上面的代码片段所示,如果后续的请求处理过程中出现异常,ExceptionHandlerMiddleware中间件会利用指定的作为异常处理器的RequestDelegate对象来完成最终的请求处理工作。如果创建ExceptionHandlerMiddleware对象时提供的ExceptionHandlerOptions对象携带了一个RequestDelegate对象,那么它将作为最终使用的异常处理器,否则作为异常处理器的实际上就是后续的中间件。换句话说,如果没有通过ExceptionHandlerOptions对象显式指定一个异常处理器,ExceptionHandlerMiddleware中间件会在后续管道处理请求抛出异常的情况下将请求再次传递给后续管道。
在ExceptionHandlerMiddleware中间件利用异常处理器来处理请求之前,它会对请求做一些前置处理工作,其中包括将响应状态码设置为500,并清空当前所有响应内容等。如果我们利用指定的ExceptionHandlerOptions对象的ExceptionHandlingPath属性设置了一个重定向路径,它会将该路径设置为当前请求的路径。除了包含前面代码片段的这些操作,ExceptionHandlerMiddleware中间件实际上还执行了一些其他的操作。
二、异常的传递与请求路径的恢复
由于ExceptionHandlerMiddleware中间件总是利用一个作为异常处理器的RequestDelegate对象来完成最终的异常处理工作,为了使后者能够得到抛出的异常,该中间件应该采用某种方式将抛出的异常传递给它。除此之外,由于ExceptionHandlerMiddleware中间件会改变当前请求的路径,当整个请求处理完成之后,它必须将请求路径恢复成原始状态,否则前置的中间件就无法获取到正确的请求路径。
请求处理过程中抛出的异常和原始请求路径的恢复是通过相应的特性完成的。具体来说,传递这两者的特性分别通过IExceptionHandlerFeature接口和IExceptionHandlerPathFeature接口来表示。如下面的代码片段所示,后者继承前者,ExceptionHandlerFeature类型同时实现了这两个接口。
public interface IExceptionHandlerFeature
{
Exception Error { get; }
} public interface IExceptionHandlerPathFeature : IExceptionHandlerFeature
{
string Path { get; }
} public class ExceptionHandlerFeature : IExceptionHandlerPathFeature,
{
public Exception Error { get; set; }
public string Path { get; set; }
}
在ExceptionHandlerMiddleware中间件将代表当前请求的HttpContext上下文传递给处理器之前,它会按照如下所示的方式根据抛出的异常和原始请求路径创建一个Exception
HandlerFeature对象,该对象最终被添加到HttpContext上下文的特性集合之中。当整个请求处理流程完全结束之后,ExceptionHandlerMiddleware中间件会借助这个特性得到原始的请求路径,并将其重新应用到当前HttpContext上下文中。
public class ExceptionHandlerMiddleware
{
...
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch(Exception ex)
{
context.Response.StatusCode = 500; var feature = new ExceptionHandlerFeature()
{
Error = ex,
Path = context.Request.Path,
};
context.Features.Set<IExceptionHandlerFeature>(feature);
context.Features.Set<IExceptionHandlerPathFeature>(feature); if (_options.ExceptionHandlingPath.HasValue)
{
context.Request.Path = _options.ExceptionHandlingPath;
}
RequestDelegate handler = _options.ExceptionHandler ?? _next; try
{
await handler(context);
}
finally
{
context.Request.Path = originalPath;
}
}
}
}
在进行异常处理时,我们可以从当前HttpContext上下文中提取ExceptionHandlerFeature特性对象,进而获取抛出的异常和原始请求路径。如下面的代码片段所示,我们利用HandleError方法来呈现一个定制的错误页面。在这个方法中,我们正是借助ExceptionHandlerFeature特性得到抛出的异常的,并将其类型、消息及堆栈追踪信息显示出来。
public class Program
{
public static void Main()
{
Host.CreateDefaultBuilder()
.ConfigureWebHostDefaults(builder => builder
.ConfigureServices(svcs => svcs.AddRouting())
.Configure(app => app
.UseExceptionHandler("/error")
.UseRouting()
.UseEndpoints(endpoints => endpoints.MapGet("error", HandleErrorAsync))
.Run(context => Task.FromException(new InvalidOperationException("Manually thrown exception")))))
.Build()
.Run(); static async Task HandleErrorAsync(HttpContext context)
{
context.Response.ContentType = "text/html";
var ex = context.Features.Get<IExceptionHandlerPathFeature>().Error; await context.Response.WriteAsync("<html><head><title>Error</title></head><body>");
await context.Response.WriteAsync($"<h3>{ex.Message}</h3>");
await context.Response.WriteAsync($"<p>Type: {ex.GetType().FullName}");
await context.Response.WriteAsync($"<p>StackTrace: {ex.StackTrace}");
await context.Response.WriteAsync("</body></html>");
}
}
}
在上面这个应用中,我们注册了一个模板为“error”的路由指向HandleError方法。对于通过调用UseExceptionHandler扩展方法注册的ExceptionHandlerMiddleware中间件来说,我们将该路径设置为异常处理路径。对于任意从浏览器发出的请求,都会得到下图所示的错误页面。
三、清除缓存
对于一个用于获取资源的GET请求来说,如果请求目标是一个相对稳定的资源,我们可以利用缓存避免相同资源的频繁获取和传输。对于作为资源提供者的Web应用来说,当它在处理请求的时候,除了将目标资源作为响应的主体内容,它还需要设置用于控制缓存的相关响应报头。由于缓存在大部分情况下只适用于成功状态的响应,如果服务端在处理请求过程中出现异常,之前设置的缓存报头是不应该出现在响应报文中的。对于ExceptionHandlerMiddleware中间件来说,清除缓存报头也是它负责的一项重要工作。
我们同样可以通过一个简单的实例来演示ExceptionHandlerMiddleware中间件针对缓存响应报头的清除。在如下所示的应用中,我们将针对请求的处理实现在ProcessAsync方法中,它有50%的可能会抛出异常。不论是返回正常的响应内容还是抛出异常,这个方法都会先设置一个Cache-Control的响应报头,并将缓存时间设置为1小时(Cache-Control: max-age=3600)。
public class Program
{
private static readonly Random _random = new Random();
public static void Main()
{
Host.CreateDefaultBuilder()
.ConfigureWebHostDefaults(builder => builder.Configure(app => app
.UseExceptionHandler(app2 => app2.Run(HandleAsync))
.Run(ProcessAsync)))
.Build()
.Run(); static Task HandleAsync(HttpContext context) => context.Response.WriteAsync("Error occurred!"); static async Task ProcessAsync(HttpContext context)
{
context.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue
{
MaxAge = TimeSpan.FromHours(1)
}; if (_random.Next() % 2 == 0)
{
throw new InvalidOperationException("Manually thrown exception...");
}
await context.Response.WriteAsync("Succeed...");
}
}
}
通过调用UseExceptionHandler扩展方法注册的ExceptionHandlerMiddleware中间件在处理异常时会响应一个内容为“Error occurred!”的字符串。如下所示的两个响应报文分别对应正常响应和抛出异常的情况,我们会发现程序中设置的缓存报头Cache-Control: max-age=3600只会出现在状态码为“200 OK”的响应中。在状态码为“500 Internal Server Error”的响应中,则会出现3个与缓存相关的报头(Cache-Control、Pragma和Expires),它们的目的都是禁止缓存或者将缓存标识为过期。(S1612)
HTTP/1.1 200 OK
Date: Sat, 21 Sep 2019 11:25:27 GMT
Server: Kestrel
Cache-Control: max-age=3600
Content-Length: 10 Succeed...
HTTP/1.1 500 Internal Server Error
Date: Sat, 21 Sep 2019 11:26:11 GMT
Server: Kestrel
Cache-Control: no-cache
Pragma: no-cache
Expires: -1
Content-Length: 15 Error occurred!
ExceptionHandlerMiddleware中间件针对缓存响应报头的清除体现在如下所示的代码片段中。可以看出,它通过调用HttpResponse对象的OnStarting方法注册了一个回调(ClearCacheHeaders),上述这3个缓存报头是在这个回调中设置的。除此之外,这个回调方法还会清除ETag报头。既然目标资源没有得到正常的响应,表示资源“签名”的ETag报头就不应该出现在响应报文中。
public class ExceptionHandlerMiddleware
{
...
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
...
context.Response.OnStarting(ClearCacheHeaders, context.Response);
var handler = _options.ExceptionHandler ?? _next;
await handler(context);
}
} private Task ClearCacheHeaders(object state)
{
var response = (HttpResponse)state;
response.Headers[HeaderNames.CacheControl] = "no-cache";
response.Headers[HeaderNames.Pragma] = "no-cache";
response.Headers[HeaderNames.Expires] = "-1";
response.Headers.Remove(HeaderNames.ETag);
return Task.CompletedTask;
}
}
ASP.NET Core错误处理中间件[3]: 异常处理器的更多相关文章
- ASP.NET Core错误处理中间件[2]: 开发者异常页面
<呈现错误信息>通过几个简单的实例演示了如何呈现一个错误页面,该过程由3个对应的中间件来完成.下面先介绍用来呈现开发者异常页面的DeveloperExceptionPageMiddlewa ...
- ASP.NET Core错误处理中间件[1]: 呈现错误信息
NuGet包"Microsoft.AspNetCore.Diagnostics"中提供了几个与异常处理相关的中间件.当ASP.NET Core应用在处理请求过程中出现错误时,我们可 ...
- ASP.NET Core错误处理中间件[4]: 响应状态码页面
StatusCodePagesMiddleware中间件与ExceptionHandlerMiddleware中间件类似,它们都是在后续请求处理过程中"出错"的情况下利用一个错误处 ...
- asp.net core 自定义异常处理中间件
asp.net core 自定义异常处理中间件 Intro 在 asp.net core 中全局异常处理,有时候可能不能满足我们的需要,可能就需要自己自定义一个中间件处理了,最近遇到一个问题,有一些异 ...
- ASP.NET Core 3.1 中间件
参考微软官方文档 : https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/middleware/?view=aspnetcore-3.1 ...
- 理解ASP.NET Core - 错误处理(Handle Errors)
注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或[点击此处查看全文目录](https://www.cnblogs.com/xiaoxiaotank/p/151852 ...
- ASP.NET Core 中的中间件
前言 由于是第一次写博客,如果您看到此文章,希望大家抱着找错误.批判的心态来看. sky! 何为中间件? 在 ASP.NET Framework 中应该都知道请求管道.可参考:浅谈 ASP.NET ...
- Asp.Net Core 通过自定义中间件防止图片盗链的实例(转)
一.原理 要实现防盗链,我们就必须先理解盗链的实现原理,提到防盗链的实现原理就不得不从HTTP协议说起,在HTTP协议中,有一个表头字段叫referer,采用URL的格式来表示从哪儿链接到当前的网页或 ...
- 在Asp.net Core中使用中间件来管理websocket
介绍 ASP.NET Core SignalR是一个有用的库,可以简化Web应用程序中实时通信的管理.但是,我宁愿使用WebSockets,因为我想要更灵活,并且与任何WebSocket客户端兼容. ...
随机推荐
- # spring boot + mybatis 读取数据库
spring boot + mybatis 读取数据库 创建数据库 use testdb; drop table if exists t_city; create table t_city( id i ...
- fMRI数据分析学习笔记——常用工具
背景 在学习fMRI数据处理的过程中,通过其他的资料看到了别人推荐的有用的fMRI数据处理软件和小插件,在此记录一下,以便后期慢慢学习使用. 1.NeuroImaging Analysis Kit ( ...
- 点击劫持(Iframe clickJack)练习
实验内容: 寻找一个合适的网站放入到iframe标签中.实验中测试了包括知网首页及登录界面.淘宝首页及登录界面,百度首页,微信下载界面.发现淘宝登录界面无法放入,会直接跳转到淘宝真实的登录界面,其他的 ...
- (二)、vim即gvim的炫酷搜索模式与技巧
一.进入搜索模式 1. 打开文件,狂按 <Esc> 进入normal模式,然后按 / 或者 :/ 进入搜索模式,添加上关键字例如world,按回车即搜索world: :/wo ...
- 报错 ncclCommInitRank failed.
环境 4 GeForce GTX 1080 GPUS docker image nnabla/nnabla-ext-cuda-multi-gpu:py36-cuda102-mpi3.1.6-v1.14 ...
- 持续提升程序员幸福指数——使用abp vnext设计一款面向微服务的单体架构
可能你会面临这样一种情况,在架构设计之前,你对业务不甚了解,需求给到的也模棱两可,这个时候你既无法明确到底是要使用单体架构还是使用微服务架构,如果使用单体,后续业务扩展可能带来大量修改,如果使用微服务 ...
- Deep Neural Networks for YouTube Recommendations YouTube的经典推荐框架
https://zhuanlan.zhihu.com/p/52169807 王喆大佬的讲解
- GraduateDesign-给APP添加获取位置信息和天气预报的功能(json)
首先,我的app需要通过网络来获取当前所在的位置.这里我找到了一个json来获取本地位置信息. http://int.dpool.sina.com.cn/iplookup/iplookup.php?f ...
- 表单综合HTML
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding= ...
- mysql提权神器
java -jar udf.jar 127.0.0.1 root 123456 [32/64]