通过重建Hosting系统理解HTTP请求在ASP.NET Core管道中的处理流程[上]:采用管道处理请求
之所以称ASP.NET Core是一个Web开发平台,而不是一个单纯的开发框架,源于它具有一个极具扩展性的请求处理管道,我们可以通过对这个管道的定制来满足各种场景下的HTTP处理需求。ASP. NET Core应用的很多特性,比如路由、认证、会话、缓存等,都是通过对管道的定制来实现的。我们甚至可以通过管道定制在ASP.NET Core平台上创建我们自己的Web框架,实际上MVC和SingalR这两个重要的Web框架也是采用这样的方式创建的。 [源代码从这里下载]
目录 一、从Hello World说起 二、管道的构成 三、管道的定制
一、从Hello World说起
HTTP协议自身的特性决定了任何一个Web应用的工作方式都是监听、接收并处理HTTP请求,并在最终对请求予以响应,HTTP请求处理是管道式设计典型的应用场景。具体来说,我们根据具体的HTTP处理请求构建一个管道,接收到的HTTP请求消息想水一样流入这个管道,组成这个管道的各个环节依次对它作相应的处理。处理的结果同样转变成消息逆向流入这个管道进行处理,并最终转变成回复给客户端的HTTP响应。ASP.NET Core的消息处理管道从设计的角度来讲是非常简单的,但是从具体实现的角度则相对复杂并相对难以理解,为了让读者朋友们通过本章对此具有深刻的理解,我们从简单的部分讲起。
为了使读者朋友们能够以最直观的感受认识ASP.NET Core的消息处理管道,我们来创建一个最简单的Hello World程序。这是一个控制台应用,整个程序由如下所示的五行代码组成。当我们运行这个程序之后,一个名为KestrelServer的服务器将会启动并绑定到本机上的5000端口进行请求监听。针对所有接收到的请求,我们都有会响应一个“Hello World”字符串。
1: public class Program
2: {
3: public static void Main()
4: {
5: new WebHostBuilder()
6: .UseKestrel()
7: .Configure(app => app.Run(async context=> await context.Response.WriteAsync("Hello World")))
8: .Build()
9: .Run();
10: }
11: }
这个程序涉及到一个名为WebHost重要的对象, 它可以看成是Web应用的宿主,启动Web应用本质上就是启动作为宿主的WebHost对象。WebHostBuilder是WebHost的创建者,我们调用它的Build方法创建相应的WebHost。当我们调用WebHost的扩展方法Run启动应用的时候,用于监听、接收、处理和响应HTTP请求的管道随之被建立。那么在这个过程中,通过调用Configure方法注册到WebHostBuilder上的委托对象(委托类型为Action<IApplicationBuilder>)将用于管道的定制。总的来说,ASP.NET Core管道由WebHost在启动的时候构建,WebHostBuilder则是后者的创建者,下图揭示了三者之间的关系。
二、管道的构成
HTTP请求处理流程始于对请求的监听与接收,终于对请求的响应,这两项工作均由同一个对象来完成,我们称之为 “服务器(Server)” 。尽管ASP.NET Core的请求处理管道可以被自由地订制,但是该管道必须有一个Server,Server是整个管道的 “龙头” 。在上面的这个Hello World应用中,在调用WebHostBuilder的Build方法创建一个WebHost之前,我们调用了它的一个扩展方法UseKestrel,这个方法的作用就是为后续构建的管道注册一个名为KestrelServer的Server。
随着WebHost的Start方法(当我们调用WebHost的扩展方法Run时,它的Start方法会自动被调用)的调用,定制的管道会被构建出来,管道的服务器将会绑定到一个预设的端口(比如KestrelServer默认采用5000作为监听端口)开始监听请求。HTTP请求一旦抵达,Server会并将其标准并分发给管道后续的节点,我们将管道中位于服务器之后的节点称为“中间件(Middleware)”。每个中间件都具有各自独立的功能,比如我们有专门实现路由功能的中间件,有专门实施用户认证的中间件。所谓的管道定制体现在根据具体的需求选择对应的中间件组成最终处理请求的管道。下图揭示了由一个服务器和一组中间件构成的请求处理管道。
一个建立在ASP.NET Core之上的应用一般都是根据某个框架开发的,一般来说,开发框架本身就是通过某一个或者多个中间件构建的。以ASP.NET Core MVC这个最著名的开发框架为例,它实际上是借助于一个叫做 “路由” 的中间件实现了请求地址与Controller/Action之间的映射,并在此基础实现了激活Controller、执行Action以及呈现View等一系列的功能。所以应用程序可以视为某个中间件的一部分,如果一定要将它独立出来,整个请求处理管道将呈现出如下图所示的结构。
三、管道的定制
在演示的Hello World程序中,我们在调用扩展方法UseKestrel注册KestrelServer服务器之后,还调用WebHostBuilder如下一个名为Configure的扩展方法注册了一个类型为Action<IApplicationBuilder>的委托对象。从请求处理管道的角度来讲,注册这个委托对象的目的在于对构建的管道进行定制,说得更加具体一点,我们利用这个类型为管道注册需要的中间件。演示实例中注册的这个委托对象调用ApplicationBuilder的扩展方法Run注册了一个中间件来为每个请求响应一个 “Hello World” 字符串。
1: public static IWebHostBuilder Configure(this IWebHostBuilder hostBuilder, Action<IApplicationBuilder> configureApp)
除了通过调用WebHostBuilder的Configure方法注册一个Action<IApplicationBuilder>类型的委托,注册中间定义管道的逻辑更多地还是定义在一个单独的类型中。由于管道的定制总是在应用启动(Startup)的时候进行,我们一般称这个用于定制管道的类型为“启动类型”,并在大部分情况下会直接命名为Startup。按照约定,通过注册中间件定制管道的操作会实现在名为Configure的方法中,方法的第一个参数类型必须是IApplicationBuilder接口,后面可定义任意数量和类型的参数,当这个方法被ASP.NET Core框架调用的时候,这些参数会采用依赖注入的方式来提供。启动类型可以通过调用WebHostBuilder的扩展方法UseStartup<T>进行注册,如下面的代码与前面演示的实例是完全等效的。
1: public class Program
2: {
3: public static void Main()
4: {
5: new WebHostBuilder()
6: .UseKestrel()
7: .UseStartup<Startup>()
8: .Build()
9: .Run();
10: }
11: public class Startup
12: {
13: public void Configure(IApplicationBuilder app)
14: {
15: app.Run(async context => await context.Response.WriteAsync("Hello World"));
16: }
17: }
18: }
在真正的项目开发中,我们会利用ApplicationBuilder注册相应的中间件进而构建一个适合当前请求处理需求的管道。如下面的代码片段所示,我们除了按照如上的方式调用扩展方法UseMvc注册了支撑MVC框架的中间件(实际上是一个实现路由的中间件)之外,我们还通过调用其它的扩展方法注册了相应的中间件实现了对静态文件的访问(UseStaticFiles)、错误页面的呈现(UseExceptionHandler)以及基于ASP.NET Identity Framework的认证(UseIdentity)。
1: public class Startup
2: {
3: public void Configure(IApplicationBuilder app)
4: {
5: app.UseExceptionHandler("/Home/Error");
6: app.UseStaticFiles();
7: app.UseIdentity();
8:
9: app.UseMvc();
10: }
11: }
- Windows Server 2008群集仲裁机制
- [C#2] 5-迭代器
- 服务器未能识别 HTTP 标头 SOAPAction 的值
- 实用代码-C#获取本机网络适配器信息及MAC地址
- WordPress 自定义 login (登录页面)CSS 样式
- [C#1] 12-特性
- HTTP Basic Authentication for RESTFul Service
- [C#2] 4-可空类型、静态类
- jquery 操作css 尺寸
- Windows 7上IIS出现http 500错误
- [C#2] 2-匿名方法
- jquery 操作css 选择器
- 主页后台源码及释义
- [C#2] 3-局部类型、属性访问器保护级别、命名空间别名限定符
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- MySQL集群高可用架构之MHA
- MySQL 主从同步架构中你不知道的“坑”
- Nginx+keepalived高可用配置实战(内附彩蛋)
- MySQL 主从同步架构中你不知道的“坑”(完结篇)
- 分布式文件系统FastDFS详解
- 高效、易用、功能强大的 api 管理平台(内附彩蛋)
- Docker容器技术入门(一)
- 【容器技术】Docker容器技术入门(二)
- Mysql+Mycat实现数据库主从同步与读写分离
- 性能优化|Tomcat 服务优化
- zookeeper技术介绍
- Nosql数据库服务之redis
- 计算机网络基础:这是一份详细HTTP学习指南
- Docker容器技术之Docker file
- 学会这15点,让你分分钟拿下Redis数据库