.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 ...
随机推荐
- WPF原理剖析——路由事件
一.路由事件与传统事件传统事件的触发者和处理者是紧密相连的,而路由事件则不是,路由事件允许一个元素的事件有另外的元素触发.也即就是说路由事件的拥有者和响应者之间没有显示的订阅关系.事件的拥有者只负责激 ...
- PE文件结构(32/64差异)
1 基本概念 下表描述了贯穿于本文中的一些概念: 名称 描述 地址 是"虚拟地址"而不是"物理地址".为什么不是"物理地址"呢?因为数据在内 ...
- Object--Date--calendar--System--StringBuilder--基本数据类型包装类型
Object java.lang.Object类是Java语言中的根类,即所有类的父类 默认toString()方法打印的是对象在堆中的地址值 默认equals()方法比较的也是地址(String中对 ...
- AOP的底层实现-CGLIB动态代理和JDK动态代理
AOP是目前Spring框架中的核心之一,在应用中具有非常重要的作用,也是Spring其他组件的基础.它是一种面向切面编程的思想.关于AOP的基础知识,相信多数童鞋都已经了如指掌,我们就略过这部分,来 ...
- 网络流量预测入门(三)之LSTM预测网络流量
目录 网络流量预测入门(三)之LSTM预测网络流量 数据集介绍 预测流程 数据集准备 SVR预测 LSTM 预测 优化点 网络流量预测入门(三)之LSTM预测网络流量 在上篇博客LSTM机器学习生成音 ...
- 从新建文件夹开始构建ShadowPlay Engine(5)
本篇序言 从本篇开始,我们要开始构建引擎核心中的系统组件部分,广义上讲其实我们从开始到现在一直都是在构建引擎核心中的系统部分,但严格的定义中系统组件大概有这么几个:内存管理,线程管理,文件管理,时间系 ...
- Longhorn 云原生容器分布式存储 - Python Client
内容来源于官方 Longhorn 1.1.2 英文技术手册. 系列 Longhorn 是什么? Longhorn 云原生容器分布式存储 - 设计架构和概念 Longhorn 云原生容器分布式存储 - ...
- kubernetes部署一个应用程序
文章原文 部署 nginx Deployment 如果你已经完成了Kubernetes的搭建,那我跟我一块来部署第一个应用程序吧.没有完成 Kubernetes 集群搭建的,请参考文档 使用 kube ...
- noip模拟41
A. 你相信引力吗 很明显的单调栈的题,考场上没有想到平移最大值,而是想着复制一倍序列破环成链,然后发现最大值的部分特别难维护,而且耗费时间过长,只好牺牲时间复杂度加了个 \(map\) 去重. 首先 ...
- [考试总结]noip模拟43
这个题目出的还是很偷懒.... 第一题...第二题...第三题...四.... 好吧... 这几次考得都有些问题,似乎可能是有些疲惫,脑袋也是转不太动,考完总觉得自己是能力的问题,但是改一分钟之后会发 ...