In IIS, the file corresponding to the disk path can be downloaded directly, while native IIS does not require additional configuration to resume the download. The file download address used in Xiaozhu’s project does not correspond to the file address of the disk path. Instead, it is necessary to verify whether the user has permission to download and then use fileresult to provide file download. In this way, the entire download process needs to be completed by writing code yourself. In order to make the client experience better, the function of resuming the download must be provided.
The principle of resuming download from breakpoint
In fact, the principle of resumable downloading is very simple, it is just that the Http request is different from the general download.
For example, when a browser requests a file on the server, the request it makes is as follows:
Assume that the server domain name is wwww.smallerpig.com and the file name is down.zip.
GET /down.zip HTTP/1.1 Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms- excel, application/msword, application/vnd.ms-powerpoint, */* Accept-Language: zh-cn Accept-Encoding: gzip, deflate User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0) Connection: Keep-Alive
After the server receives the request, it searches for the requested file as required, extracts the file information, and then returns it to the browser. The returned information is as follows:
200 Content-Length=106786028 Accept-Ranges=bytes Date=Mon, 30 Apr 2001 12:56:11 GMT ETag=W/"02ca57e173c11:95b" Content-Type=application/octet-stream Server=Microsoft-IIS/5.0 Last-Modified=Mon, 30 Apr 2001 12:56:11 GMT
The so-called resume download means to continue downloading from the point where the file has been downloaded. So there is one more piece of information that the client browser passes to the web server – where to start.
The following is a “browser” compiled by myself to pass the request information to the Web server. The request starts from 2000070 bytes.
GET /down.zip HTTP/1.0 User-Agent: NetFox RANGE: bytes=2000070- Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
If you look carefully, you will find that there is an extra line RANGE: bytes=2000070-
The meaning of this line is to tell the server to transmit the file down.zip starting from 2000070 bytes, and the previous bytes do not need to be transmitted.
After the server receives this request, the information returned is as follows:
206 Content-Length=106786028 Content-Range=bytes 2000070-106786027/106786028 Date=Mon, 30 Apr 2001 12:55:20 GMT ETag=W/"02ca57e173c11:95b" Content-Type=application/octet-stream Server=Microsoft-IIS/5.0 Last-Modified=Mon, 30 Apr 2001 12:55:20 GMT
Comparing it with the information returned by the previous server, you will find that a line has been added:
Content-Range=bytes 2000070-106786027/106786028
The return code is also changed to 206 instead of 200.
Knowing the above principles, you can program breakpoint resume downloading.
C# implements breakpoint resume download
/// <summary> ///Supports breakpoint resumption /// </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 {<!-- --> #region--Verification: HttpMethod, whether the requested file exists switch (httpContext.Request.HttpMethod.ToUpper()) {<!-- --> //Currently only supports GET and HEAD methods case "GET": case "HEAD": break; default: httpContext.Response.StatusCode = 501; return false; } if (!System.IO.File.Exists(filePath)) {<!-- --> httpContext.Response.StatusCode = 404; return false; } #endregion #region defines local variables long startBytes = 0; int packSize = 1024 * 40; //Read in blocks, each block is 40K 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: the time interval for reading the next data block string lastUpdateTiemStr = System.IO.File.GetLastWriteTimeUtc(filePath).ToString("r"); string eTag = HttpUtility.UrlEncode(fileName, Encoding.UTF8) + lastUpdateTiemStr;//Easy to extract the request header when resuming download; #endregion #region--Verify: whether the file is too large, whether it is a resumable transfer, and whether it has been repaired after the last requested date if (myFile.Length > Int32.MaxValue) {<!-- -->//-------The file is too large------- httpContext.Response.StatusCode = 413;//The request entity is too large return false; } if (httpContext.Request.Headers["If-Range"] != null)//Corresponding response header ETag: file name + file last modification time {<!-- --> //----------Modified since the last requested date-------------- if (httpContext.Request.Headers["If-Range"].Replace(""", "") != eTag) {<!-- -->//File modified httpContext.Response.StatusCode = 412;//Preprocessing failed return false; } } #endregion try {<!-- --> #region -------Add important response headers, parse request headers, and related verification------------------ httpContext.Response.Clear(); httpContext.Response.Buffer = false; httpContext.Response.AddHeader("Content-MD5",Common.ASE.GetMD5Hash(filePath));//Used to verify files httpContext.Response.AddHeader("Accept-Ranges", "bytes");//Important: Resuming the transmission is required httpContext.Response.AppendHeader("ETag", """ + eTag + """);//Important: Resuming the download is required httpContext.Response.AppendHeader("Last-Modified", lastUpdateTiemStr);//Write the last modified date into 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(" + ", " ")); 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 resumption request, get the starting position of the resumption, that is, the number of bytes that have been downloaded to the client------ httpContext.Response.StatusCode = 206; //Important: Resume transmission is required, indicating a local scope response. Defaults to 200 on initial download string[] range = httpContext.Request.Headers["Range"].Split(new char[] {<!-- --> '=', '-' });//" bytes=1474560-" startBytes = Convert.ToInt64(range[1]);//The number of bytes that have been downloaded, that is, the starting position of this download if (startBytes < 0 || startBytes >= fileLength) {<!-- -->//Invalid starting position return false; } } if (startBytes > 0) {<!-- -->//------If it is a resumption request, tell the client the starting number of bytes and the total length, so that the client can append the resuming data to the startBytes position-- -------- httpContext.Response.AddHeader("Content-Range", string.Format(" bytes {0}-{1}/{2}", startBytes, fileLength - 1, fileLength)); } #endregion #region -------Send data block to client------------------- br.BaseStream.Seek(startBytes, SeekOrigin.Begin); int maxCount = (int)Math.Ceiling((fileLength - startBytes + 0.0) / packSize);//Download in chunks, the number of chunks that the remaining part can be divided into for (int i = 0; i < maxCount & amp; & amp; httpContext.Response.IsClientConnected; i + + ) {<!-- -->//The client interrupts the connection and then 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; }
Reference article: http://blog.ncmem.com/wordpress/2023/11/13/c-http-Breakpoint Resume/
Welcome to join the group to discuss