欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

IIS/ASP.NET 管道

程序员文章站 2024-02-27 21:15:03
...

ASP.NET MVC 是建立在 ASP.NET 平台上基于 MVC 模式的 Web 应用框架,深刻理解 ASP.NET MVC 的前提是对 ASP.NET 管道式设计具有深刻的认识。由于 ASP.NET Web 应用大都寄宿于 IIS 上,将两者结合起来了解在 IIS 和 ASP.NET 管道中是如何流动的。

IIS5.x与ASP.NET

IIS5.x 是如何处理基于 ASP.NET 资源(如.aspx.asmx...)请求的呢?

IIS5.x运行在进程 InetInfo.exe 中,该进程寄存着一个名为 World Wide Web Publishing Service(简称W3SVC)的 Windows 服务。W3SVC 主要负责 HTTP 请求的监听、**、管理工作进程、加载配置(从 Metabase 中加载相关配置信息)等。

IIS/ASP.NET 管道
IIS5.x与ASP.NET

当检测到某个 HTTP 请求时,IIS 先根据扩展名判断请求的是静态资源还是动态资源。对于静态资源 IIS 会将文件内容直接响应给客户端,对于动态资源则通过扩展名从 IIS 的脚本映射(Script Map)中找到相应的ISAPI 动态链接库(Dynamic Link Library, DLL)。

ISAPI(Internet Server Application Programming Interface)是一套本地的(Native)Win32 API,是 IIS 和其他动态 Web 应用或平台之间的纽带。

ISAPI 定义在一个动态链接库(DLL)文件中, ASP.NET ISAPI 对应的 DLL 文件名称为 aspnet_isapi.dll,位于 %windir%\Microsoft.NET\Framework\{version no}\中。

ISAPI 支持 ISAPI 扩展(ISAPI Extension)和 ISAPI 筛选(ISAPI Filter),前者是真正处理 HTTP 请求的接口,后者则可以在 HTTP 请求真正被处理之前查看、修改、转发、拒绝请求,比如 IIS 可利用 ISAPI 筛选进行请求的验证。

若请求的是一个基于 ASP.NET 的资源类型,如.aspx.asmx.svc等,aspnet_isapi.dll 会被加载,ASP.NET ISAPI 随后会创建 ASP.NET 的工作进程(若该进程尚未启动)。对于 IIS5.x 来说,该工作进程为 aspnet.exe。IIS 进程与工作进程之间通过命名管道(Named Pipes)进行通信。

在工作进程初始化过程中,.NET 运行时(CLR)会被加载以构建一个托管的环境。对于某个 Web 应用的初次请求,CLR 会被其创建一个应用程序域(Application Domain)。

在应用程序域中,HTTP 运行时(HTTP Runtime)被加载并用以创建相应的应用。寄存于 IIS5.x 的所有Web应用都运行在同一个进程(工作进程 aspnet_wp.exe)的不同应用程序域中。

IIS6.0 与 ASP.NET

IIS5.x存在两个方面的不足:

  • ISAPI动态链接库被加载到 InetInfo.exe 进程中,它和工作进程之间是一种典型的跨进程通信方式,尽管采用命名管道,仍会带来性能瓶颈。

解决方案:IIS6.0将ISAPI动态链接库直接加载到工作进程中

  • ASP.NET 应用运行在相同进程 aspnet_wp.exe 的不同程序域中,基于应用程序域的隔离不能从根本上解决一个应用程序对另一个程序的影响,需不同的Web应用运行在不同的进程中。

解决方案:IIS6.0 加入了应用程序池 Application Pool 的机制,为一个或多个 Web 应用创建一个应用程序池,每个应用程序池对应一个独立的工作进程 w3wp.exe,所以运行在不同应用程序池中的 Web 应用提供基于进程级别的隔离机制。

HTTP.SYS

IIS6.0 中最重要的一点是创建了一个名为 HTTP.SYS 的 HTTP 监听器,HTTP.SYS 以驱动程序的形式运行在Windows 的内核模式 Kernal Mode下,它是 Windows TCP/IP 网络子系统的一部分,从结构上看它属于 TCP 之上的一个网络驱动程序。

严格地说,HTTP.SYS 已经不属于 IIS 的范畴了,所以 HTTP.SYS 的配置信息也没有保存在 IIS 的元数据库Metabase,而是定义在注册表中。HTTP.SYS 的注册表项的路径为HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/HTTP

HTTP.SYS能够带来如下好处:

  • 持续监听

由于 HTTP.SYS 是一个网路驱动程序,始终处于运行状态,所以对于用户会的 HTTP 请求能够及时作出反应。

  • 稳定性

HTTP.SYS 运行在操作系统内核模式下,并不执行任何用户代码,其本身不会受到 Web应用、工作进程、IIS进程的影响。

  • 内核模式下的数据缓存

如果某个资源被频繁请求,HTTP.SYS 会把响应的内容进行缓存,缓存的内容可直接响应后续的请求。由于这是基于内核模式的缓存,不存在内核模式和用户模式的切换,响应速度得到极大的改进。

IIS6.0结构

与 IIS5.x 不同,W3SVC 在 IIS6.0 中从 InetInfo.exe 进程脱离出来,对于 IIS6.0 来说,InetInfo.exe基本上可以看做单纯的 IIS 管理进程,运行在另一个进程 SvcHost.exe中。不过 W3SVC 的基本功能并没有发生变化,只是在功能的实现上作出相应的改进。与 IIS5.x 一样,元数据库 Metabase 依然存在于InetInfo.exe进程中。

IIS/ASP.NET 管道
IIS6.0与ASP.NET

IIS6.0处理HTTP请求的流程

当监听到HTTP请求时,HTTP.SYS 将其分发给 W3SVC,W3SVC 解析出请求的 URL ,并根据从 Metabase 获取的 URL 与 Web 应用之间的映射关系得到目标应用,进而得到目标应用运行的应用程序池或工作进程。如果工作进程不存在或尚未创建或被回收,它为请求创建新的工作进程。在工作进程的初始化过程中,相应的 ISAPI 动态链接库被加载,对于 ASP.NET 应用来说,被加载的 ISAPI.dllaspnet_isapi.dll。ASP.NET ISAPI 负责进行 CLR 的加载、应用程序域的创建和 Web 应用的初始化等操作。

IIS7.0与ASP.NET

IIS7.0在请求的监听和分发机制上进行了革新,体现在引入Windows进程**服务(Windows Process Activation Service, WAS),分流了原来IIS6.0中W3SVC承载的部分功能。

IIS6.0中的W3SVC主要承载着如下三个功能:

  • HTTP请求接收:接收HTTP.SYS监听到的HTTP请求
  • 配置管理:从元数据Metabase中加载配置信息对相关组件进行配置
  • 进程管理:创建、回收、监控工作进程

IIS7.0将后两组功能实现到了WAS中,但接收HTTP请求的任务依然落在W3SVC上。WAS的引入为IIS7.0提供了对非HTTP协议的支持,通过监听适配器接口 Listener Adapter Interface抽象出针对不同协议的监听器。具体来说或,除了专门用于监听HTTP请求的HTTP.SYS之外,WAS利用TCP监听器、命名管道监听器和MSMQ监听器提供基于TCP、命名管道、MSMQ传输协议的监听支持。

与此3种监听器相对应的是3种监听适配器,它们提供监听器与WAS中的监听适配器接口之间的适配,从这个意义上讲,IIS7.0中的W3SVC相当于HTTP.SYS的监听适配器。这3种非HTTP监听器和监听适配器定义在程序集 SMSvcHost.exe 中。可在目录%windir%\Microsoft.NET\Framework\v3.0\Windows Communication Foundation\中找到。

从程序集所在的目录名称可以看出,这3种监听器/监听适配器是为WCF设计的,它们以Windows服务的形式进行工作。虽然定义在一个程序集中。但依然可通过服务工作管理器对其进行单独的启动、终止、配置。总的来说,SMSvcHost.exe提供了4个重要的Windows Service。

  • NetTcpPortSharing
    为WCF提供TCP端口共享,即同一个监听端口被多个进程共享。
  • NetTcpActivator
    为WAS提供基于TCP的**请求,包含TCP监听器和对应的监听适配器。
  • NetPipeActivator
    为WAS提供基于命名管道的**请求,包含命名管道和对应的监听适配器。
  • NetMsmqActivator
    为WAS提供基于MSMQ的**请求,包含MSMQ监听器和对应的监听适配器。
IIS/ASP.NET 管道
Windows Service

IIS7.0架构

IIS/ASP.NET 管道
IIS7.0与ASP.NET

无论是从W3SVC接收到的HTTP请求,还是通过WCF提供的监听适配器接收到的针对其他传输协议的请求,最终都胡被传递到WAS。如果相应的工作进程,针对单个应用程序池尚未创建,则WAS会创建工作进程。WAS在进行请求处理过程中通过内置的配置管理模块加载相关的配置信息,并对相关的组件进行配置。

与IIS5.x和IIS6.0基于Metabase的配置信息存储不同的是,IIS7.0大多将配置信息存放在XML形式的配置文件中,基本的配置存放在 applicationHost.config 中。

API.NET 集成

从IIS5.x和IIS6.0中不难发现,IIS与ASP.NET是两个独立的管道 Pipeline。在各自管辖范围内,各自具有自己的一套机制对HTTP请求进行处理。两个管道通过ISAPI实现连通。IIS是第一道屏障,当对HTTP请求进行必要的前期处理,如身份认证等。IIS通过ISAPI将请求分发给ASP.NET管道。当ASP.NET在自身管道范围内完成对HTTP请求的处理时,处理后的结果再返回到IIS,IIS对其进行后期处理,如日志记录、压缩等。之后生成HTTP回复对请求予以响应。

IIS/ASP.NET 管道
IIS6.0与ASP.NET之间的桥接关系 基于IIS6.0和ASP.NET双管道设计

从另一个角度讲,IIS运行在非托管的环境中,而ASP.NET管道则是托管的,所以说ISAPI是连接非托管环境和托管环境的纽带。IIS5.x和IIS6.0把两个管道进行隔离带来的局限的不足是:

  • 相同操作的重复执行

IIS和ASP.NET之间具有一些重复的操作,如身份验证。

  • 动态文件与静态文件处理的不一致

因为只有基于ASP.NET 动态文件的HTTP请求才能通过ASP.NET ISAPI进入ASP.NET管道,对于静态文件的请求则由IIS直接响应,那么ASP.NET管道中的一些功能将不能作用域这些基于静态文件的请求,比如通过Forms认证应用于基于图片文件的请求就做不到。

  • IIS难以扩展

对于IIS的扩展基本上提现在自定义ISAPI,对于大部分人来说,这不是一件容易的事情,因为ISAPI是基于Win32的非托管的API,并非一种面向应用的编程接口。通常希望的是诸如定义ASP.NET的HttpModule和HttpHandler一样,托管代理的方式来扩展IIS。

对于Windows平台下的IIS来讲,ASP.NET无疑是一等公民,IIS7.0中实现了两者的集成,通过集成可获得如下好处:

  • 允许通过本地代码Native Code和托管代码Managed Code两种方式定义IIS Module

这些IIS Module注册到IIS中形成一个通用的请求处理管道,由IIS Module组成的这个管道能够处理所有的请求,无论请求基于怎样的资源类型。例如,可将FormAuthenticationModule提供的Forms认证应用到基于 .aspx、CGI和静态文件的请求。

  • 将ASP.NET提供的一些强大的功能应用到原来难以企及的地方

比如将ASP.NET的URL重写功能置于身份验证之前

  • 采用相同的方式去实现、配置、检测和支持一些服务器特性 Feature

比如Module、Handler映射、定制错误配置 Custom Error Configuration 等

IIS/ASP.NET 管道
基于IIS7.0 与 ASP.NET集成管道设计

ASP.NET 管道

以 IIS6.0为例,它的工作进程 w3wp.exe 中会利用 aspnet_isapi.dll 加载 .NET 运行时(如果 .NET 运行时尚未加载)。IIS6.0引入了应用程序池的概念,一个工作进程对应着一个应用程序池。一个应用程序池可以承载一个或多个Web应用,每个Web应用映射到一个IIS虚拟目录,与IIS5.x一样,每个Web应用运行在各自的应用程序域中。

IIS/ASP.NET 管道
ASP.NET 处理管道

如果HTTP.SYS接收到的HTTP请求是对该 Web 应用的第一次访问,在成功加载运行使之后,IIS会通过 AppDomainFactory 为该Web应用创建一个应用程序域。随后一个特殊的运行时 IsapiRuntime 被加载。IsapiRuntime 定义在程序集 System.Web.dll中,对应的命名空间为System.Web.Hosting, 被加载的 IsapiRuntime 会接管该HTTP请求。

接管 HTTP 请求的 IsapiRuntime 会首先创建一个 IsapiWorkerRequest 对象来封装当前的HTTP请求,随后将次对象传递给 ASP.NET 运行时 HttpRuntime。从此时起, HTTP请求正式进入了ASP.NET管道。HttpRuntime会根据 IsapiWorkerRequest 对象创建用于表示当前HTTP请求的上下文 Context 对象 HttpContext。

随着 HttpContext 的创建,HttpRuntime 会利用 HttpApplicationFactory 创建新的或获取现有的 HttpApplication 对象。实际上 ASP.NET 维护着一个 HttpApplication 对象池, HttpApplicationFactory 从池中选取可用的 HttpApplication 用于处理 HTTP 请求,处理完毕后将其释放到对象池中。HttpApplication 负责处理当前的 HTTP 请求。

在 HttpApplication 初始化过程中,ASP.NET 会根据配置文件加载并初始化注册的 HttpModule 对象。对于 HttpApplication 来说,在它处理HTTP请求的不同阶段会触发不同的事件 Event,而HttpModule的意义在于通过注册HttpApplication的相应事件,将所需的操作注入整个HTTP请求的处理流程。ASP.NET的很多功能都是通过相应的HttpModule实现的。

最终完成对HTTP请求的处理实现在HttpHandler中,不同的资源类型对应着不同类型的HttpHandler。比如.aspx页面对应的HttpHandler类型为System.Web.UI.Page,WCF的.svc文件对应的HttpHandler类型为System.ServiceModel.Activation.HttpHandler

HttpApplication

HttpApplication 是整个 ASP.NET 基础架构的核心,负责处理分发给它的HTTP请求。由于一个HttpApplication对象在某个时刻只能处理一个请求,只有完成对某个请求的处理后才能用于后续请求的处理,所以ASP.NET采用对象池的机制来创建或获取 HttpApplication 对象。

当第一个请求抵达时,ASP.NET会一次创建多个HttpApplication对象,并将其置于池中,然后选择其中一个对象来处理该请求。处理完毕后,HttpApplication不会被回收,而是释放到池中。对于后续的请求,空闲的 HttpApplication 对象会从池中取出。如果池中所有的 HttpApplication 对象都处于繁忙的状态,在没有超出 HttpApplication 池最大容量的情况下,ASP.NET 会创建新的HttpApplication对象,否则将请求放入队列等待现有HttpApplication的释放。

HttpApplication 处理请求的整个生命周期是一个相对复杂的过程,在该过程的不同阶段会触发相应的事件。可注册相应的事件,将处理逻辑注入到HttpApplication处理请求的某个阶段。

HttpApplication在处理每个请求时触发的事件

  • BeginRequest
    HTTP管道开始处理请求时,会触发BeginRequest事件。

  • AuthenticateRequest, PostAuthenticateRequest
    ASP.NET 先后触发这两个事件,使安全模块对请求进行身份验证。

  • AuthorizeRequest, PostAuthorizeRequest
    ASP.NET先后触发这两个事件,使安全模块对请求进行授权。

  • ResolveRequestCache, PostResolveRequestCache
    ASP.NET 先后触发这两个事件,以使缓存模块利用缓存的内容对请求直接进行响应(缓存模块可将响应内容进行缓存,对于后续的请求,直接将缓存的内容返回,从而提高响应能力。)

  • PostMapRequestHandler
    对于访问不同资源类型,ASP.NET具有不同的HttpHandler对其进行处理,对于每个请求,ASP.NET会通过扩展名选择匹配响应的HttpHandler类型,成功匹配后,该事件被触发。

  • AcquireRequestState, PostAcquestRequestState
    ASP.NET先后触发这两个事件,使状态管理模块获取基于当前请求相应的状态,如SessionState。

  • PreRequestHandlerExecute、PostRequestHandlerExecute
    ASP.NET 最终通过与请求资源类型相对应的 HttpHandler 实现对请求的处理,在实行 HttpHandler 前后,这两个事件被先后触发。

  • ReleaseRequestState、PostReleaseRequestState
    ASP.NET 先后触发这两个事件,使状态管理模块释放基于当前请求响应的状态。

  • UpdateRequesetCache、PostUpdateRequestCache
    ASP.NET先后触发这两个事件,以使缓存模块将HttpHandler处理请求得到的内容得以保存到输出缓存中。

  • LogRequest、PostLogRequest
    ASP.NET先后触发这两个事件为当前请求进行日志记录

  • EndRequest 整个请求处理完成后,EndRequest事件会触发。

对于一个ASP.NET应用来说,HttpApplication派生于Global.asax文件,通过创建Global.asax文件对HttpApplication的请求处理行为进行定制。Global.asax采用一种很直接的方式实现了这样的功能,这种方式不是方法重写或事件注册,而是直接采用方法名匹配。在Global.asax中按照“Application_{Event Name}”的命名规则进行事件注册。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;

namespace Huyoo.Backend
{
    // 注意: 有关启用 IIS6 或 IIS7 经典模式的说明,
    // 请访问 http://go.microsoft.com/?LinkId=9394801

    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
        }
    }
}

HttpModule

ASP.NET拥有一个具有高度的可扩展的引擎,能处理不同资源类型的请求,HttpModule是功不可没的。

当请求转入ASP.NET管道时,最终负责处理该请求的是与请求资源类型相匹配的HttpHandler对象,但是在HttpHandler正式工作之前。ASP.NET会先加载并初始化所有配置的HttpModule对象。HttpModule在初始化过程中,会将一些回调操作注册到HttpApplication相应的事件中。在HttpApplication请求处理生命周期中的某个阶段,相应的事件会被触发,通过HttpModule注册的事件处理程序也得以执行。

所有的HttpModule都实现了具有如下定义的System.Web.IHttpModule接口,其Init方法实现了针对自身的初始化。该方法接受一个HttpApplication对象,有了这个对象,事件注册就很容易了。

public interface IHttpModule
{
  void Dispose();
  void Init(HttpApplication context);
}

ASP.NET提供了很多基础功能是通过相应的HttpModule实现的。除了系统定义的HttpModule之外,可自定义HttpModule,通过 Web.config 可很容易地将其注册到 Web 应用中。

  • OutputCacheModule
    实现了输出缓存(Output Caching)的功能
  • SessionStateModule
    在无状态的HTTP协议上实现了基于会话Session的状态保持
  • WindowsAuthenticationModule + FormsAuthenticationModule + PassportAuthenticationModule
    实现了Windows、Forms、Passport三种典型的身份认证方式
  • UrlAuthorizationModule + FileAuthorizationModule
    实现了基于URL和文件ACL(Access Control List)的授权

HttpHandler

对于不同资源类型的请求,ASP.NET会加载不同的Handler来处理,比如.aspx页面与.asmx Web服务对应的Handler是不同的。

所有的HttpHandler都实现了具有如下定义的接口 System.Web.IHttpHandler,定义其中的方法ProcessRequest提供了处理请求的实现。

另一个代表异步版本的HttpHandlerIHttpAsyncHandler接口继承自IHttpHandler,它通过调用BeginProcessRequest/EndProcessRequest方法以异步的方式处理请求。

public interface IHttpHandler
{
  void ProcessRequest(HttpContext context);
  bool IsReusable{ get; }
}
public interface IHttpAsyncHandler:IHttpHandler
{
  IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData);
  void EndProcessRequest(IAsyncResult result);
}

某些 HttpHandler 具有一个与之相关的HttpHandlerFactory,后者实现了具有如下定义的接口 System.Web.IHttpHandlerFactory,定义其中的方法GetHandler用于创建新的HttpHandler或获取已经存在的HttpHandler

public interface IHttpHandlerFactory
{
  IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTransslated);
  void ReleaseHandler(IHttpHandler handler);
}

HttpHandler和HttpHandlerFactory的类型都可以通过相同的方式配置到 Web.config中。

在调用当前HttpContext的RemapHandler方法时指定一个具体的HttpHandler对象,是为了让ASP.NET直接跳过默认的HttpHandler映射操作。此外,由于这个默认的HttpHandler映射发生在HttpApplication的PostMapRequestHandler事件触发之前,所以只有在这之前调用RemapHandler方法才有意义。