Self Host模式下的ASP. NET Web API是如何进行请求的监听与处理的?
2014-03-20 22:30
549 查看
[code] internal sealed class HttpMessage : Message
{
//其他成员
public HttpMessage(HttpRequestMessage request);
public HttpMessage(HttpResponseMessage response);
public HttpRequestMessageGetHttpRequestMessage(bool extract);
public HttpResponseMessage GetHttpResponseMessage(bool extract);
}
[/code]
[/code]
这两个方法均具有一个布尔类型的参数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方法进行请求的监听和接收。
[code] [code] 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());
}
}
}
[/code]
[/code]
对于成功接收的消息,我们调用具有如下定义的PrintRequestMessage方法将相关的信息打印在控制台上。通过上面的介绍我们知道这个接收到的消息实际上是一个HttpMessage对象,由于这是一个内部类型,所以我们只能以反射的方式调用其GetHttpRequestMessage方法获取被封装的HttpRequestMessage对象。在得到表示请求的HttpRequestMessage对象之后,我们将请求地址和所有报头输出到控制台上。
[code] [code] 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()));
}
}
[/code]
[/code]
在对请求进行处理之后,我们需要创建一个Message对象对该请求予以响应,响应消息的创建是通过CreateResponseMessage方法完成的。如下面的代码片断所示,我们首先创建了一个响应状态为“200, OK”的HttpResponseMessage对象,并将其表示主体内容的Content属性设置为一个ObjectContent<Employee>对象。由于我们采用MediaTypeFormatter类型为JsonMediaTypeFormatter,指定的Employee对象会以JSON格式进行序列化。最终的响应消息依然是一个HttpMessage对象,它是对我们创建的HttpResponseMessage对象的封装。限于“内部类型”的限制,我们也不得不采用反射的方式来创建这么一个HttpMessage对象。
[code] [code] 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;
}
}
[/code]
[/code]
[code] public sealed class HttpSelfHostServer : HttpServer
{
public HttpSelfHostServer(HttpSelfHostConfiguration configuration);
public HttpSelfHostServer(HttpSelfHostConfiguration configuration, HttpMessageHandler dispatcher);
public Task OpenAsync();
public Task CloseAsync();
protected override void Dispose(bool disposing);
}
[/code]
[/code]
ASP.NET Web API的消息处理管道的配置利用通过HttpServer的Configuration属性表示的HttpConfiguration对象来完成,对于HttpSelfHostServer来说,它的Configuration属性返回一个HttpSelfHostConfiguration对象(HttpSelfHostConfiguration类型定义在 “System.Web.Http.SelfHost” 命名空间下)。如上面的代码片断所示,当我们调用构造函数创建一个HttpSelfHostServer对象时,需要通过参数指定此HttpSelfHostConfiguration。
HttpSelfHostConfiguration
如下面的代码片断所示,HttpSelfHostConfiguration直接继承自HttpConfiguration。我们在创建一个HttpSelfHostConfiguration对象的时候需要指定一个Uri对象作为监听基地址,这个地址通过只读属性BaseAddress返回。[code] [code] public class HttpSelfHostConfiguration : HttpConfiguration
{
public HttpSelfHostConfiguration(Uri baseAddress);
public Uri BaseAddress{ get;}
public HostNameComparisonModeHostNameComparisonMode{ get; set;}
public TransferModeTransferMode{ get; set;}
public intMaxBufferSize{ get; set;}
public intMaxConcurrentRequests{ get; set;}
public long MaxReceivedMessageSize{ get; set;}
public TimeSpan ReceiveTimeout{ get; set;}
public TimeSpan SendTimeout{ get; set;}
public HttpClientCredentialTypeClientCredentialType{ get; set;}
public UserNamePasswordValidator UserNamePasswordValidator{ get; set;}
public X509CertificateValidatorX509CertificateValidator{ get; set;}
}
[/code]
[/code]
由于Self Host寄宿模式下请求的监听、接收和响应基本上全部是通过HttpBinding实现的,所以定义在HttpSelfHostConfiguration中的众多属性实际上基本都用于对创建的HttpBinding进行配置。从如下给出的代码片断可以看出HttpBinding类型与HttpSelfHostConfiguration具有类似的属性定义。
[code] [code] public abstract class Binding : IDefaultCommunicationTimeouts
{
//其他成员
public TimeSpan ReceiveTimeout{ get;set;}
public TimeSpan SendTimeout{ get;set;}
}
public class HttpBinding : Binding, IBindingRuntimePreferences
{
//其他成员
public HostNameComparisonMode HostNameComparisonMode{ get; set;}
public TransferMode TransferMode{ get; set;}
public long MaxBufferPoolSize{ get; set;}
public intMaxBufferSize{ get; set;}
public long MaxReceivedMessageSize{ get; set;}
public HttpBindingSecurity Security{ get; set;}
}
[/code]
[/code]
由于Binding在WCF是一个核心组件,其设计本身相对复杂,要深入了解定义在HttpBinding中的这些属性需要相关的背景知识。篇幅所限,我们不能因为这些属性将Binding相关的内容全部搬过来,所以在这里我们仅仅通过下表对它们进行概括性的介绍。
属性 | 描述 |
HostNameComparisonMode | 如果请求URL没有指定服务器的IP地址而是主机名称,当从URL提取主机名称后会按照相应的比较模式来最终确定匹配的主机名。该属性的类型为System.ServiceModel.HostNameComparisonMode枚举,用以确定主机名比较模式。 |
TransferMode MaxBufferSize | 消息传输具有Streamed和Buffered两种模式,前者以流的形式进行消息传输,后者则将整个消息的内容先保存于内存缓冲区后一并传输。该属性类型为System.ServiceModel.TransferMode枚举,用以控制针对请求消息和响应消息的传输模式。在默认情况下,请求消息和响应消息均以Buffered模式进行传输。MaxBufferSize属性表示在采用Buffered模式下消息最大缓冲大小,默认值为65536(0x10000)。 |
MaxConcurrentRequests MaxReceivedMessageSize | 这两个是针对请求的限制,分别表示运行的最大并发访问量和请求消息允许的最大尺寸。两者的默认值分别为100和65536(0x10000)。值得一提的是MaxConcurrentRequests针对最大并发请求的限制是针对单个处理器设定的,对于多处理器或者多核处理来说,应该乘以处理器的数量。 |
ReceiveTimeout SendTimeout | 这两个属性分别表示接收请求消息和发送响应消息的超时时限,默认值分别为10分钟和1分钟。 |
ClientCredentialType UserNamePasswordValidator X509CertificateValidator | 这是三个与安全认证相关的属性。ClientCredentialType表示客户端采用的用户凭证类型,而UserNamePasswordValidator和X509CertificateValidator属性值分别在用户凭证为“用户名+密码”和“X.509证书”的情况下实施认证。 |
HttpSelfHostServer与消息处理管道
在采用Self Host模式寄宿Web API时,我们会根据指定的监听基地址创建一个HttpSelfHostConfiguration对象,然后据此创建HttpSelfHostServer。当我们调用方法OpenAsync开启它时,HttpSelfHostServer会创建一个HttpBinding对象,并根据指定的HttpSelfHostConfiguration对其作相应的设置。随后HttpBinding会针对指定的监听地址创建一个ChannelListener管道,并调用其BeginOpen方法以异步的方式开启。除了OpenAsync方法外,HttpSelfHostServer还定义了一个CloseAsync方法使我们可以以异步的方式关闭已经开启的ChannelListener管道。一旦ChannelListener管道被成功开启后,它便绑定到指定的监听地址进行请求的监听。当抵达的请求被探测到,它会利用创建的消息处理管道来接收该请求。通过上面对HttpBinding的介绍我们知道,接收到的二进制数据经过解码之后会生成一个HttpMessage对象,后者是对一个HttpRequestMessage的封装。
[code] public class MyHttpSelfHostServer: HttpServer
{
public Uri BaseAddress{ get; private set;}
public IChannelListener<IReplyChannel> ChannelListener{ get; private set;}
public MyHttpSelfHostServer(HttpConfiguration configuration, Uri baseAddress)
: base(configuration)
{
this.BaseAddress = baseAddress;
}
public void Open()
{
HttpBinding binding = new HttpBinding();
this.ChannelListener = binding.BuildChannelListener<IReplyChannel>(this.BaseAddress);
this.ChannelListener.Open();
IReplyChannel channnel = this.ChannelListener.AcceptChannel();
channnel.Open();
while (true)
{
RequestContext requestContext = channnel.ReceiveRequest(TimeSpan.MaxValue);
Message message = requestContext.RequestMessage;
MethodInfo method = message.GetType().GetMethod("GetHttpRequestMessage");
HttpRequestMessage request = (HttpRequestMessage)method.Invoke(message, new object[]{true});
Task<HttpResponseMessage> processResponse = base.SendAsync(request, new CancellationTokenSource().Token);
processResponse.ContinueWith(task =>
{
string httpMessageTypeName = "System.Web.Http.SelfHost.Channels.HttpMessage, System.Web.Http.SelfHost";
Type httpMessageType = Type.GetType(httpMessageTypeName);
Message reply = (Message)Activator.CreateInstance(httpMessageType, new object[]{ task.Result});
requestContext.Reply(reply);
});
}
}
public void Close()
{
if (null != this.ChannelListener && this.ChannelListener.State == CommunicationState.Opened)
{
this.ChannelListener.Close();
}
}
}
[/code]
[/code]
MyHttpSelfHostServer的只读属性BaseAddress表示监听基地址,该属性直接在构造函数中指定。与HttpSelfHostServer不同的是,用于创建MyHttpSelfHostServer提供的配置对象是一个HttpConfiguration对象而不再是HttpSelfHostConfiguration。
在Open方法中,我们根据提供的监听基地址利用HttpBinding对象创建一个ChannelListener对象,MyHttpSelfHostServer的只读属性ChannelListener引用的也正是这个对象。在开启该ChannelListener之后,我们调用其AccpetChannel方法创建信道栈,最终返回位于栈顶的Channel。在该Channel开启的情况下,我们在一个“永不终止”的While循环中调用其ReceiveRequest方法进行请求的监听。
当信道栈成功接收请求消息后(这是一个HttpMessage对象),我们从中提取出被封装的HttpRequestMessage对象,并将其作为参数调用SendAsync方法,表示请求的HttpReuqestMessage自此进入了消息处理管道这个流水车间。
调用SendAsync方法返回的是一个Task<HttpResponseMessage>对象,我们执行这个Task对象并获得表示响应的HttpResponseMessage对象,然后以反射的形式将其封装成HttpMessage对象。我们最终利用此HttpMessage对象对请求作最终的响应。HttpSelfHostServer定义了OpenAsync和CloseAsync方法开启和关闭监听器,与之相匹配,我们也为Open方法定义了匹配的Close方法来关闭已经开启的ChannelListener。
为了验证我们自定义的MyHttpSelfHostServer是否能够替代“原生”的HttpSelfHostServer,我们在一个控制台中定义了如下一个继承自ApiController的ContactsController。它具有两个重载的Action方法Get,前者用于返回所有的联系人列表,后者返回指定ID的某个联系人信息。
[code] [code]public class ContactsController : ApiController
{
private static List<Contact> contacts = new List<Contact>
{
new Contact{ Id="001", Name = "张三",PhoneNo="123", EmailAddress="zhangsan@gmail.com"},
new Contact{ Id="002",Name = "李四", PhoneNo="456", EmailAddress="lisi@gmail.com"}
};
public IEnumerable<Contact> Get()
{
return contacts;
}
public Contact Get(string id)
{
return contacts.FirstOrDefault(c => c.Id == id);
}
}
public class Contact
{
public string Id{ get; set;}
public string Name{ get; set;}
public string PhoneNo{ get; set;}
public string EmailAddress{ get; set;}
}
[/code]
[/code]
在作为入口的Main方法中我们编写了如下一段简单的“寄宿”程序。我们根据创建的HttpConfiguration对象和指定的监听基地址(“http://127.0.0.1:3721”)创建了一个MyHttpSelfHostServer对象。在调用Open方法开始监听之前,我们注册了一个URL模板为“http://127.0.0.1:3721”的HttpRoute。
[code] [code] class Program
{
static void Main(string[] args)
{
using (MyHttpSelfHostServer httpServer = new MyHttpSelfHostServer(new HttpConfiguration(), new Uri("http://127.0.0.1:3721")))
{
httpServer.Configuration.Routes.MapHttpRoute(
name : "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults : new{ id = RouteParameter.Optional});
httpServer.Open();
Console.Read();
}
}
}
[/code]
[/code]
运行该程序之后,这个“宿主”程序便开始进行请求的监听。现在我们直接利用浏览器对定义在ContactsController中的两个Action方法Get发起请求,通过注册的HttpRoute和“请求的HTTP方法直接作为Action名称”的原理,我们使用的URL分别为“http://127.0.0.1:3721/api/contacts”和“http://127.0.0.1:3721/api/contacts/001”。如右图所示,我们期望的联系人信息直接以XML的形式显示在浏览器中,由此可见我们自定义的MyHttpSelfHostServer“不辱使命”。
相关文章推荐
- Self Host模式下的ASP. NET Web API是如何进行请求的监听与处理的?
- ASP.NET Web API消息处理管道:Self Host下的消息处理管道[下篇]
- ASP.NET Web API的消息处理管道: Self Host下的消息处理管道[上篇]
- asp.net web api的自托管模式HttpSelfHostServer可以以控制台程序或windows服务程序为宿主,不单单依赖于IIS web服务器
- 学习ASP.NET Core, 怎能不了解请求处理管道[3]: 自定义一个服务器感受一下管道是如何监听、接收和响应请求的
- ASP.NET Web API是如何根据请求选择Action的?[下篇]
- IIS是如何处理ASP.NET请求的
- ASP.NET MVC 中如何用自定义 Handler 来处理来自 AJAX 请求的 HttpRequestValidationException 错误
- ASP.NET Core应用针对静态文件请求的处理[3]: StaticFileMiddleware中间件如何处理针对文件请求
- IIS是如何处理ASP.NET请求的
- 使用 OWIN Self-Host ASP.NET Web API 2
- Self-Host c#学习笔记之Application.DoEvents应用 不用IIS也能執行ASP.NET Web API
- Android客户端发送Post/Get请求到Asp.Net服务端一般处理程序Asp.Net进行参数解析
- IIS如何处理Asp.net请求和Asp.net页面生命周期方法
- ASP.NET Core应用针对静态文件请求的处理[5]: DefaultFilesMiddleware中间件如何显示默认页面
- [ASP.NET Web API]如何Host定义在独立程序集中的Controller
- IIS是如何处理ASP.NET请求的
- IIS是如何处理ASP.NET请求的
- 学习ASP.NET Core, 怎能不了解请求处理管道[6]: 管道是如何随着WebHost的开启被构建出来的?
- IIS是如何处理ASP.NET请求的