axios onUploadProgress和onDownloadProgress无法与CORS配合使用

时间:2019-03-22 07:48:25

标签: javascript node.js cors xmlhttprequest axios

我有一个用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回调添加为suggested by its documentationaxios调用中:

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

如果我现在运行客户端,则上传仍然有效-但从未调用onUploadProgress回调。我了解到,并非所有浏览器都支持此功能(内部仅绑定到基础onprogress对象的XmlHttpRequest事件),但是最新的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 GB,而不是几个KB)重新运行相同的设置,但是结果是相同的。有人知道我在这里做错了吗?

更新

要回复一些评论:首先,我的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。

1 个答案:

答案 0 :(得分:3)

由于该代码可以很好地运行(无论是否使用cors),因此我假设您使用的是旧版axios版本。 onUploadProgress是在版本0.14.0上添加的。

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

0.14.0(2016年8月27日)

  

BREAKING progress事件处理程序拆分为onUploadProgress和   onDownloadProgress(#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.js中运行axios时,它使用的是one time work requests而不是/lib/adapters/http.js

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

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

您在这里遇到了一个问题:/lib/adapters/xhr.js,如果您希望取得进展,则建议使用https://github.com/axios/axios/issues/1966,但不会在onUploadProress函数上触发。 / p>

更新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 }启动木偶。

这样,您将看到错误已正确触发。

progress-stream

XMLHttpRequestUpload.onUploadProgress