一、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

.Net Core3 新特性整理

.Net Core 实现 自定义Http的Range输出实现断点续传或者分段下载的更多相关文章

  1. ASP.NET Core MVC – 自定义 Tag Helpers

    ASP.NET Core Tag Helpers系列目录,共四篇: ASP.NET Core MVC Tag Helpers 介绍 ASP.NET Core MVC – Caching Tag Hel ...

  2. 如何在ASP.NET Core中自定义Azure Storage File Provider

    文章标题:如何在ASP.NET Core中自定义Azure Storage File Provider 作者:Lamond Lu 地址:https://www.cnblogs.com/lwqlun/p ...

  3. ASP.NET Core Identity自定义数据库结构和完全使用Dapper而非EntityFramework Core

    前言 原本本节内容是不存在的,出于有几个人问到了我:我想使用ASP.NET Core Identity,但是我又不想使用默认生成的数据库表,想自定义一套,我想要使用ASP.NE Core Identi ...

  4. .Net Core IIS下无Log4Net日志输出,命令行下却有(dotnet运行)

    .Net Core IIS下无Log4Net日志输出,命令行下却有(dotnet运行) 遇到个诡异的问题,项目发布并寄宿到 IIS上后,Log4Net没有日志输出 1.原因分析 这不应该啊,所有的配置 ...

  5. ASP.NET Core AutoWrapper 自定义响应输出

    前言 AutoWrapper是一个简单可自定义全局异常处理程序和ASP.NET Core API响应的包装.他使用ASP.NET Core middleware拦截传入的HTTP请求,并将最后的结果使 ...

  6. asp.net core如何自定义端口/修改默认端口

    .net core运行的默认端口是5000,但是很多时候我们需要自定义端口.有两种方式 写在Program的Main方法里面 添加 .UseUrls() var host = new WebHostB ...

  7. ASP.NET Core中自定义路由约束

    路由约束 ASP.NET Core中,通过定义路由模板,可以在Url上传递变量,同时可以针对变量提供默认值.可选和约束. 约束的使用方法是在属性路由上添加指定的约束名,用法如下: // 单个使用 [R ...

  8. asp.net core razor自定义taghelper

    又一个新的名词(taghelper),这个名词在netcore razor中也替代了(Htmlhelper),通过taghelper是可以操作html标签.条件输出.更是自由添加内外元素.当然也内置了 ...

  9. ASP.NET Core 中间件自定义全局异常处理

    目录 背景 ASP.NET Core过滤器(Filter) ASP.NET Core 中间件(Middleware) 自定义全局异常处理 .Net Core中使用ExceptionFilter .Ne ...

随机推荐

  1. java中的静态内部类

    静态内部类是 static 修饰的内部类,这种内部类的特点是: 1. 静态内部类不能直接访问外部类的非静态成员,但可以通过 new 外部类().成员 的方式访问 2. 如果外部类的静态成员与内部类的成 ...

  2. jQuery中ajax请求的六种方法(三、六):load()方法

    6.load()方法 load的html页面 <!DOCTYPE html> <html> <head> <meta charset="UTF-8& ...

  3. cookie实现访问时间查看

    package day01.cookies; import java.io.IOException; import java.net.URLDecoder; import java.net.URLEn ...

  4. HTTP状态码:3XX

    3XX状态码 含义:表明浏览器需要执行某些特殊的处理以正确的处理请求,大部分都需要进行重定向.这类状态码代表需要客户端采取进一步的操作才能完成请求.通常,这些状态码用来重定向,后续的请求地址(重定向目 ...

  5. 聊聊spring事务失效的12种场景,太坑了

    前言 对于从事java开发工作的同学来说,spring的事务肯定再熟悉不过了. 在某些业务场景下,如果一个请求中,需要同时写入多张表的数据.为了保证操作的原子性(要么同时成功,要么同时失败),避免数据 ...

  6. JDK1.8源码阅读笔记(2) AtomicInteger AtomicLong AtomicBoolean原子类

    JDK1.8源码阅读笔记(2) AtomicInteger AtomicLong AtomicBoolean原子类 Unsafe Java中无法直接操作一块内存区域,不能像C++中那样可以自己申请内存 ...

  7. K8S集群架构的组件组成

    1.Master--主控节点 (1)apiserver:集群统一入口,以restful的方式,交给etcd存储 (2)scheduler:节点调度,选择node节点应用部署 (3)controller ...

  8. Python常见问题 - python3 使用requests发送HTTPS请求报certificate verify failed 错误

    当你使用 requests 发送HTTPS请求时 requests.get(url, parmas=parmas, headers=header, cookies=cookie) 出现了以下错误 HT ...

  9. Pytest系列(12)- 测试结果生成HTML报告插件之pytest-html的详细使用

    如果你还想从头学起Pytest,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1690628.html 环境前提 Python3.6+ 安 ...

  10. MyBatis的多表查询笔记

    MyBatis的多表查询 随着学习的进步,需求的提高,我们在实际开发中用的最多的还是多表查询,就让我们一起学习MyBatis中的多表查询. 数据库准备 Class表 Student表 项目结构 这次使 ...