ASP.NET Web API的消息处理管道: Self Host下的消息处理管道[上篇]

ASP.NET Web API服务端框架核心是一个独立于具体寄宿环境的消息处理管道,它不关心请求消息来源于何处,响应消息又回归于何方。说得具体点,这个由若然HttpMessageHandler的有序组合构成的消息处理管道并没有考虑对请求的监听、接收和响应,因为它们工作的方式取决于具体的寄宿方法。在Self Host寄宿模式下,请求的监听、接收和和最终响应是如何解决的呢?[本文已经同步到《How ASP.NET Web API Works?》]

和WCF服务一样,我们可以采用Self Host的方式将Web API寄宿于任何一种类型的托管应用中,比如Windows Form应用、WPF应用、控制台应用以及Windows Service。其实上不但Self Host模式下的WCF和ASP.NET Web API在“外在”的表现形式下相似,在内部实现原理上也一致。总的来说,ASP.NET Web API通过一个类型为HttpBinding的Binding实现了对请求的监听、接收和响应。

Binding模型

对于WCF具有基本了解的读者应该都知道:WCF是一个基于消息的分布式通信框架,消息交换借助于客户端和服务端对等的终结点(Endpoint)来完成,而终结点由经典的ABC(Address、Binding、Contract)三元素组成。与ASP.NET Web API类似,WCF提供了一个名为“信道栈(Channel Stack)”的管道来处理消息,组成该管道的信道(Channel)相当于HttpMessageHandler,而这个管道的缔造者就是Binding。

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

从结构上讲,一个Binding同时若个BindingElement的有序组合,对于最终创建的信道栈来说,每个Channel都对应着一个BindingElement。但是BindingElement并非直接创建对应的Channel,它直接创建的实际上是一个名为ChannelListener的对象,后者创建相应的Channel。针对服务端的Binding模型如右图所示。

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

对于组成这个用于接收处理请求消息和处理发送响应消息的信道栈来说,有两种类型的Channel是必不可少的,一种面向传输层用于发送和接收消息的TransportChanenl,另一种则是在消息发送前对其编码并在接收后对其解码的MessageEncodingChannel。这两种类型的Channel对应的ChannelListener和BindingElement分别称为TransportChannelListener/TransportBindingElement和MessageEncodingChannelListener和MessageEncodingBindingElement。

HttpBinding

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

如左图所示,HttpBinding仅仅由两种必需BindingElement构成,TransportBindingElement的类型决定于最终采用的传输协议。具体来说,如果采用单纯的HTTP协议,那么最终采用的TransportBindingElement类型为HttpTransportBindingElement,当我们采用HTTPS协议的情况下,对应的类型则为HttpsTransportBindingElement。由于TransportBindingElement是面向传输层的,所以网络协议决定其类型,这应该很好理解。

我们现在着重来分析与消息编码与解码相关的BindingElement,从左图可以看出这是一个HttpMessageEncodingBindingElement类型(这是一个定义在程序集System.Web.Http.SelfHost.dll中的内部类型)的对象,它最终会创建一个MessageEncoder对象完成针对消息的编码和解码工作。消息编码在WCF的消息处理管道中的意义在于:将传输层接收到的二进制数据经过解码以生成一个消息对象,需要发送的消息对象进行编码转换成交由传输层发送的二进制数据。

对于ASP.NET Web API的消息处理管道来说,请求消息和响应消息的类型分别是HttpRequestMessage和HttpResponseMessage,所以这个HttpMessageEncoder解码后生成的请求消息必须能够转换成一个HttpRequestMessage对象,而响应消息(这里指的是针对WCF的响应消息)实际上对于一个HttpResponseMessage对象的封装,并且应该按照HttpResponseMessage的“语义”进行编码。

我们不妨来看看这个具体的消息是一个怎样的对象,实际上该消息对象的类型为具有如下定义的HttpMessage,这同样是定义在程序集System.Web.Http.SelfHost.dll中的内部类型。我们可以看出,HttpMessage实际上是对一个HttpRequestMessage或者HttpResponseMessage对象的封装,两个方法GetHttpRequestMessage和GetHttpResponseMessage分别用于提取被封装的HttpRequestMessage和HttpResponseMessage对象。这个两个方法均具有一个布尔类型的参数extract,表示是否“抽取”被封装的HttpRequestMessage和HttpResponseMessage对象,如果传值为True,被封装的对象将被设置为Null。

internal sealed class HttpMessage : Message
{
//其他成员
public HttpMessage(HttpRequestMessage request);
public HttpMessage(HttpResponseMessage response); public HttpRequestMessage GetHttpRequestMessage(bool extract);
public HttpResponseMessage GetHttpResponseMessage(bool extract);
}

再将我们的关注点拉回到HttpBinding及其创建的用于接收请求发送响应的管道。开启后的HttpBinding会创建ChannelListener监听请求,一旦探测到抵达的请求,ChannelListener会先利用基于HTTP或者HTTPS的TransportChannel来接收请求。接收的二进制数据会被由HttpMessageEncodingBindingElement创建的MessageEncoder用于生成一个HttpRequestMessage对象,该对象进而被封装成一个HttpMessage对象。后面传入ASP.NET Web API消息处理管道的HttpRequestMessage是直接通过调用GetHttpRequestMessage方法从该HttpMessage对象中提取的。

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

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

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

我们创建一个空的控制台程序作为监听服务器,它相当于Self Host寄宿模式下的宿主程序。在添加相应程序集引用后,我们定义了如下一个Main方法。在这个方法中我们创建了一个HttpBinding,然后调用其BuildChannelListener<IReplyChannel>方法创建了一个ChannelListener对象,并且指定了监听地址("http://127.0.0.1:3721")。在开启ChannelListener之后,我们调用其AcceptChannel方法创建了Channel对象,并调用Open方法将其开启。我们最后在一个While循环中调用Channel对象的ReceiveRequest方法进行请求的监听和接收。

class Program
{
static void Main(string[] args)
{
Uri listenUri = new Uri("http://127.0.0.1:3721");
Binding binding = new HttpBinding(); //创建、开启信道监听器
IChannelListener<IReplyChannel> channelListener = binding.BuildChannelListener<IReplyChannel>(listenUri);
channelListener.Open(); //创建、开启回复信道
IReplyChannel channel = channelListener.AcceptChannel(TimeSpan.MaxValue);
channel.Open(); //开始监听
while (true)
{
//接收输出请求消息
RequestContext requestContext = channel.ReceiveRequest(TimeSpan.MaxValue);
PrintRequestMessage(requestContext.RequestMessage);
//消息回复
requestContext.Reply(CreateResponseMessage());
}
}
}

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

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

在对请求进行处理之后,我们需要创建一个消息对该请求予以响应,响应消息的创建是通过具有如下定义的。如下面的代码片断所示,我们定义了一个Employee类型,而响应的内容就是一个序列化为JSON的Employee对象。我们首先创建了一个响应状态为“200, OK”的HttpResponseMessage对象,并将其Content属性设置为一个ObjectContent<Employee>对象。顾名思义,ObjectContent直接将指定的对象作为响应的内容,对指定对象的序列化通过指定的MediaTypeFormatter来完成。在这里的ObjectContent是对一个Employee对象的封装,并利用指定的JsonMediaTypeFormatter对象将其序列化成JSON格式。

private static Message CreateResponseMessage()
{
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
Employee employee = new Employee("001","Zhang San","123456", "zhangsan@gmail.com");
response.Content = new ObjectContent<Employee>(employee, new JsonMediaTypeFormatter()); string httpMessageTypeName = "System.Web.Http.SelfHost.Channels.HttpMessage, System.Web.Http.SelfHost";
Type httpMessageType = Type.GetType(httpMessageTypeName);
return (Message)Activator.CreateInstance(httpMessageType, new object[] { response });
} public class Employee
{
public string Id { get; set; }
public string Name { get; set; }
public string PhoneNo { get; set; }
public string EmailAddress { get; set; } public Employee(string id, string name, string phoneNo, string emailAddress)
{
this.Id = id;
this.Name = name;
this.PhoneNo = phoneNo;
this.EmailAddress = emailAddress;
}
}

最终的响应消息依然是一个HttpMessage对象,它是对我们创建的HttpResponseMessage对象的封装。限于“内部类型”的限制,我们也不得不采用反射的方式来创建这么一个HttpMessage对象。

当我们直接运行该程序后,实际上就已经启动了针对基地址"http://127.0.0.1:3721"的监听器。现在我们通过浏览器对这个监听器发起请求,为了使请求更像一个针对Web API的调用,我们将请求地址设置为“http://127.0.0.1:3721/employees/001”(看起来好像是获取某个编号为001的员工信息)。如右图所示,通过浏览器发送的请求相关信息会显示在控制台上,而浏览器上也会显示基于JSON格式的员工信息。

作者:Artech
出处:http://artech.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
 

ASP.NET Web API的消息处理管道: Self Host下的消息处理管道[上篇]的更多相关文章

  1. ASP.NET Web API实践系列02,在MVC4下的一个实例, 包含EF Code First,依赖注入, Bootstrap等

    本篇体验在MVC4下,实现一个对Book信息的管理,包括增删查等,用到了EF Code First, 使用Unity进行依赖注入,前端使用Bootstrap美化.先上最终效果: →创建一个MVC4项目 ...

  2. ASP.NET Web API消息处理管道:Self Host下的消息处理管道[下篇]

    ASP.NET Web API消息处理管道:Self Host下的消息处理管道[下篇] 我们知道ASP.NET Web API借助于HttpSelfHostServer以Self Host模式寄宿于当 ...

  3. ASP.NET Web API实践系列05,消息处理管道

    ASP.NET Web API的消息处理管道可以理解为请求到达Controller之前.Controller返回响应之后的处理机制.之所以需要了解消息处理管道,是因为我们可以借助它来实现对请求和响应的 ...

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

    构成ASP.NET Web API核心框架的消息处理管道既不关心请求消息来源于何处,也不需要考虑响应消息归于何方.当我们采用Web Host模式将一个ASP.NET应用作为目标Web API的宿主时, ...

  5. 总体介绍ASP.NET Web API下Controller的激活与释放流程

    通过<ASP.NET Web API的Controller是如何被创建的?>我们已经对HttpController激活系统的核心对象有了深刻的了解,这些对象包括用于解析程序集和有效Http ...

  6. ASP.NET Web API下Controller激活

    一.HttpController激活流程 对于组成ASP.NET Web API核心框架的消息处理管道来说,处于末端的HttpMessageHandler是一个HttpRoutingDispatche ...

  7. 目标HttpController在ASP.NET Web API中是如何被激活的:目标HttpController的创建

    目标HttpController在ASP.NET Web API中是如何被激活的:目标HttpController的创建 通过上面的介绍我们知道利用HttpControllerSelector可以根据 ...

  8. ASP.NET Web API 框架研究 Controller实例的销毁

    我们知道项目中创建的Controller,如ProductController都继承自ApiController抽象类,其又实现了接口IDisposable,所以,框架中自动调用Dispose方法来释 ...

  9. ASP.NET Web API 控制器创建过程(一)

    ASP.NET Web API 控制器创建过程(一) 前言 在前面对管道.路由有了基础的了解过后,本篇将带大家一起学习一下在ASP.NET Web API中控制器的创建过程,这过程分为几个部分下面的内 ...

随机推荐

  1. 项目管理实践 -- 健身小管家(Fitness housekeeper)的管理(5)(终结)

    App已经上线了,应用宝上搜索“健身小管家”即可找到,不过存在几个问题:

  2. Vim实用小技巧

    Vim实用小技巧 一些网络上质量较高的Vim资料 从我07年接触Vim以来,已经过去了8个年头,期间看过很多的Vim文章,我自己觉得非常不错,而且创作时间也比较近的文章有如下这些. Vim入门 目前为 ...

  3. DirectX11 学习笔记2 - 加入关键事件 实现视角转换 旋转

    上的程序的的基础上.在基类D3DBase添加摄像头功能 //录影机 void D3DBase::setCamera() { //关键事件 //假定A,S,D,W,Q,E,Z,X,C键被按下.动摄像机 ...

  4. c语言下多线程

    原文:c语言下多线程 [问题]创建了10个线程,10个线程公用一个线程体,创建如下: int t1=0,t2=1,t3=2,t4=3,t5=4,t6=5,t7=6,t8=7,t9=8,t10=9; i ...

  5. Javascript规范

    本文地址: http://www.hicss.net/evolve-your-javascript-code/ 方才在程序里看到一段JS代码,写法极为高明,私心想着若是其按照规范来写,定可培养对这门语 ...

  6. 发现新大陆:一个最简单的破解SSL加密网络数据包的方法

    1. 简介 相信能访问到这篇文章的同行基本上都会用过流行的网络抓包工具WireShark,用它来抓取相应的网络数据包来进行问题分析或者其他你懂的之类的事情. 一般来说,我们用WireShark来抓取包 ...

  7. WaitHandle、AutoResetEvent、ManualResetEvent

    多线程中的锁系统(三)-WaitHandle.AutoResetEvent.ManualResetEvent 介绍 本章主要说下基于内核模式构造的线程同步方式,事件,信号量. 目录 一:理论 二:Wa ...

  8. 阐述linux IPC(五岁以下儿童):system V共享内存

    [版权声明:尊重原创.转载请保留源:blog.csdn.net/shallnet 要么 .../gentleliu,文章学习交流,不用于商业用途]         system V共享内存和posix ...

  9. 编程算法 - 二叉树的深度 代码(C)

    二叉树的深度 代码(C) 本文地址: http://blog.csdn.net/caroline_wendy 题目: 输入一棵二叉树的根节点, 求该树的深度. 依次选择最深的左右子树, 然后递归加1. ...

  10. UC编程:输入输出重定向(系统调用)

    在Unix下,系统重定向是使用dup和dup2函数完成的 在学习使用这两个函数之前,必须要搞懂一个概念就是文件描述符 摘自:<文件描述符和文件指针的区别> 文件描述符就是open文件时产生 ...