asp.net下大檔案上傳知識整理
來源:互聯網
上載者:User
最近做在做ePartner項目,涉及到檔案上傳的問題。 以前也做過檔案上傳,但都是些小檔案,不超過2M。 這次要求上傳100M以上的東西。 沒辦法找來資料研究了一下。基於WEB的檔案上傳可以使用FTP和HTTP兩種協議,用FTP的話雖然傳輸穩定,但安全性是個嚴重的問題,而且FTP伺服器讀使用者庫擷取許可權,這樣對於使用者使用來說還是不太方便。 剩下只有HTTP。在HTTP中有3種方式,PUT、WEBDAV、RFC1867,前2種方法不適合大檔案上傳,目前我們使用的web上傳都是基於RFC1867標準的HTML中基於表單的檔案上傳。
一、先簡要介紹一下RFC1867(Form-based File Upload in HTML)標準:
1.帶有檔案提交功能的HTML表單
現有的HTML規範為INPUT元素的TYPE屬性定義了八種可能的值,分別是:CHECKBOX, HIDDEN, IMAGE, PASSWORD, RADIO, RESET, SUBMIT, TEXT. 另外,當表單採用POST方式的時候,表單預設的具有"application/x-www -form-urlencoded" 的ENCTYPE屬性。
RFC1867標準對HTML做出了兩處修改:
1)為INPUT元素的TYPE屬性增加了一個FILE選項。
2)INPUT標記可以具有ACCEPT屬性,該屬效能夠指定可被上傳的檔案類型或檔案格式列表。
另外,本標準還定義了一種新的MIME類型:multipart/form-data,以及當處理一個帶有ENCTYPE="multipart/form-data" 並且/或含有<INPUT type="file">的標記的表單時所應該採取的行為。
舉例來說,當HTML表單作者想讓使用者能夠上傳一個或更多的檔案時,他可以這麼寫:
<FORM ENCTYPE="multipart/form-data" ACTION="_URL_" METHOD=POST>
File to process:
<INPUT NAME="userfile1" TYPE="file">
<INPUT TYPE="submit" VALUE="Send File">
</FORM>
HTML DTD裡所需要做出的改動是為InputType實體增加一個選項。此外,我們也建議用一系列用逗號分隔的檔案類型來作為INPUT標記的ACCEPT屬性。
... (其他元素) ...
<!ENTITY % InputType "(TEXT | PASSWORD | CHECKBOX |
RADIO | SUBMIT | RESET |
IMAGE | HIDDEN | FILE )">
<!ELEMENT INPUT - 0 EMPTY>
<!ATTLIST INPUT
TYPE %InputType TEXT
NAME CDATA #IMPLIED -- required for all but submit and reset
VALUE CDATA #IMPLIED
SRC %URI #IMPLIED -- for image inputs --
CHECKED (CHECKED) #IMPLIED
SIZE CDATA #IMPLIED --like NUMBERS,
but delimited with comma, not space
MAXLENGTH NUMBER #IMPLIED
ALIGN (top|middle|bottom) #IMPLIED
ACCEPT CDATA #IMPLIED --list of content types
>
... (其他元素) ...
2.檔案傳輸延遲
在某些情況下,在確實準備接受資料前,伺服器先對錶單資料中的某些元素(比如說使用者名稱,帳號等)進行驗證是推薦的做法。但是,經過一定的考慮後,我們認為如果伺服器想這樣做的話,最好是採用一系列的表單,並將前面所驗證過的資料元素作為“隱藏”欄位傳回給用戶端,或者是通過安排表單使那些需要驗證的元素先顯示出來。這樣的話,那些需要做複雜的應用的伺服器可以自己維持交易處理的狀態,而那些簡單的應用的則可以實現得簡單些。
HTTP 協議可能需要知道整個交易處理中的內容總長度。即使沒有明確要求,HTTP用戶端也應該提供上傳的所有檔案的內容總長度,這樣一個繁忙的伺服器就能夠判斷檔案的內容是否是過大以至於將不能完整地處理,從而返回一個錯誤碼並關閉該串連,而不用等到接受了所有的資料才進行判斷。目前一些現有的CGI應用對所有的POST事務都需要知道內容總長度。
如果INPUT標記含有一個MAXLENGTH屬性,用戶端可以將這個屬性值看作是伺服器端所能夠接受的傳送檔案的最大位元組數。在這種情況下,伺服器能夠在上傳開始前,提示用戶端在伺服器上有多少空間可以用來進行檔案上傳。但是應該引起注意的是,這僅僅是一個提示,在表單被建立後和檔案上傳前,伺服器的實際需求可能會發生改變。
在任何情況下,如果接受的檔案過大的話,任何一個HTTP伺服器都有可能在檔案傳輸的過程中中斷傳輸。
3.傳輸位元據的其他解決辦法
有些人曾經建議使用一種新的MIME類型"aggregate",比如說aggregate/mixed 或是content-transfer- encoding "包"來描述那些不確定長度的位元據,而不是靠分解為多個部分來表示。雖然我們並不反對這麼做,但這需要增加額外的設計和標準化工作來讓大家接受並理解"aggregate"。 從另一方面來說,"分解為多部分"的機制工作得很好,能夠非常簡單的在客戶發送端和伺服器接受端加以實現,而且能像其他一些綜合處理位元據的方式一樣高效率地工作。
4.例子
假設伺服器段提供的是如下的HTML:
<FORM ACTION="http://server.dom/cgi/handle"
ENCTYPE="multipart/form-data"
METHOD=POST>
What is your name? <INPUT TYPE=TEXT NAME=submitter>
What files are you sending? <INPUT TYPE=FILE NAME=pics>
</FORM>
使用者在“姓名”欄位裡面填寫"Joe Blow",對問題'What files are you sending?',使用者選擇
了一個文字檔"file1.txt"。
客戶段可能發送回如下的資料:
Content-type: multipart/form-data, boundary=AaB03x
--AaB03x
content-disposition: form-data; name="field1"
Joe Blow
--AaB03x
content-disposition: form-data; name="pics"; filename="file1.txt"
Content-Type: text/plain
... file1.txt 的內容...
--AaB03x--
如果使用者同時還選擇了另一個圖片檔案"file2.gif",那麼用戶端可能發送的資料將是:
Content-type: multipart/form-data, boundary=AaB03x
--AaB03x
content-disposition: form-data; name="field1"
Joe Blow
--AaB03x
content-disposition: form-data; name="pics"
Content-type: multipart/mixed, boundary=BbC04y
--BbC04y
Content-disposition: attachment; filename="file1.txt"
Content-Type: text/plain
... file1.txt 的內容...
--BbC04y
Content-disposition: attachment; filename="file2.gif"
Content-type: image/gif
Content-Transfer-Encoding: binary
... file2.gif的內容...
--BbC04y--
--AaB03x--
二、利用RFC1867標準處理檔案上傳的兩種方式:
1.一次性得到上傳的資料,然後分析處理。
看了N多代碼之後發現,目前無組件程式和一些COM組件都是使用Request.BinaryRead方法。一次性得到上傳的資料,然後分析處理。這就是為什麼上傳大檔案很慢的原因了,IIS逾時不說,就算幾百M檔案上去了,分析處理也得一陣子。
2.一邊接收檔案,一邊寫硬碟。
瞭解了一下國外的商業組件,比較流行的有Power-Web,AspUpload,ActiveFile,ABCUpload, aspSmartUpload,SA-FileUp。其中比較優秀的是ASPUPLOAD和SA-FILE,他們號稱可以處理2G的檔案(SA- FILE EE版甚至沒有檔案大小的限制),而且效率也是非常棒,難道程式設計語言的效率差這麼多?查了一些資料,覺得他們都是直接操作檔案流。這樣就不受檔案大小的制約。但老外的東西也不是絕對完美,ASPUPLOAD處理大檔案後,記憶體佔用情況驚人。1G左右都是稀鬆平常。至於SA-FILE雖然是好東西但是破解難尋。然後發現2款.NET上傳組件,Lion.Web.UpLoadModule和AspnetUpload也是操作檔案流。但是上傳速度和 CPU佔用率都不如老外的商業組件。
做了個測試,LAN內傳1G的檔案。ASPUPLOAD上傳速度平均是4.4M/s,CPU佔用10 -15,記憶體佔用700M。SA-FILE也差不多這樣。而AspnetUpload最快也只有1.5M/s,平均是700K/s,CPU佔用15- 39,測試環境: PIII800,256M記憶體,100M LAN。我想AspnetUpload速度慢是可能因為一邊接收檔案,一邊寫硬碟。資源佔用低的代價就是降低傳輸速度。但也不得不佩服老外的程式,CPU佔用如此之低.....
三、ASP.NET上傳檔案遇到的問題
我們在用ASP.NET上傳大檔案時都遇到過這樣或那樣的問題。設定很大的maxRequestLength值並不能完全解決問題,因為ASP.NET會 block直到把整個檔案載入記憶體後,再加以處理。實際上,如果檔案很大的話,我們經常會見到Internet Explorer顯示 "The page cannot be displayed - Cannot find server or DNS Error",好像是怎麼也 catch不了這個錯誤。為什嗎?因為這是個client side錯誤,server side端的Application_Error是處理不到的。
四、ASP.NET大檔案上傳解決方案
解決的方法是利用隱含的HttpWorkerRequest,用它的GetPreloadedEntityBody 和 ReadEntityBody方法從IIS為ASP.NET建立的pipe裡分塊讀取資料。Chris Hynes為我們提供了這樣的一個方案(用HttpModule),該方案除了允許你上傳大檔案外,還能即時顯示上傳進度。
Lion.Web.UpLoadModule和AspnetUpload 兩個.NET組件都是利用的這個方案。
方案原理:
利用HttpHandler實現了類似於ISAPI Extention的功能,處理請求(Request)的資訊和發送響應(Response)。
方案要點:
1. httpHandler or HttpModule
a.在asp.net進程處理request請求之前截獲request對象
b.分塊讀取和寫入資料
c.即時跟蹤上傳進度更新meta資訊
2. 利用隱含的HttpWorkerRequest用它的GetPreloadedEntityBody 和 ReadEntityBody方法處理檔案流
IServiceProvider provider = (IServiceProvider) HttpContext.Current;
HttpWorkerRequest wr = (HttpWorkerRequest) provider.GetService(typeof(HttpWorkerRequest));
byte[] bs = wr.GetPreloadedEntityBody();
....
if (!wr.IsEntireEntityBodyIsPreloaded())
{
int n = 1024;
byte[] bs2 = new byte[n];
while (wr.ReadEntityBody(bs2,n) >0)
{
.....
}
}
3. 自訂Multipart MIME 解析器
自動截獲MIME分割符
將檔案分塊寫如臨時檔案
即時更新Appliaction 狀態(ReceivingData, Error, Complete)