.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
.Net Core 实现 自定义Http的Range输出实现断点续传或者分段下载的更多相关文章
- ASP.NET Core MVC – 自定义 Tag Helpers
ASP.NET Core Tag Helpers系列目录,共四篇: ASP.NET Core MVC Tag Helpers 介绍 ASP.NET Core MVC – Caching Tag Hel ...
- 如何在ASP.NET Core中自定义Azure Storage File Provider
文章标题:如何在ASP.NET Core中自定义Azure Storage File Provider 作者:Lamond Lu 地址:https://www.cnblogs.com/lwqlun/p ...
- ASP.NET Core Identity自定义数据库结构和完全使用Dapper而非EntityFramework Core
前言 原本本节内容是不存在的,出于有几个人问到了我:我想使用ASP.NET Core Identity,但是我又不想使用默认生成的数据库表,想自定义一套,我想要使用ASP.NE Core Identi ...
- .Net Core IIS下无Log4Net日志输出,命令行下却有(dotnet运行)
.Net Core IIS下无Log4Net日志输出,命令行下却有(dotnet运行) 遇到个诡异的问题,项目发布并寄宿到 IIS上后,Log4Net没有日志输出 1.原因分析 这不应该啊,所有的配置 ...
- ASP.NET Core AutoWrapper 自定义响应输出
前言 AutoWrapper是一个简单可自定义全局异常处理程序和ASP.NET Core API响应的包装.他使用ASP.NET Core middleware拦截传入的HTTP请求,并将最后的结果使 ...
- asp.net core如何自定义端口/修改默认端口
.net core运行的默认端口是5000,但是很多时候我们需要自定义端口.有两种方式 写在Program的Main方法里面 添加 .UseUrls() var host = new WebHostB ...
- ASP.NET Core中自定义路由约束
路由约束 ASP.NET Core中,通过定义路由模板,可以在Url上传递变量,同时可以针对变量提供默认值.可选和约束. 约束的使用方法是在属性路由上添加指定的约束名,用法如下: // 单个使用 [R ...
- asp.net core razor自定义taghelper
又一个新的名词(taghelper),这个名词在netcore razor中也替代了(Htmlhelper),通过taghelper是可以操作html标签.条件输出.更是自由添加内外元素.当然也内置了 ...
- ASP.NET Core 中间件自定义全局异常处理
目录 背景 ASP.NET Core过滤器(Filter) ASP.NET Core 中间件(Middleware) 自定义全局异常处理 .Net Core中使用ExceptionFilter .Ne ...
随机推荐
- springboot中@Mapper和@Repository的区别
@Mapper和@Repository是常用的两个注解,两者都是用在dao上,两者功能差不多,容易混淆,有必要清楚其细微区别: 区别: @Repository需要在Spring中配置扫描地址,然后生成 ...
- Semaphore 类 的使用理解C#
示例 下面的代码示例创建一个信号量,其最大计数为3,初始计数为零. 该示例启动五个线程,这会阻止等待信号量. 主线程使用 Release(Int32) 方法重载将信号量计数增加到其最大值,从而允许三个 ...
- JAVA中的clone方法剖析
原文出自:http://blog.csdn.net/shootyou/article/details/3945221 java中也有这么一个概念,它可以让我们很方便的"制造"出一个 ...
- 解决servlet中get方式中中文乱码问题前驱(一):装饰者模式再理解
package day02; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; ...
- 《网页布局基础篇》—浮动布局和float属性
浮动布局 <html> <head> <meta charset="utf-8"> <title>浮动</title> ...
- 从kratos分析breaker熔断器源码实现
为什么要用熔断 前面我们讲过限流保证服务的可用性,不被突如其来的流量打爆.但是两种情况是限流解决不了的. 如果我们服务只能处理1000QPS,但是有10wQPS打过来,服务还是会炸.因为拒绝请求也需要 ...
- 并发编程之:BlockingQueue
大家好,我是小黑,一个在互联网苟且偷生的农民工. 队列 学过数据结构的同学应该都知道,队列是数据结构中一种特殊的线性表结构,和平时使用的List,Set这些数据结构相比有点特殊,它的特殊之处在于它只允 ...
- Docker(40)- docker 实战三之安装 ES+Kibana
背景 参考了狂神老师的 Docker 教程,非常棒! https://www.bilibili.com/video/BV1og4y1q7M4?p=16 es 前言 es 暴露的端口很多 es 十分耗内 ...
- Winform EF CodeFist方式连接数据库
直接生成ado.net 实体数据模型挺方便的,但只有一步步的手写代码才能更好的理解EF,在学习asp.net core过程中手写代码已经明白了怎么回事,但实现过程有些麻烦不知道如何记录,但Winfor ...
- 常量&&变量
一.常量:程序运行期间不变 常量使用: 二.八大基本数据类型变量: 其中,一个Byte类型数据占八个bit位,所以范围是(-2^7 ~ 2^7-1),数据最小的存储单位,是四分之一的int类型空间,所 ...