IIS處理瀏覽器請求的流程 | 應用程式的生命週期 |反編譯工具用法 |管道事件
我們看到應用程式池裡面有多個(5個)與各個網站同名的應用程式池,為什麼每個網站都要有自己的應用程式池呢? 因為分多個應用程式池,後臺就有多個程序,處理不同的網站使用單獨的程序,各個網站之間就不會相互影響,即便某個網站流量過大,蹦了,癱瘓了,都不會影響其他的網站的正常執行)
IIS裡預設的網站名可能與它的應用程式池名不一樣,這個我們不管,但是隻要是我們自己在IIS上建立的網站,它的網站名稱與應用程式池的名稱一般都是是同名的。
當我們訪問IIS上的一個網站的時候,比如例如我們在這裡訪問MyTestWebSite這個網站,我們會發現windows工作管理員裡多了一個w3wp.exe的程序,使用者名稱是MyTestWebSite 。這個程序就是這個應用程式池裡面的程序,幫我們處理對於這個網站的所有請求訪問。說白了,這個應用程式池就是支撐這個網站的後臺核心程序。
當我們在瀏覽器一下那個預設的網站Default Web Site 又會發現windows工作管理員又多了一個w3wp.exe的程序,使用者名稱是:DefaultAppPool
假如IIS上部署了10個網站,我訪問了這個10個網站,windows工作管理員中就會有10個w3wp.exe的程序,如果我很長時間不訪問其中的幾個了,那麼因之前訪問這幾個網站而啟動的w3wp.exe就會自動銷燬。如果你再訪問的話,它有會自動啟動(記住每一個網站都會有對應的一個應用程式池,每一個應用程式池在處理你對這個網站的請求的時候都會啟動一個w3wp.exe的程序)
有這麼一句話:一個應用程式池對於的就是一個w3wp.exe程序? 這句話是不對的。答案是:不一定,一個應用程式啟動以後池至少有一個w3wp.exe程序,但是我們也在這個應用程式池裡也可以設定多個程序(即:在應用程式池->右鍵->高階設定->程序模型>最大工作程序數。預設是1,我們也可以將它設定成多個)
現在我們來看看瀏覽器請求一個網站,IIS是如何工作的?
我們開啟windows工作管理員可以看到一個叫InetMgr.exe的程序,和一個inetinfo.exe程序(注:開啟IIS後才會有netMgr.exe這個程序)InetMgr.exe這個程序是IIS的管理介面程序 (IIS的管理介面就是下圖)
而這個inetinfo.exe程序是IIS的後臺服務的核心程序,它停止後IIS也就停止了
現在我們就正式來看看瀏覽器請求一個網站,IIS是如何工作的。
當瀏覽器向伺服器傳送一個請求,首先瀏覽器是根據使用者輸入的URL地址去訪問DNS,然後獲取URL對應的IP地址和埠號,然後來封裝一個http請求報文,然後通過Socket傳送到伺服器 (我們知道Socket與伺服器連線需要IP地址和埠號的)
,在伺服器端 有一個執行在windows核心模式下的http.sys核心元件,這個核心元件是可以監聽任何瀏覽器對伺服器80埠的請求
【
注:windows把cpu執行的時候分成2中模式,一種是核心模式,另外一種是使用者模式。
使用者模式:我們一般執行的應用程式是在使用者模式下執行的,使用者模式下,當你啟動一個應用程式以後,它會給你啟動對應的程序,然後會給這個程序分配對應的地址空間來執行我們的程式,這樣即便我們的應用程式崩潰了也不會影響其他的應用程式的正常執行。
核心模式:windows作業系統核心的元件是在核心模式下執行的,所有的核心模式下執行的程式碼是共享單個虛擬地址空間的(即:共享一個地址空間的)所以核心模式下的程式碼一旦奔潰,作業系統就崩潰了,而使用者模式下的某個應用程式崩潰並不會影響作業系統的正常執行。
windows操作CPU 會自動切換核心模式和使用者模式來執行程式
】
http.sys這個執行在核心模式的核心元件監聽到瀏覽器的請求以後,並不是由它來處理瀏覽器的請求,它首先會讀取系統的登錄檔,然後從登錄檔中查詢當前哪個程式可以處理對80埠的http請求,如果系統中安裝了IIS,它就會監測到登錄檔裡IIS這個程式(程序)可以對這個80埠的http請求進行處理(如果沒有安裝,這個http.sys核心元件發現沒有可以處理對這個80埠的請求的應用程式,,http.sysy就不不理會了,所以就也不會有響應了) 因為inetinfo.exe是IIS後臺服務的核心程序,所以http.sys會把請求交給inetinfo.exe這個程序去處理。
IIS接收到瀏覽器對這個80埠請求以後,會查詢IIS自己的配置資訊,根據請求報文的URL字尾名來判斷瀏覽器請求的是什麼型別的資源(靜態資源或動態資源),
如果瀏覽器請求的是靜態資源,會啟動一個叫w3wp.exe的程序(因為w3wp.exe是瀏覽器請求的網站的應用程式池裡面的程序,而IIS開始處理請求的時候又會啟動一個w3wp.exe程序,所以我猜測IIS是根據瀏覽器請求url的埠號來確定到底是啟動哪個網站對應的應用程式池裡的w3wp.exe程序), w3wp.exe會根據URL路徑去讀取磁碟上的靜態資源,然後把讀取到的結果返回給inetinfo.exe(即:IIS),然後IIS又把結果返回給http.sys這個核心模組的核心元件,由它來將結果返回給瀏覽器。
如果瀏覽器請求的是動態資源,它會在啟動w3wp.exe這個程序之前 去處理程式對映找aspx,ashx等.net動態網頁應該使用哪個元件來處理? 於是找到了aspnet_isapi.dll這個元件,然後啟動w3wp.exe程序,w3wp.exe會載入並執行aspnet_isapi.dll這個元件來處理(注:aspnet_isapi.dll是一個C++寫的檔案,不是.net程式集,所以這個aspnet_isapi.dll會在寄宿在w3wp.exe這個程序中開始執行。
那這個aspnet_isapi.dll是如何執行的呢?
第一步:aspnet_isapi.dll開始啟動.net執行時(執行環境),建立應用程式域(即:啟動.net runtime, 啟動.net執行時後會執行這麼幾個方法:1>獲取一個實現了IISAPIRuntime介面型別的物件,這個型別就是ISAPIRuntime,然後呼叫該物件的ProcessRequest(ecb)方法,這個引數ecb其實就是HTTP的請求報文 ,在ProcessRquest(ecb)這個方法中對瀏覽器的請求報文做了簡單的封裝,把請求報文資料封裝成了一個ISAPIWorkerRequest型別的物件(這個物件名字叫wr),注意:這個封裝是及其簡單的,不像HttpContext那麼詳細。 2>開始呼叫HttpRuntime.ProcessRequestNoDemand(wr)方法,在方法中又呼叫了ProcessRequestNow(wr)方法,而在ProcessRequestNow(wr)方法中又呼叫了_theRuntime.ProcessRequestInternal(wr)方法,然後在這個方法中對wr這個物件又進行了詳細的封裝,封裝成了HttpContext物件(注:HttpContext物件裡面有HttpRequest型別的屬性Request,和HttpResponse型別的屬性Response)。
下面提供一下ProcessRequestInternal(wr)這個方法的原始碼 ;在這個方法中獎wr物件封裝成了HttpContext物件
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);
}
}
}
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);
}
}
}
HttpApplication物件是通過工廠(池)的方式來建立的。(注意:在建立HttpApplication物件之前,如果網頁當中
沒有新增一個名字叫Global.asax的檔案(其實這個Global.asax檔案它是一個類),它會先去HttpApplication物件池中查詢是否有現成的HttpApplication物件可以使用,如果有,則從池中直接返回該物件,如果沒有,它會通過反射直接給你建立一個HttpApplication物件。
如果你在網頁在添加了一個名字叫Global.asax的檔案,它會先去HttpApplication物件池中查詢是否有現成的HttpApplication物件可以使用,如果有,則從池中直接返回該物件,如果沒有,它會將這個Global.asax檔案編譯成一個類,並且這個類是繼承自HttpApplication這個類的,然後要通過反射建立HttpApplication物件,因為Global.asax這個型別也是繼承自HttpApplication類的,所以它在這裡建立了Global.asax這個型別的物件,也就等於建立了HttpApplication類物件;注意這裡並沒有建立HttpApplication物件,只是建立了Global.asax型別物件,只是因為Global.asax型別繼承了HttpApplication類,所以是相當於建立了HttpApplication物件)
HttpApplication物件一次只能處理一個請求,所以說你每次請求都會呼叫HttpApplication物件的生命週期
那這個Global.asax檔案到底有什麼用處呢?
你要是想給HttpApplication註冊它裡面19個事件中的某一個事件的響應程式,都可以在這個Global.asax檔案中直接定義,
在這裡定義後,HttpApplication觸發事件時候,就會觸發你在Global.asax中定義的事件(特別提醒:Global.asax這個型別是繼承自HttpApplication類的)
讓我們來看看HttpApplication物件的建立過程明細。
在這個ProcessRequestInternal(wr)方法中,我們可以看到一段程式碼:IHttpHandler applicationInstance = HttpApplicationFactory.GetApplicationInstance(context);
這段程式碼就是建立HttpApplication物件的過程
現在我們開啟GetApplicationInstance(context)方法
我們再開啟這個EnsureInited()方法
我們再開啟這個CompileApplication()方法
然後我們在回到GetApplicationInstance(HttpContext context)這個方法
我們再開啟GetNormalApplicationInstance(HttpContext context)方法
以上步驟就是建立了HttpApplication物件
3> HttpRuntime.ProcessRequestNoDemand(wr)方法中通過工廠模式建立一個HttpApplication物件,建立完該物件後呼叫該物件的一系列方法對瀏覽器的請求做處理(其中肯定會呼叫一個方法,這個方法就是ProcessRequest(context) 方法),處理使用者的請求就會觸發19個事件,23步,這樣就開始了我們應用程式的生命週期。等都處理完後,就將處理的結果返回給inetinfo.exe ,然後由inetinfo.exe將結果返回給http.sys,然後http.sys將結果通過socket返回給瀏覽器)
)
(注意:在HttpApplication物件建立開始,呼叫RrocessRequest(context)方法,直到呼叫結束(這裡面會觸發19個事件,23步)整個過程叫做asp.net應用程式的生命週期,而不是asp.net頁面的生命週期,頁面的生命週期包含在應用程式生命週期裡面的,在應用程式生命週期的19個事件中的第8個和第9個事件之間建立了ashx或者aspx等頁面類物件,此時還沒有開始頁面的生命週期。然後再第11個事件和第12個事件之間開始了一系列的頁面生命週期)
(當執行到 1>獲取一個實現了IISAPIRuntime介面型別的物件,這個型別就是ISAPIRuntime,然後呼叫該物件的ProcessRequest(ecb)方法,。而呼叫這個ProcessRequest(ecb)方法,就實現了在非託管資源中封裝的請求報文傳遞到.net framework的託管資源中了,因為這個引數ecb就是HTTP請求報文)
(通過配置IIS中的處理程式對映,來決定不同的字尾名使用什麼樣的方式來處理來處理,比如請求的URL字尾名是.aspx 就呼叫aspnet_isapi.dll這個元件來處理)
Global.asax檔案
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.SessionState;
namespace HttpApplication
{
public class Global : System.Web.HttpApplication
{
//注意:Application_Start(object sender, EventArgs e)與Application_End(object sender, EventArgs e)這兩個事件並不是(HttpApplication)應用程式生命週期19個事件中的事件,只是額外增加的這兩個事件,這兩個事件,任何一個,只觸發一次
//在使用者第一次請求動態網頁的時候觸發一次(注意是動態網頁,如果是靜態網頁的話在真正的windows IIS中是不會被觸發的。而對於HttpApplication裡的19個事件,每次請求動態網頁都會被觸發一次)
protected void Application_Start(object sender, EventArgs e)
{
</span>//網站開啟的時候執行一次 (或者說只有應用程式啟動時執行一次)
}
//在新會話啟動時執行的程式碼
protected void Session_Start(object sender, EventArgs e)
{
</span>//會話開始的時候執行一次
}
//BeginRequest 事件發出訊號表示建立任何給定的新請求。 此事件始終被引發,並且始終是請求處理期間發生的第一個事件。
//在 ASP.NET 響應請求時作為 HTTP 執行管線鏈中的第一個事件發生。
protected void Application_BeginRequest(object sender, EventArgs e)
{
<span style="white-space:pre"> </span>//這其實就是第一個管道事件。
}
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
</span>//這是第二個管道事件
}
//在出現未處理的錯誤時執行的程式碼
protected void Application_Error(object sender, EventArgs e)
{
</span>//處理統一錯誤資訊(有的地方我們沒有使用try,cache來捕捉異常,如果出現錯誤後可以在這裡捕捉到)
}
//在會話結束時執行的程式碼
//注意:只有在web.config檔案中sessionstate模式設定為InProc時,才會引發Session_End事件,如果會話模式設定為StateServer或SQLServer則不會引發該事件
protected void Session_End(object sender, EventArgs e)
{
</span>//會話關閉的時候會執行一次(比如關閉瀏覽器)
}
//在應用程式關閉時執行的程式碼,比如:重啟IIS,重啟網站,重啟對應網站的“應用程式池”,結束w3wp.exe程序等情況下執行。
protected void Application_End(object sender, EventArgs e)
{
</span>//網站關閉的時候執行一次 (或者說 只有當應用程式停止時執行一次)
}
}
}
下面我們來看一下專案
Global檔案
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.SessionState;
namespace WebApp
{
//應用程式生命週期裡的19個事件在Global檔案中可以定義,平常用到的一般就是以下幾個
public class Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
System.IO.File.AppendAllText(@"d:/log.txt", "第一步:應用程式啟動了===" + DateTime.Now.ToString());
}
protected void Session_Start(object sender, EventArgs e)
{
System.IO.File.AppendAllText(@"d:/log.txt", "第三步:會話開始了===" + DateTime.Now.ToString());
}
protected void Application_BeginRequest(object sender, EventArgs e)
{
System.IO.File.AppendAllText(@"d:/log.txt", "第二步:請求開始了===" + DateTime.Now.ToString());
}
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
System.IO.File.AppendAllText(@"d:/log.txt", "當安全模組已建立使用者標識時發生。AuthenticateRequest 事件發出訊號表示配置的身份驗證機制已對當前請求進行了身份驗證。===" + DateTime.Now.ToString());
}
protected void Application_Error(object sender, EventArgs e)
{
System.IO.File.AppendAllText(@"d:/log.txt", "發生錯誤===" + DateTime.Now.ToString());
}
protected void Session_End(object sender, EventArgs e)
{
System.IO.File.AppendAllText(@"d:/log.txt", "第五步:會話結束了(關閉IIS後發生)===" + DateTime.Now.ToString());
}
protected void Application_End(object sender, EventArgs e)
{
System.IO.File.AppendAllText(@"d:/log.txt", "最後一步:應用程式結束了(關閉IIS後發生)===" + DateTime.Now.ToString());
}
protected void Application_EndRequest(object sender, EventArgs e)
{
System.IO.File.AppendAllText(@"d:/log.txt", "第四步:請求結束了===" + DateTime.Now.ToString());
}
}
}
WebForm.aspx.cs檔案
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace WebApp
{
//頁面生命週期裡的事件可以在這個WebForm1類中進行定義
public partial class WebForm1 : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
System.IO.File.AppendAllText(@"d:/log.txt", "頁面生命週期Page_Load事件啟動了===" + DateTime.Now.ToString()+"</n/r>");
}
protected void Page_Init(object sender, EventArgs e)
{
System.IO.File.AppendAllText(@"d:/log.txt", "頁面生命週期Page_Init事件啟動了===" + DateTime.Now.ToString());
}
protected void Page_PreInit(object sender, EventArgs e)
{
System.IO.File.AppendAllText(@"d:/log.txt", "頁面生命週期Page_PreInit事件啟動了===" + DateTime.Now.ToString());
}
}
}
我們瀏覽這個WebForm1.aspx頁面。然後再關閉IIS 。在D盤下找到 log.txt檔案開啟
以下提供應用程式生命週期裡面的19個事件的名稱 。如果要在Global檔案中使用的話,只要按照以下規則寫對應的事件就可以了。
即定義一個方法,方法名稱就是以Application開頭,後面加_下劃線,下劃線後面就是你要定義的這19個事件中的某一個事件名稱,比如AuthenticateRequest
protected void Application_事件名稱(object sender, EventArgs e)
{
System.IO.File.AppendAllText(@"d:/log.txt", "。。。。===" + DateTime.Now.ToString());
}
例如:
protected void Application_PostAuthorizeRequest(object sender, EventArgs e)
{
System.IO.File.AppendAllText(@"d:/log.txt", "使用者請求已經獲取了許可權===" + DateTime.Now.ToString());
}
下面是請求管道中的19個事件.
(1)BeginRequest: 開始處理請求
(2)AuthenticateRequest授權驗證請求,獲取使用者授權資訊
(3):PostAuthenticateRequest獲取成功
(4): AunthorizeRequest 授權,一般來檢查使用者是否獲得許可權
(5):PostAuthorizeRequest:獲得授權
(6):ResolveRequestCache:獲取頁面快取結果
(7):PostResolveRequestCache 已獲取快取 當前請求對映到MvcHandler(pr): 建立控制器工廠 ,建立控制器,呼叫action執行,view→response
//action Handler : PR()
(8):PostMapRequestHandler 建立頁面物件 :建立 最終處理當前http請求的 Handler 例項: 第一從HttpContext中獲取當前的PR Handler ,Create (在這裡建立了頁面類物件)
(9):AcquireRequestState 獲取Session (先判斷當前頁面物件是否實現了IRequiresSessionState介面,如果實現了,則從瀏覽器發來的請求報文中獲得SessionId,併到伺服器的Session池中獲得對於的Session物件。最後賦值給HttpContext的Session屬性)
(10)PostAcquireRequestState 獲得Session
(11)PreRequestHandlerExecute:準備執行頁面物件
執行頁面物件的ProcessRequest方法
(12)PostRequestHandlerExecute 執行完頁面物件了
(13)ReleaseRequestState 釋放請求狀態
(14)PostReleaseRequestState 已釋放請求狀態
(15)UpdateRequestCache 更新快取
(16)PostUpdateRequestCache 已更新快取
(17)LogRequest 日誌記錄
(18)PostLogRequest 已完成日誌
(19)EndRequest 完成、