.NET或.NET Core Web APi基于tus协议实现断点续传
前言
前两天我采用技巧式方案基本实现大文件分片上传,这里只是重点在于个人思路和亲身实践,若在实际生产环境要求比较高的话肯定不行,仍存在一些问题需要深入处理,本文继续在之前基础上给出基于tus协议的轮子方案,本打算再次尝试利用.NET Core实现此协议,但在github上一搜索早在2016年就已有此协议对应的.NET和.NET Core方案,并且一直更新到最近的.NET Core 3.x版本,完全满足各位所需,本文是我写出的一点demo。
基于tus协议实现断点续传演示
基于tus协议tusdotnet方案基本demo
关于此协议实现原理这里不做阐述,请参照上述github地址自行了解,本文只是给出.NET Core方案下的基本demo,我们上传一个大文件然后通过进度显示上传进度以及对上传可暂停可继续,专业点讲就是断点续传,首先肯定是引入tus脚本和需要用到的bootstrap样式,我们将进度条默认隐藏,当上传时才显示,所以我们给出如下HTML。
<div class="form-horizontal" style="margin-top:80px;">
<div class="form-group" id="progress-group" style="display:none;">
<div id="size"></div>
<div class="progress">
<div id="progress" class="progress-bar progress-bar-success progress-bar-animated progress-bar-striped" role="progressbar"
aria-valuemin="0" aria-valuemax="100">
<span id="percentage"></span>
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-10">
<input name="file" id="file" type="file" />
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" id="submit" value="上传" class="btn btn-success" />
<input type="button" id="pause" value="暂停" class="btn btn-danger" />
<input type="button" id="continue" value="继续" class="btn btn-info" />
</div>
</div>
</div>
接下来就是使用引入的tus脚本,也没什么太多要讲解的,直接上代码,这里稍微注意的是在如下元数据(metadata)属性对象定义给出实际文件名,便于在后台最终将上传的文件转换为目标文件,至少得知道文件扩展名,对吧。
<script type="text/javascript">
$(function () {
var upload; //上传
$('#submit').click(function () { $('#progress-group').show(); var file = $('#file')[0].files[0]; // 创建tus上传对象
upload = new tus.Upload(file, {
// 文件服务器上传终结点地址设置
endpoint: "files/",
// 重试延迟设置
retryDelays: [0, 3000, 5000, 10000, 20000],
// 附件服务器所需的元数据
metadata: {
name: file.name,
contentType: file.type || 'application/octet-stream',
emptyMetaKey: ''
},
// 回调无法通过重试解决的错误
onError: function (error) {
console.log("Failed because: " + error)
},
// 上传进度回调
onProgress: onProgress,
// 上传完成后回调
onSuccess: function () {
console.log("Download %s from %s", upload.file.name, upload.url)
}
}) upload.start()
}); //暂停
$('#pause').click(function () {
upload.abort()
}); //继续
$('#continue').click(function () {
upload.start()
}); //上传进度展示
function onProgress(bytesUploaded, bytesTotal) {
var percentage = (bytesUploaded / bytesTotal * 100).toFixed(2);
$('#progress').attr('aria-valuenow', percentage);
$('#progress').css('width', percentage + '%'); $('#percentage').html(percentage + '%'); var uploadBytes = byteToSize(bytesUploaded);
var totalBytes = byteToSize(bytesTotal); $('#size').html(uploadBytes + '/' + totalBytes);
} //将字节转换为Byte、KB、MB等
function byteToSize(bytes, separator = '', postFix = '') {
if (bytes) {
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
const i = Math.min(parseInt(Math.floor(Math.log(bytes) / Math.log(1024)).toString(), 10), sizes.length - 1);
return `${(bytes / (1024 ** i)).toFixed(i ? 1 : 0)}${separator}${sizes[i]}${postFix}`;
}
return 'n/a';
}
}); </script>
接下来进入后台,首先安装对应tus协议实现包,如下:
接下来则是添加tus中间件,说白了就是对tus的配置,各种配置都可满足你所需,这里我只实现了文件上传完成后将上传文件转换为目标文件的处理,紧接着将如下实现tus配置以单例形式注入即可
private DefaultTusConfiguration CreateTusConfiguration(IServiceProvider serviceProvider)
{
var env = (IWebHostEnvironment)serviceProvider.GetRequiredService(typeof(IWebHostEnvironment)); //文件上传路径
var tusFiles = Path.Combine(env.WebRootPath, "tusfiles"); return new DefaultTusConfiguration
{
UrlPath = "/files",
//文件存储路径
Store = new TusDiskStore(tusFiles),
//元数据是否允许空值
MetadataParsingStrategy = MetadataParsingStrategy.AllowEmptyValues,
//文件过期后不再更新
Expiration = new AbsoluteExpiration(TimeSpan.FromMinutes()),
//事件处理(各种事件,满足你所需)
Events = new Events
{
//上传完成事件回调
OnFileCompleteAsync = async ctx =>
{
//获取上传文件
var file = await ctx.GetFileAsync(); //获取上传文件元数据
var metadatas = await file.GetMetadataAsync(ctx.CancellationToken); //获取上述文件元数据中的目标文件名称
var fileNameMetadata = metadatas["name"]; //目标文件名以base64编码,所以这里需要解码
var fileName = fileNameMetadata.GetString(Encoding.UTF8); var extensionName = Path.GetExtension(fileName); //将上传文件转换为实际目标文件
File.Move(Path.Combine(tusFiles, ctx.FileId), Path.Combine(tusFiles, $"{ctx.FileId}{extensionName}"));
}
}
};
}
然后获取并使用上述添加的tus配置服务
app.UseTus(httpContext => Task.FromResult(httpContext.RequestServices.GetService<DefaultTusConfiguration>()));
在脚本中我们看到有个endpoint属性,此属性表示上传到服务器的上传结点地址,因为在上到服务器时我们可能需对此请求进行额外处理,比如元数据中的文件名是否已提供等等,所以我们在使用结点映射时,添加对上述结点名称的映射,如下:
endpoints.MapGet("/files/{fileId}", DownloadFileEndpoint.HandleRoute);
该映射第二个参数为RequestDelegate,这个参数用过.NET Core的童鞋都知道,这里我是直接拷贝该包的路由实现,如下:
public static class DownloadFileEndpoint
{
public static async Task HandleRoute(HttpContext context)
{
var config = context.RequestServices.GetRequiredService<DefaultTusConfiguration>(); if (!(config.Store is ITusReadableStore store))
{
return;
} var fileId = (string)context.Request.RouteValues["fileId"];
var file = await store.GetFileAsync(fileId, context.RequestAborted); if (file == null)
{
context.Response.StatusCode = ;
await context.Response.WriteAsync($"File with id {fileId} was not found.", context.RequestAborted);
return;
} var fileStream = await file.GetContentAsync(context.RequestAborted);
var metadata = await file.GetMetadataAsync(context.RequestAborted); context.Response.ContentType = GetContentTypeOrDefault(metadata);
context.Response.ContentLength = fileStream.Length; if (metadata.TryGetValue("name", out var nameMeta))
{
context.Response.Headers.Add("Content-Disposition",
new[] { $"attachment; filename=\"{nameMeta.GetString(Encoding.UTF8)}\"" });
} using (fileStream)
{
await fileStream.CopyToAsync(context.Response.Body, , context.RequestAborted);
}
} private static string GetContentTypeOrDefault(Dictionary<string, Metadata> metadata)
{
if (metadata.TryGetValue("contentType", out var contentType))
{
return contentType.GetString(Encoding.UTF8);
} return "application/octet-stream";
}
}
文件上传大小限制说明
我们知道无论是.NET还是.NET Core对于文件上传大小都有默认限制大小,这里对.NET Core中文件大小各种环境配置做一个统一说明,如果你将.NET Core寄宿在IIS上运行,那么请修改web.config配置文件大小限制
<system.webServer>
<security>
<requestFiltering>
//若不配置,默认是28.6兆
<requestLimits maxAllowedContentLength="" />
</requestFiltering>
</security>
</system.webServer>
如果在开发环境默认使用IIS运行应用程序,请通过如下根据实际情况配置文件上传大小
services.Configure<IISServerOptions>(options =>
{
options.MaxRequestBodySize = int.MaxValue;
});
如果程序运行在Kestrel服务器,那么请通过如下根据实际情况配置文件上传大小
services.Configure<KestrelServerOptions>(options =>
{
//若不配置,默认是30兆(没记错的话)
options.Limits.MaxRequestBodySize = int.MaxValue;
});
如果是通过表单上传文件,那么请通过如下根据实际情况配置文件上传大小
services.Configure<FormOptions>(x =>
{
x.ValueLengthLimit = int.MaxValue;
//如果不配置,默认是128兆(没记错的话)
x.MultipartBodyLengthLimit = int.MaxValue;
x.MultipartHeadersLengthLimit = int.MaxValue;
});
总结
为了更好体验可以再加上当前网络宽带情况或剩余多少分钟,更详细内容请参考:https://github.com/tusdotnet/tusdotnet、https://github.com/tus/tus-js-client,关于大文件上传处理到此结束,希望对那些苦苦寻找最终解决方案而无助的童鞋们提供最佳轮子,谢谢。
.NET或.NET Core Web APi基于tus协议实现断点续传的更多相关文章
- ASP.NET Core Web API下事件驱动型架构的实现(三):基于RabbitMQ的事件总线
在上文中,我们讨论了事件处理器中对象生命周期的问题,在进入新的讨论之前,首先让我们总结一下,我们已经实现了哪些内容.下面的类图描述了我们已经实现的组件及其之间的关系,貌似系统已经变得越来越复杂了. 其 ...
- 重温.NET下Assembly的加载过程 ASP.NET Core Web API下事件驱动型架构的实现(三):基于RabbitMQ的事件总线
重温.NET下Assembly的加载过程 最近在工作中牵涉到了.NET下的一个古老的问题:Assembly的加载过程.虽然网上有很多文章介绍这部分内容,很多文章也是很久以前就已经出现了,但阅读之后 ...
- ASP.NET Core Web API + Angular 仿B站(三)后台配置 JWT 的基于 token 的验证
前言: 本系列文章主要为对所学 Angular 框架的一次微小的实践,对 b站页面作简单的模仿. 本系列文章主要参考资料: 微软文档: https://docs.microsoft.com/zh-cn ...
- 在ASP.NET Core Web API上使用Swagger提供API文档
我在开发自己的博客系统(http://daxnet.me)时,给自己的RESTful服务增加了基于Swagger的API文档功能.当设置IISExpress的默认启动路由到Swagger的API文档页 ...
- 在docker中运行ASP.NET Core Web API应用程序
本文是一篇指导快速演练的文章,将介绍在docker中运行一个ASP.NET Core Web API应用程序的基本步骤,在介绍的过程中,也会对docker的使用进行一些简单的描述.对于.NET Cor ...
- ASP.NET Core Web API Cassandra CRUD 操作
在本文中,我们将创建一个简单的 Web API 来实现对一个 “todo” 列表的 CRUD 操作,使用 Apache Cassandra 来存储数据,在这里不会创建 UI ,Web API 的测试将 ...
- 在Mac下创建ASP.NET Core Web API
在Mac下创建ASP.NET Core Web API 这系列文章是参考了.NET Core文档和源码,可能有人要问,直接看官方的英文文档不就可以了吗,为什么还要写这些文章呢? 原因如下: 官方文档涉 ...
- ASP.NET Core Web API 开发-RESTful API实现
ASP.NET Core Web API 开发-RESTful API实现 REST 介绍: 符合REST设计风格的Web API称为RESTful API. 具象状态传输(英文:Representa ...
- Core Web API上使用Swagger提供API文档
在ASP.NET Core Web API上使用Swagger提供API文档 我在开发自己的博客系统(http://daxnet.me)时,给自己的RESTful服务增加了基于Swagger的AP ...
随机推荐
- 2019CSP-J T4 加工零件
题目描述 凯凯的工厂正在有条不紊地生产一种神奇的零件,神奇的零件的生产过程自然也很神奇.工厂里有 n 位工人,工人们从 1 ∼n 编号.某些工人之间存在双向的零件传送带.保证每两名工人之间最多只存在一 ...
- SQL语句 查询最新记录
要求:SQL语句按ID以最新时间查询最新的一条记录 方法1: select * from (select *, ROW_NUMBER() over(partition by id order by u ...
- 高效C++:实现
本章主要是解决如下问题: 类的声明和定义在什么时候提出 类与类之间的耦合关系如何降低 类型转换怎么正确使用 尽可能延后变量定义式的出现 变量用到时在定义,不要提前定义,防止变量定义而没有使用的情况,因 ...
- Java中lambda(λ)表达式的语法
举一个排序的例子,我们传入代码来检查一个字符串是否比另一个字符串短.这里要计算: first.length() - second.length() first和second是什么?他们都是字符串.Ja ...
- 题解 洛谷 P6640 【[BJOI2020] 封印】
设\(lenth_i\)为\(s\)在\(i\)位置的前缀的后缀为\(t\)的一个子串的最长长度,即为从\(i\)位置开始往前和\(t\)的最长公共子串长度.其可以通过对\(t\)建后缀自动机,然后让 ...
- 面试高频SpringMVC执行流程最优解(源码分析)
文章已托管到GitHub,大家可以去GitHub查看阅读,欢迎老板们前来Star! 搜索关注微信公众号 码出Offer 领取各种学习资料! SpringMVC执行流程 SpringMVC概述 Spri ...
- vue------反响代理
//测试项目 https://i.cnblogs.com/Files.aspx
- 前端学习(八):CSS
进击のpython ***** 前端学习--CSS 现在的互联网前端分为三层: HTML:超文本标记语言.从语义的角度描述页面结构 CSS:层叠样式表.从审美的角度负责页面样式 JS:Javascri ...
- 读懂操作系统之快表(TLB)原理(七)
前言 前不久.我们详细分析了TLB基本原理,本节我们通过一个简单的示例再次叙述TLB的算法和原理,希望借此示例能加深我们对TLB(又称之为快表,深入理解计算机系统(第三版)又称之为翻译后备缓冲区)的理 ...
- Android复习准备
1. 四大组件是什么? Activity(活动):用于表现功能 Service(服务):后台运行服务,不提供界面呈现 BroadcastReceiver(广播接收器):用来接收广播 ContentPr ...