2023-08-24 分類: 網(wǎng)站建設(shè)
談?wù)勗贏PS.NET中如何控制文件下載.
設(shè)計目的和要求
假設(shè)這么一個應(yīng)用場景:
一個主機,上面存有許多文件資料,有各種文件格式.(PDF, DOC, EXE ... 等等).
該主機上運行一個ASP.NET網(wǎng)站, 用戶注冊,并付費之后允許他/她下載資料.
文件是放在IIS服務(wù)器上的, 如果用戶知道具體路徑那么他是可以隨時下載的. (在沒有或者不能設(shè)置訪問權(quán)限的情況下.)
如果直接把下載路徑發(fā)送給付費用戶,肯定是行不通的,會被散播出去. 所以不能把讓客戶端得知具體路徑,文件內(nèi)容由 ASP.NET 服務(wù)器頁面讀取后發(fā)送給客戶端.
要做的就是: 編寫一個ASP.NET 頁面服務(wù)器代碼, 讀取指定文件,并發(fā)送給客戶.
總體思路
.net 里, 有2個函數(shù)可以用來發(fā)送文件 Response.WriteFile 和 Response.TransmiteFile
它們的主要區(qū)別是: WriteFile 是先把文件內(nèi)容讀取到服務(wù)器緩沖,然后再發(fā)送到客戶端. 所以對于大文件,會造成服務(wù)器很大的壓力.
一般用來處理小文件,比如,發(fā)送給 excel 報表之類的. TransmiteFile 不緩沖數(shù)據(jù), 直接拋給客戶端, 所以可以用來發(fā)大文件.
( 采用 TransmiteFile 來實現(xiàn).)
具體實現(xiàn)
1. 給客戶一個鏈接,形如 http://xxxx/downloads.aspx?Key=ABCD123456
2. 在downloads.aspx的服務(wù)器代碼中, 通過Key的值,查詢數(shù)據(jù)庫,得到服務(wù)器上的真實文件路徑. 這個時候,控制權(quán)在 downloads.aspx, 所以可以編寫復(fù)雜的控制功能, 比如看看用戶有沒有登錄,有沒有付費之類的,從而避免外部盜鏈.
3. 得到文件路徑后,調(diào)用 Response.TransmiteFile 發(fā)送文件給客戶端.
4. 因為給客戶的鏈接里沒有任何文件名的信息, 所以要在HTTP響應(yīng)頭里添加一句,告訴客戶端文件名: Response.AddHeader("Content-Disposition", "attachment; filename=/"" + 你的文件名 + "/""); (如果要支持中文,要考慮編碼的問題, 我這里不說,不是我們的主題.)
5. 如果是一個大文件, 比如1G, 不支持斷點續(xù)傳,是沒有意義的. 那么如何實現(xiàn)呢?
(1) 要讓客戶端知道我們的服務(wù)器支持斷點續(xù)傳, 要在HTTP響應(yīng)頭中包含 Accept-Ranges: bytes 和 ETag: "XXXX".
ETag 是一個文件的標識, 供客戶端判斷它請求的是同一個文件, ETag 的內(nèi)容在HTTP規(guī)范里并沒有具體要求,只要保證在同一個服務(wù)器上,同一個文件有相同的ETag 就行了, 一般就根據(jù)文件名和最后修改時間生成一個字符串就可以了.
代碼示例:
Response.AddHeader("Accept-Ranges", "bytes"); // 斷點續(xù)傳控制.
Response.AddHeader("ETag", "/"" + strETag + "/""); // 允許斷點續(xù)傳
(2) 要處理客戶端請求中的 "Range" 字段. 一般格式是這樣: Range: bytes=1234- 或者 Range: bytes=1234-12345
分別表示從地1235個字節(jié)開始下載和下載第1235到第12346個字節(jié)之間的數(shù)據(jù).
服務(wù)器首先要添加 Content-Range 響應(yīng)頭, 然后用 TransmiteFile 發(fā)送指定的數(shù)據(jù).
代碼示例:
Response.StatusCode = 206;
Response.AddHeader("Content-Length", (lTo - lFrom + 1).ToString());
Response.AddHeader("Content-Range", string.Format("bytes {0}-{1}/{2}", lFrom, lTo, fi.Length)); // 參數(shù)0 和 參數(shù)1 是位置. 參數(shù)2是文件長度
Response.TransmitFile(strFilePath, lFrom, lTo - lFrom + 1);
( 其中, lFrom 和 lTo 是根據(jù)客戶端請求中的 Range 字段得到的.)
說一下優(yōu)缺點:
1. 可以隨心所欲的控制下載.
2. 可以繞過服務(wù)器文件類型下載的限制, 比如服務(wù)器不允許下載 ISO 和 NRG 文件擴展名的文件, 如果直接輸入RUL會提示404, 但是用上述的方法可以下載.
3.用這種辦法的話,下載是在.net的一個線程里做的,如果用戶量大的話,需要維護多個響應(yīng)
附注:
1. TransmitFile(String) ( 函數(shù)是 .net 2.0 才加上去的.
2. TransmitFile(String, Int64, Int64) 帶發(fā)送位置參數(shù)的重載是 .net 2.0 sp1 以后才支持的. 所以要用本文所說的方法實現(xiàn)斷點續(xù)傳, 至少要支持.net 2.0 sp1
3. 沒有檢測請求頭中的 If-Range 和 Unless-Modified-Since, 如果有需要,在得到文件名之后就可以校驗一下, 分別對應(yīng) ETag 和 Last-Modified.
// 1. 獲取服務(wù)器上的文件路徑 // 這里,如果文件路徑有問題, 無法映射則會拋出異常, strURL 是根據(jù) Key從數(shù)據(jù)庫中查詢到的真實文件路徑
string strFilePath = Server.MapPath("~" + strURL);
// 2. 獲取文件名
string strFileName = System.IO.Path.GetFileName(strFilePath);
// 3. 確認文件是否存在
FileInfo fi = new FileInfo(strFilePath);
if (!fi.Exists)
{
// 退出點,文件不存在
}
// 4. 拋給客戶端
strFileName.Replace(" ", "%20"); // 處理文件名含空格的情況
string strETag = strFileName.ToUpper() + ":" + fi.Length.ToString(); // 我的Etag 是用文件名和字節(jié)數(shù)構(gòu)成,馬馬虎虎湊合用.
string strLastTime = fi.LastWriteTimeUtc.ToString("r");
Response.Clear(); // 先把響應(yīng)流清空
Response.ContentType = "application/octet-stream"; // 指定文件類型,使客戶端總是彈出保存文件的框框.
Response.AddHeader("Content-Disposition", "attachment; filename=/"" + strFileName + "/"");
Response.AddHeader("Accept-Ranges", "bytes"); // 斷點續(xù)傳控制.
Response.AddHeader("ETag", "/"" + strETag + "/""); // 允許斷點續(xù)傳
Response.AddHeader("Last-Modified", strLastTime);//把最后修改日期寫入響應(yīng)
// 獲取客戶端請求的范圍, 并且要校驗這個范圍的有效性
long lFrom = 0;
long lTo = 0;
bool bParts = false;
string strRange = Request.Headers["Range"];
if (ParseRange(strRange, out lFrom, out lTo)) /// ParseRange 是我自己寫的函數(shù), 從 Range 中讀取2個位置.代碼在后面.
{
if (-1 == lFrom && -1 == lTo)
{
// 不允許2個值都不指定
}
else
{
if (lTo == -1) lTo = fi.Length - 1; // 客戶端未指定結(jié)束位置,則認為是文件的最后一個字符 Range: bytes=123- 的情況
if (lFrom == -1) // Range: bytes=-123 的情況, 請求最后的123個字節(jié)
{
lFrom = fi.Length - lTo;
lTo = fi.Length - 1;
}
if (lFrom < 0 || lFrom >= fi.Length || lFrom > lTo || lTo < 0 || lTo >= fi.Length)
{
// 以上幾種情況下,范圍的值能解析出來,但是不合法.
// 首先 From 和 To 的下標都應(yīng)該在文件長度范圍內(nèi)
// 其次 From 應(yīng)該 <= To
}
else
{
bParts = true;
}
}
}
// 根據(jù)用戶請求,返回數(shù)據(jù)段或者整個文件
if(bParts)
{
Response.StatusCode = 206;
Response.AddHeader("Content-Length", (lTo - lFrom + 1).ToString());
Response.AddHeader("Content-Range", string.Format("bytes {0}-{1}/{2}", lFrom, lTo, fi.Length)); // 參數(shù)0 和 參數(shù)1 是位置,從0開始. 參數(shù)2是文件長度
Response.TransmitFile(strFilePath, lFrom, lTo - lFrom + 1);
}
else
{
Response.AddHeader("Content-Length", fi.Length.ToString());
Response.TransmitFile(strFilePath);
}
Response.End();
}
=============================傳說中的分割線======================================
protected bool ParseRange(string strRange, out long lFrom, out long lTo)
{
lFrom = 0;
lTo = 0;
long lTemp = 0;
if (strRange == null || strRange == "")
{
return false; // 字符串為空
}
else
{
strRange = strRange.Replace(" ", ""); // 去除多余的空格
string[] range = strRange.Split(new char[] { '=', '-' });
// 1.分割后,包含3段 第一段是 "Range: bytes", 第二段是起始位置, 第三段是結(jié)束位置
if (range.Length != 3)
{
return false; // 格式不正確 只支持 Range: bytes=89294317- 或者 Range: bytes=1234-1235 或者 Range: bytes=-500 3種格式.
}
// 2. 解析起始位置
if (range[1].Length <= 0)
{
// 起始位置未指定
lFrom = -1;
}
else
{
if (!long.TryParse(range[1], out lTemp))
{
return false; // 起始位置無法解析
}
lFrom = lTemp;
}
// 3. 解析結(jié)束位置
if (range[2].Length <= 0)
{
lTo = -1; // 沒有指定結(jié)束位置 Range: bytes=1234- 的情況
}
else
{
if (!long.TryParse(range[2], out lTemp)) // 排除 byte=xxxx- 的情況 TryParse 失敗, 會把lTemp 置零
{
return false; // 第三度的內(nèi)容不為空,但是無法解析
}
lTo = lTemp;
}
return true;
}
}
分享名稱:Godaddy服務(wù)器上關(guān)于ASP.NET網(wǎng)站建設(shè)一些經(jīng)驗-斷點續(xù)傳下載(二)
網(wǎng)站鏈接:http://jinyejixie.com/news21/278121.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站營銷、網(wǎng)站制作、靜態(tài)網(wǎng)站、虛擬主機、品牌網(wǎng)站設(shè)計、網(wǎng)站設(shè)計
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容