zoukankan      html  css  js  c++  java
  • 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/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
     
  • 相关阅读:
    085 Maximal Rectangle 最大矩形
    084 Largest Rectangle in Histogram 柱状图中最大的矩形
    083 Remove Duplicates from Sorted List 有序链表中删除重复的结点
    082 Remove Duplicates from Sorted List II 有序的链表删除重复的结点 II
    081 Search in Rotated Sorted Array II 搜索旋转排序数组 ||
    080 Remove Duplicates from Sorted Array II 从排序阵列中删除重复 II
    079 Word Search 单词搜索
    078 Subsets 子集
    bzoj2326: [HNOI2011]数学作业
    bzoj2152: 聪聪可可
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3232425.html
Copyright © 2011-2022 走看看