zoukankan      html  css  js  c++  java
  • .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 新特性整理 

  • 相关阅读:
    Dubbo简介---搭建一个最简单的Demo框架
    git学习总结
    FastJson对于JSON格式字符串、JSON对象及JavaBean之间的相互转换
    Spring AOP实现Mysql数据库主从切换(一主多从)
    Mybatis中int insertSelective()的相关问题
    主从数据库读写分离知识
    IoC理解
    AOP理解
    MyBatis中mybatis-generator代码生成的一般过程
    fread 快速读入
  • 原文地址:https://www.cnblogs.com/tianma3798/p/13445111.html
Copyright © 2011-2022 走看看