您的位置:首页 > 理论基础 > 计算机网络

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

2013-08-02 13:53 369 查看
[code] public class HttpServer : DelegatingHandler

{

public HttpServer();

public HttpServer(HttpMessageHandler dispatcher);

public HttpServer(HttpConfiguration configuration);

public HttpServer(HttpConfiguration configuration, HttpMessageHandler dispatcher);


 protected override void Dispose(bool disposing); 

 protected virtual void Initialize();

 protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);


 public HttpConfigurationConfiguration{ get;}

 public HttpMessageHandler Dispatcher{ get;}

}

[/code]
[/code]
由于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。

[code]
[code] public class HttpConfiguration : IDisposable

{

 //其他成员

 public Collection<DelegatingHandler> MessageHandlers{ get;}

}

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

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

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

[code]
[code] public class FooHandler: DelegatingHandler

{}


public class BarHandler : DelegatingHandler

{}


public class BazHandler : DelegatingHandler

{}

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

[code]
[code] public class MyHttpServer: HttpServer

{

 public MyHttpServer(HttpConfiguration configuration)

 : base(configuration)

{}


 public new void Initialize()

{

 base.Initialize();

}

}

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

[code]
[code] public class HomeController : Controller

{

 public void Index()

{

HttpConfiguration configuration = new HttpConfiguration();

 configuration.MessageHandlers.Add(new FooHandler());

 configuration.MessageHandlers.Add(new BarHandler());

 configuration.MessageHandlers.Add(new BazHandler());


 MyHttpServer httpServer = new MyHttpServer(configuration);

 Response.Write("初始化前:");

 this.PrintMessageHandlerChain(httpServer);


 httpServer.Initialize();

 Response.Write("初始化后:");

 this.PrintMessageHandlerChain(httpServer);

}


 void PrintMessageHandlerChain(DelegatingHandler handler)

{

 Response.Write(handler.GetType().Name);

 while (null != handler.InnerHandler)

{

 Response.Write(" => " + handler.InnerHandler.GetType().Name);

 handler = handler.InnerHandler as DelegatingHandler;

 if (null == handler)

{

 break;

}

}

 Response.Write("<br/>");

}

}

[/code]
[/code]


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

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

[code]
[code] //编程方式1

HttpConfiguration configuration = new HttpConfiguration();

configuration.MessageHandlers.Add (new FooHandler());

configuration.MessageHandlers.Add (new BarHandler());

configuration.MessageHandlers.Add (new BazHandler());

HttpServer httpServer = new HttpServer(configuration);


//编程方式2

HttpServer httpServer = new HttpServer();

httpServer.Configuration.MessageHandlers.Add(new FooHandler());

httpServer.Configuration.MessageHandlers.Add(new BarHandler ());

httpServer.Configuration.MessageHandlers.Add(new BazHandler ());

[/code]
[/code]

匿名Principal的设置

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

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

[code]
[code] public class MyHttpServer : HttpServer

{

public new Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)

{

 return base.SendAsync(request, cancellationToken);

}

}

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

[code]
[code] public class HomeController : Controller

{

 public void Index()

{

//Thread.CurrentPrincipal为Null

 MyHttpServer httpServer = new MyHttpServer();

 Thread.CurrentPrincipal = null;

 HttpRequestMessage request = new HttpRequestMessage();

 httpServer.SendAsync(request, new CancellationToken(false));

 GenericPrincipal principal = (GenericPrincipal)Thread.CurrentPrincipal;

 string identityName = string.IsNullOrEmpty(principal.Identity.Name) ? "N/A": principal.Identity.Name;

 Response.Write(string.Format("Identity:{0}<br/>", identityName));


 //Thread.CurrentPrincipal不为Null

 GenericIdentity identity = new GenericIdentity("Artech");

 Thread.CurrentPrincipal = new GenericPrincipal(identity, new string[0]);

 request = new HttpRequestMessage();

 httpServer.SendAsync(request, new CancellationToken(false));

 principal = (GenericPrincipal)Thread.CurrentPrincipal;

 identityName = string.IsNullOrEmpty(principal.Identity.Name) ? "N/A" : principal.Identity.Name;

 Response.Write(string.Format("Identity:{0}<br/>", identityName));

}

}

[/code]
[/code]


直接运行该程序会在浏览器中呈现出如右图所示的输出结果。由此可以证实在当前线程的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。

[code]
[code] public static class HttpPropertyKeys

{

 //其他成员

 public static readonly string HttpConfigurationKey;

 public static readonly string SynchronizationContextKey;

}


public static class HttpRequestMessageExtensions

{

 //其他成员

 public static HttpConfiguration GetConfiguration(this HttpRequestMessage request);

 public static SynchronizationContext GetSynchronizationContext(this HttpRequestMessage request);

}

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

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