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/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
 
原文地址:https://www.cnblogs.com/Leo_wl/p/3232425.html