zoukankan      html  css  js  c++  java
  • NET那点不为人知的事

    ASP.NET那点不为人知的事(一)

     

    我们上网时,在浏览器地址输入网址:Http://www.cnblogs.com,按下回车,一张网页就呈现在我们眼前。这究竟发生了什么?对于一名优秀的Programmer来说,我想有必要一下熟悉浏览器--->服务器请求的过程。

    ASP.NET

    ASP.NET是运行在公共语言运行时刻时(CLR)上的应用程序框架。他用来在服务器端构建功能强大的web应用程序。当浏览器请求 ASP.NET 文件时,IIS 会把该请求传递给服务器上的 ASP.NET 引擎,ASP.NET 引擎会逐行地读取该文件,并执行文件中的脚本,最后,ASP.NET 文件会以纯 HTML 的形式返回浏览器。

    客户端浏览器和服务器之间的请求响应是通过Socket进行通信,基于HTTP协议,客户端发送一次HTTP请求,服务器接收到请求,处理之后向浏览器回应响应报文。那么什么是HTTP协议呢?

    HTTP协议:

    当浏览器寻找到Web服务器地址后,浏览器将帮助我们把对服务器的请求转换为一系列参数(消息)发给Web服务器,浏览器和Web服务器的对话中,需要使用双方都能理解语法规范进行通信,这种程序之间进行通信的语法规定,我们称之为协议。浏览器与服务器之间的协议是应用层协议,当前遵循的协议是HTTP/1.1。HTTP/1.1协议时Web开发的基础,这是一个无状态协议,客户端浏览器和服务器通过Socket通信进行请求和响应完成一次会话。每次会话中,通信双方发送的数据称为消息,分为两种:请求消息和响应消息。

    对于消息而言,一般他有三部分组成,并且消息的头和消息体之间用一个空行进行分隔:

    我们通过浏览器插件HttpWatch Professional可以清晰看到浏览器和服务器之间的通信内容:

    了解了什么是HTTP协议之后,我们在回到先前提出的那个问题,浏览器的请求怎样到达服务器?

    HTTP.SYS组件

    我们知道要访问一个网站,必须要其部署在相应服务器软件上(如IIS),浏览器向服务器发送请求之后,当请求通过Socket到达服务器时,首先服务器Windows内核中的HTTP.SYS组件捕获请求,根据URL的请求地址将其转发到应用程序池(Application Pool,ASP.NET应用程序必须运行在一个应用程序池中),再由运行在应用程序池里的工作者进程(Worker Process,用于装载专门处理ASP.NET页面的一个ISAPI扩展程序:aspnet_isapi.dll)响应请求,当请求处理完成时,HTTP.SYS又将结果发送出去(HTTP.SYS会在内部建立一个缓存区,用于缓存近期的处理结果)。当HTTP.SYS请求分析这是一个需要交给IIS服务器处理的HTTP请求时,HTTP.SYS组件就会把这次请求交给IISl处理,服务器软件(IIS)会判断用户请求的是静态页面(Html)还是动态页面(Aspx.Ashx),如果请求的是Html静态页面或者js,css,xml以及图片等,IIS直接返回请求的Html静态页面和js等相应文件。那么如果请求的是动态页面呢?还是向处理静态页面一样吗?显然是不可能的。IIS服务器会分析请求的类型,然后从处理程序映射(即下文IIS服务器扩展)表中去匹配,当在处理程序映射表中能够匹配到请求的类型时,那么IIS服务器就将请求交给处理程序映射表中所对应的程序来处理。当IIS发现,在处理程序映射表中没有能匹配的项的时候,就直接返回请求所对应物理路径下的文件,如Html,JS,CSS,JPG,PNG等。

     

    IIS服务器扩展

    由于IIS服务器在设计时引入了开放的ISAPI接口标准,具备极高的可扩展性。在核心组件不变的情况下可灵活支持不同类型不同版本的ASP.NET应用程序。

    ISAPI(Internet Server Application Programming Interface)

    ISAPI(服务器应用编程接口),它为开发人员提供了强大的可编程能力,只要按照标准接口开发不同类型的Web应用程序的ISAPI扩展程序,就能实现对IIS功能上的扩展,从而使IIS可以处理不同类型的客户端请求。IIS管理器提供了应用程序配置功能,可以对不同的客户端请求配置不同的ISAPI扩展程序ISAPI扩展程序通常以DLL形式存在,可以被IIS加载并调用。有了基于ISAPI的扩展扩展程序,IIS服务器就可以根据客户端请求的资源扩展名,来决定应由哪个ISAPI扩展程序来处理客户端请求,然后就可以将请求转发给合适的ISAPI扩展程序。

    IIS7处理程序映射

    ASP.NET的后台辅助进程aspnet_wp.exe

    实际上客户发起的请求最终要由aspnet_isapi.dll(被工作者进程Worker Process装载)传递给aspnet_wp.exe去处理,.NET平台下称其为ASP.NET Process(简称为WP),该文件位于.Net Framework安装目录下,与aspnet_isapi.dll所在位置相同。当aspnet_isapi接收到IIS转发的ASP.NET请求后,会将请求放入队列,并根据实际情况分配请求处理任务给WP进程。一旦请求被转送给WP进程,WP进程便会通知aspnet_isapi请求正在被处理。这个通知的过程是通过同步I/O完成的,这么实现目的是为了保证处理过程的完整性,因为只有当请求在aspnet_isapi内部被标记为"executing"后,WP才会真正开始处理该请求。此后请求便在WP的上下文环境中执行。当执行结束后处理结果会通过一个异步的开放管道回送给aspnet_isapi,这时请求的状态会被更新为“Done”。接着请求就会从队列中清除。如果WP进程崩溃,所有正在处理中的请求都将维持“executing”状态一段时间,等到aspnet_isapi检测到WP进程死掉后,会自动丢弃所有的请求并释放已经分配的资源。

    WP会分析每一个请求的信息解析出其中的虚拟目录信息,并检查该虚拟目录对应的AppDomain(应用程序域)是否已经存在,如果不存在,则创建一个新的AppDomain(ApplicationManager创建应用程序域),然后使用它。否则直接重用已经建立的AppDomain对象。这里的AppDomain指的是.NET中引入的应用程序域的概念,程序集管理的最小逻辑单位为应用程序域,包括四个重要的机制,隔离、卸载、安全、配置,它可以理解为一个进程或一个边界或一个容器,它是应用程序的执行环境.NET下所有的应用程序都运行在AppDomain中,每一个ASP.NET应用程序IIS中的站点或者虚拟目录都会有一个AppDomain与之对应,它保存了Applcation对象、Cache等全局变量。

    由一张流程图回顾上述浏览器到达服务器的过程

     

     

     ISAPIRuntme.ProcessRequest方法第一个进入ASP.NET

    当aspnet_wp.exe接受到aspnet_isapi.dll的请求后,就将请求转给指定虚拟目录对应的AppDomain中的ISAPIRuntime对象,ISAPIRuntime.ProcessRequest()开始进入ASP.NET,并将浏览器发送请求消息封装成HttpWorkerRequest类(抽象类,开发环境中对应SimpleWorkRequest)。之后再执行HttpRuntime的静态方法:ProcessRequestNoDemand(参数为封装了浏览器请求的信息:HttpWorkerRequest)

     

     补充:默默无闻的工作者对象HttpWorkerRequest

    在Asp.Net中,准备用于处理的请求,必须封装为HttpWorkerRequest类型的对象,这是一个抽象类:

     

    [ComVisibleAttribute(false)]
    public abstract class HttpWorkerRequest

     

    客户的请求首先会被ISAPIRuntme对象ProcessRequest方法处理

    创建了HttpWorkerRequest 类型的wr对象,因为ISAPIWorkerRequest 继承于HttpWorkerRequest

     

    [SecurityPermission(SecurityAction.LinkDemand, Unrestricted=true)]
    public int ProcessRequest(IntPtr ecb, int iWRType)
    {
        IntPtr zero = IntPtr.Zero;
        if (iWRType == 2)
        {
            zero = ecb;
            ecb = UnsafeNativeMethods.GetEcb(zero);
        }
        ISAPIWorkerRequest wr = null;
        try
        {
            bool useOOP = iWRType == 1;
            wr = ISAPIWorkerRequest.CreateWorkerRequest(ecb, useOOP);
            wr.Initialize();
            ......
            if ((appDomainAppPathInternal == null) || StringUtil.EqualsIgnoreCase(appPathTranslated, appDomainAppPathInternal))
            {
                HttpRuntime.ProcessRequestNoDemand(wr);
                return 0;
            }
            HttpRuntime.ShutdownAppDomain(ApplicationShutdownReason.PhysicalApplicationPathChanged, SR.GetString("Hosting_Phys_Path_Changed", new object[] { appDomainAppPathInternal, appPathTranslated }));
            return 1;
        }
      ......
    }

    HttpRuntime调用ProcessRequestNoDemand方法:

     

    internal static void ProcessRequestNoDemand(HttpWorkerRequest wr)
    {
        RequestQueue queue = _theRuntime._requestQueue;
        wr.UpdateInitialCounters();
        if (queue != null)
        {
            wr = queue.GetRequestToExecute(wr);
        }
        if (wr != null)
        {
            CalculateWaitTimeAndUpdatePerfCounter(wr);
            wr.ResetStartTime();
            ProcessRequestNow(wr);
        }
    }
    

    该方法先从请求队列中取出一个请求,然后更新请求的引用计数器的信息,然后ProcessRequestNow方法处理请求。

     

     在这儿终于找到了HttpRuntime这个对象了:

    internal static void ProcessRequestNow(HttpWorkerRequest wr)
    {
        _theRuntime.ProcessRequestInternal(wr);
    }
    
     _theRuntime就是HttpRuntime类型的对象,他在HttpRuntime的静态构造函数初始化。
    static HttpRuntime()
    {
       ......
        _theRuntime = new HttpRuntime();
        _theRuntime.Init();
        AddAppDomainTraceMessage("HttpRuntime::cctor*");
    }
    

    点击进入ProcessRequsetNow(Wr)方法,Wr即封装了HTTP Message的HttpWorkRequest对象

    在HttpRuntime接受到请求后,立刻通过HttpWorkerRequest传递的参数进行分析和分解,创建方便用户网站应用程序处理用的对象。HttpRequest,HttpResponse

    终于发现了HttpContext,根据HttpWorkerRequest初始化HttpContext

    private void ProcessRequestInternal(HttpWorkerRequest wr)
    {
        ......
        else
        {
            HttpContext context;
            try
            {
                context = new HttpContext(wr, false);
            }
            catch
            {
                try
                {
                    wr.SendStatus(400, "Bad Request");
                    wr.SendKnownResponseHeader(12, "text/html; charset=utf-8");
                    byte[] data = Encoding.ASCII.GetBytes("<html><body>Bad Request</body></html>");
                    wr.SendResponseFromMemory(data, data.Length);
                    wr.FlushResponse(true);
                    wr.EndOfRequest();
                    return;
                }
                finally
                {
                    Interlocked.Decrement(ref this._activeRequestCount);
                }
            }
            wr.SetEndOfSendNotification(this._asyncEndOfSendCallback, context);
            HostingEnvironment.IncrementBusyCount();
            try
            {
                try
                {
                    this.EnsureFirstRequestInit(context);
                }
                catch
                {
                    if (!context.Request.IsDebuggingRequest)
                    {
                        throw;
                    }
                }
       ......
        }
    }
    

     

    在进入看看:根据WR,初始化了请求参数的类型HttpRequest对象和处理回应类型HttpReponse对象

    internal HttpContext(HttpWorkerRequest wr, bool initResponseWriter)
    {
        this._timeoutStartTimeUtcTicks = -1;
        this._timeoutTicks = -1;
        this._threadAbortOnTimeout = true;
        this.ThreadContextId = new object();
        this._wr = wr;
        this.Init(new HttpRequest(wr, this), new HttpResponse(wr, this));
        if (initResponseWriter)
        {
            this._response.InitResponseWriter();
        }
        PerfCounters.IncrementCounter(AppPerfCounter.REQUESTS_EXECUTING);
    }
    

    privatevoid ProcessRequestInternal(HttpWorkerRequest wr) ProcessRequestInternal这个方法很重要,前面分析了它创建了上下文对象HttpContext,接下来分析HttpApplication的创建。

    复制代码
    {
                .....
                IHttpHandler applicationInstance = HttpApplicationFactory.GetApplicationInstance(context);
                ......
    try
    {
    this.EnsureFirstRequestInit(context);
    }
    ......
    context.Response.InitResponseWriter();
    ......if (applicationInstance is IHttpAsyncHandler) { IHttpAsyncHandler handler2 = (IHttpAsyncHandler) applicationInstance; context.AsyncAppHandler = handler2; handler2.BeginProcessRequest(context, this._handlerCompletionCallback, context); } ...... } } }
    复制代码
    • EnsureFirstRequestInit()方法完成第一次请求初始化工作,该方法锁定全局变量_beforeRequestFirst,然后调用FirstRequestInit(context)完成配置文件的加载,初始化请求队列,装载Bin目录下所有程序集工作,然后更新_beforeRequestFirst=false;context.FirstRequest=true;
    复制代码
    private void EnsureFirstRequestInit(HttpContext context)
    {
        if (this._beforeFirstRequest)
        {
            lock (this)
            {
                if (this._beforeFirstRequest)
                {
                    this._firstRequestStartTime = DateTime.UtcNow;
                    this.FirstRequestInit(context);
                    this._beforeFirstRequest = false;
                    context.FirstRequest = true;
                }
            }
        }
    }
     
    复制代码
    • 执行InitResponseWrite创建HttpWrite对象,用于写入结果返回信息。
    • 创建HttpApplication实例,HttpApplicationFactory.GetApplicationInstance(注意其实不是这个方法直接创建,而是通过这个方法里面又调用了GetNormalApplicationInstance方法来创建默认的HttpApplication实例
    • 那什么是HttpApplicationFactotry?
    • HttpApplicationFactotry用于负责管理一个HttpApplication的对象池。

    看一下HttpApplication这个类的申明:

    [ToolboxItem(false)]
    public class HttpApplication : IComponent, IDisposable, IHttpAsyncHandler, IHttpHandler, IRequestCompletedNotifier, ISyncContext
    {

    }


    调用HttpApplicationFactory对象的GetNormalApplicationInstance得到一个HttpApplication实例:

    internal static IHttpHandler GetApplicationInstance(HttpContext context)
    {
        ......return _theApplicationFactory.GetNormalApplicationInstance(context);
    }

    GetApplicationInstance方法生成一个默认的HttpApplication对象,HttpApplication实现了IHttpAsyncHandler接口。

    调用HttpApplication对象(实现了IHttpAsyncHandler接口)的BeginProcessRequest方法执行客户请求。

     if (applicationInstance is IHttpAsyncHandler)
    {
    IHttpAsyncHandler handler2 = (IHttpAsyncHandler) applicationInstance;
    context.AsyncAppHandler = handler2;
    handler2.BeginProcessRequest(context, this._handlerCompletionCallback, context);
    }

    OK,回到前一步,再深入一步,进入GetNormalApplicationInstance方法之后,我们看到了HttpApplication对象是如何被创建和初始化:

    复制代码
    private HttpApplication GetNormalApplicationInstance(HttpContext context)
    {
        HttpApplication state = null;
      ......
        if (state == null)
        {
            state = (HttpApplication) HttpRuntime.CreateNonPublicInstance(this._theApplicationType);
            using (new ApplicationImpersonationContext())
            {
                state.InitInternal(context, this._state, this._eventHandlerMethods);
            }
        }
     ......
    }
    复制代码

    internal void InitInternal(HttpContext context, HttpApplicationState state, MethodInfo[] handlers)我们发现HttpApplication类提供了一个名为InitInternal的方法,调用它来完成HttpApplication实例的初始化工作,点击进入InitInternal方法内部:

    复制代码
    internal void InitInternal(HttpContext context, HttpApplicationState state, MethodInfo[] handlers)
    {
    
        this._state = state;
        PerfCounters.IncrementCounter(AppPerfCounter.PIPELINES);
    
    ......
                    this.InitModules();
                Label_006B:
                    if (handlers != null)
                    {
                        this.HookupEventHandlersForApplicationAndModules(handlers);
                    }
                 ......
              .....
                if (HttpRuntime.UseIntegratedPipeline)
                {
                    this._stepManager = new PipelineStepManager(this);
                }
                else
                {
                    this._stepManager = new ApplicationStepManager(this);
                }
                this._stepManager.BuildSteps(this._resumeStepsWaitCallback);
            }
     ......
    }
    复制代码

    首先初始化Modules(InitModules

    复制代码
    private void InitModules()
    {
        HttpModuleCollection modules = RuntimeConfig.GetAppConfig().HttpModules.CreateModules();
        HttpModuleCollection other = this.CreateDynamicModules();
        modules.AppendCollection(other);
        this._moduleCollection = modules;
        this.InitModulesCommon();
    }
    复制代码

    接下来完成事件的绑定(19个管道事件):BuildSteps: 

    复制代码
              if (HttpRuntime.UseIntegratedPipeline)
                {
                    this._stepManager = new PipelineStepManager(this);
                }
                else
                {
                    this._stepManager = new ApplicationStepManager(this);
                }
                this._stepManager.BuildSteps(this._resumeStepsWaitCallback);
            }
     ......
    复制代码

     BuildSteps完成HttpApplication19个管道事件的注册:

    复制代码
    internal override void BuildSteps(WaitCallback stepCallback)
    {
        ArrayList steps = new ArrayList();
        HttpApplication app = base._application;
        bool flag = false;
        UrlMappingsSection urlMappings = RuntimeConfig.GetConfig().UrlMappings;
        flag = urlMappings.IsEnabled && (urlMappings.UrlMappings.Count > 0);
        steps.Add(new HttpApplication.ValidateRequestExecutionStep(app));
        steps.Add(new HttpApplication.ValidatePathExecutionStep(app));
        if (flag)
        {
            steps.Add(new HttpApplication.UrlMappingsExecutionStep(app));
        }
        app.CreateEventExecutionSteps(HttpApplication.EventBeginRequest, steps);
        app.CreateEventExecutionSteps(HttpApplication.EventAuthenticateRequest, steps);
        app.CreateEventExecutionSteps(HttpApplication.EventDefaultAuthentication, steps);
        app.CreateEventExecutionSteps(HttpApplication.EventPostAuthenticateRequest, steps);
        app.CreateEventExecutionSteps(HttpApplication.EventAuthorizeRequest, steps);
        app.CreateEventExecutionSteps(HttpApplication.EventPostAuthorizeRequest, steps);
        app.CreateEventExecutionSteps(HttpApplication.EventResolveRequestCache, steps);
        app.CreateEventExecutionSteps(HttpApplication.EventPostResolveRequestCache, steps);
        steps.Add(new HttpApplication.MapHandlerExecutionStep(app));//---------------------->
        app.CreateEventExecutionSteps(HttpApplication.EventPostMapRequestHandler, steps);
        app.CreateEventExecutionSteps(HttpApplication.EventAcquireRequestState, steps);
        app.CreateEventExecutionSteps(HttpApplication.EventPostAcquireRequestState, steps);
        app.CreateEventExecutionSteps(HttpApplication.EventPreRequestHandlerExecute, steps);
        steps.Add(app.CreateImplicitAsyncPreloadExecutionStep());
        steps.Add(newHttpApplication.CallHandlerExecutionStep(app));//---------------------->用于创建处理用户请求的对象(Handler)
    private void CreateEventExecutionSteps(object eventIndex, ArrayList steps)
    {
        AsyncAppEventHandler handler = this.AsyncEvents[eventIndex];
        if (handler != null)
        {
            handler.CreateExecutionSteps(this, steps);
        }
        EventHandler handler2 = (EventHandler) this.Events[eventIndex];
        if (handler2 != null)
        {
            Delegate[] invocationList = handler2.GetInvocationList();
            for (int i = 0; i < invocationList.Length; i++)
            {
                steps.Add(new SyncEventExecutionStep(this, (EventHandler) invocationList[i]));
            }
        }
    }
    
    
    
    复制代码

    HttpApplication对象初始化时,首先会调用InitModules方法来加载在web.config文件中配置的所有HttpModule模块。

    接着HookupEventHandlersForApplicationAndModules方法被调用,这个方法完成global.asax文件中配置的HttpModuleHttpApplication事件的绑定

    最后ApplicationStopManager对象的BuildSteps方法被调用,完成HttpApplication19个管道事件的注册。这个方法很重要,它将创建各种HttpApplication.IExecutionStep保存到一个数组列表:

    复制代码
    internal override void BuildSteps(WaitCallback stepCallback)
    {
    .....
    this._execSteps = new HttpApplication.IExecutionStep[steps.Count];
    steps.CopyTo(this._execSteps);
    .....
    }
    复制代码

    以便在BeginProcessRequest方法内部调用ResumeSteps方法依次执行这些对象的Execute()方法,完成各种处置。 

     调用BeginProcessRequest方法来实现IHttpAsyncHandler接口中定义的方法处理请求:

    复制代码
    IAsyncResult IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
    {
        this._context = context;
        this._context.ApplicationInstance = this;
        this._stepManager.InitRequest();
        this._context.Root();
        HttpAsyncResult result = new HttpAsyncResult(cb, extraData);
        this.AsyncResult = result;
        if (this._context.TraceIsEnabled)
        {
            HttpRuntime.Profile.StartRequest(this._context);
        }
        this.ResumeSteps(null);//---------->依次执行管道事件
        return result;
    }
    
    复制代码

    BeginProcessRequest执行过程

    • 在取得HttpApplication对象实例之后HttpRuntime对象开始调用其的BeginProcessRequest方法来实现IHttpAsyncHandler接口中定义的方法处理请求:
    • 该方法首先调用ApplicationStepManager对象的InitRequest方法完成一些初始化工作例如将记录当前执行步骤的变量清0、置请求处理完成标志为false等。
    • 然后根据上下文创建HttpAsyncResult对象记录执行结果最后ResumeSteps方法被调用这个方法会依次取出在数组列表中的HttpApplication.IExecutionStep对象传递给HttpApplication的ExecuteStep方法由它调用执行IExecutionStep对象的Execute方法。
    • 当执行到MapHandlerExecutionStep时会执行如下代码获取最终执行请求:context.Handler =this._application.MapHttpHandler()。HttpApplication对象的MapHttpHandler方法将根据配置文件结合请求类型和URL以调用相应的IHttpHandlerFactory来获取HttpHandler对象。例如与.aspx页面对应的Page类就是一种HttpHandler。此后请求处理的执行权被转交至对应的HttpHandler对象上。下面代码演示了过程:
    复制代码
        void HttpApplication.IExecutionStep.Execute()
        {
            HttpContext context = this._application.Context;
            HttpRequest request = context.Request;
            if (EtwTrace.IsTraceEnabled(5, 1))
            {
                EtwTrace.Trace(EtwTraceType.ETW_TYPE_MAPHANDLER_ENTER, context.WorkerRequest);
            }
            context.Handler = this._application.MapHttpHandler(context, request.RequestType, request.FilePathObject, request.PhysicalPathInternal, false);
            if (EtwTrace.IsTraceEnabled(5, 1))
            {
                EtwTrace.Trace(EtwTraceType.ETW_TYPE_MAPHANDLER_LEAVE, context.WorkerRequest);
            }
        }
    复制代码

     这儿调用了一个很重要的方法MapHttpHandler:

     context.Handler = this._application.MapHttpHandler(context, request.RequestType, request.FilePathObject, request.PhysicalPathInternal, false);
    复制代码
    internal IHttpHandler MapHttpHandler(HttpContext context, string requestType, VirtualPath path, string pathTranslated, bool useAppConfig)
    {
        IHttpHandler handler = (context.ServerExecuteDepth == 0) ? context.RemapHandlerInstance : null;
       ...
            IHttpHandlerFactory factory = this.GetFactory(mapping);
            try
            {
                IHttpHandlerFactory2 factory2 = factory as IHttpHandlerFactory2;
                if (factory2 != null)
                {
                    handler = factory2.GetHandler(context, requestType, path, pathTranslated);
                }
                else
                {
                    handler = factory.GetHandler(context, requestType, path.VirtualPathString, pathTranslated);
                }
            }
         ...
            ....
        }
        return handler;
    }
    复制代码

    通过实现了IHttpHandlerFactory(PageHandlerFactory 或者 SimpleHandlerFactory等)创建了HttpHandler

     因为steps.Add(new HttpApplication.MapHandlerExecutionStep(app))注册了Handler,所以会在第八个事件里通过反射创建了页面请求的对象(实现了IHttpHandler接口)。
    复制代码
    void HttpApplication.IExecutionStep.Execute()
    {
        HttpContext context = this._application.Context;
        IHttpHandler handler = context.Handler;
      .....
      ...
            IHttpAsyncHandler handler2 = (IHttpAsyncHandler) handler;
            this._sync = false;
            this._handler = handler2;
     ....
    }
    
    复制代码
    然后再第11个和12个事件之间,会调用了第八个事件创建的页面对象的ProcessRequest方法,具体内容详看我下一篇文章:《ASP.NET那点不为人知的事(二)》

      
    补充:BuildSteps方法里注册的HttpApplication管道的19个事件:

    19个事件的处理过程:

    • 在Asp.Net中,Asp.Net服务器对于每一次请求的处理过程是相同的,都要经过HttpApplication处理管道,管道内部的处理过程是固定的,在服务器处理请求的各个阶段,伴随着处理的进行,一次触发对应的事件,以便程序员在处理的各个阶段完成自定义的处理工作。

    • 首先触发的事件是BeginRequest,这个事件标志着ASP.NET服务器处理工作的开始,也是程序员在ASP.NET中针对请求能够处理的第一个事件。

    • 开始处理请求后,第一个重要的工作就是确定请求用户的身份以及实现安全机制。这个工作通过AuthenticateRequest和PostAuthenticateRequest两个事件提供检查当前请求用户身份的机会。PostAuthenticateRequest则表示用户身份已经检查完成,检查后的用户可以通过HttpContextUser属性获取列。

    复制代码
    public IPrincipal User
    {
        get
        {
            return this._principalContainer.Principal;
        }
        [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries"), SecurityPermission(SecurityAction.Demand, ControlPrincipal=true)]
        set
        {
            this.SetPrincipalNoDemand(value);
        }
    }
     
    复制代码

     Iprincipal又有一个名为Identity,类型了System.Security.Principal.IIdentity属性

    复制代码
    [ComVisible(true), __DynamicallyInvokable]
    public interface IPrincipal
    {
        // Methods
        [__DynamicallyInvokable]
        bool IsInRole(string role);
    
        // Properties
        [__DynamicallyInvokable]
        IIdentity Identity { [__DynamicallyInvokable] get; }
    }
    复制代码
    复制代码
    [ComVisible(true), __DynamicallyInvokable]
    public interface IIdentity
    {
        // Properties
        [__DynamicallyInvokable]
        string AuthenticationType { [__DynamicallyInvokable] get; }
        [__DynamicallyInvokable]
        bool IsAuthenticated { [__DynamicallyInvokable] get; }
        [__DynamicallyInvokable]
        string Name { [__DynamicallyInvokable] get; }
    }
    
    复制代码
    IsAuthenticated表示当前请求用户是否已经被验证,IsAuthenticated =false,那么表示这是一个匿名用户,如果为True,那么通过IIdentity类型为string的Name属性,
    这就表示当前请求的用户名。
    • 当ASP.NET获取用户身份后,根据当前请求的用户身份,开始请求权限的检查工作。当第四个事件AuthorizeRequest触发的时候开始进行用户的权限检查,而第五个事件PostAuthorizeRequest则标志已经完成用户权限检查工作。如果用户没有通过安检,一般情况下将跳过剩余事件,直接触发EndRequest事件结束处理请求过程。

     

    • 当用户获取了请求权限,那么服务器开始准备用最快的方式来使用户得到回应结果。ResolveRequestCache事件标志着到从前缓存的结果进行检查,看看是否可以直接从以前的缓存结果中直接获取处理结果,PostResolveRequestCache表示缓存检查结束。

     

    • 当不能从缓存中获取结果时,必须通过一次处理来计算出当前请求的结果。在ASP.NET中,用户处理请求以得到结果的对象称为处理程序Handler。为了处理这个这个请求,ASP.NET必须按照匹配规则找到一个处理当前请求的处理程序,PostMapRequestHandler事件表示当前ASP.NET已经获取了这个处理程序,HttpContextHandler属性就表示这个处理程序对象。

     

    • 得到了处理程序之后,还不能马上开始进行处理,这是由于处理请求还需要与这个请求有关的数据,比如说这个用户上一次向服务器发送请求的时候,在服务器上报错了一些这个用户特有的数据。由于HTTP协议的无状态性,状态管理问题是个核心问题,所以ASP时代就引入Session,提供基于会话状态的管理。为了获取这个用户在以前保存的数据,通过AcquireRequestState事件取得请求状态,PostAcquireRequest事件则表示已经完成了用户数据的获取工作,可以再处理中使用了。

    • PreRequestHandlerExcute事件用来通知程序员,处理程序就要开始进行处理工作了,如果用户的状态已经获取之后,还有需要的处理程序之进行的工作,那么就在这个事件中处理吧。在PreRequestHandlerExcute事件之后,ASP.NET服务器将通过执行处理程序完成请求处理工作。这个处理程序有可能是一个WebForm,也可能是Web服务。这个工作是在第11个事件和第12个事件之间完成的。

     

    • 处理程序之后,服务器开始进行扫尾工作,PostRequestHandlerExcute事件通知程序员,ASP.NET服务器处理程序已经完成。

    • 在处理完成之后,由于处理程中,用户可能修改了用于特定的专属数据,那么修改之后的用户状态数据需要进行序列化或者进行保存处理。ReleaseRequestState事件通知程序员需要释放这些状态数据,PostReleaseRequestState则表示已经释放完成。

     

    • 在处理完成之后,如果需要将这次处理结果缓存起来,以便于后继的请求可以直接使用这个结果,UpdateRequestCache事件提供了处理的机会,PostUpdateRequestCache则表示缓存已经更新完毕。

    • 在ASP.NET4.0中,新增加了两个事件完成处理的日志工作:LogRequest表示将这次请求加入日志,PostLogRequest表示完成了日志工作。

    • 在前面的事件中,请求并不一定要经过所有的事件,比如说,用户没用经过授权的检查,那么将跳过后面的事件,但是,EndRequest事件是所有请求都要经过的最后一个HttpApplication处理管道的事件,也是程序员处理的ASP.NET处理请求中的最后一个机会。这个事件之后,处理的结果将被回应到浏览器,完成ASP.NET服务器的处理工作。

     小结

     未完,待续。

    Response.Redirect引起的“无法在发送HTTP标头之后进行重定向”

     

    博客后台切换至i.cnblogs.com之后,在日志中发现大量的“无法在发送HTTP标头之后进行重定向”(Cannot redirect after HTTP headers have been sent)的错误信息。

    检查代码发现问题是由下面的代码触发的:

    复制代码
    IHttpHandler IHttpHandlerFactory.GetHandler(HttpContext context, string requestType, string url, string pathTranslated)
    {
        context.Response.Redirect("http://i.cnblogs.com/" + 
            context.Request.RawUrl.Substring(context.Request.RawUrl.LastIndexOf("/") + 1));
    
        //后续也有context.Response.Redirect代码
        //...
        return PageParser.GetCompiledPageInstance(newurl, path, context);
    }
    复制代码

    “无法在发送HTTP标头之后进行重定向”问题来源于Response.Redirect之后,又进行了Response.Redirect。

    解决方法很简单:在Response.Redirect之后立即返回。

    复制代码
    IHttpHandler IHttpHandlerFactory.GetHandler(HttpContext context, string requestType, string url, string pathTranslated)
    {
        context.Response.Redirect("http://i.cnblogs.com/" + 
            context.Request.RawUrl.Substring(context.Request.RawUrl.LastIndexOf("/") + 1));
        return null;
        //...    
    }
    复制代码

    为什么之前没有加return null呢?因为以前一直以为Response.Redirect会结束当前请求,不会执行Response.Redirect之后的代码。

    现在残酷的现实说明了不完全是这样的,那问题背后的真相是什么?让我们来一探究竟。

    由于微软公开了.NET Framework的源代码,现在无需再看Reflactor出来的代码,可以直接下载源代码用Visual Studio进行查看。

    .NET Framework源代码下载链接:http://referencesource.microsoft.com/download.html (相关新闻:微软开放了.NET 4.5.1的源代码

    用Visual Studio打开DotNetReferenceSourceSource dp.sln,搜索HttpResponse.cs,找到Response.Redirect的实现代码:

    public void Redirect(String url)
    {
        Redirect(url, true, false);
    }

    实际调用的是internal void Redirect(String url, bool endResponse, bool permanent) ,传给endResponse的值的确是true啊,为什么后面的代码还会执行?

    进一步查看internal void Redirect()的实现代码(省略了无关代码):

    复制代码
    internal void Redirect(String url, bool endResponse, bool permanent) 
    {
        //...
    
        Page page = _context.Handler as Page;
        if ((page != null) && page.IsCallback) {
            //抛异常
        }
    
        // ... url处理
    
        Clear(); //Clears all headers and content output from the buffer stream.
    
        //...
        this.StatusCode = permanent ? 301 : 302; //进行重定向操作
        //...
        _isRequestBeingRedirected = true; 
    
        var redirectingHandler = Redirecting;
        if (redirectingHandler != null) {
            redirectingHandler(this, EventArgs.Empty);
        }
    
        if (endResponse)
            End(); //结束当前请求
    }
    复制代码

    从上面的代码可以看出,我们要找的真相在End()方法中,继续看HttpResponse.End()的实现代码:

    复制代码
    public void End() {
        if (_context.IsInCancellablePeriod) {
            AbortCurrentThread();
        }
        else {
            // when cannot abort execution, flush and supress further output
            _endRequiresObservation = true;
    
            if (!_flushing) { // ignore Reponse.End while flushing (in OnPreSendHeaders)
                Flush();
                _ended = true;
    
                if (_context.ApplicationInstance != null) {
                    _context.ApplicationInstance.CompleteRequest();
                }
            }
        }
    }
    复制代码

    注意啦!真相浮现了!

    以前一直以为的Response.Redirect会结束当前请求,就是上面的AbortCurrentThread()情况,如果将Response.Redirect放在try...catch中就会捕捉到ThreadAbortException异常。

    通常情况下,我们在WebForms的Page或MVC的Controller中进行Redirect,_context.IsInCancellablePeriod的值为true,执行的是AbortCurrentThread(),所以不会遇到这个问题。

    而我们现在的场景恰恰是因为_context.IsInCancellablePeriod的值为false,为什么会是false呢?

    进一步看一下_context.IsInCancellablePeriod的实现:

    private int _timeoutState; // 0=non-cancelable, 1=cancelable, -1=canceled
    
    internal bool IsInCancellablePeriod {
        get { return (Volatile.Read(ref _timeoutState) == 1); }
    }

    根据上面的代码,触发这个问题的条件是_timeoutState的值要么是0,要么是-1,根据我们的实际情况,应该是0=non-cancelable。

    再来看看我们的实际应用场景,我们是在实现IHttpHandlerFactory接口的GetHandler方法中进行Response.Redirect操作的,也就是说在这个阶段_timeoutState的值还没被设置(默认值就是0)。为了验证这个想法,继续看一下_timeoutState在哪个阶段设值的。

    Shift+F12找到所有引用_timeoutState的地方,在HttpConext中发现了设置_timeoutState的方法BeginCancellablePeriod,实现代码如下:

    复制代码
    internal void BeginCancellablePeriod() {
        // It could be caused by an exception in OnThreadStart
        if (Volatile.Read(ref _timeoutStartTimeUtcTicks) == -1) {
            SetStartTime();
        }
    
        Volatile.Write(ref _timeoutState, 1);
    }
    复制代码

    然后再Shift+F12找到了在HttpApplication.ExecuteStep()中调用了BeginCancellablePeriod():

    复制代码
    internal Exception ExecuteStep(IExecutionStep step, ref bool completedSynchronously) 
    {
        //..
        if (step.IsCancellable) {
            _context.BeginCancellablePeriod(); // request can be cancelled from this point
        }
        //..
    }
    复制代码

    从上面的代码可以看出,当step.IsCancellable为true时,会调用BeginCancellablePeriod(),就不会出现这个问题。

    而我们用到的IHttpHandlerFactory.GetHandler()所在的IExecutionStep的实现可能将IsCancellable设置为了false。

    那IHttpHandlerFactory.GetHandler()是在哪个IExecutionStep的实现中调用的呢?

    在园子里木宛城主的一篇写得非常棒的博文(ASP.NET那点不为人知的事)中找到了答案——MapHandlerExecutionStep:

    当执行到MapHandlerExecutionStep时会执行如下代码获取最终执行请求:context.Handler = this._application.MapHttpHandler()。HttpApplication对象的MapHttpHandler方法将根据配置文件结合请求类型和URL以调用相应的IHttpHandlerFactory来获取HttpHandler对象。

    我们再回到.NET Framework的源代码中看一看MapHandlerExecutionStep的实现:

    复制代码
    // execution step -- map HTTP handler (used to be a separate module)
    internal class MapHandlerExecutionStep : IExecutionStep {
        private HttpApplication _application;
    
        internal MapHandlerExecutionStep(HttpApplication app) {
            _application = app;
        }
    
        void IExecutionStep.Execute() {
            //...
        }
    
        bool IExecutionStep.CompletedSynchronously {
            get { return true;}
        }
    
        bool IExecutionStep.IsCancellable {
            get { return false; }
        }
    }
    复制代码

    看到有没有?IExecutionStep.IsCancellable返回的值是false。

    到此,水落石出,真相大白!

    请看大屏幕——

    由于MapHandlerExecutionStep(调用IHttpHandlerFactory.GetHandler()的地方)返回的IsCancellable的值是false,于是在HttpApplication.ExecuteStep()执行时没有调用_context.BeginCancellablePeriod()——也就是没有把_timeoutState设置为1,_context.IsInCancellablePeriod的值就是false。从而造成在Response.Redirect中进行Response.End()时没有执行AbortCurrentThread()(通常情况下都会执行这个)。于是代码继续执行,后面又来一次Response.Redirect,最终引发了——“无法在发送HTTP标头之后进行重定向”(Cannot redirect after HTTP headers have been sent)。

     
     
     
    标签: ASP.NET
  • 相关阅读:
    Windows Phone 一步一步从入门到精通
    备忘录模式(Memento)
    开放封闭原则(OCP)
    建造者模式(Bulider)
    原型模式(Prototype)
    Windows Workflow Foundation(WF) 一步一步从入门到精通
    模板方法模式
    代理模式(Proxy)
    装饰模式(Decorator)
    迪米特法则(LoD)最少知识原则
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3597402.html
Copyright © 2011-2022 走看看