上一篇我們介紹HttpModule & HttpHandler
對於
今天正式進入.Net CLR
處理HTTP請求的世界.
先附上Asp.net
執行請求流程圖.
現在開始講解藍色區塊.
查看原始碼好站 Reference Source
World Wide Web Publishing Service
(簡稱W3SVC
)是一個Window Service.
W3SVC
在SvcHost.exe
這個應用程式上被執行.
W3SVC
主要功能
當檢測到某個HTTP Request
後,先根據一個註冊表判斷請求的副檔名是否是靜態資源(比如.html,.img,.txt,.xml
...)
如果是則直接將文件內容以HTTP Response的形式返回。
如果是動態資源(比如.aspx,asp,php
等等),則通過副檔名從IIS
的Script Map
找到相應ISAPI.dll
前面說到透過W3SVC
服務
System.Web.Hosting.IISAPIRuntime
這個介面是一個基於COM
的Interface
,ASP.NET ISAPI
可以通過COM
的方式調用實現該Interface
的Class
物件的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
方法來執行此次請求.
在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;
}
}
這段程式碼有幾個重點:
WorkerRequest
物件中,方便日後使用.wr.Initialize()
初始化WorkerRequest
物件HttpRuntime.ProcessRequestNoDemand
方法並把剛剛初始化的WorkerRequest
物件當作參數傳入.其中參數ecb
(Execution Control Block
)是一個Unmanaged Pointer
ISAPIRuntime
不能直接調用ASP.NET ISAPI
,所以通過一個ecb
物件指標,ecb
實現ISAPI
和ISAPIRutime
之間溝通.
先來看看剛剛呼叫的HttpRuntime.ProcessRequestNoDemand
方法.
這裡需要注意兩個重點.
判斷目前執行程序池是否已經超過負荷,如果是會把wr
物件指向null
if (rq != null)
wr = rq.GetRequestToExecute(wr);
如果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);
}
在HttpRuntime
很重要的方法之一是ProcessRequestInternal
下面程式碼,我把
ProcessRequestInternal
方法中註解移除且只貼出我覺得重要的程式碼
此方法有做幾個事情:
wr.SendStatus(503, "Server Too Busy");
HttpWorkerRequest
物件封裝我們常常使用HttpContext
HttpApplicationFactory.GetApplicationInstance
返回一個IHttpHandler
物件IHttpHandler
物件支援異步請求優先執行,不然就執行同步請求.上面第3,4點最為重要,因為我們就可以很清楚了解到為什麼最後都會找到一個繼承IHttpHandler
介面的物件來執行ProcessRequest
方法.
因為Asp.net
在HttpRunTime
程式碼中倚賴一個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
方法
WorkerRequest
物件把Http內容封裝到裡面,並呼叫HttpRuntime.ProcessRequestNoDemand
方法.HttpRuntime.ProcessRequestNoDemand
方法
HttpContext
並初始化內容資料HttpApplicationFactory.GetApplicationInstance
取得IHttpHanlder
物件IHttpHanlder
ProcessRequest
方法下篇我們會來好好介紹HttpApplicationFactory
這個工廠到底如何返回IHttpHanlder
物件.