前言
在正常开发中,遇到下载文件的场景。
比如:
- 使用a标签或者url进行下载,有时候会跳转预览,有时候会是直接下载。为什么会出现这样?
- 使用axios或者fetch下载时,浏览器会等待流传输,需要等待一段时间。有没有直接下载不需要等待流全部传输完成?
1. 下载方式
下载有两种处理方式,一种是服务器端处理,一种是客户端处理。下面通过案例说明两种方式的区别。
1.1 服务器方式
可以使用koa搭建一个简单的服务器:
代码如下:
const Koa = require('koa');
const Router = require('koa-router');
const fs = require('fs');
const path = require('path');
const cors = require('koa-cors')
const app = new Koa();
const router = new Router();
app.use(cors())
router.get('/download', async (ctx) => {
const filePath = path.join(__dirname, 'files', 'file.pdf');
const fileName = 'file.pdf'; // 下载时显示的文件名
// 设置响应头
// ctx.set('Content-Disposition', `attachment; filename=${fileName}`);
// ctx.set('Content-Disposition', `inline; filename=${fileName}`);
// 响应设置为pdf
ctx.set('Content-Type', 'application/pdf');
// 创建文件读取流
const fileStream = fs.createReadStream(filePath);
// 将文件流传输到响应体
ctx.body = fileStream;
});
app.use(router.routes()).use(router.allowedMethods());
const PORT = 5500;
app.listen(PORT, () => {
console.log(`服务器在 http://localhost:${PORT} 运行`);
});
直接访问http://localhost:5500/download,浏览器会进行预览
设置响应头:
ctx.set('Content-Disposition', `attachment; filename=${fileName}`);
直接下载。
Content-disposition是MIME协议的扩展,MIME协议指示MIME用户代理如何显示附加的文件。当Internet Explorer接收到头时,他会激活
文件下载对话框,它的文件名框自动填充headers指定的文件名。
使用a标签
<a href="http://localhost:3000/download">下载文件</a>
注意:
a标签的download属性,必须是同源URL,将链接的 URL 视为下载资源。可以使用或不使用 filename 值:
-
如果没有指定值,浏览器会从多个来源决定文件名和扩展名:
Content-DispositionHTTP 标头。- URL 路径的最后一段。
- 媒体类型。来自
Content-Type标头,data:URL 的开头,或blob:URL 的Blob.type。
-
filename:决定文件名的值。/和 `` 被转化为下划线(_)。文件系统可能会阻止文件名中其他的字符,因此浏览器会在必要时适当调整文件名。
将服务响应头设置为流
ctx.set('Content-Type', 'application/octet-stream');
直接下载,名称直接为download,没有后缀
如果将接口改成download.pdf,则取接口为文件后缀
如果定义了Content-Disposition,则取服务器设置的名称:
const fileName = 'file.pdf'; // 下载时显示的文件名
// 设置响应头
ctx.set('Content-Disposition', `attachment; filename=${fileName}`);
注意,请求响应非常快,如下所示:
不需要经过浏览器,直接向文件输入内容,如下图所示:
弊端:如果需要进行token验证,就无法在请求头中携带验证信息。
1.2 客户端方式
使用 fetch为例,进行流式文件下载并保存到客户端,可以通过 Response 对象的 body 属性来实现,它返回一个 ReadableStream。
下面是一个完整的示例,通过 fetch 进行流式文件下载,并使用 Blob 对象在客户端生成和下载文件。
const download = async () => {
const url = 'http://localhost:3000/download'; // 替换为你的文件下载URL
try {
const response = await fetch(url,{headers:{token:xxx}});
if (!response.ok) throw new Error('Network response was not ok');
const blob = await response.blob();
const urlBlob = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = urlBlob;
a.download = fileName.replace(/['"]/g, '');
document.body.appendChild(a);
a.click();
a.remove();
window.URL.revokeObjectURL(urlBlob);
} catch (error) {
console.error('Download failed:', error);
}
});
读取流需要等待传输完成,需要经过浏览器,下载时间长。
对比两种方式:
总结
下面总结一下:
服务器端处理:
-
优点:
- 可以通过设置响应头来精确控制下载行为,如使用
Content-Disposition控制文件名和下载方式。 - 无需浏览器进行处理
- 可以通过设置响应头来精确控制下载行为,如使用
-
缺点:
- 可能会导致服务器的负载增加,特别是对于大文件或高并发下载请求。
- 无法进行信息验证
客户端处理:
-
优点:
- 简单易用,无需服务器端额外处理即可实现文件下载。
- 可以通过 JavaScript 控制下载行为,如使用
fetchAPI 实现流式下载。
-
缺点:
- 需要经过浏览器来处理下载流,可能会导致下载时间较长,特别是对于大文件。
根据具体的需求和场景,我们可以选择合适的方式来处理文件下载。
如有错误,请指正O^O!