iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 3
3
Software Development

從Asp.net框架角度進入Asp.net MVC原始碼系列 第 3

[Day03] 啟動吧!Asp.Net IsapiRunTime & HttpRuntime

前言:

上一篇我們介紹HttpModule & HttpHandler對於

今天正式進入.Net CLR處理HTTP請求的世界.

先附上Asp.net執行請求流程圖.

瀏覽器請求IIS流程

現在開始講解藍色區塊.

 查看原始碼好站 Reference Source

IIS 與 Asp net (W3SVC服務)

World Wide Web Publishing Service(簡稱W3SVC)是一個Window Service.

W3SVCSvcHost.exe這個應用程式上被執行.

W3SVC主要功能

  1. HTTP請求的監聽
  2. 工作執行緒的管理以及配置管理

當檢測到某個HTTP Request後,先根據一個註冊表判斷請求的副檔名是否是靜態資源(比如.html,.img,.txt,.xml...)
如果是則直接將文件內容以HTTP Response的形式返回。

如果是動態資源(比如.aspx,asp,php等等),則通過副檔名從IISScript Map找到相應ISAPI.dll

IISAPIRuntime介面

前面說到透過W3SVC服務

System.Web.Hosting.IISAPIRuntime這個介面是一個基於COMInterface,
ASP.NET ISAPI可以通過COM的方式調用實現該InterfaceClass物件的ProcessRequest方法,從非託管環境進入了託管的環境。

[ComImport, Guid("08a2c56f-7c16-41c1-a8be-432917a1a2d1"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
public interface IISAPIRuntime {

	void StartProcessing();

	void StopProcessing();

	[return: MarshalAs(UnmanagedType.I4)]
	int ProcessRequest(
					  [In]
					  IntPtr ecb, 
					  [In, MarshalAs(UnmanagedType.I4)]
					  int useProcessModel);

	void DoGCCollect();
}

 所以IISAPIRuntime.ProcessRequest是我們探討原始碼起始點.

IsapiRunTime.ProcessRequest

一開始會先呼叫IsapiRunTimeProcessRequest方法來執行此次請求.

CreateWorkerRequest會依據不同IIS版本建立不同ISAPIWorkerRequest物件,之後在呼叫Initialize方法把HTTP請求內容初次填入這個對象.

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();             
		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 {}
		
		if (wr != null && wr.Ecb == IntPtr.Zero) {
			if (pHttpCompletion != IntPtr.Zero) {
				UnsafeNativeMethods.SetDoneWithSessionCalled(pHttpCompletion);
			}
			if (e is ThreadAbortException) {
				Thread.ResetAbort();
			}                    

			return 0;
		}
		
		throw;
	}
}

這段程式碼有幾個重點:

  1. 把Http請求內文封裝到WorkerRequest物件中,方便日後使用.
  2. wr.Initialize()初始化WorkerRequest物件
  3. 呼叫HttpRuntime.ProcessRequestNoDemand方法並把剛剛初始化的WorkerRequest物件當作參數傳入.

其中參數ecb(Execution Control Block)是一個Unmanaged Pointer

ISAPIRuntime不能直接調用ASP.NET ISAPI,所以通過一個ecb物件指標,ecb實現ISAPIISAPIRutime之間溝通.

HttpRuntime.ProcessRequestNoDemand

先來看看剛剛呼叫的HttpRuntime.ProcessRequestNoDemand方法.

這裡需要注意兩個重點.

  1. 判斷目前執行程序池是否已經超過負荷,如果是會把wr物件指向null

    if (rq != null)  
    wr = rq.GetRequestToExecute(wr);
    
  2. 如果wr!=null(代表還有資源可以執行請求)就呼叫ProcessRequestNow方法會繼續呼叫ProcessRequestInternal方法.

internal static void ProcessRequestNoDemand(HttpWorkerRequest wr) {
    RequestQueue rq = _theRuntime._requestQueue;

    wr.UpdateInitialCounters();

    if (rq != null)  // could be null before first request
        wr = rq.GetRequestToExecute(wr);

    if (wr != null) {
        CalculateWaitTimeAndUpdatePerfCounter(wr);
        wr.ResetStartTime();
        ProcessRequestNow(wr);
    }
}

internal static void ProcessRequestNow(HttpWorkerRequest wr) {
    _theRuntime.ProcessRequestInternal(wr);
}

ProcessRequestInternal

HttpRuntime很重要的方法之一是ProcessRequestInternal

下面程式碼,我把ProcessRequestInternal方法中註解移除且只貼出我覺得重要的程式碼

此方法有做幾個事情:

  1. 如果Server很忙碌回傳wr.SendStatus(503, "Server Too Busy");
  2. 利用HttpWorkerRequest物件封裝我們常常使用HttpContext
  3. 透過HttpApplicationFactory.GetApplicationInstance返回一個IHttpHandler物件
  4. 如果返回的IHttpHandler物件支援異步請求優先執行,不然就執行同步請求.

上面第3,4點最為重要,因為我們就可以很清楚了解到為什麼最後都會找到一個繼承IHttpHandler介面的物件來執行ProcessRequest方法.

因為Asp.netHttpRunTime程式碼中倚賴一個IHttpHandler介面抽象才造就具有彈性的系統架構.

private void ProcessRequestInternal(HttpWorkerRequest wr) {

    HttpContext context;

    try {
        //封裝我們常常使用`HttpContext`
        context = new HttpContext(wr, false /* initResponseWriter */);
    }
    catch {
        try {
            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;
        } finally {
            Interlocked.Decrement(ref _activeRequestCount);
        }
    }

    try {
        try {
            EnsureFirstRequestInit(context);
        }
        catch {
            if (!context.Request.IsDebuggingRequest) {
                throw;
            }
        }

        context.Response.InitResponseWriter();

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

        //如果返回的IHttpHandler物件支援異步請求優先執行,不然就執行同步請求.
        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);
    }
}

下面此這個方法執行時兩個小重點.

ProcessRequestInternal方法初始化我們常用HttpContext物件,把Http內容封裝到這個類別中.

如果返回IHttpHandler物件支援異步請求優先執行,不然就執行同步請求.

小結

今天我們學到

  • ISAPIRunTime.ProcessRequest方法
    1. 建立一個WorkerRequest物件把Http內容封裝到裡面,並呼叫
    2. HttpRuntime.ProcessRequestNoDemand方法.
  • HttpRuntime.ProcessRequestNoDemand方法
    1. 檢查目前是否有資源可以處理請求
    2. 封裝HttpContext並初始化內容資料
    3. 利用HttpApplicationFactory.GetApplicationInstance取得IHttpHanlder物件
    4. 呼叫IHttpHanlder ProcessRequest方法

下篇我們會來好好介紹HttpApplicationFactory這個工廠到底如何返回IHttpHanlder物件.


上一篇
[Day02] Asp.Net支柱 IHttpMoudle & IHttphandler
下一篇
[Day04] 掌控HttpApplication物件建立 - HttpApplicationFactory
系列文
從Asp.net框架角度進入Asp.net MVC原始碼30

尚未有邦友留言

立即登入留言