·您现在的位置: 云翼网络 >> 文章中心 >> 网站建设 >> 网站建设开发 >> ASP.NET网站开发 >> Self Host模式下的ASP. NET Web API是如何进行请求的监听与处理的?

Self Host模式下的ASP. NET Web API是如何进行请求的监听与处理的?

作者:佚名      ASP.NET网站开发编辑:admin      更新时间:2022-07-23

Self Host模式下的asp. NET Web API是如何进行请求的监听与处理的?

构成asp.net Web API核心框架的消息处理管道既不关心请求消息来源于何处,也不需要考虑响应消息归于何方。当我们采用Web Host模式将一个ASP.NET应用作为目标Web API的宿主时,实际上是由ASP.NET管道解决了这两个问题。具体来说,ASP.NET自身的URL路由系统借助于HttpControllerHandler这个自定义的HttpHandler实现了ASP.NET管道和ASP.NET Web API管道之间的“连通”,但是在Self Host寄宿模式下,请求的监听、接收和响应又是如何实现的呢?[本文已经同步到《How ASP.NET Web API Works?》]

目录 一、HttpBinding模型     Binding模型     HttpBinding     实例演示:直接利用HttpBinding进行请求的接收和响应 二、HttpSelfHostServer     HttpSelfHostConfiguration     HttpSelfHostServer与消息处理管道     实例演示:创建自定义HttpServer模拟HttpSelfHostServer的工作原理

一、HttpBinding模型

和WCF服务一样,我们可以采用Self Host模式将Web API寄宿于任何一种类型的托管应用程序下,宿主可以是一个Windows Form应用、WPF应用、控制台应用以及Windows Service。Self Host模式下的WCF和ASP.NET Web API不仅外在表现形式极为相似,其实在内部实现原理上也是一致的。

Binding模型

对于WCF具有基本了解的读者应该都知道,它是一个基于消息的分布式通信框架,消息交换借助于客户端和服务端对等的终结点(Endpoint)来完成,而终结点由经典的ABC(Address、Binding、Contract)三元素组成。WCF同样具有一个处理消息的管道,这个管道是一组Channel的有序组合,WCF下的Channel相对于ASP.NET Web API下的HttpMessageHandler。

WCF的消息处理管道的缔造者是作为终结点三要素之一的Binding。Binding不仅仅为服务端创建用于接收请求回复响应的管道,同时也为客户端创建发送请求接收响应的管道。Binding模型本身也相对比较复杂,所以我们不可能对其进行详细讨论。如果读者对此比较感兴趣,可以参阅《WCF的绑定模型》。由于ASP.NET Web API只是利用HttpBinding创建服务端消息处理管道,所以我们只讨论Binding的服务端模型。

从结构上讲,一个Binding是若干BindingElement对象的有序组合。对于最终创建的消息处理管道来说,每个Channel都对应着一个BindingElement。BindingElement并非直接创建对应的Channel,由它直接创建的实际上是一个名为ChannelListener的对象,Channel由ChannelListener创建。右图基本揭示了Binding的服务端模型。

顾名思义,ChannelListener用于请求的监听。当Binding对象开启(调用其Open方法)时,每个BindingElement会创建各自的ChannelListener。这些ChannelListener按照对应BindingElement的顺序连接成串,位于底部(面向传输层)的ChannelListener被绑定到某个端口进行请求的监听。一旦探测到抵达的请求,它会利用由所有ChannelListener创建的Channel组成的管道来接收并处理该请求。对于最终需要返回的响应消息,则按照从上到下的顺序被这个管道进行处理并最终返回给客户端。

对于这个由Channel组成消息处理管道来说,有两种类型的Channel是必不可少的。一种是面向传输层用于发送和接收消息的TransportChannel,另一种被称为MessageEncodingChannel则负责对接收的消息实施解码并对发送的消息实施编码。TransportChannel由TransportChannelListener创建,而后者由TransportBindingElement创建。与之类似,MessageEncodingBindingElement是MessageEncodingChannelListener的创建者,而后者又是MessageEncodingChannel的创建者。

如果采用Self Host寄宿模式,请求的监听是由一个类型为HttpBinding的Binding对象创建的ChannelListener管道来完成的,由它创建的管道实现了针对请求的接收和针对响应的回复。HttpBinding类型定义在“System.Web.Http.SelfHost.Channels”命名空间下,我们接下来对它进行详细讲述。

HttpBinding

Binding存在的目的在于创建用于处理和传输消息的信道栈,组成信道栈的每一个Channel均对应着一个BindingElement,所以Binding本身处理消息的能力由其BindingElement的组成来决定,我们可以通过分析BindingElement的组成来了解消息最终是如何处理的。现在我们就来讨论一下ASP.NET Web API在Self Host模式下使用的HttpBinding由哪些BindingElement构成。

如左图所示,HttpBinding仅仅由两种必需的BindingElement构成,TransportBindingElement的类型决定于最终采用的传输协议。如果采用单纯的HTTP协议,采用的TransportBindingElement是一个HttpTransportBindingElement对象。在采用HTTPS协议的情况下,TransportBindingElement的类型是HttpsTransportBindingElement。

我们现在着重来分析与消息编码/解码相关的BindingElement,从图3-11可以看出这是一个HttpMessageEncodingBindingElement对象(HttpMessageEncodingBindingElement是一个定义在程序集“System.Web.Http.SelfHost.dll”中的内部类型),它最终会创建一个MessageEncoder对象完成针对消息的编码/解码工作。

ASP.NET Web API分别利用 HttPRequestMessage和HttpResponseMessage对象表示消息处理管道处理的请求和响应,而WCF消息处理管道的请求和响应均是一个Message对象(Message是定义在命名空间“System.ServiceModel.Channels”下的一个抽象类型)。经过HttpMessageEncoder解码后的Message对象会转成一个HttpRequestMessage对象并传入ASP.NET Web API消息处理管道进行处理,由此管道返回的HttpResponseMessage对象需要转换成一个Message对象并由HttpMessageEncoder根据需求进行解码。

这个具体的消息实际上是一个HttpMessage对象,HttpMessage继承自抽象类Message,它是一个定义在程序集“System.Web.Http.SelfHost.dll”中的内部类型。如下面的代码片断所示,HttpMessage实际上是对一个HttpRequestMessage或者HttpResponseMessage对象的封装,两个方法GetHttpRequestMessage和GetHttpResponseMessage分别用于提取被封装的HttpRequestMessage和HttpResponseMessage对象。

   1: internal sealed class HttpMessage : Message
   2: {
   3:     //其他成员   
   4:     public HttpMessage(HttpRequestMessage request);
   5:     public HttpMessage(HttpResponseMessage response);
   6:    
   7:     public HttpRequestMessage  GetHttpRequestMessage(bool extract);
   8:     public HttpResponseMessage GetHttpResponseMessage(bool extract);
   9: }

这两个方法均具有一个布尔类型的参数extract,它表示是否“抽取”被封装的HttpRequestMessage/HttpResponseMessage对象。如果指定的参数值为True,方法执行之后被封装的HttpRequestMessage/HttpResponseMessage对象会从HttpMessage对象中抽取出来,所以再次调用它们会返回Null。

再次将我们的关注点拉回到由HttpBinding创建的消息处理管道上。当我们开启HttpBinding后,它利用创建的ChannelListener管道监听请求。一旦探测到抵达的请求后,基于HTTP/HTTPS协议的TransportChannel会负责接收请求。接收的二进制数据会由MessageEncoder解码后生成一个HttpRequestMessage对象,该对象进而被封装成一个HttpMessage对象,传入消息处理管道的HttpRequestMessage是直接通过调用GetHttpRequestMessage方法从该HttpMessage对象中提取的。

当ASP.NET Web API消息处理管道完成了请求的处理并最终输出一个HttpResponseMessage对象后,该对象同样先被封装成一个HttpMessage对象。在通过传输层将响应返回给客户端之前,需要利用MessageEncoder对其进行编码,而解码的内容实际上就是调用GetHttpResponseMessage方法提取的HttpResponseMessage对象。

实例演示:直接利用HttpBinding进行请求的接收和响应

当我们采用Self Host寄宿模式将一个非Web应用程序作为目标Web API的宿主时,最终网络监听任务实际上是由HttpBinding创建的ChannelListener管道来完成的,而ChannelListener管道创建的消息处理管道最终实现了对请求的接收和对响应的发送。为了让读者对此具有深刻的认识,我们通过一个简单的实例来演示如何直接使用HttpBinding实现对请求的监听、接收和响应。

我们创建一个空的控制台程序作为监听服务器,它相当于Self Host寄宿模式下的宿主程序。如下面的代码片断所示,我们创建了一个HttpBinding,并指定监听地址("http://127.0.0.1:3721")调用其BuildChannelListener<IReplyChannel>方法创建了一个ChannelListener管道(返回的是组成管道的第一个ChannelListener对象)。在调用Open方法开启该ChannelListener管道之后,我们调用其AcceptChannel方法创建了消息处理管道,返回的是组成管道的第一个Channel对象。在Open方法将其开启后,我们在一个While循环中调用Channel对象的ReceiveRequest方法进行请求的监听和接收。

   1: class Program
   2: {
   3:     static void Main(string[] args)
   4:     {
   5:         Uri listenUri = new Uri("http://127.0.0.1:3721");
   6:         Binding binding = new HttpBinding();
   7:  
   8:         //创建、开启信道监听器
   9:         IChannelListener<IReplyChannel> channelListener = binding.BuildChannelListener<IReplyChannel>(listenUri);
  10:         channelListener.Open();
  11:  
  12:         //创建、开启回复信道
  13:         IReplyChannel channel = channelListener.AcceptChannel(TimeSpan.MaxValue);
  14:         channel.Open();
  15:  
  16:         //开始监听
  17:         while (true)
  18:         {
  19:             //接收输出请求消息
  20:             RequestContext requestContext = channel.ReceiveRequest(TimeSpan.MaxValue);
  21:             PrintRequestMessage(requestContext.RequestMessage);
  22:             //消息回复
  23:             requestContext.Reply(CreateResponseMessage());
  24:         }
  25:     }
  26: }

对于成功接收的消息,我们调用具有如下定义的PrintRequestMessage方法将相关的信息打印在控制台上。通过上面的介绍我们知道这个接收到的消息实际上是一个HttpMessage对象,由于这是一个内部类型,所以我们只能以反射的方式调用其GetHttpRequestMessage方法获取被封装的HttpRequestMessage对象。在得到表示请求的HttpRequestMessage对象之后,我们将请求地址和所有报头输出到控制台上。

   1: private static void PrintRequestMessage(Message message)
   2: {
   3:     MethodInfo method = message.GetType().GetMethod("GetHttpRequestMessage");
   4:     HttpRequestMessage request = (HttpRequestMessage)method.Invoke(message, new object[]{false});
   5:  
   6:     Console.WriteLine("{0, -15}:{1}", "RequestUri", request.RequestUri);
   7:     foreach (var header in request.Headers)
   8:     {
   9:         Console.WriteLine("{0, -15}:{1}", header.Key, string.Join("," ,header.Value.ToArray()));
  10:     }
  11: }

在对请求进行处理之后,我们需要创建一个Message对象对该请求予以响应,响应消息的创建是通过CreateResponseMessage方法完成的。如下面的代码片断所示,我们首先创建了一个响应状态为“200, OK”的HttpResponseMessage对象,并将其表示主体内容的Content属性设置为一个ObjectContent<Em