Response
Http 協議中有專門的指令來告知瀏覽器, 本次響應的是一個需要下載的文件. 格式如下:
Content-Disposition: attachment;filename=filename.ext
以上指令即標記此次響應流是附件,且附件文件名為 filename.ext。
文件下載方式:
protected void Page_Load(object sender, EventArgs e) { Response.WriteFile("Tree.jpg"); Response.Flush(); Response.Close(); } protected void Page_Load(object sender, EventArgs e) { Response.BinaryWrite(File.ReadAllBytes(Server.MapPath("Tree.jpg"))); Response.Flush(); Response.Close(); } protected void Page_Load(object sender, EventArgs e) { int chunkSize = 64; byte[] buffer = new byte[chunkSize]; int offset = 0; int read = 0; using (FileStream fs = File.Open(Server.MapPath("Tree.jpg"), FileMode.Open, FileAccess.Read, FileShare.Read)) { while ((read = fs.Read(buffer, offset, chunkSize)) > 0) { Response.OutputStream.Write(buffer, 0, read); Response.Flush(); } } Response.Close(); }
1.分塊下載服務器物理路徑下的文件
/// <summary> /// 使用OutputStream.Write分塊下載文件 /// </summary> /// <param name="filePath"></param> public void WriteFileBlock(string filePath) { filePath = Server.MapPath(filePath); if (!File.Exists(filePath)) { return; } FileInfo info = new FileInfo(filePath); //指定塊大小 long chunkSize = 4096; //建立一個4K的緩沖區 byte[] buffer = new byte[chunkSize]; //剩余的字節數 long dataToRead = 0; FileStream stream = null; try { //打開文件 stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); dataToRead = stream.Length; //添加Http頭 HttpContext.Current.Response.ContentType = "application/octet-stream"; HttpContext.Current.Response.AddHeader("Content-Disposition", "attachement;filename=" + Server.UrlEncode(info.FullName)); HttpContext.Current.Response.AddHeader("Content-Length", dataToRead.ToString()); while (dataToRead > 0) { if (HttpContext.Current.Response.IsClientConnected) { int length = stream.Read(buffer, 0, Convert.ToInt32(chunkSize)); HttpContext.Current.Response.OutputStream.Write(buffer, 0, length); HttpContext.Current.Response.Flush(); HttpContext.Current.Response.Clear(); dataToRead -= length; } else { //防止client失去連接 dataToRead = -1; } } } catch (Exception ex) { HttpContext.Current.Response.Write("Error:" + ex.Message); } finally { if (stream != null) { stream.Close(); } HttpContext.Current.Response.Close(); } }
2.使用WebClient下載http文件到客戶端。分塊下載,防止大文件下載阻塞瀏覽器。
Response.Flush和Response.BufferOutput
Response.Flush方法用來將緩沖區的數據立即輸出到瀏覽器當中。你可以多次調用Response.Flush 方法,當這樣使用時,瀏覽器將多次接受數據,而不是僅接受一次數據。
Response.BufferOutput是一個布爾值,指示是否緩沖輸出並在整個頁面在服務器端處理完畢后才發送緩沖區中的數據。true是其默認值。
服務器端是否緩存數據取決於Response.BufferOutput,當你將Response.BufferOutput的值設為true時,數據會緩存到buffer中,並在頁面處理完畢后,將buffer中的內容一次性全部發到客戶端。如果為false,則不緩沖數據,每執行一個response.write方法,數據就會立即發往客戶端,數據的傳送次數取決於你使用了多少個response.write方法,在這種情況下,使用response.Flush方法是沒有意義的。只用當你將Response.BufferOutput屬性的值設為true時,使用response.Flush方法才有意義。這時服務器端會將調用response.Flush方法時之前的所有response.write方法的數據發往客戶端。
只要將Response.BufferOutput的值設置為true,一定會發送buffer里的內容,只是早晚、次數的問題,這就取決於Response.Flush方法了。
至於它們的作用,在一個很大很大的網頁中,可以使用Response.Flush方法將數據分批發往客戶端,這樣就可以使瀏覽器先呈現一些html代碼,並逐步完整呈現。這樣可使用戶減少等待時間。不過你要注意一下,發送的html代碼必須是閉合完整的,否則有的瀏覽器不會立即呈現html,而是等待接受完整的html才呈現。否則使用它就沒有效果了。
public void DownloadFile(string fileUri, string programId) { Stream stream = null; try { var fileName = programId + DateTime.Now.ToString("_HHmmssms") + ".ts"; using (var client = new WebClient()) { client.Proxy = null; stream = client.OpenRead(fileUri); Response.Clear(); //Response.BufferOutput = false; Response.ContentType = "video/mpeg"; //通知瀏覽器下載文件而不是打開 Response.AddHeader("Content-Disposition", "attachment; filename=" + HttpUtility.UrlEncode(fileName, System.Text.Encoding.UTF8)); Int64 bytesTotal = Convert.ToInt64(client.ResponseHeaders["Content-Length"]);//獲取client響應頭文件長度 Response.AddHeader("Content-Length", bytesTotal.ToString());//設置文件大小 int bytesRead = 0; long chunkSize = 4096; //指定塊大小 byte[] buffer = new byte[chunkSize];//建立一個4K的緩沖區 while (Response.IsClientConnected && (bytesRead = stream.Read(buffer, 0, Convert.ToInt32(chunkSize))) > 0) { Response.OutputStream.Write(buffer, 0, bytesRead); Response.Flush();//將緩沖的輸出數據發送到客戶端 Response.Clear(); } } } catch (Exception ex) { throw ex; } finally { if (stream != null) { stream.Close(); } Response.Close(); } }
3.文件斷點續傳
/// <summary> /// 文件斷點續傳下載 /// </summary> /// <param name="httpContext"></param> /// <param name="filePath"></param> /// <param name="speed"></param> /// <returns></returns> public static bool DownloadFile(HttpContext httpContext, string filePath, long speed) { bool ret = true; try { switch (httpContext.Request.HttpMethod.ToUpper()) { //support Get and head method case "GET": case "HEAD": break; default: httpContext.Response.StatusCode = 501; return false; } if (!File.Exists(filePath)) { httpContext.Response.StatusCode = 404; return false; } long startBytes = 0; int packSize = 1024 * 10; //read in block,every block 10K bytes string fileName = Path.GetFileName(filePath); FileStream myFile = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); BinaryReader br = new BinaryReader(myFile); long fileLength = myFile.Length; int sleep = (int)Math.Ceiling(1000.0 * packSize / speed);//the number of millisecond string lastUpdateTiemStr = File.GetLastWriteTimeUtc(filePath).ToString("r"); string eTag = HttpUtility.UrlEncode(fileName, Encoding.UTF8) + lastUpdateTiemStr; //validate whether the file is too large if (myFile.Length > Int32.MaxValue) { httpContext.Response.StatusCode = 413; return false; } if (httpContext.Request.Headers["If-Range"] != null) { if (httpContext.Request.Headers["If-Range"].Replace("\"", "") != eTag) { httpContext.Response.StatusCode = 412; return false; } } try { #region -------添加重要響應頭、解析請求頭、相關驗證------------------- httpContext.Response.Clear(); httpContext.Response.BufferOutput = false; //httpContext.Response.AddHeader("Content-MD5", GetMD5Hash(myFile)); httpContext.Response.AddHeader("Accept-Ranges", "bytes"); httpContext.Response.AppendHeader("ETag", "\"" + eTag + "\""); httpContext.Response.AppendHeader("Last-Modified", lastUpdateTiemStr); httpContext.Response.ContentType = "application/octet-stream"; httpContext.Response.AddHeader("Content-Disposition", "attachment;filename=" + HttpUtility.UrlEncode(fileName, Encoding.UTF8).Replace("+", "%20")); httpContext.Response.AddHeader("Content-Length", (fileLength - startBytes).ToString()); httpContext.Response.AddHeader("Connection", "Keep-Alive"); httpContext.Response.ContentEncoding = Encoding.UTF8; if (httpContext.Request.Headers["Range"] != null) { httpContext.Response.StatusCode = 206; string[] range = httpContext.Request.Headers["Range"].Split(new char[] { '=', '-' }); startBytes = Convert.ToInt64(range[1]); if (startBytes < 0 || startBytes >= fileLength) { return false; } } if (startBytes > 0) { // 如果是續傳請求,告訴客戶端本次的開始字節數,總長度,以便客戶端將續傳數據追加到startBytes位置后 httpContext.Response.AddHeader("Content-Range", string.Format(" bytes {0}-{1}/{2}", startBytes, fileLength - 1, fileLength)); } #endregion #region -------向客戶端發送數據塊------------------- //send data br.BaseStream.Seek(startBytes, SeekOrigin.Begin); int maxCount = (int)Math.Ceiling((fileLength - startBytes + 0.0) / packSize);//download in block for (int i = 0; i < maxCount && httpContext.Response.IsClientConnected; i++) { httpContext.Response.BinaryWrite(br.ReadBytes(packSize)); httpContext.Response.Flush(); if (sleep > 1) Thread.Sleep(sleep); } #endregion } catch { ret = false; } finally { br.Close(); myFile.Close(); } } catch { ret = false; } return ret; }