Agenda
前言: 上一篇我們介紹HttpModule & HttpHandler
對於
今天正式進入.Net CLR
處理Http請求的世界.
先附上Asp.net
執行請求流程圖.
現在開始講解藍色區塊.
查看原始碼好站 Reference Source
IIS 與 Asp net (W3SVC服務) World Wide Web Publishing Service
(簡稱W3SVC
)是一個Window Service.
W3SVC
在SvcHost.exe
這個應用程式上被執行.
W3SVC
主要功能
HTTP 請求的監聽
工作執行緒的管理以及配置管理
當檢測到某個HTTP Request
後,先根據一個註冊表判斷請求的副檔名是否是靜態資源(比如.html,.img,.txt,.xml
…) 如果是則直接將文件內容以HTTP Response 的形式返回。
如果是動態資源(比如.aspx,asp,php
等等),則通過副檔名從IIS
的Script Map
找到相應ISAPI.dll
IISAPIRuntime介面 前面說到透過W3SVC
服務
System.Web.Hosting.IISAPIRuntime
這個介面是一個基於COM
的Interface
,ASP.NET ISAPI
可以通過COM
的方式調用實現該Interface
的Class
物件的ProcessRequest
方法,從非託管環境進入了託管的環境。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [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 一開始會先呼叫IsapiRunTime
的ProcessRequest
方法來執行此次請求.
在CreateWorkerRequest
會依據不同IIS版本建立不同ISAPIWorkerRequest
物件,之後在呼叫Initialize
方法把Http
請求內容初次填入這個對象.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 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 { 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 ; } }
這段程式碼有幾個重點:
把Http請求內文封裝到WorkerRequest
物件中,方便日後使用.
wr.Initialize()
初始化WorkerRequest
物件
呼叫HttpRuntime.ProcessRequestNoDemand
方法並把剛剛初始化的WorkerRequest
物件當作參數傳入.
其中參數ecb
(Execution Control Block
)是一個Unmanaged Pointer
ISAPIRuntime
不能直接調用ASP.NET ISAPI
,所以通過一個ecb
物件指標,ecb
實現ISAPI
和ISAPIRutime
之間溝通.
HttpRuntime.ProcessRequestNoDemand 先來看看剛剛呼叫的HttpRuntime.ProcessRequestNoDemand
方法.
這裡需要注意兩個重點.
判斷目前執行程序池是否已經超過負荷,如果是會把wr
物件指向null
1 2 if (rq != null ) wr = rq.GetRequestToExecute(wr);
如果wr!=null
(代表還有資源可以執行請求)就呼叫ProcessRequestNow
方法會繼續呼叫ProcessRequestInternal
方法.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 internal static void ProcessRequestNoDemand (HttpWorkerRequest wr ) { RequestQueue rq = _theRuntime._requestQueue; wr.UpdateInitialCounters(); if (rq != null ) 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
方法中註解移除且只貼出我覺得重要的程式碼
此方法有做幾個事情:
如果Server很忙碌回傳wr.SendStatus(503, "Server Too Busy");
利用HttpWorkerRequest
物件封裝我們常常使用HttpContext
透過HttpApplicationFactory.GetApplicationInstance
返回一個IHttpHandler
物件
如果返回的IHttpHandler
物件支援異步請求優先執行,不然就執行同步請求.
上面第3,4點最為重要,因為我們就可以很清楚了解到為什麼最後都會找到一個繼承IHttpHandler
介面的物件來執行ProcessRequest
方法.
因為Asp.net
在HttpRunTime
程式碼中倚賴一個IHttpHandler
介面抽象才造就具有彈性的系統架構.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 private void ProcessRequestInternal (HttpWorkerRequest wr ) { HttpContext context; try { context = new HttpContext(wr, false ); } 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" ); if (app is IHttpAsyncHandler) { IHttpAsyncHandler asyncHandler = (IHttpAsyncHandler)app; context.AsyncAppHandler = asyncHandler; asyncHandler.BeginProcessRequest(context, _handlerCompletionCallback, context); } else { app.ProcessRequest(context); FinishRequest(context.WorkerRequest, context, null ); } } catch (Exception e) { context.Response.InitResponseWriter(); FinishRequest(wr, context, e); } }
下面此這個方法執行時兩個小重點.
ProcessRequestInternal
方法初始化我們常用HttpContext
物件,把Http
內容封裝到這個類別中.
如果返回IHttpHandler
物件支援異步請求優先執行,不然就執行同步請求.
小結 今天我們學到
ISAPIRunTime.ProcessRequest
方法
建立一個WorkerRequest
物件把Http內容封裝到裡面,並呼叫
HttpRuntime.ProcessRequestNoDemand
方法.
HttpRuntime.ProcessRequestNoDemand
方法
檢查目前是否有資源可以處理請求
封裝HttpContext
並初始化內容資料
利用HttpApplicationFactory.GetApplicationInstance
取得IHttpHanlder
物件
呼叫IHttpHanlder
ProcessRequest
方法
下篇我們會來好好介紹HttpApplicationFactory
這個工廠到底如何返回IHttpHanlder
物件.
__此文作者__:Daniel Shih(石頭) __此文地址__: https://isdaniel.github.io/Ithelp-day3/ __版權聲明__:本博客所有文章除特別聲明外,均採用 CC BY-NC-SA 3.0 TW 許可協議。轉載請註明出處!