在Web應用程序中處理大文件下載的問題一直出了名的困難,因此對於大多數站點來說,如果用戶的下載被中斷了,它們只能說悲哀降臨到用戶的身上了。但是我們現在不必這樣了,因為你可以使自己的ASP.NET應用程序有能力支持可恢復(繼續)的大文件下載。使用本文提供的方法的時候,你可以跟蹤下載的過程,這樣你就可以處理動態建立的文件——而且要達到這個目標根本不需要舊式的ISAPI動態鏈接庫和非受控的(unmanaged)C++代碼。
為客戶端提供從互聯網上下載文件的服務最容易了,對嗎?僅僅只需要把可下載的文件復制到你的Web應用程序目錄中,發布鏈接並讓IIS完成所有相關的工作。但是,文件服務不應該比脖子上的疼痛還要多(還要麻煩),你不希望整個世界都能訪問自己的數據,你不希望服務器被數百個靜態文件塞滿了,你甚至於希望下載臨時文件——只有當客戶端開始下載後的空閒時間才建立這些文件。
不幸的是,使用IIS對下載請求的默認的響應是不可能達到這些效果的。因此在一般情況下,為了獲得對下載過程的控制權,開發者需要鏈接到一個定制的。aspx頁面,在這個頁面中它們檢查用戶憑證(credential)、建立可以下載的文件並使用下面的代碼把該文件推送給客戶端:
Response.WriteFile
Response.End()
而這就是出現真正麻煩的地方。
有什麼問題?
WriteFile方法看起來非常完美,它使文件的二進制數據流向客戶端。但是直到最近我們才知道,WriteFile方法是一個出名的內存占用狂,它把整個文件載入服務器的RAM中來提供服務(實際上它甚至於會占用文件兩倍大小的空間)。對於大文件,這會引起服務內存問題,並且可能重復ASP.NET過程。但是在2004年6月微軟發布了一個補丁解決了這個問題。這個補丁現在是。NET Framework 1.1補丁包(SP1)的一部分。
這個補丁引入了TransmitFile方法,它把一個磁盤文件讀入到較小的內存緩沖區之後就開始傳輸該文件。盡管這個方案解決了內存和循環的問題,但是它仍然不能令人滿意。你不能控制響應的生命周期。你無法知道下載是否正確地完成了,你沒有辦法知道下載是否被中斷了,並且(如果你建立了臨時文件)你也不知道是否應該、以及什麼時候可以刪除這些文件。更糟的是,如果下載的確失敗了,TransmitFile方法又從客戶端下次嘗試的文件頭部開始下載。
其中一種可能的解決方案——實現後台智能傳輸服務(BITS)對於多數站點來說是不可行的,因為這會毀掉維持客戶端浏覽器和操作系統獨立性而作出的努力。
令人滿意的解決方案的基礎還是來自微軟用於解決WriteFile引起的內存混亂問題的第一次嘗試(見知識庫文章812406)。那篇文章演示了智能的大塊數據下載過程,它從文件流中讀取數據。在服務器把字節塊發送給客戶端之前,它使用Response.IsClientConnected屬性檢查客戶端是否仍然保持著連接。如果仍然保持連接,它就繼續發送流字節,否則就停止,以防止服務器發送不必要的數據。
這就是我們采用的方法,特別是在下載臨時文件的時候。在IsClientConnected返回False的情況下,你就知道下載過程被中斷了,你應該保存文件;反之,當這個過程成功完成的時候,你就刪除臨時文件。此外,為了恢復中斷了的下載,你需要做的工作是從上次下載嘗試過程中客戶端連接失敗的文件點開始下載。
HTTP協議和頭信息(Header)支持
HTTP協議支持可以用於處理被中斷下載的頭信息。使用少量的HTTP頭信息,你可以增強自己的下載過程,使它完全遵循HTTP協議規范。這個規范與ranges一起提供恢復被中斷的下載所需要的一切信息。
下面是它的工作方式。首先,如果服務器支持客戶端斷點續傳,它就在初始的響應中發送Accept-Ranges頭信息。服務器還發送一個實體標簽(entity tag)頭信息(ETag),它包含一個唯一的標識字符串。
下面的代碼顯示了IIS發送給客戶端的用於響應一個初始下載請求的一些頭信息,它向客戶端傳遞了被請求的文件的詳細信息。
HTTP/1.1 200 OK
Connection: close
Date: Tue, 19 Oct 2004 15:11:23 GMT
Accept-Ranges: bytes
Last-Modified: Sun, 26 Sep 2004 15:52:45 GMT
ETag: "47febb2cfd76c41:2062"
Cache-Control: private
Content-Type: application/x-zip-compressed
Content-Length: 2844011