【问题标题】:axios onUploadProgress and onDownloadProgress not working with CORSaxios onUploadProgress 和 onDownloadProgress 不适用于 CORS
【发布时间】:2019-08-13 04:04:51
【问题描述】:

我有一个用 Node.js 编写的服务器,以及一个在浏览器中运行的 Web 客户端。客户端应从服务器上传和下载一些文件。服务端不是最初下发客户端的,所以我们这里是跨域的情况。

服务器使用cors 中间件来启用跨域请求。由于我还使用了许多自定义标头,因此我将其与如下配置一起使用:

api.use(cors({
  origin: '*',
  allowedHeaders: [ 'content-type', 'authorization', 'x-metadata', 'x-to' ],
  exposedHeaders: [ 'content-type', 'content-disposition', 'x-metadata' ]
}));

相比之下,客户端使用的是axios,上传文件的代码是这样的:

await axios({
  method: 'post',
  url: 'https://localhost:3000/api/v1/add-file',
  data: content,
  headers: {
    // ...
  }
});

到目前为止,一切都按预期工作,尤其适用于 CORS。

现在我想跟踪上传进度,因此我将onUploadProgress 回调添加到axios 调用中,如suggested by its documentation

await axios({
  method: 'post',
  url: 'https://localhost:3000/api/v1/add-file',
  data: content,
  headers: {
    // ...
  },
  onUploadProgress (progressEvent) {
    console.log({ progressEvent });
  }
});

如果我现在运行客户端,上传仍然有效 - 但永远不会调用 onUploadProgress 回调。我读过并非所有浏览器都支持这个(在内部这只是绑定到底层XmlHttpRequest对象的onprogress事件),但最新的Chrome应该能够做到这一点(如果我尝试不同的设置,CORS是不参与,一切似乎都有效)。所以我认为这是一个 CORS 问题。

我有read,一旦您向onprogress 事件添加侦听器,就会发送预检请求。这反过来意味着服务器必须接受OPTIONS 请求。为此,我在服务器上添加了以下代码,上述对api.use的调用之前:

api.options('*', cors({
  origin: '*',
  methods: [ 'GET', 'POST' ],
  allowedHeaders: [ 'content-type', 'authorization', 'x-metadata', 'x-to' ],
  exposedHeaders: [ 'content-type', 'content-disposition', 'x-metadata' ],
  optionsSuccessStatus: 200
}));

结果:上传仍然有效,但仍然没有引发 onUploadProgress 事件。所以我假设我遗漏了一些与 CORS 相关的东西。问题只是:我错过了什么?

我还尝试使用更大的文件(几乎 2 GBytes 而不是几个 KBytes)重新运行相同的设置,但结果是相同的。有谁知道我在这里做错了什么?

更新

回复部分cmets:首先,我的axios版本是0.18.0,所以我使用的是最新的稳定版本。

服务器的源代码可以在存储库thenativeweb/wolkenkit-depot 中找到。 CORS 部分配置为here。请注意,这与我上面发布的版本略有不同,因为我做了一些本地更改。但是,这是您需要处理的地方。

要构建服务器,您需要安装 Node.js 和 Docker。然后,运行:

$ npm install
$ npx roboter build

这将生成一个新的 Docker 映像,其中包含服务器的代码(您需要此 Docker 映像才能运行下面的客户端测试)。请注意,如果您更改服务器上的任何内容,则必须再次重建此 Docker 映像。

客户端可以在存储库thenativeweb/wolkenkit-depot-client-js,在分支issue-145-notifiy-about-progress-when-uploading-or-downloading-files中找到。

在文件DepotClient.js 中,我向onUploadProgress 添加了一个事件侦听器,它应该只是抛出一个异常(这反过来应该很明显地引发了事件)。

此代码随后由this test 运行(对此有更多测试,但这是其中之一),这应该会导致异常,但不会。

再次运行测试,您必须安装 Node.js 和 Docker,并且您必须按照前面所述构建服务器。然后使用以下命令代表您运行测试:

$ npm install
$ npx roboter test --type integration

我的期望是至少有一个addFile 测试应该提交。事实是,它们都变成绿色,这意味着上传本身可以完美运行,但从未引发 onUploadProgress 事件。以防万一您想知道执行测试的环境:我在这里使用的是 puppeteer,它是一个无头 Chrome。

【问题讨论】:

  • 环境如何?它是在代理后面还是类似的东西?
  • 两者都在本地主机上运行,​​只是端口不同。没有代理,没有防火墙,或其他任何东西......
  • 你能提供你的路由的服务器端代码吗?因为我没有问题,所以我在项目中使用onUploadProgress 没有问题。还有你能指定你正在使用的 axios 的版本吗?
  • 我扩展了我的问题,并提供了指向源代码的链接以及有关如何构建事物的说明。由于有可用的测试,如果您遵循构建步骤,它应该很容易重现。同样,正如文中所述:在客户端运行测试之前构建服务器非常重要!而且,在服务器上每次更改后,您都需要重新构建!
  • 您是否在客户端中看到任何对 OPTIONS 预检请求的响应?如果后端没有正确配置来处理 OPTIONS 请求,它将返回一个错误(可能是Status Code: 405 Method Not Allowed)。

标签: javascript node.js cors xmlhttprequest axios


【解决方案1】:

由于该代码运行良好(无论有无 cors),我将假设您使用的是旧的 axios 版本。 onUploadProgress 是在版本 0.14.0 上添加的。

如果您使用的是旧版本,您可以使用:progress 而不是 onUploadProgress,或者直接更新到最新版本。

0.14.0(2016 年 8 月 27 日)

BREAKINGprogress 事件处理程序拆分为 onUploadProgressonDownloadProgress (#423)

await axios({
  method: 'post',
  url: 'https://localhost:3000/api/v1/add-file',
  data: content,
  headers: {
    // ...
  },
  // Old version
  progress (progressEvent) {
    console.log({ progressEvent });
  },
  // New version
  onUploadProgress (progressEvent) {
    console.log({ progressEvent });
  }
});

更新

提供的代码在浏览器上完美运行,但是从 node 运行它时不起作用,因为在 Node.js 中运行 axios 时它使用 /lib/adapters/http.js 而不是 /lib/adapters/xhr.js

如果您阅读该代码,您会看到在 http 适配器上没有任何 onUploadProgress 选项。

我从浏览器测试了您的代码,点击了您的端点,它工作正常。 问题是当您运行从 Node.js 运行的集成测试时。

在这里你有一个问题:https://github.com/axios/axios/issues/1966 他们建议使用:progress-stream 如果你想取得进展,但不会在onUploadProress 函数上触发。

更新 2

我可以确认,在 puppeteer 上运行测试时,它使用的是 XMLHttpRequest 处理程序,而不是 Node.js。问题是您在异步回调中抛出错误,try/catch 未捕获该错误。

try {
  await request({
    method: 'post',
    url: `${protocol}://${host}:${port}/api/v1/add-file`,
    data: content,
    headers,
    onUploadProgress (progress) {
      // This error occurs in a different tick of the event loop
      // It's not catched by any of the try/catchs that you have in here
      // Pass a callback function, or emit an event. And check that on your test
      throw new Error(JSON.stringify(progress));
    }
  });

  return id;
} catch (ex) {
  if (!ex.response) {
    throw ex;
  }

  switch (ex.response.status) {
    case 401:
      throw new Error('Authentication required.');
    default:
      throw ex;
  }
}

如果您想抓住它,您必须将 axios 调用包装在 Promise 中并在其中拒绝(这没有意义,因为这仅用于测试)。

所以我的建议是将onUploadProgress 参数传递给addFile,这样您就可以检查它是否达到了测试中的onUploadProgress

为了看到:throw new Error(JSON.stringify(progress)); 被调用,使用 { headless: false } 启动 puppeteer。

这样你就会看到错误被正确触发了。

XMLHttpRequestUpload.onUploadProgress

【讨论】:

  • 我正在使用 axios 0.18.0,所以问题似乎出在其他地方。我认为问题出在服务器端,而不是客户端,即由于某种原因未正确处理 CORS 预检请求。
  • 我已经更新了问题,其中包含指向特定源代码和构建说明的链接。所以现在应该更容易重现我的问题。
  • 完美,有发现我会去看看更新。
  • 您是否在实际浏览器上对此进行了测试?不在测试中。因为它完美地工作。我在运行客户端项目时遇到问题。
  • 感谢更新的答案,但浏览器测试应该在 Puppeteer 中运行。这实际上被视为 Node.js,而不是浏览器吗?或者反过来说:为什么 Puppeteer 会这样?
猜你喜欢
  • 1970-01-01
  • 2021-02-08
  • 2015-05-12
  • 2014-02-09
  • 1970-01-01
  • 2021-08-07
  • 2021-05-10
  • 2017-04-26
  • 2020-08-17
相关资源
最近更新 更多