Support breakpoints in ES0en. NET to continue downloading large file of ZT source code

  • 2020-12-18 01:48:07
  • OfStack

There is no breakpoint continuation feature in IE's built-in download, which requires several little-known response and request headers from the HTTP protocol.

1. Two necessary response headers, ES5en-ES6en and ETag
Each time the client submits a download request, the server adds these two response headers to ensure that the client and server recognize the download as a breakable download:
Accept-Ranges: Tell the download client that this is a download that can be resumed, and store the starting byte location and the size of the file;
ETag: the only id to save the file (the file name I used + the last modification time of the file, so that the file can be verified on request continuation);
Last-Modified: An optional response header that holds the last modification time of the server file for validation

2. 1 important request header Range
Range: When downloaded for the first time, the Range header is null, and then the response headers Accept-ES25en and ETag must be added to the response header of the server.
At the time of request continuation, the value represents the number of bytes that the client has received, that is, the starting byte location of the download. The server reads the data from the corresponding location and sends it to the client based on this value.

3. Request header If-ES31en for authentication
When the response header contains ES33en-ES34en and ETag, these request headers are included when the request is continued:
If-Range: The value of the corresponding response header ETag;
Unless-Modified-Since: The value of the corresponding response header ES44en-ES45en.
Continuingly request, in order to ensure the consistency of the client and server files and correctness, it is necessary for file verification, validation need to write their own validation code, according to parse the two request header value, the client has been compared with server-side file download part, if not consistent, is to start from scratch to download, if match, the breakpoint continuingly.

4. Speed limit
Speed limits have been added to the program, traffic limits used for client permissions control.

5. Other precautions
Such as: filename confusion, filename blank change plus, forcing the client to display the download dialog box, see source comments:


/**//// <summary> 
///  Download files, support large files, continuation, speed limit. The response head for a continuous transmission Accept-Ranges , ETag Request header, Range  .  
/// Accept-Ranges : Response header that indicates to the client that this process supports recoverable downloads . Realize the background intelligent transmission service ( BITS ), the value is: bytes ;  
/// ETag : Response header, used initially for the client ( 200 ) responses, and recovery requests from the client,  
///  Must be provided for each file 1 A wei 1 the ETag Value, which can consist of the file name and the date the file was last modified, allows client software to verify that the byte blocks they have downloaded are still up to date.  
/// Range : The starting position of the continuation, that is, the number of bytes that have been downloaded to the client, the value is as follows: bytes=1474560-  .  
///  In addition: UrlEncode After encoding, Spaces in the file name are converted + ( + convert %2b ), but the browser can not understand the plus sign for the space, so the downloaded file in the browser, the space becomes a plus sign;  
///  Solutions: UrlEncode  after ,  will  "+"  replace  "%20" Because the browser will %20 Convert to space  
/// </summary> 
/// <param name="httpContext"> Current requested HttpContext</param> 
/// <param name="filePath"> Download the physical path of the file, including the path, file name </param> 
/// <param name="speed"> Download speed: the number of bytes allowed per second </param> 
/// <returns>true Downloaded successfully, false Download failed </returns> 
public static bool DownloadFile(HttpContext httpContext, string filePath, long speed) 
{ 
bool ret = true; 
try 
{ 
#region-- Validation: HttpMethod , whether the requested file exists  
switch (httpContext.Request.HttpMethod.ToUpper()) 
{ // Currently only supported GET and HEAD methods  
case "GET": 
case "HEAD": 
break; 
default: 
httpContext.Response.StatusCode = 501; 
return false; 
} 
if (!File.Exists(filePath)) 
{ 
httpContext.Response.StatusCode = 404; 
return false; 
} 
#endregion 

#region  Define local variables  
long startBytes = 0; 
int packSize = 1024 * 10; // Chunk read, chunk read 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);// Milliseconds: Read down 1 The interval between blocks of data  
string lastUpdateTiemStr = File.GetLastWriteTimeUtc(filePath).ToString("r"); 
string eTag = HttpUtility.UrlEncode(fileName, Encoding.UTF8) + lastUpdateTiemStr;// Easy to extract the request header when resuming the download ; 
#endregion 

#region-- Validation: Whether the file is too large, whether it is a continuation, and whether it has been fixed since the last requested date  
if (myFile.Length > Int32.MaxValue) 
{//------- The file is too big ------- 
httpContext.Response.StatusCode = 413;// The requesting entity is too large  
return false; 
} 

if (httpContext.Request.Headers["If-Range"] != null)// Corresponding response head ETag : the name of the file + File last modification time  
{ 
//---------- The last requested date has been modified -------------- 
if (httpContext.Request.Headers["If-Range"].Replace("\"", "") != eTag) 
{// File has been modified  
httpContext.Response.StatusCode = 412;// Preprocessing failure  
return false; 
} 
} 
#endregion 

try 
{ 
#region ------- Add critical response headers, parse request headers, and correlation validation ------------------- 
httpContext.Response.Clear(); 
httpContext.Response.Buffer = false; 
httpContext.Response.AddHeader("Content-MD5", GetMD5Hash(myFile));// For validating files  
httpContext.Response.AddHeader("Accept-Ranges", "bytes");// Important: Continuation must  
httpContext.Response.AppendHeader("ETag", "\"" + eTag + "\"");// Important: Continuation must  
httpContext.Response.AppendHeader("Last-Modified", lastUpdateTiemStr);// Writes the last modified date to the response  
httpContext.Response.ContentType = "application/octet-stream";//MIME Type: Matches any file type  
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) 
{//------ If it is a continuation request, gets the starting location of the continuation, that is, the number of bytes that have been downloaded to the client ------ 
httpContext.Response.StatusCode = 206;// Important: Continuation must represent the local scope response. Default for initial download 200 
string[] range = httpContext.Request.Headers["Range"].Split(new char[] { '=', '-' });//"bytes=1474560-" 
startBytes = Convert.ToInt64(range[1]);// The number of bytes that have been downloaded, which is where the download started  
if (startBytes < 0 || startBytes >= fileLength) 
{// Invalid starting position  
return false; 
} 
} 
if (startBytes > 0) 
{//------ If it is a continuation request, tell the client the number of bytes to start this time, the total length, so that the client can append the continuation data to startBytes After the position ---------- 
httpContext.Response.AddHeader("Content-Range", string.Format(" bytes {0}-{1}/{2}", startBytes, fileLength - 1, fileLength)); 
} 
#endregion 

#region ------- Send blocks of data to the client ------------------- 
br.BaseStream.Seek(startBytes, SeekOrigin.Begin); 
int maxCount = (int)Math.Ceiling((fileLength - startBytes + 0.0) / packSize);// Block download, the number of blocks the rest can be divided into  
for (int i = 0; i < maxCount && httpContext.Response.IsClientConnected; i++) 
{// When the client disconnects, it pauses  
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; 
}


Related articles: