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对象的释放。
当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: }
直接运行我们的程序后会在浏览器中呈现出如右图所示的输出结果。由此可见:完整的消息处理管道的创建不是发生在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: }
直接运行该程序会在浏览器中呈现出如右图所示的输出结果。由此可以证实在当前线程的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如下两个扩展方法GetConfiguration和GetSynchronizationContext得到添加的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的激活系统又是如何衔接的呢?这些问题的答案均可以在接下来的内容中找得到。
出处:http://artech.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。