有趣的是,关于浏览器下载行为,在 MDN 上找不到完整的说明文档,只能结合各个相关知识点组合在一起。
基础知识
Blob,File,ObjectURL
- Blob 对象表示一个不可变、原始数据的类文件对象。
- 文件(File)接口提供有关文件的信息,并允许网页中的 JavaScript 访问其内容。File 对象是特殊类型的 Blob。
objectURL 对象表示指定的 File 对象或 Blob 对象。
Fetch,Response
Fetch API 提供了一个获取资源的接口(包括跨域请求)。
Fetch API 的 Response 接口呈现了对一次请求的响应数据。
HTTP Headers,Content-Type,Content-Disposition
Content-Type 实体头部用于指示资源的MIME类型。
- MIME 类型现在又称为 media type,有时也被称为 content type,是指示文件类型的字符串。
- 当 content-type 为 application/octet-stream 时,意味这下载这个文件。
- Content-Disposition 响应头指示回复的内容该以何种形式展示,是以内联的形式(即网页或者页面的一部分),还是以附件的形式下载并保存到本地。
实现方式
GET: 标签
使用 GET 方式获取的内容或者文件,直接使用 标签即可。
需要指定 href 属性和 download 属性。href 属性是内容的超链接,download 表明链接的资源将被下载,而不是显示在浏览器中。该值表示下载文件的建议名称。
有趣的是,在 MDN 文档中对 download 补充了一条说明:
注意: 该值对于下载行为来说不一定是有用的,同时也不能决定下载行为是否发生。 https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLAnchorElement/download
这就要涉及到 Http Headers 中的 Content-Disposition 了。我们用一个对比实验来说明:
Content-Disposition | + download | + download=a-name.csv | |
---|---|---|---|
empty | 不下载 | 下载 [href].txt | 下载 a-name.csv |
inline | 不下载 | 下载 [href].txt | 下载 a-name.csv |
attachment | 下载 [href].txt | 下载 [href].txt | 下载 a-name.csv |
attachment; filename=header-name.csv | 下载 header-name.csv | 下载 header-name.csv | 下载 header-name.csv |
从实验结果可以看出,无论是是否下载,还是下载文件名,Content-Disposition 的优先级都高于 标签。
这里继续补充一点 Content-Type 与 Content-Disposition 的关系:
- Content-Type 应该用于指定文件本身的类型。特别的:application/octet-stream 不应该用于表达下载行为,应该表达的是:我不清楚这个文件的类型是什么,用户可以用于下载。
- Content-Disposition 应该用于指定文件的下载行为。
POST:Fetch Response -> Blob -> ObjectURL -> 标签
本质上还是 标签下载,但由于 POST 的参数不能直接作为 href 属性,因此如标题所言的一个转换流程。
用代码表示如下:
async function postDownload(url) {
// 获取 POST 接口数据,Response
const res = await fetch(url);
// 从 Response 中获取 filename
const filename = res.headers.get("content-disposition").split("filename=")[1];
// 从 Response 中获取 file content,并转为 Blob
// 也可以写成 const blob = new Blob([await res.text()]);
const blob = await res.blob();
// 将 Blob 转为 ObjectURL
const blobURL = URL.createObjectURL(blob);
// 动态创建一个 <a> 标签,触发 click 事件后删除
const a = document.createElement("a");
a.href = blobURL;
a.download = filename;
document.body.appendChild(a);
a.click();
a.remove();
// 内存管理,释放
URL.revokeObjectURL(blobURL);
}
参考资料
- Blob https://developer.mozilla.org/zh-CN/docs/Web/API/Blob
- File https://developer.mozilla.org/zh-CN/docs/Web/API/File
- URL.createObjectURL https://developer.mozilla.org/zh-CN/docs/Web/API/URL/createObjectURL
- Fetch API https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API
- Response https://developer.mozilla.org/zh-CN/docs/Web/API/Response
- Content-Type https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Type
- Content-Disposition https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Disposition
- https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/a
- HTMLAnchorElement https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLAnchorElement
- [Stack Overflow] Do I need Content-Type: application/octet-stream for file download? https://stackoverflow.com/questions/20508788/do-i-need-content-type-application-octet-stream-for-file-download