一、Http的Range請求頭,結合相應頭Accept-Ranges、Content-Range 可以實現如下功能:
1.斷點續傳。用於下載文件被中斷后,繼續下載。
2.大文件指定區塊下載,如視頻、音頻拖動播放,直接定位到指定位置下載內容。可以避免每次都讀取、傳輸整個文件,從而提升服務端性能。
3.大文件分包批量下載,再合並完整文件。可以提高下載速度。
二、Http的Range 相關說明:
1.規則要點
請求頭Range表示請求的數據起始位置。響應頭Accept-Ranges:bytes 表示支持續傳。響應頭Content-Range表示返回的其實位置、總長度
請求頭Range的數字,首尾都包含,長度是: end-begin+1
請求頭Range的指定的長度,只是意向下載量,服務端不一定返回請求的長度。比如:bytes=0-, 表示希望下載整個文件,但服務端可以返回有限長度的數據塊(有利於性能)。但數據其實位置start需按照請求。
2.在Http 響應請求是 200,表示響應結束,響應成功
Http 響應狀態:206,表示響應中,響應部分數據,不會單開Socket鏈接。
三、在Asp.Net Core中實現自定義 Range 文件響應
1.封裝處理的類:
public class DownloadRange { public HttpContext context = null; public HttpRequest request = null; public HttpResponse response = null; public DownloadRange(HttpContext ctx) { this.context = ctx; this.request = ctx.Request; this.response = ctx.Response; } private int HttpRangeSize = 1024 * 1024; //最大塊長度 1M public void WriteFile(string file) { using (var fs = File.OpenRead(file)) { WriteStream(fs); } } private void WriteStream(Stream fs) { string range = request.Headers["Range"]; range = range ?? ""; range = range.Trim().ToLower(); if (fs.CanSeek) { if (range.StartsWith("bytes=") && range.Contains("-")) { //分段輸出文件 int start = -1, end = -1; var rgs = range.Substring(6).Split('-'); int.TryParse(rgs[0], out start); int.TryParse(rgs[1], out end); if (rgs[0] == "") { start = (int)fs.Length - end; end = (int)fs.Length - 1; } if (rgs[1] == "") { end = (int)fs.Length - 1; } WriteRangeStream(fs, start, end); } else { //輸出整個文件 int l; byte[] buffer = new byte[40960]; while ((l = fs.Read(buffer, 0, buffer.Length)) > 0) { response.Body.Write(buffer, 0, l); } } } } private void WriteRangeStream(Stream fs, int start, int end) { using (fs) { int rangLen = end - start + 1; if (rangLen > 0) { if (rangLen > HttpRangeSize) { rangLen = HttpRangeSize; end = start + HttpRangeSize - 1; } } else { throw new Exception("Range error"); } long size = fs.Length; //如果是整個文件返回200,否則返回206 if (start == 0 && end + 1 >= size) { response.StatusCode = 200; } else { response.StatusCode = 206; } // response.Headers.Add("Accept-Ranges", "bytes"); response.Headers.Add("Content-Range", $"bytes {start}-{end}/{size}"); response.Headers.Add("Content-Length", rangLen.ToString()); int readLen, total = 0; byte[] buffer = new byte[40960]; //流定位到指定位置 try { fs.Seek(start, SeekOrigin.Begin); while (total < rangLen && (readLen = fs.Read(buffer, 0, buffer.Length)) > 0) { total += readLen; if (total > rangLen) { readLen -= (total - rangLen); total = rangLen; } response.Body.Write(buffer, 0, readLen); } } catch (Exception ex) { throw ex; } } } }
2.自定義中間件,處理文件輸出

public class OuterImgMiddleware { public static string RootPath { get; set; } //配置文件讀取絕對位置 private readonly RequestDelegate _next; public OuterImgMiddleware(RequestDelegate next, Microsoft.AspNetCore.Hosting.IHostingEnvironment env) { _next = next; } public async Task Invoke(HttpContext context) { var path = context.Request.Path.ToString(); var headersDictionary = context.Request.Headers; if (context.Request.Method == "GET" && !string.IsNullOrEmpty(path)) { if ( path.Contains("/upload/logo") || path.Contains("/upload/image") || path.Contains("/upload/ueimage") ) { var unauthorizedImagePath = RootPath + path; FileInfo file = new FileInfo(unauthorizedImagePath); if (file.Exists) { int length = path.LastIndexOf(".") - path.LastIndexOf("/") - 1; context.Response.Headers["Etag"] = path.Substring(path.LastIndexOf("/") + 1, length); context.Response.Headers["Last-Modified"] = new DateTime(2018).ToString("r"); context.Response.Headers["Accept-Ranges"] = "bytes"; //context.Response.Headers["Content-Length"] = file.Length.ToString(); if (path.EndsWith(".mp4")) { context.Response.ContentType = "video/mp4"; //分段讀取內容 DownloadRange download = new DownloadRange(context); download.WriteFile(unauthorizedImagePath); } else { context.Response.ContentType = "image/jpeg"; context.Response.Headers["Cache-Control"] = "public"; //指定客戶端,服務器都處理緩存 await context.Response.SendFileAsync(unauthorizedImagePath); } } return; } } await _next(context); } } public static class MvcExtensions { public static IApplicationBuilder UseOutImg(this IApplicationBuilder builder) { return builder.UseMiddleware<OuterImgMiddleware>(); } }
3. 在服務配置中 ConfigureServices,開啟同步讀取
//啟用允許同步讀取 services.Configure<KestrelServerOptions>(x => x.AllowSynchronousIO = true) .Configure<IISServerOptions>(x => x.AllowSynchronousIO = true);
4.在配置中 Configure,啟用中間件
app.UseOutImg();
更多:
EF Core Sequence contains no elements