.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>();
        }
    }
View Code

 

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 

.Net Core3 新特性整理 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM