Asp.Net 上傳大檔案專題(3)--從請求流中獲取資料並儲存為檔案[上]

iDotNetSpace發表於2009-06-03

      實在很抱歉隔了這麼久才繼續補上這篇,因為後期除錯時發現上傳很不穩定,所以除錯了幾天,目前測試基本沒什麼問題。

      回顧上一篇,我們可以瞭解到以下內容:

      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,並在節中註冊一個模組,格式如下:
      
httpModules
  
          
  


      根據上面的格式,我寫的如下:


MyHttpModule
  
          

      由於後期測試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請求的第一次資料,那麼基本確定此次上傳請求失敗。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/12639172/viewspace-605016/,如需轉載,請註明出處,否則將追究法律責任。

相關文章