.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 ...
随机推荐
- P1092 虫食算(洛谷)
今天做了一道题,我之前吹牛的时候曾经说:“这个题我觉得深搜剪枝一下就可以了.”. 我觉得我之前说的没错“这个题深搜剪枝亿下,再加点玄学就可以了!” 题目描述 所谓虫食算,就是原先的算式中有一部分被虫子 ...
- 【C#】根据开始时间和结束时间筛选存在的信息
背景 业务需求中,需要根绝开始时间和结束时间筛选一段时间内的任务存在个数. 示例图片 根据开始时间 9:00到 结束时间11:00 筛选信息 总共有这么四种情况可能出现 插入测试数据 CREATE T ...
- Java Properties集合基础解析
Java Properties集合基础解析 本期学习的properties集合是项目中经常用到的操作 什么是Properties集合? java.util.Properties集合继承于Hashtab ...
- django-rest-framework-源码解析003-视图家族和路由(APIView/GenericAPIView/mixins/generics/viewsets)
视图家族 视图家族在rest_framework源码位置和学习曲线为: rest_framework.views: 基本视图(APIView) rest_framework.generics: 工具视 ...
- C#串模板
c# 6.0 的语言特性,功能类似string.formate,更方便的地方在于不要像format一样使用索引,可以直接使用变量. 使用方法如下: string name = "zhangs ...
- Python程序设计(第3版)PDF高清完整版免费下载|百度网盘
百度网盘:Python程序设计(第3版)PDF高清完整版免费下载 提取码:48u4 内容简介 本书是面向大学计算机科学专业第一门程的教材.本书以Python语言为工具,采用相当传统的方法,强调解决问题 ...
- intellij IDEA导入maven项目
一.导入maven项目 1.打开intellij idea,点击File(如下图1),然后点击Open(如下图2)
- form表单两种提交方式的不同
我们在使用<Form>表单的时候,最常用的提交方式就是Get和Post.我们都知道这两种方式最大的差别就是安全性,除此之外,它们还有哪些其他的区别,你知道吗? 在<Form& ...
- BuuCTF Web Writeup
WarmUp index.php <html lang="en"> <head> <meta charset="UTF-8"> ...
- PHP date_sunset() 函数
------------恢复内容开始------------ 实例 返回葡萄牙里斯本今天的日落时间: <?php// Lisbon, Portugal:// Latitude: 38.4 Nor ...