HTTP响应中带有Content-Disposition头信息时,不会触发下载。

5
我使用 msw 来模拟后端响应,以进行自动化前端测试。
我想为用户创建的数据创建导出功能。我正在向所需路由 POST 请求,并希望返回一个包含 .csv; .pdf;.xls 文件的响应,应该自动下载。
我一直在寻找适当的方法来实现这一功能,然后发现了 Content-Disposition 响应头。
不幸的是,响应没有触发保存对话框,也没有启动下载,但我找不到错误所在。

EDIT: After having a few conversations and the somewhat unique circumstances, I settled for creating a ObjectURL for the Blob-Data generated, and force a download with the window.open() call.

I changed the expected Response in my Handlers and added a corresponding call in my FE.

handlers.js

rest.post('/mock/things/:thingId/export', async (req, res, ctx) => {
  const docType = req.headers.get('Content-type');
  const startDate = req.url.searchParams.get('startDate');
  const endDate = req.url.searchParams.get('endDate');
  const selected = req.body;

  const resDataString = 'testName,testLastName,\r\nDaniel,Schaller';
  const blob = new Blob([resDataString], { type: docType });
  const buffer = await blob.arrayBuffer();

  return res(
    ctx.status(200),
    ctx.set({
      'content-type': `${docType}`,
      'Content-length': buffer.byteLength.toString(),
    }),
    ctx.body(buffer),
    ctx.delay(),
  );
}),

store-module.js

async exportThings(ctx, exportData) {
  const {
    docType, startDate, endDate, selected,
  } = exportData;

  const res = await services.exportThingsForStuff(ctx.state.id, docType, startDate, endDate, selected);

  if (res.data !== undefined) {
    const dataBLOB = new Blob([res.data], { type: res.headers['content-type'] });
    window.open(URL.createObjectURL(dataBLOB));
  }
},

让我给你展示一下工作流程:

some-component.vue

async submitExport() {
  const exportDataFilters = {
    docType: this.docType,
    startDate: this.startDate,
    endDate: this.endDate,
    selected: this.selected,
  };

  const response = await this.$store.dispatch('exportThings', exportDataFilters);
  console.log(response);
},

store-module.js

async exportThings(ctx, exportData) {
  const {
    docType, startDate, endDate, selected,
  } = exportData;

  return services.exportThingsForStuff(ctx.state.id, docType, startDate, endDate, selected);
},

services.js

export const exportThingsForStuff = async (thingsId, docType, startDate, endDate, pl) => axios.post(`/mock/things/${thingsId}/export`, pl, {
  headers: {
    'Content-type': docType,
  },
  params: {
    startDate,
    endDate,
  },

最后是来自handlers.js的MSW响应函数

rest.post('/mock/things/thingId/export', (req, res, ctx) => {
  // Vars will be in use to mimic filter behavior, as soon as the download problem is solved
  const docType = req.headers.get('Content-type');
  const startDate = req.url.searchParams.get('startDate');
  const endDate = req.url.searchParams.get('endDate');
  const selected = req.body;


  
// Creating a test string and blobbing it.
const resDataString = 'testName,testLastName,\r\nJoe,Moe';
const blob = new Blob([resDataString], { type: 'text/csv' });

return res(
  ctx.status(200),
  ctx.set({
    'Content-Disposition': 'attachment; filename="things_export.csv"',
    'Content-Length': blob.size.toString(),
    'Content-Transfer-Encoding': 'binary',
    'Content-Type': 'text/csv',
  }),
  ctx.body(blob),
  ctx.delay(),
);

我找不到问题所在,因为响应看起来像预期的一样:

Request URL: http://localhost:8080/mock/things/101/export?startDate=&endDate=
Request Method: POST
Status Code: 200 OK (from service worker)
Referrer Policy: strict-origin-when-cross-origin

RESPONSE HEADERS
content-disposition: attachment; filename="things_export.csv"
content-length: 37
content-transfer-encoding: binary
content-type: text/csv
x-powered-by: msw

你能否尝试将文本发送到 ctx.body 而不是 Blob 中? - kettanaito
@kettanaito 我已经尝试了几乎所有方法。HTML、纯文本、序列化的HTML和二进制对象都试过了。但是不幸的是结果并没有改变。每种情况下我都能够得到正确的数据响应,但下载并没有像预期的那样触发。 - D.Schaller
请尝试在配置axios POSTexportThingsForStuff方法时提供responseType:'blob',如下所示:export const exportThingsForStuff = async (thingsId, docType, startDate, endDate, pl) => axios.post(/mock/things/${thingsId}/export, pl, { headers: { 'Content-type': docType, }, params: { startDate, endDate, }, responseType: 'blob'。请您尝试一下? - jccampanero
@miknik 不起作用。而且与 Content-Disposition: attachment 结合使用时,application/octet-stream 应该不是必需的。由于我知道响应数据的给定数据类型,所以在我看来,application/octet-stream 不是正确的选择。 - D.Schaller
@D.Schaller,老实说我从未使用过那个模拟库,但是你是否尝试过测试返回二进制信息的这种方法 - jccampanero
显示剩余7条评论
2个回答

0

我认为这里的问题与这个答案有关

我猜这是浏览器的一个特性...

我也不喜欢用URL.createObjectURL(blob)下载。
看来我得使用老式的HTML表单元素或HTTP get。

非常感谢你的精确描述。
我遇到了同样的问题,你的问题引导我找到了相关的答案,真的很有帮助!


0
据我所理解,您正在从JavaScript应用程序中使用链接响应,对吗?在这种情况下,浏览器不会打开下载对话框。您应该将用户重定向到一个链接以触发下载对话框。但是,如果您想使用POST方法,则应该填写一个表单并通过编程方式提交它,而不是调用axios.post

感谢您尝试提供答案。我选择创建一个ObjectURL并使用window.open(ObjectURL)打开它的选项,因为不需要最初的文件命名。 - D.Schaller

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接