Uploader un fichier avec NodeJS

Publié le .

Node.JS Memo
Uploader un fichier avec NodeJS

J’ai en tête un projet à réaliser avec NodeJs. Je vous en parlerer plus tard. Pour ce projet, j’ai besoin de pouvoir uploader des images. Je suis donc parti à la recherche de tutoriel pour voir cela. J’en ai trouvé plusieurs, mais beaucoup n’était pas adapté à ma version d’Express. Puis finalement, j’en ai trouvé un simple et efficace que vous pouvez voir à cette page. J’y ai ajouté une subtilité en m’inspirant pour le rendre un peu plus élégant à partir délément vu sur ce topic de stackoverflow. Ceci n’est pas vraiment un tutoriel, c’est plus un mémo que je rend publique afin qu’il puisse être éventuellement utile à d’autres...

Pour info, je suis sous Ubuntu.

Installer les modules nécessaires

Cette mini application de téléchargement de fichier se base principalement sur le module Formidable.

Pour l’installer en utilisant npm :

  1. npm install formidable@latest

Elle utilise aussi le module : fs-extra qui ajoute des méthodes pour gérer les fichiers qui ne sont pas présente dans le module natif de NodeJS fs.

Pour l’installer en utilisant npm :

  1. npm install fs-extra

Elle utilise aussi le module : [path>http://nodejs.org/api/path.html] qui gère les chemins. En fait, il génère des chaînes de caractères pour les chemins, mais ne vérifie pas si ceci sont valides...

Pour l’installer en utilisant npm :

  1. npm install path

Créer les dossiers nécessaires

Dans le dossier de la mini application, créer les dossier « temp » et « upload ». Soit à la main, soit en ligne de commande depuis votre dossier racine avec : mkdir upload et mkdir temp.

Coder la mini application d’upload

Je vais procéder étape par étape pour bien vois ce qui se passe.

Je vais commencer par créer un fichier app.js à la racine de mon application avec un de mes éditeur préféré : Geany. Y mettre ce code :

  1. var formidable = require('formidable'),
  2.     http = require('http'),
  3.     util = require('util'),
  4.     fs   = require('fs-extra'),
  5.     path = require("path");
  6.  
  7. http.createServer(function(req, res) {
  8.  
  9.   /* Display the file upload form. */
  10.   res.writeHead(200, {'content-type': 'text/html'});
  11.   res.end(
  12.     '<form action="/upload" enctype="multipart/form-data" method="post">'+
  13.     '<input type="text" name="title"><br>'+
  14.     '<input type="file" name="upload" multiple="multiple"><br>'+
  15.     '<input type="submit" value="Upload">'+
  16.     '</form>'
  17.   );
  18.  
  19. }).listen(8080);

Télécharger

Le but est d’afficher un formulaire sans plus d’artifice...

Dans le terminal, depuis la racine de la mini application, faire un node app.js. Puis, se rendre sur : http://localhost:8080/. Un formulaire devrait apparaitre.

Maintenant, je vais ajouter un bout de code pour traiter ce qui est soumis via le formulaire :

  1. var formidable = require('formidable'),
  2.     http = require('http'),
  3.     util = require('util'),
  4.     fs   = require('fs-extra'),
  5.     path = require("path");
  6.  
  7. http.createServer(function(req, res) {
  8.        
  9. /* Process the form uploads */
  10.   if (req.url == '/upload' && req.method.toLowerCase() == 'post') {
  11.     var form = new formidable.IncomingForm();
  12.     form.parse(req, function(err, fields, files) {
  13.       res.writeHead(200, {'content-type': 'text/plain'});
  14.       res.write('received upload:\n\n');
  15.       res.end(util.inspect({fields: fields, files: files}));
  16.     });
  17.  
  18.     return;
  19.   }
  20.  
  21.   /* Display the file upload form. */
  22.   res.writeHead(200, {'content-type': 'text/html'});
  23.   res.end(
  24.     '<form action="/upload" enctype="multipart/form-data" method="post">'+
  25.     '<input type="text" name="title"><br>'+
  26.     '<input type="file" name="upload" multiple="multiple"><br>'+
  27.     '<input type="submit" value="Upload">'+
  28.     '</form>'
  29.   );
  30.  
  31. }).listen(8080);

Télécharger

Couper Node en tapant ctrl+c puis le relancer avec node app.js depuis le terminal. Il faudra faire cela à chaque modification du fichier « app.js ».

Ici, on test si l’url est /upload et la méthode post et ensuite on utilise la méthode parse pour analyser la requête node. Ensuite, sont affichés dans le navigateur les détails du résultat de cette analyse grâce à la méthode util.inspect. Voici ce j’ai obtenu :

  1. received upload:
  2.  
  3. { fields: { title: '' },
  4.   files:
  5.    { upload:
  6.       { domain: null,
  7.         _events: {},
  8.         _maxListeners: 10,
  9.         size: 143372960,
  10.         path: '/tmp/835cfd0595c3c70373ce830f4b0f3d6f',
  11.         name: 'dni-symphonie-noire-140511.flac',
  12.         type: 'audio/flac',
  13.         hash: null,
  14.         lastModifiedDate: Sat May 31 2014 05:38:40 GMT+0200 (CEST),
  15.         _writeStream: [Object] } } }

Télécharger

Comme vous pouvez le voir, le fichier a été téléchargé dans le dossier /tmp/. Il s’appelle 835cfd0595c3c70373ce830f4b0f3d6f. Nous avons donc tout ce qu’il nous faut pour copier ce fichier en utilisant NodeJS. Notez la présence d’autres informations qui pourraient ête utile pour divers contrôles.

  1. var formidable = require('formidable'),
  2.     http = require('http'),
  3.     util = require('util'),
  4.     fs = require('fs-extra'),
  5.     path = require("path");
  6.  
  7. http.createServer(function (req, res) {
  8.     /* Process the form uploads */
  9.     if (req.url == '/upload' && req.method.toLowerCase() == 'post') {
  10.         var form = new formidable.IncomingForm();
  11.         form.parse(req, function (err, fields, files) {
  12.             res.writeHead(200, {'content-type': 'text/plain'});
  13.             res.write('received upload:\n\n');
  14.             res.end(util.inspect({fields: fields, files: files}));
  15.         });
  16.  
  17.         form.on('end', function (fields, files) {
  18.             /* Temporary location of our uploaded file */
  19.             var temp_path = this.openedFiles[0].path;
  20.             /* The file name of the uploaded file */
  21.             var file_name = this.openedFiles[0].name;
  22.             /* Location where we want to copy the uploaded file */
  23.             var new_location = path.join(__dirname, '/upload/');
  24.  
  25.             fs.copy(temp_path, new_location + file_name, function (err) {
  26.                 if (err) {
  27.                     console.error(err);
  28.                 } else {
  29.                     console.log("success!")
  30.                 }
  31.             });
  32.         });
  33.  
  34.         return;
  35.     }
  36.  
  37.     /* Display the file upload form. */
  38.     res.writeHead(200, {'content-type': 'text/html'});
  39.     res.end(
  40.             '<form action="/upload" enctype="multipart/form-data" method="post">' +
  41.             '<input type="text" name="title"><br>' +
  42.             '<input type="file" name="upload" multiple="multiple"><br>' +
  43.             '<input type="submit" value="Upload">' +
  44.             '</form>'
  45.     );
  46.  
  47. }).listen(8080);

Télécharger

Ici, est utilisé l’événement end du module Fomidable pour copier le fichier dans le dossier que l’on veut. L’événement end est émis lorsque la requête a entièrement été reçue, et tous les fichiers traités

Il est possible de tester les erreurs de téléchargement en se basant sur l’événement error. Cet événement est émis lorsqu’il y a une erreur lors du traitement du formulaire. Un requête avec une erreur est mise en pause automatiquement. Vous aurez à appeler manuellement request.resume() pour que la requête continue d’envoyer des données.

  1. form.on('error', function(err) {
  2.         console.error(err);
  3.     });

Télécharger

Vérifier la progression du téléchargement

Lorsque que sont téléchargés de gros fichiers, il est utile d’indiquer la progression du téléchargement. Formidable rend facile cela. On peut, en effet, utiliser l’événement progress émis après la réception de chaque paquet de données. Ici, nous affichons la progression seulement dans le terminal.

  1. form.on('progress', function(bytesReceived, bytesExpected) {
  2.         var percent_complete = (bytesReceived / bytesExpected) * 100;
  3.         console.log(percent_complete.toFixed(2));
  4.     });

Télécharger

Voilà, tout ce qui est nécessaire au téléchargement de fichiers avec NodeJS en utilisant formidable est là ! Voici le code complet :

  1. var formidable = require('formidable'),
  2.     http = require('http'),
  3.     util = require('util'),
  4.     fs = require('fs-extra'),
  5.     path = require("path");
  6.  
  7. http.createServer(function (req, res) {
  8.     /* Process the form uploads */
  9.     if (req.url == '/upload' && req.method.toLowerCase() == 'post') {
  10.         var form = new formidable.IncomingForm();
  11.         form.parse(req, function (err, fields, files) {
  12.             res.writeHead(200, {'content-type': 'text/plain'});
  13.             res.write('received upload:\n\n');
  14.             res.end(util.inspect({fields: fields, files: files}));
  15.         });
  16.        
  17.         form.on('progress', function(bytesReceived, bytesExpected) {
  18.         var percent_complete = (bytesReceived / bytesExpected) * 100;
  19.         console.log(percent_complete.toFixed(2));
  20.                 });
  21.  
  22.         form.on('end', function (fields, files) {
  23.             /* Temporary location of our uploaded file */
  24.             var temp_path = this.openedFiles[0].path;
  25.             /* The file name of the uploaded file */
  26.             var file_name = this.openedFiles[0].name;
  27.             /* Location where we want to copy the uploaded file */
  28.             var new_location = path.join(__dirname, '/upload/');
  29.  
  30.             fs.copy(temp_path, new_location + file_name, function (err) {
  31.                 if (err) {
  32.                     console.error(err);
  33.                 } else {
  34.                     console.log("success!")
  35.                 }
  36.             });
  37.         });
  38.  
  39.         return;
  40.     }
  41.  
  42.     /* Display the file upload form. */
  43.     res.writeHead(200, {'content-type': 'text/html'});
  44.     res.end(
  45.             '<form action="/upload" enctype="multipart/form-data" method="post">' +
  46.             '<input type="text" name="title"><br>' +
  47.             '<input type="file" name="upload" multiple="multiple"><br>' +
  48.             '<input type="submit" value="Upload">' +
  49.             '</form>'
  50.     );
  51.  
  52. }).listen(8080);

Télécharger

Changer le dossier temporaire

Il y a des options de téléchargement que l’on peut modifier dont le dossier qui accueil les fichiers temporaire. On peut faire cela en par l’intermédiaire de l’événement fileBegin émis lorsqu’une paire champ/valeur a été reçue.

  1. form.on('fileBegin', function(name, file) {
  2.         file.path = 'c:/localhost/nodejs/uploads/' + file.name;
  3.     });

Télécharger

Le code complet :

  1. var formidable = require('formidable'),
  2.     http = require('http'),
  3.     util = require('util'),
  4.     fs = require('fs-extra'),
  5.     path = require("path");
  6.  
  7. http.createServer(function (req, res) {
  8.     /* Process the form uploads */
  9.     if (req.url == '/upload' && req.method.toLowerCase() == 'post') {
  10.         var form = new formidable.IncomingForm();
  11.         form.parse(req, function (err, fields, files) {
  12.             res.writeHead(200, {'content-type': 'text/plain'});
  13.             res.write('received upload:\n\n');
  14.             res.end(util.inspect({fields: fields, files: files}));
  15.         });
  16.        
  17.         form.on('fileBegin', function(name, file) {
  18.         file.path = path.join(__dirname, '/temp/') + file.name;
  19.                 });
  20.         form.on('progress', function(bytesReceived, bytesExpected) {
  21.         var percent_complete = (bytesReceived / bytesExpected) * 100;
  22.         console.log(percent_complete.toFixed(2));
  23.                 });
  24.  
  25.         form.on('end', function (fields, files) {
  26.             /* Temporary location of our uploaded file */
  27.             var temp_path = this.openedFiles[0].path;
  28.             /* The file name of the uploaded file */
  29.             var file_name = this.openedFiles[0].name;
  30.             /* Location where we want to copy the uploaded file */
  31.             var new_location = path.join(__dirname, '/upload/');
  32.  
  33.             fs.copy(temp_path, new_location + file_name, function (err) {
  34.                 if (err) {
  35.                     console.error(err);
  36.                 } else {
  37.                     console.log("success!")
  38.                 }
  39.             });
  40.         });
  41.  
  42.         return;
  43.     }
  44.  
  45.     /* Display the file upload form. */
  46.     res.writeHead(200, {'content-type': 'text/html'});
  47.     res.end(
  48.             '<form action="/upload" enctype="multipart/form-data" method="post">' +
  49.             '<input type="text" name="title"><br>' +
  50.             '<input type="file" name="upload" multiple="multiple"><br>' +
  51.             '<input type="submit" value="Upload">' +
  52.             '</form>'
  53.     );
  54.  
  55. }).listen(8080);

Télécharger

Voici ce qui est affiché sur le navigateur :

  1. received upload:
  2.  
  3. { fields: { title: '' },
  4.   files:
  5.    { upload:
  6.       { domain: null,
  7.         _events: {},
  8.         _maxListeners: 10,
  9.         size: 143372960,
  10.         path: '/home/robomatix/nodejs/testapp/upload-app/temp/dni-symphonie-noire-140511.flac',
  11.         name: 'dni-symphonie-noire-140511.flac',
  12.         type: 'audio/flac',
  13.         hash: null,
  14.         lastModifiedDate: Sat May 31 2014 06:34:30 GMT+0200 (CEST),
  15.         _writeStream: [Object] } } }

Télécharger

On peut noter que le path est différent de la première fois. Il est maintenant celui que l’on vient de spécifier.

On remarquera que le fichier dans temp n’est pas effacé...

Il faut donc l’effacer pour ne pas encombrer inutilement le seveur.

Voici comment ce faire :

  1.   // Delete the "temp" file
  2.   fs.unlink(temp_path, function(err) {
  3.          if (err) {
  4.                  console.error(err);
  5.                  console.log("TROUBLE deletion temp !");
  6.          } else {
  7.                  console.log("success deletion temp !");
  8.          }
  9.   });

Télécharger

Le code complet :

  1. var formidable = require('formidable'),
  2.     http = require('http'),
  3.     util = require('util'),
  4.     fs = require('fs-extra'),
  5.     path = require("path");
  6.  
  7. http.createServer(function (req, res) {
  8.     /* Process the form uploads */
  9.     if (req.url == '/upload' && req.method.toLowerCase() == 'post') {
  10.         var form = new formidable.IncomingForm();
  11.         form.parse(req, function (err, fields, files) {
  12.             res.writeHead(200, {'content-type': 'text/plain'});
  13.             res.write('received upload:\n\n');
  14.             res.end(util.inspect({fields: fields, files: files}));
  15.         });
  16.        
  17.         form.on('fileBegin', function(name, file) {
  18.         file.path = path.join(__dirname, '/temp/') + file.name;
  19.                 });
  20.         form.on('progress', function(bytesReceived, bytesExpected) {
  21.         var percent_complete = (bytesReceived / bytesExpected) * 100;
  22.         console.log(percent_complete.toFixed(2));
  23.                 });
  24.  
  25.         form.on('end', function (fields, files) {
  26.             /* Temporary location of our uploaded file */
  27.             var temp_path = this.openedFiles[0].path;
  28.             /* The file name of the uploaded file */
  29.             var file_name = this.openedFiles[0].name;
  30.             /* Location where we want to copy the uploaded file */
  31.             var new_location = path.join(__dirname, '/upload/');
  32.  
  33.             fs.copy(temp_path, new_location + file_name, function (err) {
  34.                 if (err) {
  35.                     console.error(err);
  36.                 } else {
  37.                     console.log("success!");
  38.                     // Delete the "temp" file
  39.                                         fs.unlink(temp_path, function(err) {
  40.                                         if (err) {
  41.                                                 console.error(err);
  42.                                                 console.log("TROUBLE deletion temp !");
  43.                                                 } else {
  44.                                                 console.log("success deletion temp !");
  45.                                                 }
  46.                                         });      
  47.                 }
  48.             });        
  49.            
  50.        
  51.         });
  52.  
  53.         return;
  54.     }
  55.  
  56.     /* Display the file upload form. */
  57.     res.writeHead(200, {'content-type': 'text/html'});
  58.     res.end(
  59.             '<form action="/upload" enctype="multipart/form-data" method="post">' +
  60.             '<input type="text" name="title"><br>' +
  61.             '<input type="file" name="upload" multiple="multiple"><br>' +
  62.             '<input type="submit" value="Upload">' +
  63.             '</form>'
  64.     );
  65.  
  66. }).listen(8080);

Télécharger

Et voilà !

Petite précision : normalement, lorsque l’on code avec NodeJS, c’est en mode Code less do more. En Express ou avec Sails ce genre de chose doit prendre pas plus de 20 lignes... C’est juste pour comprendre certains mécanismes...

Vous avez aimé ? Partager !

Twitter Facebook Google Plus Tumblr Linkedin