axios onUploadProgress и onDownloadProgress не работают с CORS - PullRequest
8 голосов
/ 22 марта 2019

У меня есть сервер, написанный на Node.js, и веб-клиент, работающий в браузере. Клиент должен загрузить и загрузить некоторые файлы с сервера и на сервер. Сервер не тот, который изначально доставляет клиента, поэтому мы имеем междоменную ситуацию здесь.

Сервер использует промежуточное программное обеспечение 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 запросов. Для этого я добавил следующий код на сервере: перед вышеупомянутым вызовом 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 ГБ вместо нескольких КБ), но результат тот же. У кого-нибудь есть идеи, что я здесь делаю не так?

UPDATE

Чтобы ответить на некоторые комментарии: во-первых, моя axios версия 0.18.0, поэтому я использую последний доступный стабильный выпуск.

Исходный код для сервера можно найти в репозитории thenativeweb / wolkenkit-depot . Часть CORS настроена здесь . Обратите внимание, что это немного отличается от версии, которую я разместил выше, так как я сделал несколько локальных изменений. Однако это то место, с которым вам нужно иметь дело.

Чтобы построить сервер, вам нужно иметь Node.js и Docker. Затем выполните:

$ npm install
$ npx roboter build

Это приведет к созданию нового образа Docker, который содержит код сервера (этот образ Docker вам понадобится для запуска клиентских тестов, указанных ниже). Обратите внимание, что если вы что-то изменили на сервере, вам придется заново создать этот образ Docker.

Клиент может быть найден в хранилище thenativeweb / wolkenkit-depot-client-js , в ветке Issue-145-Notifiy-about-progress-when-uploading-or-download -файлы .

В файле DepotClient.js Я добавил прослушиватель событий в onUploadProgress, который должен просто вызвать исключение (что, в свою очередь, должно сделать довольно очевидным, что событие было вызвано).

Этот код затем запускается этим тестом (есть еще тесты для этого, но это один из них), что должно привести к исключению, но это не так.

Чтобы снова запустить тесты, у вас должны быть установлены Node.js и Docker, , и вы должны собрать сервер, как описано выше . Затем используйте следующие команды для запуска тестов от вашего имени:

$ npm install
$ npx roboter test --type integration

Я ожидаю, что должен подать хотя бы один из addFile тестов. На самом деле все они становятся зелеными, что означает, что сама загрузка работает отлично, но событие onUploadProgress никогда не вызывается. На тот случай, если вам интересно, в какой среде выполняются тесты: я использую кукольника, который является Chrome без головы.

1 Ответ

3 голосов
/ 25 марта 2019

Так как этот код работает отлично (как с сердечниками, так и без них), я предполагаю, что вы используете старую версию Axios. onUploadProgress был добавлен в версию 0.14.0.

Если вы используете более старую версию, вы можете использовать: progress вместо onUploadProgress или просто обновить до последней версии.

0,14,0 (27 августа 2016 г.)

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 });
  }
});

UPDATE .

Предоставленный код отлично работает в браузере, но не работает при запуске его с узла, поскольку при запуске axios в Node.js он использует /lib/adapters/http.js вместо /lib/adapters/xhr.js

Если вы прочитаете этот код, вы увидите, что на адаптере http нет опции onUploadProgress.

Я проверил ваш код из браузера, попал в вашу конечную точку, и он работает нормально. Проблема в том, что вы запускаете интеграционные тесты из Node.js.

Здесь у вас есть проблема по этому поводу: https://github.com/axios/axios/issues/1966, где они рекомендуют использовать: progress-stream , если вы хотите получить прогресс, но он не будет запущен на onUploadProress функция.

ОБНОВЛЕНИЕ 2

Я могу подтвердить, что при запуске теста на кукловоде он использует обработчик 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 }.

Таким образом, вы увидите, что ошибка срабатывает правильно.

enter image description here

XMLHttpRequestUpload.onUploadProgress

...