前言

DeveloperExceptionPageMiddleware中间件利用呈现出来的错误页面实现抛出异常和当前请求的详细信息以辅助开发人员更好地进行纠错诊断工作,而ExceptionHandlerMiddleware中间件则是面向最终用户的,我们可以利用它来显示一个友好的定制化的错误页面。按照惯例,我们还是先来看看ExceptionHandlerMiddleware的类型定义。

 public class ExceptionHandlerMiddleware
 { 
 public ExceptionHandlerMiddleware(RequestDelegate next, ILoggerFactory loggerFactory, IOptions<ExceptionHandlerOptions> options, DiagnosticSource diagnosticSource); 
 public Task Invoke(HttpContext context);
 }
 
 public class ExceptionHandlerOptions
 {
 public RequestDelegate ExceptionHandler { get; set; }
 public PathString  ExceptionHandlingPath { get; set; }
 }

与DeveloperExceptionPageMiddleware类似,我们在创建一个ExceptionHandlerMiddleware对象的时候同样需要提供一个携带配置选项的对象,从上面的代码可以看出这是一个ExceptionHandlerOptions。具体来说,一个ExceptionHandlerOptions对象通过其ExceptionHandler属性提供了一个最终用来处理请求的RequestDelegate对象。如果希望发生异常后自动重定向到某个指定的路径,我们可以利用ExceptionHandlerOptions对象的ExceptionHandlingPath属性来指定这个路径。我们一般会调用ApplicationBuilder的扩展方法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)
 { 
  return app.UseExceptionHandler(new  {
  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;
  }
  RequestDelegate handler = _options.ExceptionHandler "color: #ff0000">二、异常的传递与请求路径的恢复

由于ExceptionHandlerMiddleware中间件总会利用一个作为异常处理器的RequestDelegate对象来完成最终的异常处理工作,为了让后者能够得到抛出的异常,该中间件应该采用某种方式将异常传递给它。除此之外,由于ExceptionHandlerMiddleware中间件会改变当前请求的路径,当整个请求处理完成之后,它必须将请求路径恢复成原始的状态,否则前置的中间件就无法获取到正确的请求路径。

请求处理过程中抛出的异常和原始请求路径的恢复是通过相应的特性完成的。具体来说,传递这两者的特性分别叫做ExceptionHandlerFeature和ExceptionHandlerPathFeature,对应的接口分别为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传递给请求处理器之前,它会按照如下所示的方式根据抛出的异常的原始的请求路径创建一个ExceptionHandlerFeature对象,该对象最终被添加到HttpContext之上。当整个请求处理流程完全结束之后,ExceptionHandlerMiddleware中间件会借助这个特性得到原始的请求路径,并将其重新应用到当前请求上下文上。

 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 "htmlcode">
 public class Program
 {
  public static void Main()
  {
   new WebHostBuilder()
    .UseKestrel()
    .ConfigureServices(svcs=>svcs.AddRouting())
    .Configure(app => app
     .UseExceptionHandler("/error")
     .UseRouter(builder=>builder.MapRoute("error", HandleError))
     .Run(context=> Task.FromException(new InvalidOperationException("Manually thrown exception"))))
    .Build()
    .Run();
  }
 
  private async static Task HandleError(HttpContext context)
  {
   context.Response.ContentType = "text/html";
   Exception 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来说,我们将该路径设置为异常处理路径。那么对于任意从浏览器发出的请求,都会得到如下图所示的错误页面。

ASP.NET Core应用错误处理之ExceptionHandlerMiddleware中间件呈现“定制化错误页面”

三、清除缓存

对于一个用于获取资源的GET请求来说,如果请求目标是一个相对稳定的资源,我们可以采用客户端缓存的方式避免相同资源的频繁获取和传输。对于作为资源提供者的Web应用来说,当它在处理请求的时候,除了将目标资源作为响应的主体内容之外,它还需要设置用于控制缓存的相关响应报头。由于缓存在大部分情况下只适用于成功的响应,如果服务端在处理请求过程中出现异常,之前设置的缓存报头是不应该出现在响应报文中。对于ExceptionHandlerMiddleware中间件来说,清楚缓存报头也是它负责的一项重要工作。

我们同样可以通过一个简单的实例来演示ExceptionHandlerMiddleware中间件针对缓存响应报头的清除。在如下这个应用中,我们将针对请求的处理实现在Invoke方法中,它有50%的可能会抛出异常。不论是返回正常的响应内容还是抛出异常,这个方法都会先设置一个“Cache-Control”的响应报头,并将缓存时间设置为1个小时(“Cache-Control: max-age=3600”)。

 public class Program
 {
  public static void Main()
  {
   new WebHostBuilder()
    .UseKestrel()
    .ConfigureServices(svcs => svcs.AddRouting())
    .Configure(app => app
     .UseExceptionHandler(builder => builder.Run(async context => await context.Response.WriteAsync("Error occurred!")))
     .Run(Invoke))
    .Build()
    .Run();
  }
 
  private static Random _random = new Random();
  private async static Task Invoke(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”的响应中,则会出现三个与缓存相关的报头,它们的目的都会为了禁止缓存(或者指示缓存过期)。

 HTTP/1.1 200 OK
 Date: Sat, 17 Dec 2016 14:39:02 GMT
 Server: Kestrel
 Cache-Control: max-age=3600
 Content-Length: 10
 
 Succeed...
 
 
 HTTP/1.1 500 Internal Server Error
 Date: Sat, 17 Dec 2016 14:38:39 GMT
 Server: Kestrel
 Cache-Control: no-cache
 Pragma: no-cache
 Expires: -1
 Content-Length: 15
 
 Error occurred!

ExceptionHandlerMiddleware中间件针对缓存响应报头的清除体现在如下所示的代码片段中。我们可以看出它通过调用HttpResponse的OnStarting方法注册了一个回调(ClearCacheHeaders),上述的这三个缓存报头在这个回调中设置的。除此之外,我们还看到这个回调方法还会清除ETag报头,这也很好理解:由于目标资源没有得到正常的响应,表示资源“签名”的ETag报头自然不应该出现在响应报文中。

 public class ExceptionHandlerMiddleware
 {
  ...
  public async Task Invoke(HttpContext context)
  {
   try
   {
    await _next(context);
   }
   catch (Exception ex)
   {
    …
    context.Response.OnStarting(ClearCacheHeaders, context.Response);
    RequestDelegate handler = _options.ExceptionHandler "no-cache";
   response.Headers[HeaderNames.Pragma]   = "no-cache";
   response.Headers[HeaderNames.Expires]   = "-1";
   response.Headers.Remove(HeaderNames.ETag);
   return Task.CompletedTask;
  }
 }

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。

广告合作:本站广告合作请联系QQ:858582 申请时备注:广告合作(否则不回)
免责声明:本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除!

RTX 5090要首发 性能要翻倍!三星展示GDDR7显存

三星在GTC上展示了专为下一代游戏GPU设计的GDDR7内存。

首次推出的GDDR7内存模块密度为16GB,每个模块容量为2GB。其速度预设为32 Gbps(PAM3),但也可以降至28 Gbps,以提高产量和初始阶段的整体性能和成本效益。

据三星表示,GDDR7内存的能效将提高20%,同时工作电压仅为1.1V,低于标准的1.2V。通过采用更新的封装材料和优化的电路设计,使得在高速运行时的发热量降低,GDDR7的热阻比GDDR6降低了70%。