因前几天碰到一个node转发Java请求并返回二进制流给前端,前端取不到服务端返回的header头 Content-Disposition 文件名。在做这个内容的时候,由于接触node的时间过短,走了很多弯路,查了好多,总算是搞出来了。特此写一篇文章来纪念一下。
node核心代码:
async download() {
const ctx = this.ctx;
const result = await ctx.curl(api.download, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: {
...ctx.request.body,
},
});
ctx.set({
'Access-Control-Expose-Headers': 'Content-Disposition',//核心配置
'Content-Type': 'application/octet-stream',//核心配置
...result.headers,
});
ctx.body = result.data;
}
前端之所以一直取不到header头返回的Content-Disposition是因为没有配置'Access-Control-Expose-Headers'。重要的事情标注了,不讲三遍啦!
前端代码:
使用react+umi+dva实现:
server.js
export async function download(params) {
return fetch('/download', {
method: 'POST',
credentials: 'include',
headers: {
'content-type': 'application/json',
},
body: JSON.stringify(params),
}).then(res => {
return res.blob().then(blob =>
Promise.resolve({
blob,
res,
}),
);
});
}
model.js
import { saga } from 'dva';
const { call, select, put } = saga.effects;
const ModelPage = {
namespace: 'history',
effects: {
*download({ payload }) {
const { blob, res } = yield call(download, payload);
let blobUrl = window.URL.createObjectURL(blob);
//console.log(res.headers.get('content-disposition'));
let filename = res.headers.get('content-disposition').split('filename=')[1];
filename = filename.substring(1, filename.length - 1);
const tagA = document.createElement('a');
tagA.href = blobUrl;
tagA.download = filename;
tagA.click();
// window.URL.revokeObjectURL(blobUrl);
},
}
}
export default ModelPage;
结论
1. 无论下载什么链接,异步请求都无法直接下载,必须通过a标签或打开新窗口进行下载
(1)a标签设置download属性,可以直接下载普通链接能够访问的文件
(2)响应头设置'content-type': 'application/json'
2. node转发服务端将文件流返回给前端的时候,要设置请求头
'Access-Control-Expose-Headers': 'Content-Disposition',//核心配置
'Content-Type': 'application/octet-stream',//核心配置
3.在返回文件流的时服务端一定要在请求头带上文件流的字节长度(Content-Length)!!切记!切记!切记!
否则,前端要么下载文件失败,要么下载下来的文件无法打开。