web-dev-qa-db-ja.com

axios onUploadProgressおよびonDownloadProgressがCORSで機能しない

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コールに追加します ドキュメントで推奨

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リクエストを受け入れる必要があります。そのために、サーバーに次のコードを追加しましたbeforeapi.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ギガバイト)で同じセットアップを再実行しようとしましたが、結果は同じです。誰かが私がここで間違っていることを知っていますか?

更新

コメントの一部に返信するには:最初に、私の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-ファイル

ファイル内 DepotClient.jsonUploadProgressにイベントリスナーを追加しました。これにより、単に例外がスローされます(これにより、イベントが発生したことが明らかになります)。

次に、このコードは this test によって実行されます(これには他にもテストがありますが、これはその1つです)。これにより例外が発生しますが、そうではありません。

テストを実行するには、Node.jsとDockerをインストールし、前述のようにサーバーを構築する必要があります。次に、次のコマンドを使用して、代わりにテストを実行します。

$ npm install
$ npx roboter test --type integration

私の期待は、少なくとも1つのaddFileテストを提出することです。事実、すべてが緑色に変わります。つまり、アップロード自体は完全に機能しますが、onUploadProgressイベントは発生しません。テストが実行される環境について不思議に思った場合に備えて、ここでは操り人形師を使用しています。これはヘッドレスChromeです。

8
Golo Roden

そのコードは(corsの有無にかかわらず)完全に正常に動作するので、古いaxiosバージョンを使用していると想定します。 onUploadProgressがバージョン_0.14.0_に追加されました。

古いバージョンを使用している場合は、progressの代わりにonUploadProgressを使用するか、最新バージョンに更新することができます。

0.14.0(2016年8月27日)

[〜#〜] breaking [〜#〜]progressイベントハンドラを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.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でテストを実行すると、Node.jsではなくXMLHttpRequestハンドラーを使用していることが確認できます。問題は、_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 }_を使用してパペットを起動します。

これにより、エラーが正しくトリガーされていることがわかります。

enter image description here

_XMLHttpRequestUpload.onUploadProgress_

4