文章出處

先看一張圖, 從這張圖里, 能看到請求是如何從CLR進入HttpRuntime的.

一、AppManagerAppDomainFactory 

看到這張圖是從 AppManagerAppDomainFactory 開始的, 按照湯姆大叔博文中所說, 是在CLR初始化加載的時候, 來加載這個類的. 那么來看一下這個類吧.

使用Reflector反編譯搜索AppManagerAppDomainFactory 類, 可以看到(由于這個類并不多, 那么我先貼一個完整的出來吧):

[SecurityPermission(SecurityAction.LinkDemand, Unrestricted=true)]
public sealed class AppManagerAppDomainFactory : IAppManagerAppDomainFactory
{
    // Fields
    private ApplicationManager _appManager = ApplicationManager.GetApplicationManager();

    // Methods
    public AppManagerAppDomainFactory()
    {
        this._appManager.Open();
    }

    internal static string ConstructSimpleAppName(string virtPath)
    {
        if (virtPath.Length > 1)
        {
            return virtPath.Substring(1).ToLower(CultureInfo.InvariantCulture).Replace('/', '_');
        }
        if (!BuildManagerHost.InClientBuildManager && HostingEnvironment.IsDevelopmentEnvironment)
        {
            return "vs";
        }
        return "root";
    }

    [return: MarshalAs(UnmanagedType.Interface)]
    public object Create(string appId, string appPath)
    {
        object obj2;
        try
        {
            if (appPath[0] == '.')
            {
                FileInfo info = new FileInfo(appPath);
                appPath = info.FullName;
            }
            if (!StringUtil.StringEndsWith(appPath, '\\'))
            {
                appPath = appPath + @"\";
            }
            ISAPIApplicationHost appHost = new ISAPIApplicationHost(appId, appPath, false);
            ISAPIRuntime o = (ISAPIRuntime) this._appManager.CreateObjectInternal(appId, typeof(ISAPIRuntime), appHost, false, null);
            o.StartProcessing();
            obj2 = new ObjectHandle(o);
        }
        catch (Exception)
        {
            throw;
        }
        return obj2;
    }

    public void Stop()
    {
        this._appManager.Close();
    }
}

至于這里詳細的解說, 推薦去 MVC之前的那些事兒 去瞧瞧, 這里并不是我想表述的重點, 就不介紹了. 

只要知道, 按照大叔的說法, 這里, 在CreateObjectInternal方法中, 創建了AppDomain, 創建了HostingEnvironment等一些列操作.

后續所有的比如HttpRuntime, HttpContext等, 都是依托于這個AppDomain. 

 

二、主題

經過各種我不知道的內部處理, 非托管代碼開始正式調用 ISAPIRuntime 的 ProcessRequest(后面簡稱為PR方法)了.

(ISPAIRuntime繼承了IISPAIRuntime接口,該接口可以和COM進行交互,并且暴露了ProcessRequest接口方法)

不要問我為什么會調用PR方法, 因為我也不知道, 但是真的是這個方法. 

public sealed class ISAPIRuntime : MarshalByRefObject, IISAPIRuntime, IISAPIRuntime2, IRegisteredObject
{
    // Fields
    private static int _isThisAppDomainRemovedFromUnmanagedTable;
    private const int WORKER_REQUEST_TYPE_IN_PROC = 0;
    private const int WORKER_REQUEST_TYPE_IN_PROC_VERSION_2 = 2;
    private const int WORKER_REQUEST_TYPE_OOP = 1;

    // Methods
    [SecurityPermission(SecurityAction.Demand, Unrestricted=true)]
    public ISAPIRuntime();
    [SecurityPermission(SecurityAction.LinkDemand, Unrestricted=true)]
    public void DoGCCollect();
    public override object InitializeLifetimeService();
    [SecurityPermission(SecurityAction.LinkDemand, Unrestricted=true)]
    public int ProcessRequest(IntPtr ecb, int iWRType);
    internal static void RemoveThisAppDomainFromUnmanagedTable();
    [SecurityPermission(SecurityAction.LinkDemand, Unrestricted=true)]
    public void StartProcessing();
    [SecurityPermission(SecurityAction.LinkDemand, Unrestricted=true)]
    public void StopProcessing();
    [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
    void IISAPIRuntime2.DoGCCollect();
    [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
    int IISAPIRuntime2.ProcessRequest(IntPtr ecb, int iWRType);
    [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
    void IISAPIRuntime2.StartProcessing();
    [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
    void IISAPIRuntime2.StopProcessing();
    void IRegisteredObject.Stop(bool immediate);
}

這里有個方法, 看名字就覺得好熟悉, 好吧, 點進去看一下:

GC 一個叫垃圾回收的東東, 好熟悉的名字. OK, 這不是重點, 接下來繼續.

[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();
        string appPathTranslated = wr.GetAppPathTranslated();
        string appDomainAppPathInternal = HttpRuntime.AppDomainAppPathInternal;
        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; } catch (Exception exception) { try { WebBaseEvent.RaiseRuntimeError(exception, this); } catch { } if ((wr == null) || !(wr.Ecb == IntPtr.Zero)) { throw; } if (zero != IntPtr.Zero) { UnsafeNativeMethods.SetDoneWithSessionCalled(zero); } if (exception is ThreadAbortException) { Thread.ResetAbort(); } return 0; } }

第一個注意到的就是該方法的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獲得一些所需的數據。

 1. CreateWorkerRequest

 這個方法還是要看一下的, 有收獲哦.

internal static ISAPIWorkerRequest CreateWorkerRequest(IntPtr ecb, bool useOOP)
{
    if (useOOP)
    {
        EtwTrace.TraceEnableCheck(EtwTraceConfigType.DOWNLEVEL, IntPtr.Zero);
        if (EtwTrace.IsTraceEnabled(5, 1))
        {
            EtwTrace.Trace(EtwTraceType.ETW_TYPE_APPDOMAIN_ENTER, ecb, Thread.GetDomain().FriendlyName, null, false);
        }
        return new ISAPIWorkerRequestOutOfProc(ecb);
    }
    int num = UnsafeNativeMethods.EcbGetVersion(ecb) >> 0x10;
    if (num >= 7)
    {
        EtwTrace.TraceEnableCheck(EtwTraceConfigType.IIS7_ISAPI, ecb);
    }
    else
    {
        EtwTrace.TraceEnableCheck(EtwTraceConfigType.DOWNLEVEL, IntPtr.Zero);
    }
    if (EtwTrace.IsTraceEnabled(5, 1))
    {
        EtwTrace.Trace(EtwTraceType.ETW_TYPE_APPDOMAIN_ENTER, ecb, Thread.GetDomain().FriendlyName, null, true);
    }
    if (num >= 7)
    {
        return new ISAPIWorkerRequestInProcForIIS7(ecb);
    }
    if (num == 6)
    {
        return new ISAPIWorkerRequestInProcForIIS6(ecb);
    }
    return new ISAPIWorkerRequestInProc(ecb);
}

通過判斷ecb和type類型的具體內容,來決定創建什么類型的WorkerRequest(上述類型的ISPAIWorkerRequest都繼承于HttpWorkerRequest),上面的代碼可以看出對不同版本的IIS進行了不同的包裝,通過其Initialize方法來初始化一些基本的信息(比如:contentType, querystring的長度,filepath等相關信息)。

 

2. ProcessRequestNoDemand 

 這個方法, 是真正進入ASP.NET Runtime Pipeline的唯一入口, 傳遞的參數是上面屏蔽了差異化以后的WorkerRequest對象實例.來看一下這個方法

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

Ok, 接下來, 繼續看, PRNow方法, 其實內部調用的是 HttpRuntime的 ProcessRequestInternal 方法.

private void ProcessRequestInternal(HttpWorkerRequest wr)
{
    Interlocked.Increment(ref this._activeRequestCount);
    if (this._disposingHttpRuntime)
    {
        try
        {
            wr.SendStatus(0x1f7, "Server Too Busy");
            wr.SendKnownResponseHeader(12, "text/html; charset=utf-8");
            byte[] bytes = Encoding.ASCII.GetBytes("<html><body>Server Too Busy</body></html>");
            wr.SendResponseFromMemory(bytes, bytes.Length);
            wr.FlushResponse(true);
            wr.EndOfRequest();
        }
        finally
        {
            Interlocked.Decrement(ref this._activeRequestCount);
        }
    }
    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;
                }
            }
            context.Response.InitResponseWriter();
            IHttpHandler applicationInstance = HttpApplicationFactory.GetApplicationInstance(context);
            if (applicationInstance == null)
            {
                throw new HttpException(SR.GetString("Unable_create_app_object"));
            }
            if (EtwTrace.IsTraceEnabled(5, 1))
            {
                EtwTrace.Trace(EtwTraceType.ETW_TYPE_START_HANDLER, context.WorkerRequest, 
            applicationInstance.GetType().FullName,
"Start"); } if (applicationInstance is IHttpAsyncHandler) //異步處理 { IHttpAsyncHandler handler2 = (IHttpAsyncHandler) applicationInstance; context.AsyncAppHandler = handler2; handler2.BeginProcessRequest(context, this._handlerCompletionCallback, context); } else //同步處理 { applicationInstance.ProcessRequest(context); this.FinishRequest(context.WorkerRequest, context, null); } } catch (Exception exception) { context.Response.InitResponseWriter(); this.FinishRequest(wr, context, exception); } } }

最讓人開心的, 可能就是看到, 在這個方法中創建了 HttpContext 對象和 HttpApplication 對象.

接下來, 分別看一下這兩個對象的創建.

1). HttpContext 

internal HttpContext(HttpWorkerRequest wr, bool initResponseWriter)
{
    this._timeoutStartTimeUtcTicks = -1L;
    this._timeoutTicks = -1L;
    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);
}

我們又看到了2個驚喜的代碼,HttpRequest和HttpResponse的實例化,通過對WorkerRequest和對HttpContext對象this參數的傳遞,將獲取各自需要的信息

 

2). HttpApplication 

這個對象的創建, 是后面那句標紅的部分. 

IHttpHandler applicationInstance = HttpApplicationFactory.GetApplicationInstance(context);

通過HttpApplicationFactory的GetApplicationInstance靜態方法,獲取我們熟悉的HttpApplication對象實例,由于HttpApplication對象是繼承IHttpAsyncHandler,而IHttpAsyncHandler又繼承于IHttpHandler,所以上面app的類型是IHttpHandler是沒有錯的。繼續看后面的if (app is IHttpAsyncHandler)代碼,就知道了app肯定走這里的分支,然后執行調用asyncHandler.BeginProcessRequest方法了。

 

至此,HttpRuntime已經正式發揮其無可替代的作用了,也正式通過此對象正式進入了HttpApplication對象的創建以及大家熟知的HttpApplication以后的生命周期了。

 

轉載參考:

  MVC之前的那點事兒

目錄已同步


文章列表




Avast logo

Avast 防毒軟體已檢查此封電子郵件的病毒。
www.avast.com


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()