• ASP.NET Web API的消息处理管道:"龙头"HttpServer


    ASP.NET Web API的消息处理管道:"龙头"HttpServer

    一般来说,对于构成ASP.NET Web API消息处理管道的所有HttpMessageHandler来说,除了出于尾端的那一个之外,其余的均为DelegatingHandler,那么通过InnerHandler属性维持着“下一个” HttpMessageHandler。作为这个HttpMessageHandler链“龙头”的则是一个类型为HttpServer的对象。其实从其命名也可以看出这一点:这是因为整个消息处理管道由HttpServer“牵头”,所以才称它为“服务器(Server)”。[本文已经同步到《How ASP.NET Web API Works?》]

    如下面的代码片断所示,HttpServer直接继承自DelegatingHandler。它具有两个重要的只读属性,我们可以通过属性Configuration获得用于配置整个消息处理管道的HttpConfiguration对象,另外一个属性Dispatcher返回的是处于整个消息处理管道“尾端”的HttpMessageHandler。这两个属性均在相应的构造函数中初始化,如果在构造HttpServer的时候没有显式指定这两个属性的值,默认会创建一个HttpConfiguration作为Configuration的属性值,而作为Dispatcher属性值的则是一个类型为HttpRoutingDispatcher的对象。

       1: public class HttpServer : DelegatingHandler
       2: {
       3:     public HttpServer();
       4:     public HttpServer(HttpMessageHandler dispatcher);
       5:     public HttpServer(HttpConfiguration configuration);
       6:     public HttpServer(HttpConfiguration configuration, HttpMessageHandler dispatcher);
       7:  
       8:     protected override void Dispose(bool disposing);   
       9:     protected virtual void Initialize();  
      10:     protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);
      11:  
      12:     public HttpConfiguration      Configuration { get; }
      13:     public HttpMessageHandler     Dispatcher { get; }
      14: }

    由于HttpConfiguration类型实现了IDisposable接口,所以HttpServer重写了虚方法Dispose并在该方法中完成了对被Configuration属性引用的HttpConfiguration对象的释放。

    image当HttpServer对象被成功创建之后,对应的消息处理管道的“一头一尾”已经确定下来,一头即位HttpServer本身,而一尾则自然指的就是通过Dispatcher属性引用的HttpMessageHandler。ASP.NET Web API框架最大的扩展性就在于我们 可以根据具体的消息处理需求来“定制”这个消息处理管道,具体来说就是将自定义的HttpMessageHandler“安装”到这一头一尾的某个位置。

    那么这些处于“中间位置”的HttpMessageHandler来源于何处呢?我想有些读者应该想得到,既然整个管道都是由HttpConfiguration进行配置,那么这些位于一头一尾之间的HttpMessageHandler自然来源构建HttpServer时指定的HttpConfiguration对象。如下面的代码片断所示,HttpConfiguration具有一个只读的集合类型的MessageHandlers,它组成了整个消息处理管道的终结点部分。值得一提的是,该属性类型为Collection<DelegatingHandler>,所以我们自定义的HttpMessageHandler必须继承自DelegatingHandler。右图所示的消息处理管道不仅仅包含一头一尾的HttpServer和HttpRoutingDispatcher,还包括动态注册的自定义的DelegatingHandler。

       1: public class HttpConfiguration : IDisposable
       2: {
       3:     //其他成员    
       4:     public Collection<DelegatingHandler> MessageHandlers { get; }
       5: }

    通过上面的给出的关于HttpServer定义的代码我们可以看到一个受保护的Initialize方法被定义其中,用于初始化HttpServer方法最终完成了对整个消息处理管道的构建。在重写的SendAsync方法中,如果自身尚未被初始化,该Initialize方法会自动被调用以确保整个消息处理管道已经被成功构建。

    实例演示:HttpServer对消息处理管道的构建

    为了让读者对HttpServer在初始化过程中对整个消息处理管道的构架有一个深刻的认识,我们来做一个简单的实例演示。我们在一个空的ASP.NET MVC应用中创建了如下3个继承自类型DelegatingHandler的自定义HttpMessageHandler:FooHandler、BarHandler和BazHandler。

       1: public class FooHandler: DelegatingHandler
       2: {}
       3:  
       4: public class BarHandler : DelegatingHandler
       5: {}
       6:  
       7: public class BazHandler : DelegatingHandler
       8: {}

    然后我们通过继承HttpServer定义了如下一个MyHttpServer,其目的在于将基类的受保护方法SendAsync“转化”成子类的公有方法以便可以直接调用。该MyHttpServer具有一个单一参数类型为HttpConfiguration的构造函数。

       1: public class MyHttpServer: HttpServer
       2: {
       3:     public MyHttpServer(HttpConfiguration configuration)
       4:         : base(configuration)
       5:     { }
       6:  
       7:     public new void Initialize()
       8:     {
       9:         base.Initialize();
      10:     }
      11: }

    我们创建了一个具有如下定义的HomeController。在默认的Action方法Index中,我们创建了一个HttpConfiguration对象,并先后在其MessageHandlers属性表示的HttpMessageHandler集合中添加了三个HttpMessageHandler(对应的类型分别是FooHandler、BarHandler和BazHandler)。在调用Initialize方法对HttpServer进行初始化的前后,我们调用辅助方法PrintMessageHandlerChain由此HttpServer牵头的消息处理管道的所有HttpMessageHandler的类型打印出来。

       1: public class HomeController : Controller
       2: {
       3:     public void Index()
       4:     {
       5:         HttpConfiguration configuration = new HttpConfiguration();
       6:         configuration.MessageHandlers.Add(new FooHandler());
       7:         configuration.MessageHandlers.Add(new BarHandler());
       8:         configuration.MessageHandlers.Add(new BazHandler());
       9:  
      10:         MyHttpServer httpServer = new MyHttpServer(configuration);
      11:         Response.Write("初始化前:");
      12:         this.PrintMessageHandlerChain(httpServer);
      13:  
      14:         httpServer.Initialize();
      15:         Response.Write("初始化后:");
      16:         this.PrintMessageHandlerChain(httpServer);
      17:     }
      18:  
      19:     void PrintMessageHandlerChain(DelegatingHandler handler)
      20:     {
      21:         Response.Write(handler.GetType().Name);
      22:         while (null != handler.InnerHandler)
      23:         {
      24:             Response.Write(" => " + handler.InnerHandler.GetType().Name);
      25:             handler = handler.InnerHandler as DelegatingHandler;
      26:             if (null == handler)
      27:             {
      28:                 break;
      29:             }
      30:         }
      31:         Response.Write("<br/>");
      32:     }
      33: }

    image直接运行我们的程序后会在浏览器中呈现出如右图所示的输出结果。由此可见:完整的消息处理管道的创建不是发生在HttpServer创建的时候,而是在调用Initialize方法对其进行初始化时完成的。HttpMessageHandler对象被添加到HttpConfiguration的MessageHandlers集合属性中的顺序决定了它们在最终消息处理管道中的顺序。除此之外,默认情况下会添加一个HttpRoutingDispatcher对象作为HttpServer的Dispatcher属性值在该实例中也得到了印证。

    消息处理管道的构建发生在初始化HttpServer时而非创建HttpServer时体现了另一点:在构造函数中指定的HttpConfiguration实际上是在初始化的时候才被真正使用。换句话说,在HttpServer尚未被初始化之前,我们可以直接通过其Configuration属性返回的HttpConfiguration对即将创建的消息处理管道进行配置,如下两种编程方式是完全等效的。

       1: //编程方式1
       2: HttpConfiguration configuration = new HttpConfiguration();
       3: configuration.MessageHandlers.Add (new FooHandler());
       4: configuration.MessageHandlers.Add (new BarHandler());
       5: configuration.MessageHandlers.Add (new BazHandler());
       6: HttpServer httpServer = new HttpServer(configuration);
       7:  
       8: //编程方式2
       9: HttpServer httpServer = new HttpServer();
      10: httpServer.Configuration.MessageHandlers.Add(new FooHandler());
      11: httpServer.Configuration.MessageHandlers.Add(new BarHandler ());
      12: httpServer.Configuration.MessageHandlers.Add(new BazHandler ());

    匿名Principal的设置

    Principal可以简单看成是身份与权限的封装,它是当前线程安全上下文的一部分。对于HttpServer来说,在它实现的SendAsync方法中,如果通过当前线程的属性CurrentPrincipal属性表示的Principal不存在,它会为之创建一个表示“匿名身份”的Principal。其实这个所谓的匿名Principal就是一个不具有身份名称,并且角色列表为空的GenericPrincipal。

    HttpServer在执行SendAsync方法的过程中对匿名Principal的设置也可以通过一个简单的实例来证实。我们在一个空的ASP.NET MVC应用中定义了如下一个继承自HttpServer的MyHttpServer,其目的在于将基类的受保护方法SendAsync“转化”成子类的公有方法以便可以直接调用。

       1: public class MyHttpServer : HttpServer
       2: {
       3:      public new Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
       4:     {
       5:         return base.SendAsync(request, cancellationToken);
       6:     }
       7: }

    我们定义了如下一个HomeController。在默认Action方法Index中,我们创建了一个MyHttpServer对象,我们在两种情形下对其SendAsync方法进行了调用。第一次调用是在将当前线程的CurrentPrincipal设置为Null的情况下完成的,而对于第二次SendAsync方法调用之前,我们为当前线程设置了一个身份名称为“Artech”的GenericPrincipal。对于这两次SendAsync方法调用,我们均在调用之后获取调用线程的CurrentPrincipal,并强行将其转换成GenericPrincipal,最后将身份名称输出来。

       1: public class HomeController : Controller
       2: {
       3:     public void Index()
       4:     {
       5:        //Thread.CurrentPrincipal为Null
       6:         MyHttpServer httpServer = new MyHttpServer();
       7:         Thread.CurrentPrincipal = null;
       8:         HttpRequestMessage request = new HttpRequestMessage();
       9:         httpServer.SendAsync(request, new CancellationToken(false));
      10:         GenericPrincipal principal = (GenericPrincipal)Thread.CurrentPrincipal;
      11:         string identityName = string.IsNullOrEmpty(principal.Identity.Name) ? "N/A": principal.Identity.Name;
      12:         Response.Write(string.Format("Identity: {0}<br/>", identityName));
      13:  
      14:         //Thread.CurrentPrincipal不为Null
      15:         GenericIdentity identity = new GenericIdentity("Artech");
      16:         Thread.CurrentPrincipal = new GenericPrincipal(identity, new string[0]);
      17:         request = new HttpRequestMessage();
      18:         httpServer.SendAsync(request, new CancellationToken(false));
      19:         principal = (GenericPrincipal)Thread.CurrentPrincipal;
      20:         identityName = string.IsNullOrEmpty(principal.Identity.Name) ? "N/A" : principal.Identity.Name;
      21:         Response.Write(string.Format("Identity: {0}<br/>", identityName));
      22:     }
      23: }

    image直接运行该程序会在浏览器中呈现出如右图所示的输出结果。由此可以证实在当前线程的Principla不存在的情况下,HttpServer会在执行SendAsync方法的时候会自动为其设置一个“空”的GenericPrincipal作为匿名的Principal。

    其他消息处理操作

    对于HttpServer重写的SendAsync方法来说,在将请求传递给“下一个毗邻”的HttpMessageHandler作进一步处理之前,它会将通过Configuration属性表示的HttpConfiguration对象,以及通过SynchronizationContext的静态属性Current表示的当前同步上下文(如果不为Null)添加到当前请求的属性字典中,对应的Key分别为“MS_SynchronizationContext”和“MS_HttpConfiguration”。这意味着我们可以在后续过程中直接从当前请求中获取当前的SynchronizationContext对象以作“线程同步之用”,也可以获取HttpConfiguration查看当前的配置信息。

    对于这两个特殊的Key,我们可以直接通过定义在HttpPropertyKeys的两个静态只读字段HttpConfigurationKey和SynchronizationContextKey得到。我们也可以直接HttpRequestMessage如下两个扩展方法GetConfigurationGetSynchronizationContext得到添加的HttpConfiguration和SynchronizationContext。

       1: public static class HttpPropertyKeys
       2: {
       3:     //其他成员
       4:     public static readonly string HttpConfigurationKey;
       5:     public static readonly string SynchronizationContextKey;
       6: }
       7:  
       8: public static class HttpRequestMessageExtensions
       9: {
      10:     //其他成员
      11:     public static HttpConfiguration GetConfiguration(this HttpRequestMessage request);
      12:     public static SynchronizationContext GetSynchronizationContext(this HttpRequestMessage request);
      13: }

    我们不止一次地强调ASP.NET Web API的消息处理管道是独立于具体寄宿环境的,其本身就是有多个HttpMessageHandler的有序组合。也正是因为其本身的独立性,我们才能采用不同的寄宿方式使我们定义的Web API能够承载于不同的应用进程之中。

    不论是Web Host还是Self Host,路由系统都是请求抵达服务端遇到的第一道屏障,那么经过路由系统拦截、解析后的请求是如何分法给这个消息处理管道的呢?此外,抵达的请求基本上都是针对定义在某个HttpController中的某个方法而言,那么消息处理管道最终需要根据路由数据激活目标HttpController,消息处理管道和HttpController的激活系统又是如何衔接的呢?这些问题的答案均可以在接下来的内容中找得到。

    作者:Artech
    出处:http://artech.cnblogs.com/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
     
  • 相关阅读:
    Mac下安装配置SDK
    macOS brew install 404:Bottle missing, falling back to the default domain错误的修复
    如何修复“无法打开应用,因为Apple无法检查其是否包含恶意软件“
    Selenium4+Python3系列(六) Selenium的三种等待,强制等待、隐式等待、显式等待
    Selenium4.0+Python3系列(四) 常见元素操作(含鼠标键盘事件)
    解决selenium+python 打开浏览器报错 DeprecationWarning executable_path has been deprecated, please pass in a Service object问题
    Selenium4+Python3系列(五) 多窗口处理之句柄切换
    技术团队:给代码评审发起者的4个建议
    中台建设:中台有效落地的6脉神剑
    在代码评审中用好这7招,很容易就能建立起你的反对同盟
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3232425.html
Copyright © 2020-2023  润新知