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