跟着url走一圈(ASP.NET请求底层流转个人总结 一)

    刚开始接触ASP.NET的人一定认为它很简单,鼠标点把点吧就能搞个有模有样的网站出来,大部分情况下都不需要关心我们写的那些代码是怎么运行的,也不必关注那些令人厌恶的邪恶接口。至于指针啥的可以直接无视---微软都说是不安全代码默认禁止了。 然而随着学习的深入大家会发现微软用美丽的外表欺骗了我们。想要成为高级.NET开发学习曲线陡然上升,微软想让我们知其然不想让我们知道其所以然。

    于是牛逼的程序员们怒了,他们不想当不明真相的群众。于是各种研究底层的文章如雨后春笋般冒出来。特别是对ASP.NET运行的底层研究更是比比皆是。然后在我看过很多文章很总也没有一个清晰的思路,我可能知道了某一个点比如httpmodule的使用但对从一个请求发起直到最后响应的整个过程并不是特别清晰明了。在参考了很多资料后试着写出我自己的理解,可能有不准确的地方请高手指出。

    先来看一幅无名程序员画的图

         

下面我们来仔细分析这幅图并一步步给出响应步骤及说明,在这之前先对几个概念做一下简单了解。

 

http.sys组件:是一个操作系统的核心组件,负责所有HTTP请求侦听服务
w3wp.exe进程:名称是IIS Application Pool Process, 它是IIS工具的一部分
                        w3wp.exe是在IIS(因特网信息服务器)与应用程序池相关联的一个进程,如果你有多个应用程序池,就会有对应的多个w3wp.exe的进程实例运行

 

IIS 5.x是通过InetInfo.exe监听Request并把Request分发到Work Process。在IIS 6中,这种工作被移植到一个新的组件:http.sys来负责

 

好了,现在我们开始试着模拟一个请求从发起到最后的整个过程(假设我们的IIS版本高于5.X):

  1. 当WEB服务器启动时,IIS向HTTP.SYS注册信息,通知HTTP.SYS那些符合规则的HTTP请求可以被IIS处理--例如IIS创建了 http://www.sohu.com/ 域名网站,所有以此开头的HTTP请求都可以转到IIS来处理,否则直接404

 

  2. 用户发起一个HTTP请求(浏览器输入某URL)经过解析定位到服务器此时HTTP.SYS侦听捕获到该HTTP请求

 

  3. 假设该请求通过HTTP.SYS筛选则会被发给IIS处理,IIS根据此请求文件的后缀名检索ISAPI列表,找到相关的处理程序集(.aspx一般注册给aspnet_isapi.dll 程序集)并移交控制权,ISAPI得到控制会从HTTP.SYS中获取当前的Httq请求信息,并且将这些信息保和请求处理权发送给IIS工作进程(w3wp.exe)开始做真正的处理。

 

  4. w3wp.exe先解出请求者的信息,如果请求者请求的ASP.NET应用程序(站点或虚拟目录)尚未拥有appdomain,w3wp.exe就会建立一个appdomain,并且将被请求的ASP.NET应用所需的Assembly(就是那些DLL,例如System.Web.DLL等)载入到appdomain中

 

  5. 接下来在appdomain中开始执行创建ISAPIRuntim实例并执行实例的ProcessRequest(IntPtr ecb, int iWRType)方法,方法内会创建HttpWorkerRequest类对象,这个方法实现代码如下:

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;
}

 

  6. 开始执行HttpRuntime.ProcessRequest(HttpWorkerRequest wr) --- wr对象是第3步中创建的 HttpWorkerRequest ,可以参看上面代码实现

 

  7. 根据HttpWorkerRequest对象 wr 生成HttpContext(HttpContext包含request、response等属性)对象

 

  8. 将 HttpWorkerRequest对象 wr 传递给HttpApplicationFactory.GetApplicationInstance(HttpWorkerRequest obj)获取一个HttpApplication实例。
      HttpApplicationFactory.GetApplicationInstance创建HttpApplication实例中有三个关键方法:

      * HttpApplicationFactory._theApplicationFactory.EnsureInited()  该方法检查HttpApplicationFactory是否被初始化,如果没有,就通过HttpApplicationFactory.Init()进行初始化。在Init()中,先获取global.asax文件的完整路径,然后调用CompileApplication()对global.asax进行编译。

 

      * HttpApplicationFactory._theApplicationFactory.EnsureAppStartCalled(context)  创建特定的HttpApplication实例,触发ApplicationOnStart事件,执行 ASP.global_asax中的Application_Start(object sender, EventArgs e)方法。这里创建的HttpApplication实例在处理完事件后,就被回收。

 

      * HttpApplicationFactory._theApplicationFactory.GetNormalApplicationInstance(context) 该方法创建HttpApplication实例并进行初始化,调用System.Web.HttpApplication.InitInternal()方法。

      这里要注意:HttpApplicationFactory负责HttpApplication的管理,每个对象在创建并使用后不会立刻销毁而是被放到对象池中供其他请求重复使用。

 

      特别提示:每个事件在Global.assx文件中以Application_前缀开头的空事件作为实现.例如, Application_BeginRequest(), Application_AuthorizeRequest()..这些处理器为了便于使用而提供因为它们是在程序中经常被使用的,这样你就不用显式的创建这些事件处理委托了(下面有介绍). 

 

  9. 实例调用HttpApplication.Init()方法来设置管道的事件 Init方法的主要功能如下:
     * InitModules():根据Web.Config的设置,创建相应的HttpModules。
     * 在创建Module实例的时候会调用Module的Init()方法,Init方法接受HttpApplication对象,所以可以对想要作出响应的HttpApplication暴露出的事件进行注册。 
     * HookupEventHandlersForAppplicationAndModules:根据发生的事件,调用HttpApplication实例中相应的事件处理函数。

 

  10. 触发管道事件HttpApplication.BeginRequest()进入管道

 

  现在开发人员能参与的地方至少有下列几处:

     * ISAPI 我们可以通过IIS管理设置定义我们自己的服务器扩展,也就是说当我们想自定义某一类扩展名文件请求被交给真正的处理程序前设置一些内容就可以通过ISAPI,典型应用是URL重写,我们一般都要设置ISAPI,当符合规则的请求被w3wp.exe进程处理前会重新设置请求参数(最主要是重设请求路径,让用户输入的虚拟URL“变”成服务器上真正存在的文件请求路径),然后再交给w3wp.exe去处理。

     * global.asax 文件,这里能做一些某些事件触发时我们要做的事情,比如写一些日志什么的

     * HttpApplication.Init() 这个事件非常重要,仔细研究第9步我们发现:创建httpmodules类并配置在WEB.CONIFG后事件触发时会创建配置的类实例。 实例创建后还会被调用其方法init(),重要的是方法参数是当前的HttpApplication。如此一来我们就能在httpmodules的init()方法方法中为HttpApplication对象做一些操作了。

 

  HttpApplication有很多事件(对应不同的生命期),而第9步 系统允许我们自定义一个对象来 “侵入” 到HttpApplication.init()方法中,我们可以通过这个侵入对象为HttpApplication各个阶段的事件注册不同的委托方法。这样可以很好的控制任意周期运行点,在HttpApplication生命周期中大概会发生以下事件:

 

  a.BeginRequest和EndRequest分别是HttpModule容器最开始的的和最后的事件;

  b.EndRequest之后还会触发PreSendRequestHeaders事件和PreSendRequestContent事件

  HttpApplication贯穿于整个.NET框架处理周期,更具前面知识我们编写自己的HttpModule并侵入,再给HttpApplication的 BeginRequest 和 EndRequest  注册自定义方法。

 

  要实现HttpModule,必须实现接口IHttpModule。下面是IHttpModule接口分析:

using System;
namespace System.Web
{
    public interface IHttpModule
    {
        //   销毁不再被HttpModule使用的资源
        void Dispose();
        // 初始化一个Module,为捕获HttpRequest做准备
        void Init(HttpApplication context);
    }
}

  下面是自己的HttpModule:

using System;
using System.Web;
namespace ClassLibrary1
{
    public class MyHttpModule : IHttpModule
    {
        public void Dispose() { }
        public void Init(HttpApplication context)
        {
            context.BeginRequest += new EventHandler(Application_BeginRequest);
            context.EndRequest += new EventHandler(Application_EndRequest);
        }
        public void Application_BeginRequest(object sender, EventArgs e)
        {
            HttpApplication application = sender as HttpApplication;
            HttpContext context = application.Context;
            HttpResponse response = context.Response;
            response.Write("这是来自自定义HttpModule中有BeginRequest");
        }
        public void Application_EndRequest(object sender, EventArgs e)
        {
            HttpApplication application = sender as HttpApplication;
            HttpContext context = application.Context;
            HttpResponse response = context.Response;
            response.Write("这是来自自定义HttpModule中有EndRequest");
        }
    }
}


  web.config

    <httpModules>
      <add name="myHttpModule" type="ClassLibrary1.MyHttpModule,ClassLibrary1"/>
    </httpModules>

  default.aspx.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
public partial class _Default : System.Web.UI.Page 
{
    protected void Page_Load(object sender, EventArgs e)
    {
        Response.Write("<br/><br/>来自Default.aspx页面<br/>");
    }
}

  运行结果:

  

  用同样的方法可以给任何事件注册自定义的方法。

 

  我们可以在使用HttpApplication.CompleteRequest()方法提前终止程序处理,此时会触发EndRequest事件,以及PreSendRequestHeaders事件和PreSendRequestContent事件。也可以说是直接跳转到EndRequest事件,而不会调用期间的事件,下面代码是假设我们在BeginRequest中终止处理:

using System;
using System.Web;
namespace ClassLibrary1
{
    public class MyHttpModule : IHttpModule
    {
        public void Dispose() { }
        public void Init(HttpApplication context)
        {
            context.BeginRequest += new EventHandler(Application_BeginRequest);
        }
        public void Application_BeginRequest(object sender, EventArgs e)
        {
            HttpApplication application = sender as HttpApplication;
            application.CompleteRequest();
            application.Context.Response.Write("请求被终止");
        }
    }
}

 以上我们说明了TOP10步骤的大概情况,也验证了系统允许我们自定义IhttpModule为HttpApplication各个事件注册自己的处理方法,或者说系统允许我们干预到任意的请求周期。 实际上除了我们自己定义 IhttpModule ,系统内部实现了很多IhttpModule 处理不同阶段的不同内容。掌握这些可以让我们明白每个阶段那些东西可用那些东西暂时不可用,什么时候初始化什么系统对象,详细内容请继续关注...

posted on 2012-07-17 17:51  老金  阅读(920)  评论(1编辑  收藏  举报