實在很抱歉隔了這麼久才繼續補上這篇,因為後期調試時發現上傳很不穩定,所以調試了幾天,目前測試基本沒什麼問題。
回顧上一篇,我們可以瞭解到以下內容:
HTTP請求流到達伺服器後,由IIS進程或http.sys接收並調用ASP.NET ISAPI 擴充,接著產生HttpWorkerRequest並將HttpWorkerRequest傳遞給ProcessRequestInternal方法,這之後才建立了HttpContext請求上下文和HttpApplication 類的執行個體,然後又經過一系列處理並最終將訊息返回(也就是送回用戶端瀏覽器)。
本篇概述:
在本篇中,主要是從HTTP請求流中將資料部分進行截取,同時將資料相關資訊進行儲存。通過本篇你可以實現多個大檔案的上傳功能(實驗平台 XP SP2,IIS 5.1, VS 2005)。
註:因為當初只是為了實現這個功能,所以並沒有太多的考慮過效能和斷點續傳的功能。不過針對這些內容,大概已經有了個構思,可能會在寫完這個專題後找時間在目前所實現的功能之上再考慮進這些元素,然後對這個專題進行相應的補充。
本文部分:
本篇中我們要做的就是在儘可能早的事件中對這個請求進行修改,截取其中上傳的資料部分,然後重新將請求進行封裝。這樣就不會因為上傳檔案太大等原因引發異常。Asp.NET提供了HttpModule(HTTP模組),在模組的 Init 方法內,可以訂閱各種應用程式事件(如 BeginRequest 或 EndRequest),而HttpApplication.BeginRequest 事件始終是請求處理期間發生的第一個事件。為了讓大家更多的理解模組,我引用了一段MSDN上關於HTTP模組的說明:
HTTP 模組是一個在每次針對應用程式發出請求時調用的程式集。HTTP 模組作為 ASP.NET 請求管線的一部分調用,它們能夠在整個請求過程中訪問壽命周期事件。因此,HTTP 模組使您有機會檢查傳入的請求並根據該請求採取操作。它們還使您有機會檢查出站響應並修改它。
在應用程式的 Web.config 檔案中註冊自訂的 HTTP 模組。當 ASP.NET 建立表示您的應用程式的 HttpApplication 類的執行個體時,將建立登入的任何模組的執行個體。在建立模組時,將調用它的 Init 方法,並且模組會自行初始化。有關更多資訊,請參見 ASP.NET 應用程式生命週期概述。
在模組的 Init 方法內,可以訂閱各種應用程式事件(如 BeginRequest 或 EndRequest),這可以通過將事件綁定到模組中已建立的方法來完成。當這些事件被引發時,會調用模組中適當的方法,並且模組可以執行所需的任何邏輯,如身分識別驗證檢查或記錄請求資訊。在事件處理過程中,模組能夠訪問當前請求的 Context 屬性。這使您可以將請求重新導向到其他頁、修改請求或者執行任何其他請求操作。例如,如果您的模組中包括身分識別驗證檢查,則模組可能會檢查憑據,如果憑據不正確的話,會重新導向到登入頁或錯誤頁。否則,當模組的事件處理常式完成運行時,ASP.NET 會調用管線中的下一個進程,這可能是另一個模組,也可能是用於該請求的 HTTP 處理常式(如 .aspx 檔案)。 好了,相信大家對模組有了一個基本的認識。我們的目的就是要在模組的Init方法內,訂閱BeginRequest事件,來攔截HTTP請求,並對請求進行修改。那麼接下來我們就要正式開始了,我會對步驟進行編號,以便大家理解。在開始之前大家可以先參考下MSDN的相關文章:建立自訂 HTTP 模組。
1、在Web.config檔案中設定maxRequestLength,並在<system.web>節中註冊一個模組,格式如下:
httpModules
<httpModules>
<add name="modulename" type="classname,assemblyname" />
</httpModules>
根據上面的格式,我寫的如下:
MyHttpModule
<httpModules>
<add name="MyHttpModule1" type="MyHttpModule" />
</httpModules>
由於後期測試8M左右基本夠用,因些設定maxRequestLength="8192"。
2、建立一個與type的值相同的類MyHttpModule,注意這個類必須實現 IHttpModule 介面,這樣才能實現HttpModule的準系統。
當我們實現IHttpModule介面後,在"I"的下方會出現短橫,我們只需要將滑鼠移上去便會出現可用的選項,你只需要傻瓜式的點擊,系統便自動替我們往類中添加了模組的初始化事件和處置事件,當然有的朋友喜歡完全自己寫也可以。代碼如下:
IHttpModule 成員
public void Dispose()
{
throw new Exception("The method or operation is not implemented.");
}
public void Init(HttpApplication context)
{
throw new Exception("The method or operation is not implemented.");
}
3、去掉原Init中的事件,添加我們自己的事件
總算到了關鍵的步驟了,我們就是要在這裡實現我們截取HTTP請求,擷取檔案資訊,擷取上傳進度等事件。
3.1 判斷是否是上傳檔案
因為需要上傳檔案的FORM表單都必須設定FORM表彰中的enctype屬性為multipart/form-data(如果我們使用FileUpload組件,則會在編譯時間自動替我們加上這個屬性)。所以我們只需要判斷HTTP要求標頭的ContentType值是否為multipart/form-data即可,如果不是,我們就沒必要對該請求進行處理了。
enctype="multipart/form-data實際上是一種編碼規範(預設的話是application/x-www-form-urlencoded,該方式不適合大塊位元據傳輸),該編碼方式的基本思想就是用分隔字元來分隔資料項目,分隔字元如“-----------------------------7d87d1cc0a88”。
3.2 擷取HTTP請求總長度和HttpWorkerRequest對象
通過擷取HttpApplication.Request.ContentLength屬性便可擷取HTTP請求內容的總長度,以便我們後來的使用。
HttpWorkerRequest對象在第二篇中有所提到,如果朋友們有看過,那應該可以認識到它其實內含了HTTP請求的全部內容,除此之外,還包括一些對請求進行處理的方法。
接下來我們就要使用這個對象對請求進行處理。
以下代碼用來擷取該對象(摘自網上):
GetWorkerRequest(HttpContext context)
HttpWorkerRequest GetWorkerRequest(HttpContext context)
{
IServiceProvider provider = (IServiceProvider)HttpContext.Current;
return (HttpWorkerRequest)provider.GetService(typeof(HttpWorkerRequest));
}
3.3 判斷是否已經積極式載入了HTTP請求的部分資料
因為包含上傳檔案的HTTP請求含有大量的資料,所以用戶端並非一次性將整個請求全部發送到網上,而是分多次發送,大家可以通過協議分析軟體進行觀察。因此伺服器的偵聽端可能會預先偵聽到已經發送到伺服器端的部分資料。我們可以使用HttpWorkerRequest.GetPreloadedEntityBody()方法擷取HTTP請求上下文已經被讀取的部分,[這裡有一個非常頭痛的問題,我幾乎就快因為這個問題放棄繼續實現這個功能了。問題就是在.net 調試情況下,GetPreloadedEntityBody()時常取不到值,後在網上發現很多類似問題,有朋友說明是因為在IIS中使用ISAPIWorkerRequest , 而.net內建的web伺服器則使用SimpleWorkerRequest。因此換成IIS調試,幾乎100%可以收到資料。但是這樣斷點什麼的便無法使用,鬱悶。現在根據第二篇提到的資訊分析下,可能原因是ISAPIRuntime的ProcessRequest方法未能每次都將SimpleWorkerRequest轉換成合適的HttpWorkRequest,於是造成資料擷取失敗]
如果沒有積極式載入,可以使用ReadEntityBody方法直接從異名管道中提取HTTP請求資料,[註:如果在.net 調試情況下無法擷取積極式載入的資料,那99%使用這個辦法同樣無法擷取] ,由於我在使用IIS進行調試N次也沒有發生過讀取不到積極式載入的資料的現象,所以基本不需要另外使用此方法來擷取第一次資料。
如果以上方法還是無法讀取到HTTP請求的第一次資料,那麼基本確定此次上傳請求失敗。
實在是困死了,剩下的明天補上。
下一篇