J'ai un serveur écrit en Node.js et un client Web fonctionnant dans le navigateur. Le client doit télécharger et télécharger certains fichiers depuis et vers le serveur. Le serveur n'est pas celui qui fournit à l'origine le client, nous avons donc ici une situation inter-domaines.
Le serveur utilise le middleware cors pour activer les requêtes interdomaines. Étant donné que j'utilise également un certain nombre d'en-têtes personnalisés, je l'utilise avec une configuration comme celle-ci:
api.use(cors({
Origin: '*',
allowedHeaders: [ 'content-type', 'authorization', 'x-metadata', 'x-to' ],
exposedHeaders: [ 'content-type', 'content-disposition', 'x-metadata' ]
}));
En revanche, le client utilise axios , et le code pour télécharger un fichier ressemble à ceci:
await axios({
method: 'post',
url: 'https://localhost:3000/api/v1/add-file',
data: content,
headers: {
// ...
}
});
Jusqu'à présent, tout fonctionne comme prévu, et cela fonctionne particulièrement avec la chose CORS.
Maintenant, je voudrais suivre la progression du téléchargement, et donc j'ajoute le rappel onUploadProgress
à l'appel axios
, comme suggéré par sa documentation :
await axios({
method: 'post',
url: 'https://localhost:3000/api/v1/add-file',
data: content,
headers: {
// ...
},
onUploadProgress (progressEvent) {
console.log({ progressEvent });
}
});
Si j'exécute maintenant le client, le téléchargement fonctionne toujours - mais le rappel onUploadProgress
n'est jamais appelé. J'ai lu que tous les navigateurs ne le prennent pas en charge (en interne, cela se lie simplement à l'événement onprogress
de l'objet XmlHttpRequest
sous-jacent), mais le dernier Chrome devrait être capable pour le faire (et si j'essaie une configuration différente, où CORS n'est pas impliqué, tout semble fonctionner.) Je suppose donc que c'est un problème CORS.
J'ai l que dès que vous ajoutez un écouteur à l'événement onprogress
, une demande de contrôle en amont sera envoyée. Cela signifie à son tour que le serveur doit accepter les demandes OPTIONS
. Pour cela, j'ai ajouté le code suivant sur le serveur, avant l'appel précité à 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
}));
Résultat: le téléchargement fonctionne toujours, mais aucun événement onUploadProgress
n'est déclenché. Je suppose donc que je manque quelque chose, lié à CORS. La question est juste: qu'est-ce que je manque?
J'ai également essayé de relancer la même configuration avec un fichier plus volumineux (près de 2 Go au lieu de quelques Ko), mais le résultat est le même. Quelqu'un at-il une idée de ce que je fais mal ici?
Pour répondre à certains des commentaires: d'abord, ma version axios
est 0.18.0
, j'utilise donc la dernière version stable disponible.
Le code source du serveur se trouve dans le référentiel thenativeweb/wolkenkit-depot . La partie CORS est configurée ici . Veuillez noter que cela est légèrement différent de la version que j'ai publiée ci-dessus, car j'ai apporté quelques modifications locales. Cependant, c'est l'endroit auquel vous devez faire face.
Pour construire le serveur, vous devez avoir installé Node.js ainsi que Docker. Ensuite, exécutez:
$ npm install
$ npx roboter build
Cela se traduira par une nouvelle image Docker, qui contient le code du serveur (vous aurez besoin de cette image Docker pour pouvoir exécuter les tests client ci-dessous). Veuillez noter que si vous modifiez quoi que ce soit sur le serveur, vous devez à nouveau reconstruire cette image Docker.
Le client peut être trouvé dans le référentiel thenativeweb/wolkenkit-depot-client-js , dans la branche issue-145-notifiy-about-progress-when-uploading-or-download- fichiers .
Dans le fichier DepotClient.js J'ai ajouté un écouteur d'événements à onUploadProgress
, qui devrait simplement lever une exception (ce qui devrait à son tour rendre assez évident que l'événement a été déclenché).
Ce code est ensuite exécuté par ce test (il y a plus de tests pour cela, mais c'est l'un d'entre eux), ce qui devrait entraîner une exception, mais ce n'est pas le cas.
Pour exécuter les tests, encore une fois, vous devez avoir Node.js et Docker installés, et vous devez avoir construit le serveur comme décrit précédemment . Utilisez ensuite les commandes suivantes pour exécuter les tests en votre nom:
$ npm install
$ npx roboter test --type integration
Je m'attends à ce qu'au moins un des tests addFile
soit déposé. Le fait est qu'ils deviennent tous verts, ce qui signifie que le téléchargement lui-même fonctionne parfaitement, mais l'événement onUploadProgress
n'est jamais déclenché. Juste au cas où vous vous interrogeriez sur l'environnement dans lequel les tests sont exécutés: j'utilise ici un marionnettiste, qui est un Chrome sans tête.
Étant donné que ce code fonctionne parfaitement bien (avec ou sans cors), je vais supposer que vous utilisez une ancienne version d'Axios. onUploadProgress
a été ajouté sur la version 0.14.0
.
Si vous utilisez une ancienne version, vous pouvez utiliser: progress
au lieu de onUploadProgress
, ou simplement mettre à jour vers la dernière version.
0,14,0 (27 août 2016)
[~ # ~] rupture [~ # ~] Fractionnement des gestionnaires d'événements
progress
enonUploadProgress
etonDownloadProgress
(# 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 });
}
});
[~ # ~] mise à jour [~ # ~] .
Le code fourni fonctionne parfaitement sur le navigateur, mais il ne fonctionne pas lors de son exécution à partir d'un nœud, car lors de l'exécution d'Axios dans Node.js, il utilise /lib/adapters/http.js
au lieu de /lib/adapters/xhr.js
Si vous lisez ce code, vous verrez que sur l'adaptateur http
il n'y a pas d'option onUploadProgress
.
J'ai testé votre code à partir d'un navigateur, frappant votre point de terminaison et cela fonctionne très bien. Le problème est lorsque vous exécutez les tests d'intégration qui s'exécutent à partir de Node.js.
Ici, vous avez un problème à ce sujet: https://github.com/axios/axios/issues/1966 où ils recommandent d'utiliser: progress-stream si vous voulez avoir une progression, mais elle ne sera pas déclenchée sur la fonction onUploadProress
.
MISE À JOUR 2
Je peux confirmer que lors de l'exécution du test sur le marionnettiste, il utilise le gestionnaire XMLHttpRequest, pas Node.js. Le problème est que vous lancez l'erreur dans un rappel asynchrone, qui n'est pas intercepté, par le 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;
}
}
Si vous voulez attraper cela, vous devrez encapsuler l'appel axios dans un Promise
et le rejeter là-dedans (ce qui n'a pas de sens, car c'est uniquement pour les tests).
Donc, ma recommandation est de passer un argument onUploadProgress
à addFile
, de cette façon, vous pouvez vérifier s'il atteint le onUploadProgress
à partir de vos tests.
Pour voir que: throw new Error(JSON.stringify(progress));
était appelée, lancez marionnettiste avec { headless: false }
.
De cette façon, vous verrez que l'erreur se déclenche correctement.
XMLHttpRequestUpload.onUploadProgress