• MVC之前的那点事儿系列(3):HttpRuntime详解分析(下)


    文章内容

    话说,经过各种各样复杂的我们不知道的内部处理,非托管代码正式开始调用ISPAIRuntime的ProcessRequest方法了(ISPAIRuntime继承了IISPAIRuntime接口,该接口可以和COM进行交互,并且暴露了ProcessRequest接口方法)。至于为什么要调用这个方法,大叔也不太清楚,找不到微软相关的资料哦。但大叔确定该方法就是我们进入HttpRuntime的正式大门,接着看吧。

    public int ProcessRequest(IntPtr ecb, int iWRType) {
    
        IntPtr pHttpCompletion = IntPtr.Zero;
    
        if (iWRType == WORKER_REQUEST_TYPE_IN_PROC_VERSION_2) {
    
            pHttpCompletion = ecb;
    
            ecb = UnsafeNativeMethods.GetEcb(pHttpCompletion);
    
        }
    
        ISAPIWorkerRequest wr = null;
    
        try {
    
            bool useOOP = (iWRType == WORKER_REQUEST_TYPE_OOP);
    
            wr = ISAPIWorkerRequest.CreateWorkerRequest(ecb, useOOP);
    
            wr.Initialize();
    
     
    
            // check if app path matches (need to restart app domain?)
    
            String wrPath = wr.GetAppPathTranslated();
    
            String adPath = HttpRuntime.AppDomainAppPathInternal;
    
     
    
            if (adPath == null ||
    
                StringUtil.EqualsIgnoreCase(wrPath, adPath)) { 
    
                HttpRuntime.ProcessRequestNoDemand(wr);
    
                return 0;
    
            }
    
            else {
    
                // need to restart app domain
    
                HttpRuntime.ShutdownAppDomain(ApplicationShutdownReason.PhysicalApplicationPathChanged,
    
                                                SR.GetString(SR.Hosting_Phys_Path_Changed,
    
                                                                                adPath,
    
                                                                                wrPath));
    
                return 1;
    
            }
    
        }
    
        catch(Exception e) {
    
            try {
    
                WebBaseEvent.RaiseRuntimeError(e, this);
    
            } catch {}
    
     
    
            // Have we called HSE_REQ_DONE_WITH_SESSION?  If so, don't re-throw.
    
            if (wr != null && wr.Ecb == IntPtr.Zero) {
    
                if (pHttpCompletion != IntPtr.Zero) {
    
                    UnsafeNativeMethods.SetDoneWithSessionCalled(pHttpCompletion);
    
                }
    
                // if this is a thread abort exception, cancel the abort
    
                if (e is ThreadAbortException) {
    
                    Thread.ResetAbort();
    
                }
    
                // IMPORTANT: if this thread is being aborted because of an AppDomain.Unload,
    
                // the CLR will still throw an AppDomainUnloadedException. The native caller
    
                // must special case COR_E_APPDOMAINUNLOADED(0x80131014) and not
    
                // call HSE_REQ_DONE_WITH_SESSION more than once.
    
                return 0;
    
            } 
    
            // re-throw if we have not called HSE_REQ_DONE_WITH_SESSION
    
            throw;
        }
    }

     

    第一个注意到的就是该方法的IntPtr类型的参数ecb,ecb是啥?ecb是一个非托管的指针,全称是Execution Control Block,在整个Http Request Processing过程中起着非常重要的作用,我们现在来简单介绍一个ECB。

     

    非托管环境ISAPI对ISAPIRuntime的调用,需要传递一些必须的数据,比如ISAPIRuntime要获取Server Variable的数据,获取通过Post Mehod传回Server的数据;以及最终将Response的内容返回给非托管环境ISAPI,然后呈现给Client用户。一般地ISAPIRuntime不能直接调用ISAPI,所以这里就通过一个对象指针实现对其的调用,这个对象就是ECB,ECB实现了对非托管环境ISAPI的访问。

     

    还有一点特别需要强调的是,ISAPI对ISAPIRutime的调用是异步的,也就是说ISAPI调用ISAPIRutime之后立即返回。这主要是出于Performance和Responsibility考虑的,因为ASP.NET Application天生就是一个多线程的应用,为了具有更好的响应能力,异步操作是最有效的解决方式。但是这里就会有一个问题,我们知道我们对ASP.NET 资源的调用本质上是一个Request/Response的Message Exchange Pattern,异步调用往往意味着ISAPI将Request传递给ISAPIRuntime,将不能得到ISAPIRuntime最终生成的Response,这显然是不能接受的。而ECB解决了这个问题,ISAPI在调用ISAPIRutime的ProcessRequest方法时会将自己对应的ECB的指针传给它,ISAPIRutime不但可以将最终生成的Response返回给ISAPI,还能通过ECB调用ISAPI获得一些所需的数据。

     

    上述代码里第2个加粗的代码是执行ISAPIWorkerRequest的静态方法CreateWorkerRequest从而创建ISAPIWorkerRequest对象实例,参数分别为ecb和代表WorkerRequest类型的int参数iWRType,让我们来看看这个方法的代码:

    internal static ISAPIWorkerRequest CreateWorkerRequest(IntPtr ecb, bool useOOP) {
     
    
        ISAPIWorkerRequest wr = null;
    
        if (useOOP) {
    
            EtwTrace.TraceEnableCheck(EtwTraceConfigType.DOWNLEVEL, IntPtr.Zero);
    
     
    
            if (EtwTrace.IsTraceEnabled(EtwTraceLevel.Verbose, EtwTraceFlags.Infrastructure)) EtwTrace.Trace(EtwTraceType.ETW_TYPE_APPDOMAIN_ENTER, ecb, Thread.GetDomain().FriendlyName, null, false);
    
     
    
            wr = new ISAPIWorkerRequestOutOfProc(ecb);
    
        }
    
        else {
    
            int version = UnsafeNativeMethods.EcbGetVersion(ecb) >> 16;
    
     
    
            if (version >= 7) {
    
                EtwTrace.TraceEnableCheck(EtwTraceConfigType.IIS7_ISAPI, ecb);
    
            }
    
            else {
    
                EtwTrace.TraceEnableCheck(EtwTraceConfigType.DOWNLEVEL, IntPtr.Zero);
    
            }
     
    
            if (EtwTrace.IsTraceEnabled(EtwTraceLevel.Verbose, EtwTraceFlags.Infrastructure)) EtwTrace.Trace(EtwTraceType.ETW_TYPE_APPDOMAIN_ENTER, ecb, Thread.GetDomain().FriendlyName, null, true);
    
     
    
            if (version >= 7) {
    
                wr = new ISAPIWorkerRequestInProcForIIS7(ecb);
    
            }
    
            else if (version == 6) {
    
                wr = new ISAPIWorkerRequestInProcForIIS6(ecb);
    
            }
    
            else {
    
                wr = new ISAPIWorkerRequestInProc(ecb);
    
            }
        }
        return wr;
    }

    通过判断ecb和type类型的具体内容,来决定创建什么类型的WorkerRequest(上述类型的ISPAIWorkerRequest都继承于HttpWorkerRequest),上面的代码可以看出对不同版本的IIS进行了不同的包装,通过其Initialize方法来初始化一些基本的信息(比如:contentType, querystring的长度,filepath等相关信息)。

     

    OK,继续看ProcessRequest方法的加粗代码,激动人心的时刻来了,看到HttpRuntime.ProcessRequestNoDemand(wr)这行代码了么?这就是真正进入了ASP.NET Runtime Pipeline的唯一入口,传递的参数是上面屏蔽了差异化以后的WorkerRequest对象实例。HttpRuntime.ProcessRequestNoDemand最终体现在调用ProcessRequestInternal方法上,让我们来看看该方法都是做了什么事情。

    private void ProcessRequestInternal(HttpWorkerRequest wr) {
    
        // Construct the Context on HttpWorkerRequest, hook everything together
    
        HttpContext context;
     
    
        try {
    
            context = new HttpContext(wr, false /* initResponseWriter */);
        }
        catch {
    
            // If we fail to create the context for any reason, send back a 400 to make sure
    
            // the request is correctly closed (relates to VSUQFE3962)
    
            wr.SendStatus(400, "Bad Request");
    
            wr.SendKnownResponseHeader(HttpWorkerRequest.HeaderContentType, "text/html; charset=utf-8");
    
            byte[] body = Encoding.ASCII.GetBytes("<html><body>Bad Request</body></html>");
    
            wr.SendResponseFromMemory(body, body.Length);
    
            wr.FlushResponse(true);
    
            wr.EndOfRequest();
    
            return;
    
        } 
    
        wr.SetEndOfSendNotification(_asyncEndOfSendCallback, context); 
    
        // Count active requests
    
        Interlocked.Increment(ref _activeRequestCount);
    
        HostingEnvironment.IncrementBusyCount();
     
    
        try {
            // First request initialization
            try {
                EnsureFirstRequestInit(context);
            }
            catch {
                // If we are handling a DEBUG request, ignore the FirstRequestInit exception.
                // This allows the HttpDebugHandler to execute, and lets the debugger attach to
                // the process (VSWhidbey 358135)
                if (!context.Request.IsDebuggingRequest) {
                    throw;
                }
            } 
    
            // Init response writer (after we have config in first request init)
            // no need for impersonation as it is handled in config system
            context.Response.InitResponseWriter(); 
    
            // Get application instance
            IHttpHandler app = HttpApplicationFactory.GetApplicationInstance(context);
    
            if (app == null)
                throw new HttpException(SR.GetString(SR.Unable_create_app_object));
    
             if (EtwTrace.IsTraceEnabled(EtwTraceLevel.Verbose, EtwTraceFlags.Infrastructure)) EtwTrace.Trace(EtwTraceType.ETW_TYPE_START_HANDLER, context.WorkerRequest, app.GetType().FullName, "Start");
    
    
            if (app is IHttpAsyncHandler) {
                // asynchronous handler
    
                IHttpAsyncHandler asyncHandler = (IHttpAsyncHandler)app;
                context.AsyncAppHandler = asyncHandler;
                asyncHandler.BeginProcessRequest(context, _handlerCompletionCallback, context);
            }
            else {
                // synchronous handler
                app.ProcessRequest(context);
                FinishRequest(context.WorkerRequest, context, null);
            }
        }
        catch (Exception e) {
            context.Response.InitResponseWriter();
            FinishRequest(wr, context, e);
        }
    }

    首先映入眼帘的是try/catch里的HttpContext对象的实例化代码,这就是我们期待已久的全局HttpContext对象产生的地方,参数依然是WorkerRequest的实例,HttpContext构造函数代码如下:

    // ctor used in HttpRuntime
    
    internal HttpContext(HttpWorkerRequest wr, bool initResponseWriter) {
    
        _wr = wr;
    
        Init(new HttpRequest(wr, this), new HttpResponse(wr, this)); 
    
        if (initResponseWriter)
            _response.InitResponseWriter(); 
    
        PerfCounters.IncrementCounter(AppPerfCounter.REQUESTS_EXECUTING);
    }

    我们又看到了2个惊喜的代码,HttpRequest和HttpResponse的实例化,通过对WorkerRequest和对HttpContext对象this参数的传递,将获取各自需要的信息,具体内部是怎么判断操作赋值的,我们就不仔细看了,另外再花2秒钟看一下,catch里面的代码,有我们经常看到的Bad Request页面显示的HTML代码组装逻辑,也就是说如果HttpContext对象创建失败的话,就会给我们显示Bad Request页面。

     

    我们继续更重要的代码,这又是另外一个入口,让我们进入我们熟悉的HttpApplication,代码如下:

    // Get application instance
    
    IHttpHandler app = HttpApplicationFactory.GetApplicationInstance(context);

    通过HttpApplicationFactory的GetApplicationInstance静态方法,获取我们熟悉的HttpApplication对象实例,由于HttpApplication对象是继承IHttpAsyncHandler,而IHttpAsyncHandler又继承于IHttpHandler,所以上面app的类型是IHttpHandler是没有错的。继续看后面的if (app is IHttpAsyncHandler)代码,就知道了app肯定走这里的分支,然后执行调用asyncHandler.BeginProcessRequest方法了。

     

    至此,HttpRuntime已经正式发挥其无可替代的作用了,也正式通过此对象正式进入了HttpApplication对象的创建以及大家熟知的HttpApplication以后的生命周期了。

    参考资料:

    http://www.ixwebhosting.mobi/asp-net-process-model-of-the-two-asp-net-http-runtime-pipeline-the-articles/

    http://dotnetslackers.com/articles/iis/ASPNETInternalsIISAndTheProcessModel2.aspx

    http://msdn.microsoft.com/en-us/library/bb470252.aspx

    http://msdn.microsoft.com/en-us/library/Aa479328.aspx 

    http://learn.iis.net/page.aspx/101/introduction-to-iis-architecture/

    http://www.cnblogs.com/zhaoyang/archive/2011/11/16/2251200.html

    http://www.dotnetfunda.com/articles/article821-beginners-guide-how-iis-process-aspnet-request.aspx

    同步与推荐

    本文已同步至目录索引:MVC之前的那点事儿系列

    MVC之前的那点事儿系列文章,包括了原创,翻译,转载等各类型的文章,如果对你有用,请推荐支持一把,给大叔写作的动力。

  • 相关阅读:
    组合模式
    MySQL8.0 下载安装启动(Windows10)
    OI如逆旅,我亦是行人——省选
    闲话—江湖痴情浅,信步余生。平剑红烛,青丝微绾,却话奁中。
    此时彼方
    CSP 2019游记 & 退役记
    西狂 杨过
    SDOI 2019 Round1 游记
    NOIP2018游记
    未来可期,不知所终
  • 原文地址:https://www.cnblogs.com/TomXu/p/3756824.html
Copyright © 2020-2023  润新知