前端流式下载两种方式

3,684 阅读3分钟

前言

在正常开发中,遇到下载文件的场景。

比如:

  1. 使用a标签或者url进行下载,有时候会跳转预览,有时候会是直接下载。为什么会出现这样?
  2. 使用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,浏览器会进行预览

image.png

设置响应头:

ctx.set('Content-Disposition', `attachment; filename=${fileName}`);

image.png

直接下载。

Content-disposition是MIME协议的扩展,MIME协议指示MIME用户代理如何显示附加的文件。当Internet Explorer接收到头时,他会激活文件下载对话框,它的文件名框自动填充headers指定的文件名。

使用a标签

<a href="http://localhost:3000/download">下载文件</a>

注意:

a标签的download属性,必须是同源URL,将链接的 URL 视为下载资源。可以使用或不使用 filename 值:

  • 如果没有指定值,浏览器会从多个来源决定文件名和扩展名:

  • filename:决定文件名的值。/ 和 `` 被转化为下划线(_)。文件系统可能会阻止文件名中其他的字符,因此浏览器会在必要时适当调整文件名。

将服务响应头设置为流

ctx.set('Content-Type', 'application/octet-stream');

直接下载,名称直接为download,没有后缀

image.png

如果将接口改成download.pdf,则取接口为文件后缀

image.png

如果定义了Content-Disposition,则取服务器设置的名称:

 const fileName = 'file.pdf'; // 下载时显示的文件名

    // 设置响应头
    ctx.set('Content-Disposition', `attachment; filename=${fileName}`);

image.png

注意,请求响应非常快,如下所示:

image.png

不需要经过浏览器,直接向文件输入内容,如下图所示:

image.png

弊端:如果需要进行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);
    }
});

读取流需要等待传输完成,需要经过浏览器,下载时间长。

image.png

对比两种方式:

image.png

总结

下面总结一下:

服务器端处理:

  • 优点

    • 可以通过设置响应头来精确控制下载行为,如使用 Content-Disposition 控制文件名和下载方式。
    • 无需浏览器进行处理
  • 缺点

    • 可能会导致服务器的负载增加,特别是对于大文件或高并发下载请求。
    • 无法进行信息验证

客户端处理:

  • 优点

    • 简单易用,无需服务器端额外处理即可实现文件下载。
    • 可以通过 JavaScript 控制下载行为,如使用 fetch API 实现流式下载。
  • 缺点

    • 需要经过浏览器来处理下载流,可能会导致下载时间较长,特别是对于大文件。

根据具体的需求和场景,我们可以选择合适的方式来处理文件下载。

如有错误,请指正O^O!