.Net Core 实现 自定义Http的Range输出实现断点续传或者分段下载
一、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