A low-level Look at the ASP.NET Architecture
Getting Low Level 进入底层:
This article looks at how Web requests flow through the ASP.NET framework from a very low level perspective, from Web Server, through ISAPI all the way up the request handler and your code. See what happens behind the scenes and stop thinking of ASP.NET as a black box.
这篇文章以非常底层的视角讲述了Web请求(request)在ASP.NET框架中是如何流转的,从Web服务器,通过ISAPI直到请求处理器(handler)和你的代码.看看在幕后都发生了些什么,不要再把ASP.NET看成一个黑盒了.
ASP.NET is a powerful platform for building Web applications, that provides a tremendous(英 /trəˈmendəs/ adj. 巨大的,极大的;极好的,精彩的;令人望而生畏的,可怕的) amount of flexibility and power for building just about any kind of Web application. Most people are familiar only with the high level frameworks like WebForms and WebServices which sit at the very top level of the ASP.NET hierarchy. In this article I’ll describe the lower level aspects of ASP.NET and explain how requests move from Web Server to the ASP.NET runtime and then through the ASP.NET Http Pipeline to process requests.
ASP.NET是一个用于构建Web程序的强大平台,提供了巨大的弹性和能力以至于它可以构建任意的Web程序。许多人仅仅对处于ASP.NET高层次的框架如:WebForms和WebServices比较熟悉,因此,在这篇文章里,我将会阐述有关ASP.NET比较底层的知识,并且将会解释,如何将请求从Web Server移交给ASP.NET运行时,然后通过ASP.NET HTTP管道处理这些请求。
To me understanding the innards of a platform always provides certain satisfaction and level of comfort, as well as insight that helps to write better applications. Knowing what tools are available and how they fit together as part of the whole complex framework makes it easier to find the best solution to a problem and more importantly helps in troubleshooting and debugging of problems when they occur. The goal of this article is to look at ASP.NET from the System level and help understand how requests flow into the ASP.NET processing pipeline. As such we’ll look at the core engine and how Web requests end up there. Much of this information is not something that you need to know in your daily work, but it’s good to understand how the ASP.NET architecture routes request into your application code that usually sits at a much higher level.
对于我来说,了解一个平台的内部工作机制总是会让我感到一些满足和安慰,如同洞察,可以帮助我写出更好的程序。知道了工具有什么用途,以及它们如何组装成复杂框架的一部分,这些将会使你很容易的找到问题的解决方案,以及在你修改和调试错误时,都显得非常重要。这篇文章的目的就是从底层了解ASP.NET以及帮助你理解请求如何流入ASP.NET处理管道里。同时,你将会了解ASP.NET引擎的核心,以及一个Web请求如何在这里结束。这里讲到的许多知识都是你日常工作中没必要知道的,但是,如果你理解了ASP.NET如何把请求路由到应用程序的代码里(通常比较高层次的),这将对你非常有用。
The entire ASP.NET engine was completely built in managed code and all extensibility is provided via managed code extensions.
整个ASP.NET引擎完全构建在托管代码里,其所有的扩展性都是通过托管代码去构建。
Most people using ASP.NET are familiar with WebForms and WebServices. These high level implementations are abstractions that make it easy to build Web based application logic and ASP.NET is the driving engine that provides the underlying interface to the Web Server and routing mechanics to provide the base for these high level front end services typically used for your applications. WebForms and WebServices are merely(英 /ˈmɪəli/ adv. 仅仅,只) two very sophisticated(英 /səˈfɪstɪkeɪtɪd/ adj. 见多识广的,老练的,见过世面的) implementations of HTTP Handlers built on top of the core ASP.NET framework.
使用ASP.NET的大多数都比较熟悉WebForms和WebServices。这些高层次的实现,使得构建Web程序变得非常容易。ASP.NET被设计为驱动引擎,它把底层的接口提供给Web Server,为高层次Web应用程序的前端和末端提供了路由服务。WebForms和WebServices是建立在ASP.NET框架之上,有关HTTP处理的两种最常用的方式。
However, ASP.NET provides much more flexibility from a lower level. The HTTP Runtime and the request pipeline provide all the same power that went into building the WebForms and WebService implementations – these implementations were actually built with .NET managed code. And all of that same functionality is available to you, should you decide you need to build a custom platform that sits at a level a little lower than WebForms.
其实,在较低的层次上,ASP.NET也提供了足够多的灵活性。HTTP运行时和请求管道提供了同样的能力,可以构建类似于WebForms和WebServices的实现,当然,这些已经使用.NET托管代码实现了。如果你需要构建一个自定义HTTP处理平台,而这个平台要比WebForms所处的层次低一点,那么你就会用到所有这些类似的功能。
WebForms are definitely the easiest way to build most Web interfaces, but if you’re building custom content handlers, or have special needs for processing the incoming or outgoing content, or you need to build a custom application server interface to another application, using these lower level handlers or modules can provide better performance and more control over the actual request process. With all the power that the high level implementations of WebForms and WebServices provide they also add quite a bit of overhead to requests that you can bypass by working at a lower level.
构建大多的Web界面,使用WebForms无疑是最容易的方法,但是,如果你想自定义一个内容处理器,或者需要对流入和流出的内容做特殊的处理,或者需要为一个应用程序定制一个应用服务器接口,那么使用这些低层次的处理或者模块将会得到更好的性能,以及可以在真正的请求处理中获得更多的控制权。尽管那些高层次的实现,如:WebForms和WebServices已提供了类似的功能,但由于它们针对请求添加了太多的控制(导致性能下降)。所以你完全可以另辟佳境,在较低层次上处理这些请求。
What is ASP.NET ASP.NET是什么?
Let’s start with a simple definition: What is ASP.NET? I like to define ASP.NET as follows:
ASP.NET is a sophisticated engine using Managed Code for front to back processing of Web Requests.It's much more than just WebForms and Web Services…
让我们从最简单的定义开始,ASP.NET是什么?我通常喜欢用如下语句来描述ASP.NET。
ASP.NET是完全使用托管代码处理Web请求的一个成熟引擎平台。它不仅仅只是WebForms和WebServices。
ASP.NET is a request processing engine. It takes an incoming request and passes it through its internal pipeline to an end point where you as a developer can attach code to process that request. This engine is actually completely separated from HTTP or the Web Server. In fact, the HTTP Runtime is a component that you can host in your own applications outside of IIS or any server side application altogether. For example, you can host the ASP.NET runtime in a Windows form (check out http://www.west-wind.com/presentations/aspnetruntime/aspnetruntime.asp for more detailed information on runtime hosting in Windows Forms apps).
ASP.NET是一个请求处理引擎。它获取客户端请求,然后通过它内置的管道,把请求传到一个终点,在这个终点,开发者可以添加处理这个请求的逻辑代码。实际上这个引擎和HTTP或者Web Server是完全分开的。事实上,HTTP运行时是一个组件,你可以把它宿主在IIS之外的应用程序上。甚至完全可以和其它的服务组合在一起。例如,你可以把HTTP运行时宿主在Windows桌面应用程序里(详细的内容请查看:http://www.west-wind.com/presentations/aspnetruntime/aspnetruntime.aspx 链接已经失效)。
The runtime provides a complex yet very elegant (英 /ˈelɪɡənt/精美的,雅致的;简练的,巧妙的)mechanism for routing requests through this pipeline. There are a number of interrelated objects, most of which are extensible either via subclassing or through event interfaces at almost every level of the process, so the framework is highly extensible. Through this mechanism it’s possible to hook into very low level interfaces such as the caching, authentication(英 /ɔːˌθentɪˈkeɪʃn/ ) and authorization. You can even filter content by pre or post processing requests or simply route incoming requests that match a specific signature directly to your code or another URL. There are a lot of different ways to accomplish the same thing, but all of the approaches are straightforward to implement, yet provide flexibility in finding the best match for performance and ease of development.
通过使用内置的管道路由请求,HTTP运行时提供了一套复杂的,但却很优雅的机制。在处理请求的每一个层面都牵涉到许多对象,但大多数对象都可以通过派生或者事件接口来扩展。所以,此框架具有非常高的可扩展性。通过这一套机制,可以进入较低层次的接口如:缓存,身份验证,授权等是有可能的。你可以在处理请求之前或之后过滤内容,或者仅仅把匹配指定签名的客户端请求直接路由到你的代码里或转向其它的URL。针对同一件事情,可以通过不同的处理方法完成,而且实现代码都非常的直观。除此之外,在容易开发和性能之间,HTTP运行时还提供了最佳的灵活性。The entire ASP.NET engine was completely built in managed code and all of the extensibility functionality is provided via managed code extensions. This is a testament to the power of the .NET framework in its ability to build sophisticated and very performance oriented architectures. Above all though, the most impressive part of ASP.NET is the thoughtful design that makes the architecture easy to work with, yet provides hooks into just about any part of the request processing.整个ASP.NET引擎完全构建在托管代码里,所有的扩展性功能都是通过托管代码的扩展提供。对于功能强大的.NET框架而言,使用自己的东西,构建一个成熟的、高性能的引擎体系结构已经成为一个遗嘱。尽管如此,但重要的是,ASP.NET给人印象最深的是高瞻远瞩的设计,这使得在其之上的工作变得非常容易,并且提供了几乎可以钩住请求处理当中任意部分的能力.
With ASP.NET you can perform tasks that previously were the domain of ISAPI extensions and filters on IIS – with some limitations, but it’s a lot closer than say ASP was. ISAPI is a low level Win32 style API that had a very meager(英 /ˈmiːɡə(r)/adj. 贫乏的;瘦的) interface and was very difficult to work for sophisticated applications. Since ISAPI is very low level it also is very fast, but fairly unmanageable for application level development. So, ISAPI has been mainly relegated for some time to providing bridge interfaces to other application or platforms. But ISAPI isn’t dead by any means. In fact, ASP.NET on Microsoft platforms interfaces with IIS through an ISAPI extension that hosts .NET and through it the ASP.NET runtime. ISAPI provides the core interface from the Web Server and ASP.NET uses the unmanaged ISAPI code to retrieve input and send output back to the client. The content that ISAPI provides is available via common objects like HttpRequest and HttpResponse that expose the unmanaged data as managed objects with a nice and accessible interface.
使用ASP.NET可以完成一些任务,之前这些任务是使用IIS上的ISAPI扩展和过滤来完成的。尽管还有一些限制,但与ASP相比,已经有了很大的进步。ISAPI是底层Win32样式的API,仅它的接口就有1兆,这对于大型的程序开发是非常困难的。由于ISAPI是底层的接口,因此它的速度也是非常的快。但对于企业级的程序开发是相当的难于管理的。所以,在一定的时间内,ISAPI主要充当其它应用程序或平台的桥接口。但是无论如何,ISAPI没有被废弃。事实上,微软平台上的ASP.NET和IIS的接口是通过宿主在.NET里的ISAPI扩展来通信的,然后直达ASP.NET运行时。ISAPI提供了与Web Server通信的核心接口,然后ASP.NET使用非托管代码获取请求以及对客户端请求发出响应。ISAPI提供的内容经由公共对象类似于HttpRequest和HttpResponse,通过一个设计优良的、可访问的接口,以托管对象的方式暴露非托管数据。
From Browser to ASP.NET 从浏览器到ASP.NET
Let’s start at the beginning of the lifetime of a typical ASP.NET Web Request. A request starts on the browser where the user types in a URL, clicks on a hyperlink or submits an HTML form (a POST request). Or a client application might make call against an ASP.NET based Web Service, which is also serviced by ASP.NET. On the server side the Web Server – Internet Information Server 5 or 6 – picks up the request. At the lowest level ASP.NET interfaces with IIS through an ISAPI extension. With ASP.NET this request usually is routed to a page with an .aspx extension, but how the process works depends entirely on the implementation of the HTTP Handler that is set up to handle the specified extension. In IIS .aspx is mapped through an ‘Application Extension’ (aka. as a script map) that is mapped to the ASP.NET ISAPI dll - aspnet_isapi.dll. Every request that fires ASP.NET must go through an extension that is registered and points at aspnet_isapi.dll.
让我们从一个典型的ASP.NET Web请求的生命周期的起点开始。用户通过在浏览器中键入一个URL,点击一个超链接,提交一个HTML表单(一个post请求),或者一个客户端程序调用基于ASP.NET的WebService(通过ASP.NET提供服务)。在服务器端,IIS5或者IIS6将会收到这个请求。ASP.NET的底层通过ISAPI扩展与IIS通信,然后,通过ASP.NET,这个请求通常被路由到一个带有.aspx扩展名的页面。但是,这个处理过程如何工作,则完全依赖于HTTP处理器(handler)的执行。这个处理器将被安装用于处理指定的扩展。在IIS中,.aspx经由“应用程序扩展”被映射到ASP.NET ISAPI的dll文件:aspnet_isapi.dll。每一个触发ASP.NET的请求,都必须经由一个已经注册的,并且指向aspnet_isapi.dll的扩展名来标识。
Depending on the extension ASP.NET routes the request to an appropriate handler that is responsible for picking up requests. For example, the .asmx extension for Web Services routes requests not to a page on disk but a specially attributed class that identifies it as a Web Service implementation. Many other handlers are installed with ASP.NET and you can also define your own. All of these HttpHandlers are mapped to point at the ASP.NET ISAPI extension in IIS, and configured in web.config to get routed to a specific HTTP Handler implementation. Each handler, is a .NET class that handles a specific extension which can range from simple Hello World behavior with a couple of lines of code, to very complex handlers like the ASP.NET Page or Web Service implementations. For now, just understand that an extension is the basic mapping mechanism that ASP.NET uses to receive a request from ISAPI and then route it to a specific handler that processes the request.
依靠扩展名,ASP.NET把一个请求路由到一个恰当的处理器,该处理器则负责处理这个请求。举个例子,WebServices的扩展名.asmx不会把一个请求路由到磁盘上的某一个页面,而是会路由到在定义中附加了指定特性(WebMethodAttribute)的类,此特性会把它标识成一个Web Services的实现。许多其它的处理器将随着ASP.NET一起被安装。当然也可以定义你自己的处理器。在IIS里所有的HttpHandler被映射并指向ASP.NET ISAPI扩展,并且这些HttpHandler也都在web.config里配置,用于把请求路由到指定的HTTP处理器里执行。
每一个处理器都是一个.NET类,用于处理指定的扩展。而这些处理器可以处理简单到只有几行代码的Hello World,也可以处理复杂到类似ASP.NET的页面以及执行WebService。就目前而言,仅仅需要理解扩展就是一种基本的映射机制,ASP.NET用它可以从ISAPI里获取一个请求,然后把请求路由到指定处理该请求的处理器中。
ISAPI is the first and highest performance entry point into IIS for custom Web Request handling.注:ISAPI是自定义Web请求处理中第一个并且具有最高性能的IIS入口点。
The ISAPI Connection ISAPI连接
ISAPI is a low level unmanged Win32 API. The interfaces defined by the ISAPI spec are very simplistic and optimized for performance. They are very low level – dealing with raw pointers and function pointer tables for callbacks - but they provide he lowest and most performance oriented interface that developers and tool vendors can use to hook into IIS. Because ISAPI is very low level it’s not well suited for building application level code, and ISAPI tends to be used primarily as a bridge interface to provide Application Server type functionality to higher level tools. For example, ASP and ASP.NET both are layered on top of ISAPI as is Cold Fusion(英 /ˈfjuːʒn/n. 融合,结合;核聚变), most Perl, PHP and JSP implementations running on IIS as well as many third party solutions such as my own Web Connection framework for Visual FoxPro. ISAPI is an excellent tool to provide the high performance plumbing(英 /ˈplʌmɪŋ/ n. (建筑物的)管路系统,管道设备) interface to higher level applications, which can then abstract the information that ISAPI provides. In ASP and ASP.NET, the engines abstract the information provided by the ISAPI interface in the form of objects like Request and Response that read their content out of the ISAPI request information. Think of ISAPI as the plumbing. For ASP.NET the ISAPI dll is very lean and acts merely as a routing mechanism to pipe the inbound request into the ASP.NET runtime. All the heavy lifting and processing, and even the request thread management happens inside of the ASP.NET engine and your code.
ISAPI是底层非托管的Win32 API。它定义的接口非常的单一并且性能最优。用这些接口处理原始指针(raw pointer),而函数指针列表(function pointer tables)则用于回调。ISAPI提供了最低层的、高性能的接口,开发者和工具厂商可以使用这些接口深入到IIS里。由于ISAPI是非常低层的,所以不太适合使用它构建应用级的程序。ISAPI趋向于被当作桥接口使用,用于给高层次的工具提供应用服务类型的功能。例如,ASP和ASP.NET都是被当作冷聚变(cold fusion)构建于ISAPI之上。大多Perl、PHP和JSP的执行如同许多第三方解决方案一样,可以在IIS运行。ISAPI是个非常好的工具,它给高层次的应用程序提供了高性能垂直访问接口。这使得那些高层次的应用程序需要的信息可以从ISAPI提供的信息中提炼。在ASP和ASP.NET里,引擎可以提炼ISAPI接口提供的表单里的对象如:Request和Response,这些对象可以从ISAPI请求的信息中读取它们各自的内容。将ISAPI想像成管道.对ASP.NET来说,ISAPI dll是非常的”瘦”的,只是作为一个路由机制来将原始的请求转发到ASP.NET运行时.所有那些沉重的负担和处理,甚至请求线程的管理都发生在ASP.NET引擎内部和你的代码中.
As a protocol ISAPI supports both ISAPI extensions and ISAPI Filters. Extensions are a request handling interface and provide the logic to handle input and output with the Web Server – it’s essentially(英 /ɪˈsenʃəli/adv. 本质上,根本上;大体上) a transaction interface. ASP and ASP.NET are implemented as ISAPI extensions. ISAPI filters are hook interfaces that allow the ability to look at EVERY request that comes into IIS and to modify the content or change the behavior of functionalities like Authentication. Incidentally ASP.NET maps ISAPI-like functionality via two concepts: Http Handlers (extensions) and Http Modules (filters). We’ll look at these later in more detail.
作为约定,ISAPI支持ISAPI扩展(extensions)和ISAPI过滤(filters)。扩展是请求处理接口,提供了跟Web Server输入和输出相关的逻辑处理。从本质上来说,它是一个事务接口。ASP和ASP.NET都被看作ISAPI扩展的实现。ISAPI是钩子接口,它允许你查看进入IIS的每一个请求并且可以修改请求的内容(包括输入和输出)或者改变模块(如:身份验证等)的行为。顺便提一下,ASP.NET通过两个方面的内容:HTTP处理器(对应ISAPI扩展)和HTTP 模块(对应ISAPI过滤)映射到ISAPI。这些相关的内容我将会在后面详细描述。
ISAPI is the initial code point that marks the beginning of an ASP.NET request. ASP.NET maps various extensions to its ISAPI extension which lives in the .NET Framework directory:
ISAPI是代码的初始点,标识ASP.NET一个请求的开始。ASP.NET映射了不同的扩展到它的ISAPI扩展,ISAPI扩展位于.NET Framework目录:
<.NET FrameworkDir>\aspnet_isapi.dll
You can interactively see these mapping in the IIS Service manager as shown in Figure 1. Look at the root of the Web Site and the Home Directory tab, then Configuration | Mappings
你可以在IIS服务管理器里看到这些映射,如图1所示。打开Web站点的根目录的属性,选择主目录选项卡,然后查看 配置|应用程序映射。
Figure 1: IIS maps various extensions like .ASPX to the ASP.NET ISAPI extension. Through this mechanism requests are routed into ASP.NET's processing pipeline at the Web Server level.
You shouldn’t set these extensions manually as .NET requires a number of them. Instead use the aspnet_regiis.exe utility to make sure that all the various scriptmaps get registered properly:
cd <.NetFrameworkDirectory>
aspnet_regiis - i
图1:IIS把不同的扩展名如.aspx映射到ASP.NET的ISAPI扩展。通过这种机制,在Web Server里,请求就可以被路由到ASP.NET的处理管道里。
尽管.NET需要很多扩展名,但不必手工设置它们,你可以使用aspnet_regiis.exe实用工具确保所有的脚本映射都被注册。
cd <.NetFrameworkDirectory> aspnet_regiis – i
This will register the particular version of the ASP.NET runtime for the entire Web site by registering the scriptmaps and setting up the client side scripting libraries used by the various controls for uplevel browsers. Note that it registers the particular version of the CLR that is installed in the above directory. Options on aspnet_regiis let you configure virtual directories individually. Each version of the .NET framework has its own version of aspnet_regiis and you need to run the appropriate one to register a site or virtual directory for a specific version of the .NET framework. Starting with ASP.NET 2.0, an IIS ASP.NET configuration page lets you pick the .NET version interactively in the IIS management console.
这将会把ASP.NET运行时的个别版本,通过脚本映射注册到整个Web站点,并且安装客户端脚本库,这些脚本库将会被浏览器上的控件所使用。注意,它是注册了安装在上面目录里的CLR的那个版本。aspnet_regiis有一个可选项,可以使你单独配置一个虚拟目录。每一个.NET框架的版本,都拥有各自的aspnet_regiis,对于不同版本的.NET框架,你需要运行适当版本的aspnet_regiis,注册到整个站点或者虚拟目录。从ASP.NET2.0开始,在IIS控制台里,有一个IIS ASP.NET配置页面,在这个页面你可以挑选.NET的版本。
IIS 5 and 6 work differently IIS5和IIS6的不同之处
When a request comes in, IIS checks for the script map and routes the request to the aspnet_isapi.dll. The operation of the DLL and how it gets to the ASP.NET runtime varies significantly between IIS 5 and 6. Figure 2 shows a rough(英 /rʌf/adj. (表面)粗糙的,不平的) overview of the flow.
当一个请求进来的时候,IIS会检查脚本映射,然后把请求路由到aspnet_isapi.dll。接下来这个DLL文件的操作是什么呢?在IIS5和IIS6里,这个请求又是如何到达ASP.NET运行时的呢?它们两者的处理方式有没有重大变化呢?图2展示了一个大致的流程。
Figure 2 – Request flow from IIS to the ASP.NET Runtime and through the request processing pipeline from a high level. IIS 5 and IIS 6 interface with ASP.NET in different ways but the overall process once it reaches the ASP.NET Pipeline is the same.
图2:站在比较高的角度,观看请求从IIS到ASP.NET运行时的流程,然后直达请求处理管道。IIS5和IIS6与ASP.NET的接口采用了不同的方式,但从请求到达ASP.NET管道后的整个过程是完全一样的。
In IIS 5 hosts aspnet_isapi.dll directly in the inetinfo.exe process or one of its isolated worker processes if you have isolation set to medium or high for the Web or virtual directory. When the first ASP.NET request comes in the DLL will spawn(英 /spɔːn/引发,促生;繁衍) a new process in another EXE – aspnet_wp.exe – and route processing to this spawned(英 /spɔːnd/v. 引起(spawn 的过去分词);孵出) process. This process in turn loads and hosts the .NET runtime. Every request that comes into the ISAPI DLL then routes to this worker process via Named Pipe calls.
IIS5直接把aspnet_isapi.dll寄宿在inetinfo.exe进程里,或者它们中的一个将会与工作进程隔离,如果你拥有隔离权限,那么可以把Web站点和虚拟目录隔离的级别设置为中等或者高级。当第一个ASP.NET请求进来的时候,DLL将会在另一个EXE-aspnet_wp.exe里分配一个新的进程,然后把相关信息路由到这个新分配的进程里。接着这个新的进程会依次加载和寄宿.NET运行时。每一个进入到ISAPI DLL的请求,都将通过调用命名管道路由给这个工作者进程。
IIS6, unlike previous servers, is fully optimized for ASP.NET IIS 6 – Viva the Application Pool注:IIS6与之前的Web Server不同,已经对ASP.NET进行了优化处理。IIS6中使用了应用程序池。
IIS 6 changes the processing model significantly in that IIS no longer hosts any foreign executable code like ISAPI extensions directly. Instead IIS 6 always creates a separate worker process – an Application Pool – and all processing occurs inside of this process, including execution of the ISAPI dll. Application Pools are a big improvement for IIS 6, as they allow very granular(英 /ˈɡrænjələ(r)/adj. 颗粒的;粒状的) control over what executes in a given process. Application Pools can be configured for every virtual directory or the entire Web site, so you can isolate every Web application easily into its own process that will be completely isolated from any other Web application running on the same machine. If one process dies it will not affect any others at least from the Web processing perspective(英 /pəˈspektɪv/n. (观察问题的)视角,观点).
值得注意的是,IIS6改变了这个处理模型,IIS不再直接寄宿像ISAPI扩展的任何外部可执行代码。代替的是,IIS总会保持一个单独的工作进程:应用程序池。所有的处理都发生在这个进程里,包括ISAPI dll的执行。对于IIS6而言,应用程序池是一个重大的改进,因为它们允许以更小的粒度控制一个指定进程的执行。你可以为每一个虚拟目录或者整个Web站点配置应用程序池,这可以使你很容易的把每一个应用程序隔离到各自的进程里,这样就可以把它与运行在同一台机器上其他程序完全隔离。从Web处理的角度看,如果一个进程死掉,至少它不会影响到其它的进程。
In addition, Application Pools are highly configurable. You can configure their execution security environment by setting an execution impersonation(英 /ɪmˌpɜːsəˈneɪʃn/n. 扮演;模仿;装扮) level for the pool which allows you to customize the rights given to a Web application in that same granular fashion. One big improvement for ASP.NET is that the Application Pool replaces most of the ProcessModel entry in machine.config. This entry was difficult to manage in IIS 5, because the settings were global and could not be overridden in an application specific web.config file. When running IIS 6, the ProcessModel setting is mostly ignored and settings are instead read from the Application Pool. I say mostly – some settings, like the size of the ThreadPool and IO threads still are configured through this key since they have no equivalent in the Application Pool settings of the server.
另外,应用程序池是高度可配置的。通过设置应用程序池的执行许可,可以配置它们的执行安全环境。并且可以为指定的应用程序按照相同的粒度定制这些配置。对于ASP.NET而言,IIS6最大的改进是使用应用程序池代替了machine.config里的ProcessModel实体的大部分功能。在IIS5里,这个实体是很难管理的,因为它的设置是全局的,而且不能够在指定Web程序的web.config里覆盖这些设置。当IIS6运行的时候,ProcessModel里的大部分配置将被忽略,取而代之的是读取应用程序池的配置。注意我这里说的是大部分,另外的一些配置,像线程池的大小和IO线程数目等仍然需要通过这个节点配置,这是因为,在服务器的应用程序池里没有提供类似功能的配置。
Because Application Pools are external executables these executables can also be easily monitored and managed. IIS 6 provides a number of health checking, restarting and timeout options that can detect and in many cases correct problems with an application. Finally IIS 6’s Application Pools don’t rely on COM+ as IIS 5 isolation processes did which has improved performance and stability especially for applications that need to use COM objects internally.
由于应用程序池是在外部执行的,所以这些执行可以很容易的被监控和管理。IIS6还提供了许多性能计数器,可以对重启和超时选项进行跟踪。在许多情况下,这可以帮助应用程序纠正问题。最后,IIS6的应用程序池的实现不依赖COM+,正如IIS5隔离进程一样,这提高了程序的性能和稳定性,特别是那些需要在内部使用COM对象的程序。
Although IIS 6 application pools are separate EXEs, they are highly optimized for HTTP operations by directly communicating with a kernel mode HTTP.SYS driver. Incoming requests are directly routed to the appropriate application pool. InetInfo acts merely as an Administration and configuration service – most interaction actually occurs directly between HTTP.SYS and the Application Pools, all of which translates into a more stable and higher performance environment over IIS 5. This is especially true for static content and ASP.NET applications.
尽管IIS6的应用程序池是单独的EXE,但是它们对HTTP操作进行了高度的优化,它们直接和内核模式下的HTTP.SYS驱动程序进行通讯.收到的请求被直接路由给适当的应用程序池.InetInfo基本上只是一个管理程序和一个配置服务程序-大部分的交互实际上是直接在HTTP.SYS和应用程序池之间发生,所有这些使IIS6成为了比IIS5更加的稳定和高效的环境.特别对静态内容和ASP.NET程序来说这是千真万确的
An IIS 6 application pool also has intrinsic(英 /ɪnˈtrɪnzɪk; ɪnˈtrɪnsɪk/adj. 内在的,固有的) knowledge of ASP.NET and ASP.NET can communicate with new low level APIs that allow direct access to the HTTP Cache APIs which can offload caching from the ASP.NET level directly into the Web Server’s cache.
IIS6应用程序池也包含了ASP.NET固有的东西,ASP.NET可以和新的底层API通信,这些API允许直接访问HTTP缓冲存储器的API,而HTTP缓冲存储器的API可以直接进入Web Server的缓冲存储器,卸载ASP.NET级别的缓存
In IIS 6, ISAPI extensions run in the Application Pool worker process. The .NET Runtime also runs in this same process, so communication between the ISAPI extension and the .NET runtime happens in-process which is inherently(英 /ɪnˈherəntli; ɪnˈhɪərəntli/adv. 内在地,固有地) more efficient than the named pipe interface that IIS 5 must use. Although the IIS hosting models are very different the actual interfaces into managed code are very similar – only the process in getting the request routed varies a bit.
在IIS6里,ISAPI扩展运行在应用程序池的工作进程里。.而NET运行时也运行在这个进程里,所以ISAPI扩展和.NET运行时的通信是发生在进程内的。这就使得比必须使用命名管道接口的IIS5具有更高的性能。尽管IIS的宿主模型不同,但是真正进入托管代码的接口是类似的,仅仅在获取被路由的请求时有一些变动。
The ISAPIRuntime.ProcessRequest() method is the first entry point into ASP.NET ISAPIRuntime.ProcessRequest()函数是进入ASP.NET的第一站
Getting into the .NET runtime进入.NET运行时
The actual entry points into the .NET Runtime occur through a number of undocumented classes and interfaces. Little is known about these interfaces outside of Microsoft, and Microsoft folks are not eager(英 /ˈiːɡə(r)/adj. 热切的,渴望的,渴求的) to talk about the details, as they deem(英 /diːm/ v. 认为,相信) this an implementation detail that has little effect on developers building applications with ASP.NET.
进入.NET运行时的真正登录点发生在一些没有正式文档的类和接口之间。在微软外面的世界,这些接口鲜为人知。微软的民间也不太热衷于讨论这些细节,可能是因为他们认为这些,对于使用ASP.NET构建程序的开发者没有太多的影响。
The worker processes ASPNET_WP.EXE (IIS5) and W3WP.EXE (IIS6) host the .NET runtime and the ISAPI DLL calls into small set of unmanged interfaces via low level COM that eventually forward calls to an instance subclass of the ISAPIRuntime class. The first entry point to the runtime is the undocumented ISAPIRuntime class which exposes the IISAPI Runtime interface via COM to a caller. These COM interfaces low level IUnknown based interfaces that are meant for internal calls from the ISAPI extension into ASP.NET.
工作进程aspnet_wp.exe(IIS5)和w3wp.exe(IIS6)宿主在.NET运行时里。ISAPI DLL通过底层的COM调用一小撮非托管类型的接口,其实,最终调用的是ISAPIRuntime派生类的实例。进入运行时的第一个登录点是未归档ISAPI Runtime类,它通过COM把接口IISAPIRuntime暴露给调用者。这些COM接口是底层的IUnknown,基于这些接口,就意味着从ISAPI扩展到ASP.NET之间的调用属于内部调用。
Figure 3 shows the interface and call signatures for the IISAPIRuntime interface as shown in Lutz Roeder’s excellent .NET Reflector tool (.NET Decompiler: Decompile Any .NET Code | .NET Reflector). Reflector an assembly viewer and disassembler that makes it very easy to look at medadata and disassembled code (in IL, C#, VB) as shown in Figure 3. It’s a great way to explore the bootstrapping process.
图3是使用有名的反射工具Refector(http://www.aisto.com/roeder/dotnet/)看到的IISAPIRuntime接口的签名。Refector是一个可以查看和反编译程序集的工具,使用它可以很容易的查看元数据、反编译代码,就像图3中看到的那样。使用它一步一步地探究处理的过程,这是个非常不错的方法。
Figure 3 – If you want to dig into the low level interfaces open up Reflector, and point at the System.Web.Hosting namespace. The entry point to ASP.NET occurs through a managed COM Interface called from the ISAPI dll, that receives an unmanaged pointer to the ISAPI ECB(extension control block). The ECB contains has access to the full ISAPI interface to allow retrieving request data and sending back to IIS.
图3:如果你想深入了解这个底层的接口,你可以打开Refector工具,然后指向System.Web.Hosting命名空间。进入ASP.NET的登录点以一个托管的COM接口出现,该接口将在ISAPI dll里被调用。该登录点接收一个指向ISAPI ECB非托管类型的指针。ECB(扩展控制块(extension control block))拥有访问整个ISAPI接口的权限,它可以获取请求的数据以及把返回的数据发回IIS。
The IISAPIRuntime interface acts as the interface point between the unmanaged code coming from the ISAPI extension (directly in IIS 6 and indirectly via the Named Pipe handler in IIS 5). If you take a look at this class you’ll find a ProcessRequest method with a signature like this:
[return: MarshalAs(UnmanagedType.I4)]
int ProcessRequest([In] IntPtr ecb, [In, MarshalAs(UnmanagedType.I4)] int useProcessModel);
IISAPIRuntime接口担当着来自于ISAPI扩展(在IIS6里是直接通信的,在IIS5里间接的通过命名管道通信的)的非托管代码和托管代码之间的桥梁。如果你留意一下这个类,你会发现ProcessRequest方法的签名像下面的样子:
[return: MarshalAs(UnmanagedType.I4)]
int ProcessRequest([In] IntPtr ecb, [In, MarshalAs(UnmanagedType.I4)] int useProcessModel);
The ecb parameter is the ISAPI Extension Control Block (ECB) which is passed as an unmanaged resource to ProcessRequest. The method then takes the ECB and uses it as the base input and output interface used with the Request and Response objects. An ISAPI ECB contains all low level request information including server variables, an input stream for form variables as well as an output stream that is used to write data back to the client. The single ecb reference basically provides access to all of the functionality an ISAPI request has access to and ProcessRequest is the entry and exit point where this resource initially makes contact with managed code.
ecb参数是ISAPI扩展控制块(extension control block),它被作为非托管资源传给ProcessRequest方法。此方法将获取ECB,然后把它作为基本的输入和输出接口,用于Request和Response对象。ISAPI ECB包含着所有底层的请求信息,这其中包括服务器变量,用于表单变量(form variables)的输入流,以及用于写数据并把数据发送到客户端的输出流中。一个单独的ECB引用基本上提供了一个ISAPI请求可以访问的所有功能。ProcessRequest既是登录点也是登出点,在这里非托管资源最先与托管代码相联系。
The ISAPI extension runs requests asynchronously. In this mode the ISAPI extension immediately returns on the calling worker process or IIS thread, but keeps the ECB for the current request alive. The ECB then includes a mechanism for letting ISAPI know when the request is complete (via ecb.ServerSupportFunction) which then releases the ECB. This asynchronous processing releases the ISAPI worker thread immediately, and offloads processing to a separate thread that is managed by ASP.NET.
ISAPI扩展以异步的方式处理请求。所以,当ISAPI扩展调用了工作进程或者IIS的线程后,会立即返回,但会为当前有效的请求保留ECB。因此,ECB需要包含这样的机制,即当请求结束的时候通知ISAPI(通过ecb.ServerSupportFunction实现),然后ISAPI扩展释放ECB资源。接着以异步的方式立即释放ISAPI工作线程,和卸载由ASP.NET托管的那个隔离的处理线程。
ASP.NET receives this ecb reference and uses it internally to retrieve information about the current request such as server variables, POST data as well as returning output back to the server. The ecb stays alive until the request finishes or times out in IIS and ASP.NET continues to communicate with it until the request is done. Output is written into the ISAPI output stream (ecb.WriteClient()) and when the request is done, the ISAPI extension is notified of request completion to let it know that the ECB can be freed. This implementation is very efficient as the .NET classes essentially act as a fairly thin wrapper around the high performance, unmanaged ISAPI ECB.
ASP.NET得到ecb引用后,会在内部使用它来获取当前请求的相关信息,如服务器变量,POST的数据以及返回输出到客户端的数据。Ecb将继续存活直到这个请求结束或者IIS超时,在这之前,ASP.NET将会与ecb继续保持通信。当请求结束的时候,输出的内容会写进ISAPI的输出流里(通过ecb.WriteClient()实现)。然后ISAPI扩展会被通知请求已经结束,让它知道ECB可以被释放了。这个执行过程是非常高效的,这是因为,.NET类本质上只是担当着一个相当瘦小的包装器,而它包装的内容就是具有高性能的非托管ISAPI ECB。
Loading .NET – somewhat of a mystery加载.NET—稍微有点神秘
Let’s back up one step here: I skipped over how the .NET runtime gets loaded. Here’s where things get a bit fuzzy. I haven’t found any documentation on this process and since we’re talking about native code there’s no easy way to disassemble the ISAPI DLL and figure it out.
让我们回到之前略过的一个话题:当请求到达时,.NET运行时是如何被加载的。具体在哪里加载的,这是比较模糊的。关于这个处理过程,我没有找到相关的文档,由于我们现在讨论的是本地代码,所以通过反编译ISAPI DLL文件并把它描述出来显得不太容易。
My best guess is that the worker process bootstraps(英 /ˈbuːtstræp/[计] 引导程序,辅助程序) the .NET runtime from within the ISAPI extension on the first hit against an ASP.NET mapped extension. Once the runtime exists, the unmanaged code can request an instance of an ISAPIRuntime object for a given virtual path if one doesn’t exist yet. Each virtual directory gets its own AppDomain and within that AppDomain the ISAPIRuntime exists from which the bootstrapping process for an individual application starts. Instantiation appears to occur over COM as the interface methods are exposed as COM callable methods.
我的最佳猜测是,在ISAPI扩展里,当第一个请求命中一个ASP.NET的映射扩展时,工作线程就会引导.NET运行时启动。一旦运行时存在了,非托管代码就可以为指定的虚拟目录请求一个ISAPIRuntime对象的实例,当然前提条件是,这个实例还不存在。每一个虚拟目录都会拥有一个AppDomain,在ISAPIRuntime存在的AppDomain里,它将引导一个单独的程序启动。由于接口被作为COM可调用的方法暴露,所以实例化操作将发生在COM之上。
To create the ISAPIRuntime instance the System.Web.Hosting.AppDomainFactory.Create() method is called when the first request for a specific virtual directory is requested. This starts the ‘Application’ bootstrapping process. The call receives parameters for type and module name and virtual path information for the application which is used by ASP.NET to create an AppDomain and launch the ASP.NET application for the given virtual directory. This HttpRuntime derived object is created in a new AppDomain. Each virtual directory or ASP.NET application is hosted in a separate AppDomain and they get loaded only as requests hit the particular ASP.NET Application. The ISAPI extension manages these instances of the HttpRuntime objects, and routes inbound requests to the right one based on the virtual path of the request.
为了创建ISAPIRuntime的实例,当指定虚拟目录的第一个请求到达时,System.Web.Hosting.AppDomainFactory.Create()方法将被调用。这将会启动程序的引导过程。这个方法接收的参数为:类型,模块名以及应用程序的虚拟路径,这些都将被ASP.NET用于创建AppDomain,接着会启动指定虚拟目录的ASP.NET程序。HttpRuntime的根对象将会在一个新的AppDomain里创建。每一个虚拟目录或者ASP.NET程序将寄宿在属于自己的AppDomain里。它们仅仅在有请求到达时启动。ISAPI扩展管理这些HttpRuntime对象的实例,然后基于请求的虚拟路径,把请求路由到正确的应用程序里。
Figure 4 – The transfer of the ISAPI request into the HTTP Pipeline of ASP.NET uses a number of undocumented classes and interfaces and requires several factory method calls. Each Web Application/Virtual runs in its own AppDomain with the caller holding a reference to an IISAPIRuntime interface that triggers the ASP.NET request processing.
图4:把ISAPI的请求转到ASP.NET通道需要调用很多没有正式文档的类和接口,以及几个工厂方法。每一个Web程序/虚拟目录都运行在属于自己的AppDomain里。调用者将维护一个IISAPIRuntime接口的代理引用,负责触发ASP.NET的请求处理。
Back in the runtime回到运行时
At this point we have an instance of ISAPIRuntime active and callable from the ISAPI extension. Once the runtime is up and running the ISAPI code calls into the ISAPIRuntime.ProcessRequest() method which is the real entry point into the ASP.NET Pipeline. The flow from there is shown in Figure 4.
这个时候,你已经拥有了一个ISAPIRuntime的活动实例,并且可以在ISAPI扩展里调用。一旦运行时启动并运行起来,ISAPI扩展就可以调用ISAPIRuntime.ProcessRequest()方法了,而这个方法就是进入ASP.NET通道真正的登录点。图4展示了这里的流程。
Remember ISAPI is multi-threaded so requests will come in on multiple threads through the reference that was returned by ApplicationDomainFactory.Create(). Listing 1 shows the disassembled code from the IsapiRuntime.ProcessRequest method that receives an ISAPI ecb object and server type as parameters. The method is thread safe, so multiple ISAPI threads can safely call this single returned object instance simultaneously(英 /ˌsɪm(ə)lˈteɪniəsli/ adv. 同时地).
记住,ISAPI是多线程的,因此请求可以以多线程的方式穿过AppDomainFactory.Create()返回的对象引用。列表1展现了从IsapiRuntime.ProcessRequest方法反编译得到的代码。这个方法接收一个ISAPI ecb(ISAPI扩展控制块(extension control block))对象和一个服务器类型参数(这个参数用于指定创建何种版本的ISAPIWorkerRequest),这个方法是线程安全的,因此多个ISAPI线程可以同时安全的调用单个返回对象的实例。
Listing 1: The Process request method receives an ISAPI Ecb and passes it on to the Worker request
public int ProcessRequest(IntPtr ecb, int iWRType)
{
HttpWorkerRequest request1 = ISAPIWorkerRequest.CreateWorkerRequest(ecb, iWRType);
string text1 = request1.GetAppPathTranslated();
string text2 = HttpRuntime.AppDomainAppPathInternal;
if (((text2 == null) || text1.Equals(".")) || (string.Compare(text1, text2, true,
CultureInfo.InvariantCulture) == 0))
{
HttpRuntime.ProcessRequest(request1);
return 0;
}
HttpRuntime.ShutdownAppDomain("Physical application path changed from " +
text2 + " to " + text1);
return 1;
}
1: ProcessRequest请求进入 .NET的登录点
public int ProcessRequest(IntPtr ecb, int iWRType)
{
// ISAPIWorkerRequest从HttpWorkerRequest 继承,这里创建的是
// ISAPIWorkerRequest派生类的一个实例
HttpWorkerRequest request1 =
ISAPIWorkerRequest.CreateWorkerRequest(ecb,iWRType);
//得到请求的物理路径
string text1 = request1.GetAppPathTranslated();
//得到AppDomain的物理路径
string text2 = HttpRuntime.AppDomainAppPathInternal;
if (((text2 == null) || text1.Equals(".")) ||
(string.Compare(text1, text2, true,
CultureInfo.InvariantCulture) == 0))
{
HttpRuntime.ProcessRequest(request1);
return 0;
}
//如果外部请求的AppDomain物理路径和原来AppDomain的路径不同,说明ISAPI维持
的AppDomain的引用已经失效了,所以,需要把原来的程序关闭,当有新的请求时,会
再次启动程序。
HttpRuntime.ShutdownAppDomain("Physical path changed from " +
text2 + " to " + text1);
return 1;
}
The actual code here is not important, and keep in mind that this is disassembled internal framework code that you’ll never deal with directly and that might change in the future. It’s meant to demonstrate(英 /ˈdemənstreɪt/v. 证明;示范,演示) what’s happening behind the scenes(英 /siːnz/n. 场景(scene 的复数)). ProcessRequest receives the unmanaged ECB reference and passes it on to the ISAPIWorkerRequest object which is in charge of creating the Request Context for the current request as shown in Listing 2.
这里实际的代码并不重要,需要提醒的是,这里的代码是通过反编译.NET框架内的代码得到的,你永远也不会和这些代码打交道,而且这些代码以后可能会有所变动。这里的用意是揭示ASP.NET在底层发生了什么。ProcessRequest接收了非托管参数ecb的引用,然后把它传给了ISAPIWorkerRequest对象,这个对象负责创建当前请求的内容。如列表2所示。
The System.Web.Hosting.ISAPIWorkerRequest class is an abstract subclass of HttpWorkerRequest, whose job it is to create an abstracted view of the input and output that serves as the input for the Web application. Notice another factory method here: CreateWorkerRequest, which as a second parameter receives the type of worker request object to create. There are three different versions: ISAPIWorkerRequestInProc, ISAPIWorkerRequestInProcForIIS6, ISAPIWorkerRequestOutOfProc. This object is created on each incoming hit and serves as the basis for the Request and Response objects which will receive their data and streams from the data provided by the WorkerRequest.
System.Web.Hosting.ISAPIWorkerRequest继承于抽象类HttpWorkerRequest,它的职责是创建一个抽象的输入和输出视图,为Web程序的输入提供服务。注意这里的另外一个工厂方法CreateWorkerRequest,它的第二个参数用于指定创建什么样的工作请求对象(即ISAPIWorkerRequest的派生类)。这里有3个不同的版本:ISAPIWorkerRequestInProc,ISAPIWorkerRequestInProcForIIS6,ISAPIWorkerRequestOutOfProc。当请求到来时,这个对象(指ISAPIWorkerRequest对象)将被创建,用于给Request和Response对象提供基础服务,而这两个对象将从数据的提供者WorkerRequest接收数据流。
The abstract HttpWorkerRequest class is meant to provide a highlevel abstraction around the low level interfaces so that regardless(英 /rɪˈɡɑːdləs/adv. 不顾,不加理会;不管怎样,无论如何) of where the data comes from, whether it’s a CGI Web Server, the Web Browser Control or some custom mechanism you use to feed the data to the HTTP Runtime. The key is that ASP.NET can retrieve the information consistently.
抽象类HttpWorkerRequest围绕着底层的接口提供了高层的抽象(译注:抽象的目的是要把数据的处理与数据的来源解耦)。这样,就不用考虑数据的来源,无论它是一个CGI Web Server,Web浏览器控件还是你自定义的机制(用于把数据流入HTTP运行时),ASP.NET都可以以同样的方式从中获取数据。
In the case of IIS the abstraction is centered around an ISAPI ECB block. In our request processing, ISAPIWorkerRequest hangs on to the ISAPI ECB and retrieves data from it as needed. Listing 2 shows how the query string value is retrieved for example.
有关IIS的抽象主要集中在ISAPI ECB块。在我们的请求处理当中,ISAPIWorkerRequest依赖于ISAPI ECB,当有需要的时候,会从中读取数据。列表2展示了如何从ECB里获取查询字符串的值的例子。(hangs英 /hæŋz/v. 悬( hang的过去分词 );(被)绞死;贴;逗留
hang o坚持下去;不挂断;握住不放)
Listing 2: An ISAPIWorkerRequest method that uses the unmanged
// *** Implemented in ISAPIWorkerRequest
public override byte[] GetQueryStringRawBytes()
{
byte[] buffer1 = new byte[this._queryStringLength];
if (this._queryStringLength > 0)
{
int num1 = this.GetQueryStringRawBytesCore(buffer1, this._queryStringLength);
if (num1 != 1)
{
throw new HttpException( "Cannot_get_query_string_bytes");
}
}
return buffer1;
}
// *** Implemented in a specific implementation class ISAPIWorkerRequestInProcIIS6
internal override int GetQueryStringCore(int encode, StringBuilder buffer, int size)
{
if (this._ecb == IntPtr.Zero)//ecb:extension control block
{
return 0;
}
return UnsafeNativeMethods.EcbGetQueryString(this._ecb, encode, buffer, size);
}
2: 一个ISAPIWorkerRequest 的方法
// *** ISAPIWorkerRequest里的实现代码
public override byte[] GetQueryStringRawBytes()
{
byte[] buffer1 = new byte[this._queryStringLength];
if (this._queryStringLength > 0)
{
int num1 = this.GetQueryStringRawBytesCore(buffer1,
this._queryStringLength);
if (num1 != 1)
{
throw new HttpException( "Cannot_get_query_string_bytes");
}
}
return buffer1;
}
// *** 再派生于ISAPIWorkerRequest的类ISAPIWorkerRequestInProcIIS6的实现// *** 代码
// *** ISAPIWorkerRequestInProcIIS6
internal override int GetQueryStringCore(int encode, StringBuilder
buffer, int size)
{
if (this._ecb == IntPtr.Zero)
{
return 0;
}
return UnsafeNativeMethods.EcbGetQueryString(this._ecb, encode,
buffer, size);
}
ISAPIWorkerRequest implements a high level wrapper method, that calls into lower level Core methods, which are responsible for performing the actual access to the unmanaged APIs – or the ‘service level implementation’. The Core methods are implemented in the specific ISAPIWorkerRequest instance subclasses and thus provide the specific implementation for the environment that it’s hosted in. This makes for an easily pluggable environment where additional implementation classes can be provided later as newer Web Server interfaces or other platforms are targeted by ASP.NET. There’s also a helper class System.Web.UnsafeNativeMethods. Many of these methods operate on the ISAPI ECB structure performing unmanaged calls into the ISAPI extension.
ISAPIWorkerRequest实现了一个高层次包装器方法(wrapper method),它调用了低层次的核心方法,而这些方法负责实际调用非托管API或者说是“服务层的实现”。核心的方法在ISAPIWorkerRequest的派生类里得以实现。这样可以针对它宿主的环境提供特定的实现。为以后增加一个额外环境的实现类作为新的Web Server接口提供了便利。同样使ASP.NET运行在其它平台上成为可能。另外这里还有一个帮助类:System.Web.UnsafeNativeMethods。它的许多方法是对ISAPI ECB进行操作,用于执行关于ISAPI扩展的非托管操作。
HttpRuntime, HttpContext, and HttpApplication – Oh my
HttpRuntime,HttpContext以及HttpApplication
When a request hits, it is routed to the ISAPIRuntime.ProcessRequest() method. This method in turn calls HttpRuntime.ProcessRequest that does several important things (look at System.Web.HttpRuntime.ProcessRequestInternal with Reflector):
>Create a new HttpContext instance for the request
>Retrieves an HttpApplication Instance
>Calls HttpApplication.Init() to set up Pipeline Events
>Init() fires HttpApplication.ResumeProcessing() which starts the ASP.NET pipeline processing
当一个请求到来时,它将被路由到ISAPIRuntime.ProcessRequest()方法里。这个方法会接着调用HttpRuntime.ProcessRequest,在这个方法里,做了几件重要的事情(使用Refector反编译System.Web.HttpRuntime.ProcessRequestInternal可以看到)。
>为请求创建了一个新的HttpContext实例
> 获取一个HttpApplication实例
>调用HttpApplication.Init()初始化管道事件
> Init()触发HttpApplication.ResumeProcessing(),启动ASP.NET管道处理
First a new HttpContext object is created and it is passed the ISAPIWorkerRequest that wrappers the ISAPI ECB. The Context is available throughout the lifetime of the request and ALWAYS accessible via the static HttpContext.Current property. As the name implies, the HttpContext object represents the context of the currently active request as it contains references to all of the vital objects you typically access during the request lifetime: Request, Response, Application, Server, Cache. At any time during request processing HttpContext.Current gives you access to all of these object.
首先,一个新的HttpContext对象被创建,并且给它传递一个封装了ISAPI ECB 的ISAPIWorkerRequest。在请求的生命周期里,这个上下文(context)一直是有效的。并且可以通过静态的HttpContext.Current属性访问。正如它的名字暗示的那样,HttpContext对象表示当前活动请求的上下文,因为它包含了在请求生命周期里你会用到的所有必需对象的引用,如:Request,Response,Application,Server,Cache。在请求处理过程的任何时候,你都可以使用HttpContext.Current访问这些对象。
The HttpContext object also contains a very useful Items collection that you can use to store data that is request specific. The context object gets created at the begging of the request cycle and released when the request finishes, so data stored there in the Items collection is specific only to the current request. A good example use is a request logging mechanism(英 /ˈmekənɪzəm/n. 机械装置,机件;途径,方法;) where you want to track start and end times of a request by hooking the Application_BeginRequest and Application_EndRequest methods in Global.asax as shown in Listing 3. HttpContext is your friend – you’ll use it liberally if you need data in different parts of the request or page processing.
HttpContext对象还包含了一个非常有用的列表集合,你可以使用它存储有关特定的请求需要的数据。上下文(context)对象创建于一个请求生命周期的开始,在请求结束时被释放。因此,保存在列表集合里的数据仅仅对当前的请求有效。一个很好的例子,就是记录请求的日志机制,在这里,通过使用Global.asax里的Application_BeginRequest和Application_EndRequest方法,你可以从请求的开始时间至结束时间段内,对请求进行跟踪。如列表3所示。记住HttpContext是你的朋友,在请求或者页面处理的不同阶段,如果你需要相关数据都可以使用它获取。
Listing 3 – Using the HttpContext.Items collection lets you save data between pipeline events
3: 通过在通道事件里使用HttpContext.Items 集合保存数据
protected void Application_BeginRequest(Object sender, EventArgs e)
{
//*** Request Logging
if (App.Configuration.LogWebRequests)
Context.Items.Add("WebLog_StartTime",DateTime.Now);
}
protected void Application_EndRequest(Object sender, EventArgs e)
{
// *** Request Logging
if (App.Configuration.LogWebRequests)
{
try
{
TimeSpan Span =
DateTime.Now.Subtract((DateTime)Context.Items["WebLog_StartTime"] );
int MiliSecs = Span.TotalMilliseconds;
// do your logging
WebRequestLog.Log(App.Configuration.ConnectionString, true,MilliSecs);
}
}
Once the Context has been set up, ASP.NET needs to route your incoming request to the appropriate application/virtual directory by way of an HttpApplication object. Every ASP.NET application must be set up as a Virtual (or Web Root) directory and each of these ‘applications’ are handled independently.
一旦请求的上下文对象被搭建起来,ASP.NET就需要通过一个HttpApplication对象,把你的请求路由到合适的程序/虚拟目录里。每一个ASP.NET程序都拥有各自的虚拟目录(Web根目录),并且它们都是独立处理请求的。
The HttpApplication is like a master of ceremonies – it is where the processing action starts
HttpApplication类似活动仪式上的主人-它是处理动作开始的地方
Master of your domain: HttpApplication Web
程序的主要部分:HttpApplication
Each request is routed to an HttpApplication object. The HttpApplicationFactory class creates a pool of HttpApplication objects for your ASP.NET application depending on the load on the application and hands out references for each incoming request. The size of the pool is limited to the setting of the MaxWorkerThreads setting in machine.config’s ProcessModel Key, which by default is 20.
每一个请求都将被路由到一个HttpApplication对象。HttpApplicationFactory类会为你的ASP.NET程序创建一个HttpApplication对象池,它负责加载程序和给每一个到来的请求分发HttpApplication的引用。这个HttpApplication对象池的大小可以通过machine.config里的ProcessModel节点中的MaxWorkerThreads选项配置,默认值是20。
The pool starts out with a smaller number though; usually one and it then grows as multiple simulataneous requests need to be processed. The Pool is monitored so under load it may grow to its max number of instances, which is later scaled back to a smaller number as the load drops.
HttpApplication对象池尽管以比较少的数目开始启动,通常是一个。但是当同时有多个请求需要处理时,池中的对象将会随之增加。而HttpApplication对象池,也将会被监控,目的是保持池中对象的数目不超过设置的最大值。当请求的数量减小时,池中的数目就会跌回一个较小的值。
HttpApplication is the outer container for your specific Web application and it maps to the class that is defined in Global.asax. It’s the first entry point into the HTTP Runtime that you actually see on a regular basis in your applications. If you look in Global.asax (or the code behind class) you’ll find that this class derives directly from HttpApplication:
public class Global : System.Web.HttpApplication
对于你的Web程序而言,HttpApplication是一个外部容器,它对应到Global.asax文件里定义的类。基于标准的Web程序,它是你实际可以看到的进入HTTP运行时的第一个登录点。如果你查看Global.asax(后台代码),你就会看到这个类直接派生于HttpApplication。
public class Global : System.Web.HttpApplication
HttpApplication’s primary purpose is to act as the event controller of the Http Pipeline and so its interface consists primarily of events. The event hooks are extensive and include:
BeginRequest
AuthenticateRequest
AuthorizeRequest
ResolveRequestCache
AquireRequestState
PreRequestHandlerExecute
…Handler Execution…
PostRequestHandlerExecute
ReleaseRequestState
UpdateRequestCache
EndRequest
HttpApplication主要用作HTTP管道的事件控制器,因此,它的接口主要有事件组成,这些事件包括:
BeginRequest
AuthenticateRequest
AuthorizeRequest 英 /ˈɔːθəraɪz/v. 批准,许可;授权
ResolveRequestCache
[此处创建处理程序(即与请求 URL 对应的页)。]
AcquireRequestState
PreRequestHandlerExecute
[执行处理程序。]
PostRequestHandlerExecute
ReleaseRequestState
[响应筛选器(如果有的话),筛选输出。]
UpdateRequestCache
EndRequest
Each of these events are also implemented in the Global.asax file via empty methods that start with an Application_ prefix. For example, Application_BeginRequest(), Application_AuthorizeRequest(). These handlers are provided for convenience since they are frequently used in applications and make it so that you don’t have to explicitly create the event handler delegates.
这里的每一个事件都在Global.asax文件中以Application_为前缀,无实现代码的方法出现。举个例子,如Application_BeginRequest()和Application_AuthorizeRequest()。由于它们在程序中会经常用到,所以出于方便的考虑,这些事件的处理器都已经被提供了,这样你就不必再显式的创建这些事件处理器的委托了。
It’s important to understand that each ASP.NET virtual application runs in its own AppDomain and that there inside of the AppDomain multiple HttpApplication instances running simultaneously, fed out of a pool that ASP.NET manages. This is so that multiple requests can process at the same time without interfering with each other.
每一个ASP.NET Web程序运行在各自的AppDomain里,在AppDomain里同时运行着多个HttpApplication的实例,这些实例存放在ASP.NET管理的一个HttpApplication对象池里,认识到这一点,是非常重要的。这就是为什么可以同时处理多个请求,而这些请求不会互相干扰的原因。
To see the relationship between the AppDomain, Threads and the HttpApplication check out the code in Listing 4.
使用列表4的代码,可以进一步了解AppDomain,线程,HttpApplication之间的关系。
Listing 4 – Showing the relation between AppDomain, Threads and HttpApplication instances
列表 4: AppDomain, Threads and HttpApplication instances之间的关系
private void Page_Load(object sender, System.EventArgs e)
{
// Put user code to initialize the page here 将初始化页面的用户代码放在这里
this.ApplicationId = ((HowAspNetWorks.Global)
HttpContext.Current.ApplicationInstance).ApplicationId ;
this.ThreadId = AppDomain.GetCurrentThreadId();
this.DomainId = AppDomain.CurrentDomain.FriendlyName;
this.ThreadInfo = "ThreadPool Thread: " +
System.Threading.Thread.CurrentThread.IsThreadPoolThread.ToString() +
"<br>Thread Apartment: " +
System.Threading.Thread.CurrentThread.ApartmentState.ToString();
// *** Simulate a slow request so we can see multiple
// requests side by side. *** 为了可以同时看到多个请求一起到达,故意放慢速度
System.Threading.Thread.Sleep(3000);
}
This is part of a demo is provided with your samples and the running form is shown in Figure 5. To check this out run two instances of a browser and hit this sample page and watch the various Ids.这是样例程序的一部分,运行的结果如图5所示。为了检验结果,你应该打开两个浏览器,输入相同的地址,观察那些不同的ID的值。
Figure 5 – You can easily check out how AppDomains, Application Pool instances, and Request Threads interact with each other by running a couple of browser instances simultaneously(英 /ˌsɪm(ə)lˈteɪniəsli/ adv. 同时地). When multiple requests fire you’ll see the thread and Application ids change, but the AppDomain staying the same.
图5:同时运行几个浏览器,你会很容易的看到AppDomains,application对象以及处理请求的线程之间内在的关系。当多个请求触发时,你会看到线程和application的ID在改变,而AppDomain的ID却没有发生变化。
You’ll notice that the AppDomain ID stays steady while thread and HttpApplication Ids change on most requests, although they likely will repeat. HttpApplications are running out of a collection and are reused for subsequent requests so the ids repeat at times. Note though that Application instance are not tied(英 /taɪd/绑在) to a specific thread – rather they are assigned to the active executing thread of the current request.
你将会观察到AppDomain ID一直保持不变,而线程和HttpApplication的ID在请求多的时候会发生改变,尽管它们会出现重复。这是因为HttpApplications是在一个集合里面运行,下一个请求可能会再次使用同一个HttpApplication实例,所以有时候HttpApplication的ID会重复。注意,一个HttpApplication实例对象并不依赖于一个特定的线程,它们仅仅是被分配给处理当前请求的线程而已。
Threads are served from the .NET ThreadPool and by default are Multithreaded Apartment (MTA) style threads. You can override this apartment state in ASP.NET pages with the ASPCOMPAT="true" attribute in the @Page directive. ASPCOMPAT is meant to provide COM components a safe environment to run in and ASPCOMPAT (英 /kəmˈpæt/ n. 兼容)uses special Single Threaded Apartment (STA) threads to service those requests. STA threads are set aside and pooled separately as they require special handling.(compat 英 /kəmˈpæt/n. 兼容)
线程由.NET的ThreadPool提供服务,默认情况下,线程模型为多线程单元(MTA)。你可以通过在ASP.NET的页面的@Page指令里设置属性ASPCOMPAT="true"覆盖线程单元的状态。ASPCOMPAT(英 /kəmˈpæt/ n. 兼容)意味着COM组件将在一个安全的环境下运行。ASPCOMPAT使用了单线程单元(STA)的线程为请求提供服务。STA线程在线程池里是单独设置的,这是因为它们需要特殊的处理方式。
The fact that these HttpApplication objects are all running in the same AppDomain is very important. This is how ASP.NET can guarantee that changes to web.config or individual ASP.NET pages get recognized throughout the AppDomain. Making a change to a value in web.config causes the AppDomain to be shut down and restarted. This makes sure that all instances of HttpApplication see the changes made because when the AppDomain reloads the changes from ASP.NET are re-read at startup. Any static references are also reloaded when the AppDomain so if the application reads values from App Configuration settings these values also get refreshed.
实际上,这些HttpApplication对象运行在同一个AppDomain里是很重要的。这就是ASP.NET如何保证web.config的改变或者单独的ASP.NET页面得到验证可以贯穿整个AppDomain。改变web.config里的一个值,将导致AppDomain关闭并重新启动。这确保了所有的HttpApplication实例可以看到这些改变,这是因为当AppDomain重新加载的时候,来自ASP.NET的那些改变将会在AppDomain启动的时候重新读取。当AppDomain重新启动的时候任何静态的引用都将重新加载。这样,如果程序是从程序的配置文件读取的值,这些值将会被刷新。
To see this in the sample, hit the ApplicationPoolsAndThreads.aspx page and note the AppDomain Id. Then go in and make a change in web.config (add a space and save). Then reload the page. You’ll l find that a new AppDomain has been created.
在示例程序里可以看到这些,打开一个ApplicationPoolsAndThreads.aspx页面,注意观察AppDomain的ID。然后在web.config里做一些改动(增加一个空格,然后保存),重新加载这个页面(译注:由于缓存的影响可能会在原来的页面上刷新无效,需要先删除缓存再刷新即可),你就会看到一个新的AppDomain被创建了。
In essence the Web Application/Virtual completely ‘restarts’ when this happens. Any requests that are already in the pipeline processing will continue running through the existing pipeline, while any new requests coming in are routed to the new AppDomain. In order to deal with ‘hung requests’ ASP.NET forcefully shuts down the AppDomain after the request timeout period is up even if requests are still pending. So it’s actually possible that two AppDomains exist for the same HttpApplication at a given point in time as the old one’s shutting down and the new one is ramping up. Both AppDomains continue to serve their clients until the old one has run out its pending requests and shuts down leaving just the new AppDomain running.
本质上,这些改变将引起Web程序重新启动。对于已经存在于处理管道的请求,将继续通过原来的管道处理。而对于那些新的请求,将被路由到新的AppDomain里。为了处理这些“挂起的请求”,在这些请求超时结束之后,ASP.NET将强制关闭AppDomain甚至某些请求还没有被处理。因此,在一个特定的时间点上,同一个HttpApplication实例在两个AppDomain里存在是有可能的,这个时间点就是旧的AppDomain正在关闭,而新的AppDomain正在启动。这两个AppDomain将继续为客户端请求提供服务,直到旧的AppDomain处理完所有的未处理的请求,然后关闭,这时候才会仅剩下新的AppDomain在运行。
Flowing through the ASP.NET Pipeline 穿过ASP.NET管道
The HttpApplication is responsible for the request flow by firing events that signal your application that things are happening. This occurs as part of the HttpApplication.Init() method (look at System.Web.HttpApplication.InitInternal and HttpApplication.ResumeSteps() with Reflector) which sets up and starts a series of events in succession including the call to execute any handlers. The event handlers map to the events that are automatically set up in global.asax, and they also map any attached HTTPModules, which are essentially an externalized event sink for the events that HttpApplication publishes.
HttpApplication负责请求的传输,通过触发事件,通知应用程序正在发生的事情。这个是作为HttpApplication.Init()方法的一部分实现的(使用Refector查看System.Web.HttpApplication.InitInternal和HttpApplication.ResumeSteps()的代码可以看到这一点)。在这个方法里,创建和启动了一系列事件,包括绑定事件处理器。Global.asax里的事件处理器会自动映射到对应的事件,它们也可以映射到额外添加的HTTPModules,这些HTTPModules本质上是HttpApplication已发布事件的一种扩展。
Both HttpModules and HttpHandlers are loaded dynamically via entries in Web.config and attached to the event chain. HttpModules are actual event handlers that hook specific HttpApplication events, while HttpHandlers are an end point that gets called to handle ‘application level request processing’.
通过在web.config里注册,HTTPModules和HttpHandlers可以被动态的加载,并且可以添加到事件链条上。HTTPModules实际上就是事件处理器,它可以钩住指定HttpApplication的事件。而HttpHandlers就是一个端点,它可以被调用处理“应用程序级的请求处理”。
Both Modules and Handlers are loaded and attached to the call chain as part of the HttpApplication.Init() method call. Figure 6 shows the various events and when they happen and which parts of the pipeline they affect.
HTTPModules和HttpHandlers将被加载,然后添加到调用链上作为HttpApplication.Init()方法调用的一部分。图6展示了不同的事件,这些事件何时被触发以及通道上的哪些部分会受到它们的影响。
Figure 6 – Events flowing through the ASP.NET HTTP Pipeline. The HttpApplication object’s events drive requests through the pipeline. Http Modules can intercept(intercept 英 /ˌɪntəˈsept/ v. 拦截,阻截) these events and override or enhance existing functionality.
图6:事件在ASP.NET HTTP管道里传输。HttpApplication对象的事件驱动请求在管道里传输。HTTPModules可以截获这些事件,可以覆盖或增强已存在的功能。
HttpContext, HttpModules and HttpHandlers
The HttpApplication itself knows nothing about the data being sent to the application – it is a merely messaging object that communicates via events. It fires events and passes information via the HttpContext object to the called methods. The actual state data for the current request is maintained in the HttpContext object mentioned earlier. It provides all the request specific data and follows each request from beginning to end through the pipeline. Figure 7 shows the flow through ASP.NET pipeline. Notice the Context object which is your compadre(英 /kəmˈpɑːdreɪ/ n. 朋友;密友) from beginning to end of the request and can be used to store information in one event method and retrieve it in a later event method.
HttpApplication本身并不知晓发送给Web程序的数据。它仅仅是个消息邮递者,只负责事件之间的通信。它触发事件,然后通过传递HttpContext对象,把信息发送给被调用的方法。在之前我们提到,当前请求的数据是在HttpContext对象里保存。它提供了所有特定于请求的数据,并通过管道从头到尾跟踪每个请求(它提供了请求从开始到结束需要的所有数据。)。图7展示了ASP.NET的管道之间的传输流程。注意,从请求的开始至结束,上下文对象(Context)都是你的伙伴,你可以在一个事件方法里使用它保存数据,然后在之后的事件方法里获取这些数据。
Once the pipeline is started, HttpApplication starts firing events one by one as shown in Figure 6. Each of the event handlers is fired and if events are hooked up those handlers execute and perform their tasks. The main purpose of this process is to eventually call the HttpHandler hooked up to a specific request. Handlers are the core processing mechanism for ASP.NET requests and usually the place where any application level code is executed. Remember that the ASP.NET Page and Web Service frameworks are implemented as HTTPHandlers and that’s where all the core processing of the request is handled. Modules tend to be of a more core nature used to prepare or post process the Context that is delivered to the handler. Typical default handlers in ASP.NET are Authentication, Caching for pre-processing and various encoding mechanisms on post processing.
ASP.NET管道一旦启动,HttpApplication将逐一触发事件,如图6展示的那样。每一个事件都将被触发,如果事件绑定了事件处理器,那么这些事件处理器将被调用,执行它们的任务。这个过程的主要目的是通过调用HttpHandler处理指定的请求。对于ASP.NET请求而言,HttpHandler是处理请求机制的核心,在这里任意的应用程序级的代码被执行。记住,ASP.NET的页面和Web Service都是HttpHandler的具体实现,在这里,所有请求处理的核心功能被实现。HTTPModule则倾向于在分发给事件处理器之前或者之后对内容进行处理。在ASP.NET里典型的默认操作有:鉴定(Authentication),处理前的缓存操作以及各种处理后的编码操作机制。
There’s plenty(英 /ˈplenti/pron. 大量,众多) of information available on HttpHandlers and HttpModules so to keep this article a reasonable length I’m going to provide only a brief overview of handlers.
关于HTTPModule和HttpHandler,这里有很多有用的信息,但为了保持这篇文章合理的尺度,我将仅仅讲述关于它们一些简短的、整体的看法。
Figure 7 – The ASP.NET Request pipeline flows requests through a set of event interfaces that provide much flexibility. The Application acts as the hosting container that loads up the Web application and fires events as requests come in and pass through the pipeline. Each request follows a common path through the Http Filters and Modules configured. Filters can examine each request going through the pipeline and Handlers allow implementation of application logic or application level interfaces like Web Forms and Web Services. To provide Input and Output for the application the Context object provides request specific information throughout the entire process.
图7:ASP.NET管道在一系列事件接口之间传输请求,这提供了足够的灵活性。HttpApplication担当主容器,负责加载Web程序,当请求到来时触发事件以及在管道之间传输请求。经过已配置的HTTP过滤和模块时,每一个请求都将遵循一个公有的路径。过滤器可以检查穿梭在管道里的每一个请求,而处理器允许实现应用程序的逻辑或者应用程序级的接口像WebForms和WebServices一样。为了给程序提供输入和输出,上下文对象(Context)给请求提供了所需的信息,它贯穿了请求生命周期的始终。
HttpModules
As requests move through the pipeline a number of events fire on the HttpApplication object. We’ve already seen that these events are published as event methods in Global.asax. This approach is application specific though which is not always what you want. If you want to build generic HttpApplication event hooks that can be plugged into any Web applications you can use HttpModules which are reusable and don’t require application specific code except for an entry in web.config.
伴随着HttpApplication触发的一系列事件,请求将会在管道之间穿梭。你已经看到了这些发布的事件,在Global.asax里都有对应事件的处理方法。这个方法(步骤)是程序(ASP.NET应用程序)携带的,尽管这个并不总是你需要的。如果你想构建一套通用的HttpApplication事件处理程序,并以插件的形式添加到任意的Web程序里。那么你可以使用HTTPModule,它是可重复使用的,不需要添加任何实现代码就可以在其它程序里使用,而你所做的仅仅在web.config里注册。
Modules are in essence filters – similar in functionality to ISAPI filters at the ASP.NET request level. Modules allow hooking events for EVERY request that pass through the ASP.NET HttpApplication object. These modules are stored as classes in external assemblies that are configured in web.config and loaded when the Application starts. By implementing specific interfaces and methods the module then gets hooked up to the HttpApplication event chain. Multiple HttpModules can hook the same event and event ordering is determined by the order they are declared in Web.config. Here’s what a handler definition looks like in Web.config:
模块本质上是过滤器,在功能上类似于ASP.NET请求级别的ISAPI过滤。对于每一个穿过ASP.NET的HttpApplication对象的请求,模块都允许在HttpApplication对象触发的事件处理方法里截获这些请求。这些模块以类的形式存储在外部程序集里,可以在web.config里配置,当程序启动的时候加载。通过实现指定的接口和方法,模块就可以被添加到HttpApplication的事件链上。多个HttpModules可以钩住相同的事件,事件发生的顺序是它们在web.config里声明(配置)的顺序。如下就是在web.config里,一个模块的声明。
<configuration>
<system.web>
<httpModules>
<add name= "BasicAuthModule"
type="HttpHandlers.BasicAuth,WebStore" />
</httpModules>
</system.web>
</configuration>
Note that you need to specify a full typename and an assembly name without the DLL extension.
注意,在这里你需要指定一个完整的类型名和一个不带扩展名的程序集的名字。
Modules allow you look at each incoming Web request and perform an action based on the events that fire. Modules are great to modify request or response content, to provide custom authentication or otherwise provide pre or post processing to every request that occurs against ASP.NET in a particular(英 /pəˈtɪkjələ(r)/) application. Many of ASP.NET’s features like the Authentication and Session engines are implemented as HTTP Modules.
模块允许你查看每一个传入的Web请求,基于触发的事件基础上执行操作。模块是非常有用的,它可以修改请求,输出响应的内容以及提供自定义的身份验证,另外还可以在特定的程序里,针对ASP.NET的每一个请求提供响应前处理和响应后处理。许多ASP.NET的特征像身份验证,会话引擎都是作为HTTP模块实现的。
While HttpModules feel similar to ISAPI Filters in that they look at every request in that comes through an ASP.NET Application, they are limited to looking at requests mapped to a single specific ASP.NET application or virtual directory and then only against requests that are mapped to ASP.NET. Thus you can look at all ASPX pages or any of the other custom extensions that are mapped to this application. You cannot however look at standard .HTM or image files unless you explicitly map the extension to the ASP.NET ISAPI dll by adding an extension as shown in Figure 1. A common use for a module might be to filter content to JPG images in a special folder and display a ‘SAMPLE’ overlay ontop of every image by drawing ontop of the returned bitmap with GDI+.
HttpModules感觉有点类似于ISAPI过滤器,是由于它们查看进入ASP.NET程序的每一个请求,但它们局限性于仅可以查看映射到某一个ASP.NET程序或者虚拟目录的请求,和映射到ASP.NET的请求。因此,你仅可以查看所有的ASPX页面或者任意其它自定义的已经映射到这个程序的扩展名(译注:作者可能漏掉了ASMX,ASHX等,这里的意思应该是所有的ASP.NET默认的扩展名)。但是,你不能查看标准的.HTM或者图像文件,除非你通过添加这些扩展名,明确的把它们映射到ASP.NET的ISAPI DLL,如图一中那样。对于模块,一个常见的用处是过滤一个指定的文件夹的JPG图片内容,然后使用GDI+在每一张返回的图片上方添加“样图”字样。
Implementing an HTTP Module is very easy: You must implement the IHttpModule interface which contains only two methods Init() and Dispose(). The event parameters passed include a reference to the HTTPApplication object, which in turn gives you access to the HttpContext object. In these methods you hook up to HttpApplication events. For example, if you want to hook the AuthenticateRequest event with a module you would do what’s shown in Listing 5.
实现一个HTTP模块是非常简单的:你必须实现一个IHttpModule接口,它包含两个方法:Init()和Dispose()。传递的事件参数中包含着一个HttpApplication对象的引用,接着,它会给你访问HttpContext对象的权限。在这两个方法里,你可以钩住HttpApplication的事件。举个例子,如果你想用一个模块钩住AuthenticateRequest事件,那么你需要做的会像列表5中展示的那样。
Listing 5: The basics of an HTTP Module are very simple to implement
列表5: 一个HTTP模块实现起来非常的简单
public class BasicAuthCustomModule : IHttpModule
{
public void Init(HttpApplication application)
{
// *** Hook up any HttpApplication events 连接任何HttpApplication事件
application.AuthenticateRequest +=
new EventHandler(this.OnAuthenticateRequest);
}
public void Dispose() { }
public void OnAuthenticateRequest(object source, EventArgs eventArgs)
{
HttpApplication app = (HttpApplication) source;
HttpContext Context = HttpContext.Current;
… do what you have to do… }
}
Remember that your Module has access the HttpContext object and from there to all the other intrinsic(英 /ɪnˈtrɪnzɪk; ɪnˈtrɪnsɪk/adj. 内在的,固有的) ASP.NET pipeline objects like Response and Request, so you can retrieve input etc. But keep in mind that certain things may not be available until later in the chain.
记住,你的模块已经有访问HttpContext对象的权限了。从这里到所有其它内置的ASP.NET管道对象如Response和Request,因此你可以获取输入的数据等等。但紧记,某些对象现在可能不能使用,只有到这条链的后面的环节才会有效,
You can hook multiple events in the Init() method so your module can manage multiple functionally different operations in one module. However, it’s probably cleaner to separate differing logic out into separate classes to make sure the module is modular. In many cases functionality that you implement may require that you hook multiple events – for example a logging filter might log the start time of a request in Begin Request and then write the request completion into the log in EndRequest.
在Init()方法里,你可以钩住多个事件,因此在一个模块里,可以管理多个不同功能的操作。但是,应该尽可能的把不同的逻辑代码放到不同的类里,这样可以确保这个模块是标准的组件。在许多情况下,你实现的功能可能需要钩住多个事件。举个例子,一个日志过滤器可能需要在BeginRequest里记录请求的开始时间,在EndRequest里记录请求完成的时间。
Watch out for one important gotcha(英 /ˈɡɒtʃə/ int. 明白了(非正式,等于 got you)) with HttpModules and HttpApplication events: Response.End() or HttpApplication.CompleteRequest() will shortcut the HttpApplication and Module event chain. See the sidebar “Watch out for Response.End() “ for more info.
在HttpModules里使用HttpApplication的事件时,有一点是需要注意的,Response.End()和HttpApplication.CompleteRequest()方法会使ASP.NET跳过HttpApplication和模块的事件链。可以查看这两个方法的帮助文档获取更多的信息。
HttpHandlers
Modules are fairly(英 /ˈfeəli/ adv. 相当地,颇;公平地,公正地) low level and fire against every inbound (英 /ˈɪnbaʊnd/ adj. 入站 入境的;归本国的;回内地的)request to the ASP.NET application. Http Handlers are more focused and operate on a specific request mapping, usually a page extension that is mapped to the handler.
模块是相当低层次的,它针对每一个传入ASP.NET程序的请求触发。HTTP处理器则着重于处理一个指定的请求映射。通常一个页面扩展已经被映射到处理器了。
Http Handler implementations are very basic in their requirements, but through access of the HttpContext object a lot of power is available. Http Handlers are implemented through a very simple IHttpHandler interface (or its asynchronous cousin, IHttpAsyncHandler) which consists of merely a single method – ProcessRequest() – and a single property IsReusable. The key is ProcessRequest() which gets passed an instance of the HttpContext object. This single method is responsible for handling a Web request start to finish.
实现一个HTTP处理器所需要做的是非常基础的,但是通过访问HttpContext对象,就会有很多有用的功能。HTTP处理器通过一个简单IHttpHandler接口实现(或者它的异步版本IHttpAsyncHandler)。它仅仅有一个方法ProcessRequest()和一个属性IsReusable。这里的关键是ProcessRequest()会得到一个HttpContext对象的实例。这个单独的方法将从开始到结束负责处理一个Web请求。
Single, simple method? Must be too simple, right? Well, simple interface, but not simplistic in what’s possible! Remember that WebForms and WebServices are both implemented as Http Handlers, so there’s a lot of power wrapped up in this seemingly simplistic interface. The key is the fact that by the time an Http Handler is reached all of ASP.NET’s internal objects are set up and configured to start processing of requests. The key is the HttpContext object, which provides all of the relevant request functionality to retireve input and send output back to the Web Server.
单一的,简单的方法?可能太简单了,是吗?可是,一个简单的接口,但它的实现可能并不简单。记住,WebForms和WebServices都是作为HTTP处理器实现的。因此,在这个看似简单的接口里,过多的实现过程被隐藏了。关键是,事实上到现在,一个HTTP处理器可以访问所有为了开始处理请求而被组建和配置起来的ASP.NET的内置对象。关键是,HttpContext对象提供了所有与请求相关的功能,可以获取流入的数据和输出数据到Web服务器。
For an HTTP Handler all action occurs through this single call to ProcessRequest(). This can be as simple as:对于HTTP处理器而言,所有的操作都通过简单地调用ProcessRequest()方法执行。可以简单到如下的样子:
public void ProcessRequest(HttpContext context)
{
context.Response.Write("Hello World");
}
to a full implementation like the WebForms Page engine that can render complex forms from HTML templates. The point is that it’s up to you to decide of what you want to do with this simple, but powerful interface!
一个完整的实现像WebForms页面引擎那样,可以根据HTML模版展现复杂的表单。所以,这里的关键点是你想用这个简单但功能强大的接口做什么。
Because the Context object is available to you, you get access to the Request, Response, Session and Cache objects, so you have all the key features of an ASP.NET request at your disposal to figure out what users submitted and return content you generate back to the client. Remember the Context object – it’s your friend throughout the lifetime of an ASP.NET request!
因为对你而言,HttpContext对象是可以使用的,这样你就可以访问Request,Response,Session和Cache对象,因此你已经拥有了ASP.NET请求的所有特征,可以自己做主如何处理用户提交的信息,然后给客户端返回处理后产生的内容。记住,在一个ASP.NET请求的生命期内,上下文对象始终都是你的朋友。
The key operation of the handler should be eventually write output into the Respone object or more specifically the Response object’s OutputStream. This output is what actually gets sent back to the client. Behind the scenes the ISAPIWorkerRequest manages sending the OutputStream back into the ISAPI ecb.WriteClient method that actually performs the IIS output generation.
处理器的关键操作通常是往Response对象里写输出数据,或者更确切的说,是往Response对象的OutputStream里写。这个就是真正返回到客户端的输出数据。在底层,由ISAPIWorkerRequest负责把OutputStream发回给ISAPI的ecb.WriteClient方法,因为ecb.WriteClient方法才是真正执行IIS产生输出数据的。
Figure 7 – The ASP.NET Request pipeline flows requests through a set of event interfaces that provide much flexibility. The Application acts as the hosting container that loads up the Web application and fires events as requests come in and pass through the pipeline. Each request follows a common path through the Http Filters and Modules configured. Filters can examine each request going through the pipeline and Handlers allow implementation of application logic or application level interfaces like Web Forms and Web Services. To provide Input and Output for the application the Context object provides request specific information throughout the entire process.
图7-ASP.NET请求管道通过一系列事件接口来转发请求,提供了更大的灵活性.Application当请求到来并通过管道时作为一个载入Web应用并触发事件的宿主容器.每个请求都沿着配置的Http过滤器和模块的路径走(译注:原文为Http Filters And Modules,应该是指Http Module和Http Handler).过滤器可以检查每个通过管道的请求,Handler允许实现应用程序逻辑或者像Web Form和WebService这样的应用层接口.为了向应用提供输入输出,Context对象在这个处理过程中提供了特定于请求的的信息.
WebForms implements an Http Handler with a much more high level interface on top of this very basic framework, but eventually a WebForm’s Render() method simply ends up using an HtmlTextWriter object to write its final final output to the context.Response.OutputStream. So while very fancy, ultimately even a high level tool like Web forms is just a high level abstraction ontop of the Request and Response object.
通过使用大量的处于高层的、基础的框架接口,WebForms实现了一个HTTP处理器。但最后,WebForm的Render()方法却简单的使用了一个HtmlTextWriter对象,把最终的输出发送给context.Response.OutputStream对象而结束。因此,相当的奇妙,最终甚至一个高层次的工具像WebForm,也仅仅是在Response和Request对象之上的一个高层次的抽象。
You might wonder at this point whether you need to deal with Http Handlers at all. After all WebForms provides an easily accessible Http Handler implementation, so why bother with something a lot more low level and give up that flexibility?
在这个时候,你可能想知道,是否需要从头实现一个完整的HTTP处理器?毕竟WebForms已经提供了一个易用的HTTP处理器的实现,因此为什么还要为这些大量底层的东西而烦恼,放弃这些已经提供的灵活性呢?
WebForms are great for generating complex HTML pages and business level logic that requires graphical layout tools and template backed pages. But the WebForms engine performs a lot of tasks that are overhead intensive. If all you want to do is read a file from the system and return it back through code it’s much more efficient to bypass the Web Forms Page framework and directly feed the file back. If you do things like Image Serving from a Database there’s no need to go into the Page framework – you don’t need templates and there surely is no Web UI that requires you to capture events off an Image served.
There’s no reason to set up a page object and session and hook up Page level events – all of that stuff requires execution of code that has nothing to do with your task at hand.
WebForms是非常棒的,使用它可以生成复杂的HTML页面和处理业务层的逻辑而这些都需要图形化的设计和模版化的页面。除此以外,WebForms引擎还可以完成更多的,需要丰富的表现层的任务。如果所有你想做的是:从系统读取文件,由执行的代码把它返回。这样的任务,如果避开使用Web Forms页面框架,代替的是直接处理文件然后返回,相信后者才是更高效的方法。如果你做的事情仅仅是像从数据库里读取图片一样,那么完全没有必要使用页面框架来处理,因为这里的确没有Web UI需要你俘获离开图片的事件。
这里找不到,组建一个页面对象和会话对象以及俘获页面层上事件的理由。对于你的任务而言,这里需要做的仅仅是执行代码,而这些与你手头上的任务毫不相干。
So handlers are more efficient. Handlers also can do things that aren’t possible with WebForms such as the ability to process requests without the need to have a physical file on disk, which is known as a virtual Url. To do this make sure you turn off ‘Check that file exists’ checkbox in the Application Extension dialog shown in Figure 1.
因此,这种情况下使用处理器会更加高效。处理器可以完成使用WebForms不可能完成的事情。比如:需要处理这样的请求,它们没有必要在磁盘上存在对应的物理文件,这些被请求的路径通常称为虚拟的URL。为了使这样的请求正常的工作,你需要确保已经在应用程序的扩展对话框(如图一所示)里关闭了“确认文件是否存在”的复选框。
This is common for content providers, such as dynamic image processing, XML servers, URL Redirectors providing vanity(英 /ˈvænəti/n. 虚荣(心),自负;) Urls, download managers and the like, none of which would benefit from the WebForm engine.
对于内容提供者来说,这是通用的,如:动态的图像处理,XML服务,提供虚拟的URL使原来的URL重定向,下载管理等等,这些均不能使用WebForms实现。
Have I stooped low enough for you? 是否已经提供了足够的底层知识?
Phew – we’ve come full circle here for the processing cycle of requests. That’s a lot of low level information and I haven’t even gone into great detail about how HTTP Modules and HTTP Handlers work. It took some time to dig up this information and I hope this gives you some of the same satisfaction it gave me in understanding how ASP.NET works under the covers.
哎呀!我们终于绕着请求的处理周期回到了原地。尽管我在这里没有探讨有关HTTP模块和HTTP处理器如何工作的更多细节,但仍旧提供了许多对你有帮助的底层信息。挖掘这些信息花费了我很多时间,通过了解ASP.NET在底层的工作模式,使我感到非常地满足,希望它也可以给你带来同样的感受。
Before I’m done let’s do the quick review of the event sequences I’ve discussed in this article from IIS to handler:
- IIS gets the request IIS得到一个请求
- Looks up a script map extension and maps to aspnet_isapi.dll 查询脚本映射扩展,然后把请求映射到aspnet_isapi.dll文件
- Code hits the worker process (aspnet_wp.exe in IIS5 or w3wp.exe in IIS6) 代码进入工作者进程(IIS5里是aspnet_wp.exe;IIS6里是w3wp.exe)
- .NET runtime is loaded NET运行时被加载
- IsapiRuntime.ProcessRequest() called by non-managed code 非托管代码调用IsapiRuntime.ProcessRequest()方法
- IsapiWorkerRequest created once per request 每一个请求调用一个IsapiWorkerRequest
- HttpRuntime.ProcessRequest() called with Worker Request
- HttpContext Object created by passing Worker Request as input
- HttpApplication.GetApplicationInstance() called with Context to retrieve instance from pool
- HttpApplication.Init() called to start pipeline event sequence and hook up modules and handlers
- HttpApplicaton.ProcessRequest called to start processing
- Pipeline events fire 触发管道事件
- Handlers are called and ProcessRequest method are fired 调用HTTP处理器和ProcessRequest方法
- Control returns to pipeline and post request events fire 把返回的数据输出到管道,触发处理请求后的事件
在结束之前,还是让我们来回顾一下,我在这篇文章中讨论的从IIS到HTTP处理器的事件序列:
IIS得到一个请求
查询脚本映射扩展,然后把请求映射到aspnet_isapi.dll文件
代码进入工作者进程(IIS5里是aspnet_wp.exe;IIS6里是w3wp.exe)
NET运行时被加载
非托管代码调用IsapiRuntime.ProcessRequest()方法
每一个请求调用一个IsapiWorkerRequest
使用WorkerRequest调用HttpRuntime.ProcessRequest()方法
通过传递进来的WorkerRequest创建一个HttpContext对象
通过把上下文对象作为参数传递给HttpApplication.GetApplicationInstance(),然后调用该方法,从应用程序池中获取一个HttpApplication实例。
调用HttpApplication.Init(),启动管道事件序列,钩住模块和处理器
调用HttpApplicaton.ProcessRequest,开始处理请求
触发管道事件
调用HTTP处理器和ProcessRequest方法
把返回的数据输出到管道,触发处理请求后的事件
It’s a lot easier to remember how all of the pieces fit together with this simple list handy. I look at it from time to time to remember. So now, get back to work and do something non-abstract…
使用手边的例子将会更容易记住这些零碎的片断是如何组合起来的。为了记住它,我会不时地看一下它。现在应该回到工作中了,去做一些不太抽象的事情吧。
Although what I discuss here is based on ASP.NET 1.1, it looks that the underlying processes described here haven’t changed in ASP.NET 2.0.
尽管讨论的这些是基于ASP.NET 1.1的。但这里描述的底层处理在ASP.NET 2.0好像并没有多大改变。
Many thanks to Mike Volodarsky from Microsoft for reviewing this article and providing a few additional hints and Michele Leroux Bustamante for providing the basis for the ASP.NET Pipeline Request Flow slide.
If you have any comments or questions feel free to post them on the Comment link below.
最后,非常感谢来自微软的Mike Volodarsky,是他校验了这篇文章,并且提出了一些宝贵的意见。还有Michele Leroux Bustamante,他为ASP.NET管道请求流程的幻灯片提供了依据。
如果你有任何意见或问题,请在下方的评论链接上发表。