1.4 IIS架构探秘
本节将详细介绍HTTP请求是如何被IIS的各个组件顺序处理的。
1.4.1 IIS的内核层实现
早期的IIS是一个运行在用户态的服务程序,这和一般程序员自己编写的桌面程序没有本质的区别。应用程序都是运行在保护模式下的用户态,由操作系统为程序分配资源来运行。到了IIS 6.0,为了进一步提高IIS的性能和数据吞吐量,最基础的I/O部分和协议处理部分被封装成了Windows的内核驱动,以内核驱动的方式在Windows上运行可以直接访问计算机的物理内存,程序运行更加高效。与IIS相关的内核驱动程序有两个:一个是tcp.sys,另一个是http.sys。
所谓TCP ,是用来定义在网络上数据传送方式的协议,它是一个位于OSI七层协议栈的传输层的协议。因此,tcp.sys专司Windows操作系统与外界使用TCP协议传输数据的功能。HTTP协议是一个定义在应用层的协议,它定义了数据交互的谓词数据的格式等等,但是传输层上是使用TCP协议进行数据包传送。了解以上内容有助于理解http.sys和tcp.sys的关系:tcp.sys位于Windows通信的最底层,凡是使用TCP协议传输的HTTP协议数据包都会被tcp.sys完成组包后再交给http.sys进行处理。
现在需要深入介绍http.sys的内部构造,因为它和IIS处理HTTP请求直接相关。
如图1.1所示的最底层,是TCP/IP协议栈,由tcp.sys负责处理。当请求的数据包包含一个HTTP请求时,就会由tcp.sys转给http.sys进行处理。http.sys中包含有HTTP引擎,专门用于对HTTP请求进行分析和解析,即从HTTP请求中分析出头部数据、查询串和域名等信息。对于合规的HTTP请求,http.sys会生成HTTP上下文对象,并把上下文对象放到网站对应的请求队列中排队等待处理。
图1.1 http.sys内部结构
http.sys提供一组不开放给第三方的API以便于IIS的用户态的程序调用,用户态的程序可以通过这些API从等待队列中提取HTTP上下文对象并传递给工作线程进行处理。对于处理结果,用户态的程序会通过API把运算结果再交给http.sys,由http.sys的数据响应处理模块把数据发送给客户端浏览器。如果IIS管理员配置了缓存策略,那么这部分数据还会同时缓存到响应缓存模块中,以待下次接收到相同请求时,直接返回缓存中的数据。
以上就是http.sys的主要工作原理,IIS管理员可以通过修改配置的方式设定缓存策略和大小以及等待队列的长度(这些在后续章节都会有详细的介绍),但无法通过编程的方式控制http.sys的行为。
1.4.2 IIS的应用层实现
http.sys在内核态上处理完HTTP请求后,IIS就会把HTTP请求对应的HTTP上下文对象转到对应的应用程序进程中,由对应的w3wp.exe进程对请求进行处理。
其实IIS本身只能处理htm或html等静态HTML页面,对于动态页面,IIS自身是无能为力的。那么怎么让IIS能够支持诸如ASP.NET或PHP等动态页面技术呢?答案就是采用ISAPI。ISAPI可以被理解为是IIS的一种扩展插件,当IIS发现某种服务器上的资源自己无法处理时,就会按照配置信息把请求转给对应的ISAPI的扩展来执行;IIS会等待ISAPI的执行结果,然后把结果透传给客户端浏览器。
如图1.2所示,IIS发现请求是一个自己无法处理的.aspx结尾的页面,于是会在w3wp.exe进程中按照预设配置创建ASP.NET ISAPI扩展的实例,让ISAPI计算好结果返回给用户。在Web园(Web Farm)模式下,一个应用程序池会有多个w3wp.exe进程实例一起工作。
图1.2 HTTP请求处理过程
以上提到了IIS会按照预先配置调用对应的ISAPI,那么ISAPI的配置在哪里呢?实际上它是在IIS的处理程序映射模块中进行配置的。如图1.3所示,ASP.NET配置了一条策略,告诉IIS当遇到以.aspx为结尾的资源时,不要自己处理而是调用aspnet_isapi.dll这个ISAPI来处理。
图1.3 ASP.NET 2.0的ISAPI映射
可以发现,针对一种特定的资源类型,IIS的处理程序映射中有多条配置,虽然它们看起来是重复的,其实不然。以.aspx为例,如果Web服务器上同时运行着.NET 4.0和.NET 2.0那么就需要两条独立的配置;如果还需要同时兼容32位和64位,那么就需要四条.aspx的配置才够,因此,处理程序映射的配置并没有重复。
1.4.3 一个HTTP请求在IIS上处理的完整流程
如图1.4所示,该图描述了一个HTTP请求的完整处理流程,通过该流程可以了解到IIS的各部分组件是如何协同工作的,如图1.4所示。
图1.4 HTTP请求处理流程
① 用户在客户端浏览器输入一个URL地址,向Web服务器发起HTTP请求,这个请求会首先被http.sys内核驱动进行处理。
② http.sys驱动联络WAS服务,从配置文件中获取网站相关的配置信息。
③ WAS服务请求从配置文件中获取网站相关配置信息。
④ WWW服务接收到网站相关的诸如应用程序池和站点配置等配置信息。
⑤ WWW服务使用的配置信息来配置http.sys内核驱动的行为,如请求队列等。
⑥ 当站点还没有工作进程为它服务时,WAS服务按照网站应用程序池配置启动一个w3wp.exe工作进程。
⑦ 工作进程处理用户的HTTP请求,并将结果返回到http.sys驱动作为对该HTTP请求的响应。
⑧ 客户端浏览器收到响应,渲染页面给用户查看。
1.4.4 一个ASP.NET页面请求的处理
1.4.2节已经介绍了IIS是通过ISAPI扩展的方式来处理自己无法处理的动态页面请求的。本节会着重介绍当HTTP请求到达ASP.NET ISAPI之后具体发生了什么,如图1.5所示。
图1.5 ISAPI创建对象和对象之间的关系
当ASP.NET ISAPI接收到某个站点的第一个请求的时候,会通过一个名为ApplicationManager的对象创建一个应用程序域(Application Domain)。应用程序域为Web应用程序的运行提供一个隔离空间,工作进程内允许每个单独应用程序域进行创建和卸载。在应用程序域内,会创建一个HostingEnvironment类型的对象,这个对象可以用来访问与应用程序相关的信息,如应用程序所在文件夹路径等信息。
当Web网站的应用程序域对象创建成功后,对每一个HTTP请求都会创建一个HttpContext核心对象,该核心对象中包含有HTTP请求相关的全部信息,因此它在内存中非常庞大,每个HttpContext对象会占据大约20~30KB的内存。在HttpContext对象中含有HttpRequest和HttpResponse对象,用于封装HTTP的请求和响应数据。
对于Web网站的全局对象如每个功能模块和Session等对象,都被封装在HttpApplication对象中。HttpContext和HttpApplication两个对象极大地方便了程序员的编程,他们可以在代码的任意位置访问这两个对象,以便获取相关数据。
HttpApplication对象还带有一些全局事件,方便程序员在HTTP请求经过特定功能模块时加入自定义的处理方法。这些事件的响应函数通常被存放在网站代码的Global.asax文件中,如常见的Application_BeginRequest和Application_EndRequest分别在HTTP请求到达和离开ASP.NET Web网站时触发。
值得一提的是,Global.asax文件中有两个特殊函数:Application_Start和Application_End。它们代表了Web应用程序的创建和退出,但是它们不是HttpApplication对象的事件响应函数。ASP.NET只在Web引用程序创建和退出时各调用这两个函数一次。
通过以上内容,读者可以了解到一个HTTP请求是如何被IIS内部的各种组件进行处理的,深入地了解HTTP请求处理流程有助于后面章节的学习。