ASP.NET中Http请求处理流程
目 录
1 .概述.... 2
1.1 背景... 2
1.2 目的... 2
2 .Http请求处理流程.... 3
2.1 Http请求刚刚到达服务器的时候... 3
2.2 理解管道(PipeLine) 3
2.3 HttpModule. 4
2.4 HttpHandler. 6
2.5 页生命周期... 7
1 .概述
1.1 背景
大部分时候,我们开发人员只关心如何在具体页面中实现需求,但是,了解ASP.NET的运作原理是非常必要的。
1.2 目的
通过对ASP.NET框架中对Http请求处理流程的讲述,让大家理解ASP.NET的运作原理,同时引出HttpModule以及HttpHandler这两个重要概念。
2 .Http请求处理流程
2.1 Http请求刚刚到达服务器的时候
当服务器接收到一个 Http请求的时候,IIS 首先需要决定如何去处理这个请求(NOTE:服务器处理一个.htm页面和一个.aspx页面肯定是不一样的么)。那IIS依据什么去处理呢?―― 根据文件的后缀名。
打开IIS配置文件,我们能看到,一般地说,所有的.aspx文件实际上都是由 aspnet_isapi.dll 这个程序来处理的,当IIS把对于.aspx页面的请求提交给了aspnet_isapi.dll以后,它就不再关心这个请求随后是如何处理的了。现在我们应该知道:Asp.Net 只是服务器(IIS)的一个组成部分而已,它是一个 ISAPI扩展。
2.2 理解管道(PipeLine)
当Http请求进入 Asp.Net Runtime以后,它的管道由托管模块(NOTE:Managed Modules)和处理程序(NOTE:Handlers)组成,并且由管道来处理这个 Http请求。
图4. 理解 Http 管道
我们按编号来看一下这幅图中的数据是如何流动的。
1. HttpRuntime将Http请求转交给 HttpApplication,HttpApplication代表着程序员创建的Web应用程序。HttpApplication创建针对此Http请求的 HttpContext对象,这些对象包含了关于此请求的诸多其他对象,主要是HttpRequest、HttpResponse、HttpSessionState等。这些对象在程序中可以通过Page类或者Context类进行访问。、
2. 接下来Http请求通过一系列Module,这些Module对Http请求具有完全的控制权。这些Module可以做一些执行某个实际工作前的事情。
3. Http请求经过所有的Module之后,它会被HttpHandler处理。在这一步,执行实际的一些操作,通常也就是.aspx页面所完成的业务逻辑。
4.HttpHandler处理完以后,Http请求再一次回到Module,此时Module可以做一些某个工作已经完成了之后的事情。
2.3 HttpModule
上一节我们提到了HttpModule,每一个HttpModule就是一个过滤器,每个Http请求在管道中流动时都需要经过各个HttpModule的处理。
一般来说,我们可以将Asp.Net中的事件分成三个级别,最顶层是 应用程序级事件、其次是页面级事件、最下面是控件级事件,事件的触发分别与 应用程序周期、页面周期、控件周期紧密相关。而 Http Module 的作用是与应用程序事件密切相关的。
下面以身份验证业务为例,说明怎样添加一个Http Module:
类代码可以添加到AppCode中,也可以写在自己单独的命名空间中,如果是在App_Code中,程序集名即为App_Code。
在AppCode中添加新类AuthenticModule.cs,内容如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Business.Entity;
using Headfree.Framework.Authority;
/// <summary>
///AuthenticModule 的摘要说明
/// </summary>
public class AuthenticModule : IHttpModule
{
public void Dispose(){}
public void Init(HttpApplication context)
{
context.PreRequestHandlerExecute += new EventHandler(context_PreRequestHandlerExecute);
}
void context_PreRequestHandlerExecute(object sender, EventArgs e)
{
HttpApplication ha = (HttpApplication)sender;
string path = ha.Context.Request.Url.ToString();
int n = path.ToLower().IndexOf("login.aspx");
int m = path.IndexOf("aspx");//判断是否是page
if (n == -1) //是否是登录页面,不是登录页面的话则进入{}
{
if (m > 0)//在发现不是登陆页面的基础上判断是否是page
{
User user = ha.Context.Session["loginman"] as User;
if (null == user)
{
string url = BasePage.ConvertToBase64(HttpContext.Current.Request.FilePath);
url = "/Login.aspx?url=" + url;
if (HttpContext.Current.Request.ApplicationPath != "/")
{
url = ha.Context.Request.ApplicationPath + url;
}
WebScript.AlertAndRedirect("用户未登录或登录已失效", url, "_top");
ha.Context.Response.End();
}
}
}
}
}
此段代码的主要目的就是在PreRequestHandlerExecute事件中附加我们的身份验证方法,这样在真正访问具体页面之前,能够根据是否已经登录选择直接进入或者跳转到登陆页。
注册:
在web.config中添加
<httpModules>
<add name="AuthenticModule" type="AuthenticModule,App_Code"></add>
</httpModules>
注意逗号后面的App_Code即为程序集名。这样就成功添加了一个自定义HttpModule。
2.4 HttpHandler
在所有HttpModule都处理完毕之后,ASP.NET还是要根据配置将具体Http请求转发到对应的HttpHandler中处理。我们可以在web.config中添加配置,为某一种类型或某一个特殊的文件名指定HttpHandler:
<system.web>
<httpHandlers>
<add path="*.jpg" verb="*" type="MyNameSpace.MyClass, MyDllName" />
</httpHandlers>
</system.web>
HttpHandler代码如下:
public class CustomHandler : IHttpHandler{
public void ProcessRequest(HttpContext context) {
// 处理请求的代码
}
public bool IsReusable {
get { return true; }
}
}
这就成功地设置为,每一个试图访问jpg格式的图片文件的Http请求都要经过我们自定义的CustomHandler的处理。
我们也可以新增一个HttpHandler,对原来IIS不支持的文件格式进行处理从而达到了扩展的目的。
2.5 页生命周期
刚才我们提到了,一般来说,我们可以将Asp.Net中的事件分成三个级别,最顶层是 应用程序级事件、其次是页面级事件、最下面是控件级事件,事件的触发分别与 应用程序周期、页面周期、控件周期紧密相关。作为软件开发人员,我们一般接触的是页面周期,下面对此讲述一二。
生命周期:
最后再来回顾一下Asp.Net中Page页的生命周期,Page中定义了几个事件:
总体上讲:一个ASPX页面被请求时,最终的生命周期就是由Page中定义的上述事件(还有一些可重载的回调方法)以及以前提到的HttpApplication类中定义的事件(以相应的回调方法)共同触发或调用,最终叠加形成的一连串处理过程。
如果先不考虑HttpApplication中的事件处理方法(即不考虑我们在Global.ascx.cs中定义的Application_XXX处理方法),Page中的事件(方法)常规触发(调用)顺序为:
01.Page_PreInit
02.Page_Init
03.Page_InitComplete
04.Page_PreLoad
05.Page_Load
06.Page_LoadComplete
07.Page_PreRender
08.Page_SaveStateComplete
09.Page_Unload
这是在Page页面未回发,且不考虑页面子控件的前提下正常的顺序,如果加入页面回发(比如在页面中放一个asp:Button,然后在Button的Click回发事件中加入处理函数)后,顺序稍微有些变化:
01.Page_PreInit
02.Page_Init
03.Page_InitComplete
04.Page_PreLoad
05.Page_Load
06.Button1_Click
07.Page_LoadComplete
08.Page_PreRender
09.Page_SaveStateComplete
10.Page_Unload
不同的地方在于:回发事件Button1_Click在Page_Load后被触发.
最后再把HttpApplication的事件考虑进来,看下叠加后的顺序,不过先别着急,我们先来看一种特殊情况,如果一个asp.net应用根目录下未设置默认页,这时直接浏览根目录,比如http://localhost:2345/ 时,Globl.ascx.cs中定义的Application_XXX方法的调用顺序如下:
2011-05-03 15:01:39 413 Application_Start
2011-05-03 15:01:39 491 Init
2011-05-03 15:01:39 491 Application_BeginRequest
2011-05-03 15:01:39 506 Application_AuthenticateRequest
2011-05-03 15:01:39 506 Application_PostAuthenticateRequest
2011-05-03 15:01:39 506 Application_AuthorizeRequest
2011-05-03 15:01:39 522 Application_PostAuthorizeRequest
2011-05-03 15:01:39 522 Application_ResolveRequestCache
2011-05-03 15:01:39 522 Application_PostResolveRequestCache
2011-05-03 15:01:39 522 Application_PostMapRequestHandler
2011-05-03 15:01:39 522 Application_AcquireRequestState
2011-05-03 15:01:39 537 Application_PostAcquireRequestState
2011-05-03 15:01:39 537 Application_PreRequestHandlerExecute
2011-05-03 15:01:39 553 Application_Error
2011-05-03 15:01:39 553 Application_EndRequest
2011-05-03 15:01:39 569 Application_PreSendRequestHeaders
2011-05-03 15:01:39 569 Application_PreSendRequestContent
可以看到会触发Application_Error事件,即HttpRuntime认为这是一个错误.
紧接着再浏览一个实际存在的页面,如果这时应用程序有严重错误,导致Application关闭(比如web.config配置错误),调用的顺序如下:
2011-05-03 15:03:47 704 Application_BeginRequest
2011-05-03 15:03:47 704 Application_AuthenticateRequest
2011-05-03 15:03:47 766 Application_PostAuthenticateRequest
2011-05-03 15:03:47 766 Application_AuthorizeRequest
2011-05-03 15:03:47 766 Application_PostAuthorizeRequest
2011-05-03 15:03:47 766 Application_ResolveRequestCache
2011-05-03 15:03:47 783 Application_PostResolveRequestCache
2011-05-03 15:03:48 667 Application_PostMapRequestHandler
2011-05-03 15:03:48 667 Application_AcquireRequestState
2011-05-03 15:03:48 683 Application_PostAcquireRequestState
2011-05-03 15:03:48 698 Application_PreRequestHandlerExecute
2011-05-03 15:03:48 745 Page_PreInit
2011-05-03 15:04:02 903 Page_Unload
2011-05-03 15:04:02 903 Application_Error
2011-05-03 15:04:02 918 Application_EndRequest
2011-05-03 15:04:02 996 Application_PreSendRequestHeaders
2011-05-03 15:04:02 996 Application_PreSendRequestContent
2011-05-03 15:04:03 371 Application_Disposed
2011-05-03 15:04:03 371 Dispose
2011-05-03 15:04:03 386 Application_End
对比刚才的顺序,会发现Application_Start及Init没有再次被调用,也印证了文章前面提到的一些结论(Application_Start在整个asp.net应用生命周期内只触发一次),而且从最后的三个输出能知道:应用程序关闭时Application_Disposed,Dispose,Application_End按顺序调用.
再"重新"浏览(指web Server重启)一下正常访问的页面,在不出错也不回发的情况下,顺序如下:
2011-05-03 15:08:11 513 Application_Start
2011-05-03 15:08:11 591 Init
2011-05-03 15:08:11 591 Application_BeginRequest
2011-05-03 15:08:11 591 Application_AuthenticateRequest
2011-05-03 15:08:11 591 Application_PostAuthenticateRequest
2011-05-03 15:08:11 606 Application_AuthorizeRequest
2011-05-03 15:08:11 606 Application_PostAuthorizeRequest
2011-05-03 15:08:11 606 Application_ResolveRequestCache
2011-05-03 15:08:11 606 Application_PostResolveRequestCache
2011-05-03 15:08:11 622 Application_PostMapRequestHandler
2011-05-03 15:08:11 637 Application_EndRequest
2011-05-03 15:08:11 637 Application_PreSendRequestHeaders
2011-05-03 15:08:11 637 Application_PreSendRequestContent
2011-05-03 15:08:11 637 Application_BeginRequest
2011-05-03 15:08:11 637 Application_AuthenticateRequest
2011-05-03 15:08:11 653 Application_PostAuthenticateRequest
2011-05-03 15:08:11 653 Application_AuthorizeRequest
2011-05-03 15:08:11 653 Application_PostAuthorizeRequest
2011-05-03 15:08:11 653 Application_ResolveRequestCache
2011-05-03 15:08:11 653 Application_PostResolveRequestCache
2011-05-03 15:08:11 653 Application_PostMapRequestHandler
2011-05-03 15:08:11 653 Session_Start
2011-05-03 15:08:11 653 Application_AcquireRequestState
2011-05-03 15:08:11 653 Application_PostAcquireRequestState
2011-05-03 15:08:11 653 Application_PreRequestHandlerExecute
2011-05-03 15:08:11 669 Page_PreInit
2011-05-03 15:08:11 684 Page_Init
2011-05-03 15:08:11 684 Page_InitComplete
2011-05-03 15:08:11 684 Page_PreLoad
2011-05-03 15:08:11 684 Page_Load
2011-05-03 15:08:11 684 Page_LoadComplete
2011-05-03 15:08:11 684 Page_PreRender
2011-05-03 15:08:11 684 Page_SaveStateComplete
2011-05-03 15:08:11 700 Page_Unload
2011-05-03 15:08:11 700 Application_PostRequestHandlerExecute
2011-05-03 15:08:11 700 Application_ReleaseRequestState
2011-05-03 15:08:11 700 Application_PostReleaseRequestState
2011-05-03 15:08:11 700 Application_UpdateRequestCache
2011-05-03 15:08:11 700 Application_PostUpdateRequestCache
2011-05-03 15:08:11 700 Application_EndRequest
2011-05-03 15:08:11 700 Application_PreSendRequestHeaders
2011-05-03 15:08:11 700 Application_PreSendRequestContent
2011-05-03 15:08:11 793 Application_BeginRequest
2011-05-03 15:08:11 793 Application_AuthenticateRequest
2011-05-03 15:08:11 793 Application_PostAuthenticateRequest
2011-05-03 15:08:11 793 Application_AuthorizeRequest
2011-05-03 15:08:11 793 Application_PostAuthorizeRequest
2011-05-03 15:08:11 793 Application_ResolveRequestCache
2011-05-03 15:08:11 793 Application_PostResolveRequestCache
2011-05-03 15:08:11 809 Application_PostMapRequestHandler
2011-05-03 15:08:11 809 Application_AcquireRequestState
2011-05-03 15:08:11 809 Application_PostAcquireRequestState
2011-05-03 15:08:11 809 Application_PreRequestHandlerExecute
2011-05-03 15:08:11 825 Application_PostRequestHandlerExecute
2011-05-03 15:08:11 825 Application_ReleaseRequestState
2011-05-03 15:08:11 840 Application_PostReleaseRequestState
2011-05-03 15:08:11 949 Application_UpdateRequestCache
2011-05-03 15:08:11 949 Application_PostUpdateRequestCache
2011-05-03 15:08:11 965 Application_EndRequest
2011-05-03 15:08:11 981 Application_PreSendRequestHeaders
2011-05-03 15:08:11 981 Application_PreSendRequestContent