Thursday, August 22, 2013

Multi-Part File Upload with Node JS and Express

Several examples exist on the internet that detail how one can use Node.js to handle a multi-part form upload. Well, the scenario I ran into was that I needed to expose the upload functionality in my RESTful service, but then needed to pass that upload along to another service. So I had to construct the request object via code. Typical request objects are not so hard to construct in Node.js but the multi-part form upload was tripping me up. Here’s what I eventually came up with.

This example uses Express. Express is a minimal web application framework. You can find it here: http://expressjs.com/. Express exposes the collection of files via the req.files object. Note that express is going to use the name assigned by request creator - so, part of the contract may require the name be a specific value, otherwise there is no way to glean what the user may have named the file - at least not that I have found so far..

We have to tell Express to use the body parser. Do some googling on the bodyParser, there are some properties for specifying the temp directory for the upload and other things as well. Adding this line will cause Express to grab and expose the files as part of the request:
app.use(express.bodyParser());

And here is the POST handler:

app.post('/me/avatar', function(req, res){
   logger.debug('handling POST /me/avatar');
   logger.debug('files :' + JSON.stringify(req.files));
     
  
   // ToDo: this is a problem. It looks like we have to force people to use 'upload' as the form name for the file.
   // Cannot find away around this after hours of searching. Probably something simple,
   // just can’t find it (node.js noob).
   if(!req.files.upload){
      res.statusCode = 400;
      res.send("File not found in request.");
      return;
   }

   var subServiceUrl = “http://some_service_i_need_to_upload_to/my/file”;
       
   // read the file from disk. It has already been uploaded to a temp directory
   var filePath = req.files.upload.path;
   logger.debug('reading file: ' + filePath);
   fs.readFile(filePath, function (err, fileData) {
      if (err){
         logger.debug("error loading file: " + filePath);
         logger.debug(err);
         res.statusCode = 500;
         res.send('');
         return;
      }
   
   
      // the options setup is the tricky part for the multi-part upload
      var options = {
        uri: subServiceUrl ,
        method: 'POST',
        headers: {
          'content-type': 'multipart/form-data'
        },
        multipart: [{
           'Content-Disposition': 'form-data; name="upload"; filename="' + req.files.upload.name + '"',
           'Content-Type': req.files.upload.type,
           body: fileData
        }]
      };
   
      request(options, function(error, response, body){
         logger.debug('statusCode : ' + response.statusCode);
     
         if((response.statusCode != 201) || (response.statusCode != 200)){
            sendError(body, res, response);
         }
         else{
           res.statusCode = response.statusCode;
           res.send(response.statusCode, body);
         }
      });
   });
});

No comments: