Server side multipart/form-data uploads from nodejs

I was trying to use nodejs for doing a server side multipart/form-data from nodejs for the test case for a feature I recently implemented on nodejs server. However I did not find a good straight way to achieve this. I found some code on the internet and modified that to make it more modular. Code as below.


var Step = require('step'),
    fs = require('fs'),
    Http = require('http');

/**
Converts a list of parameters to forum data
- `fields` - a property map of key value pairs
- `files` - a list of property maps of content
  - `type` - the type of file data
  - `keyname` - the name of the key corresponding to the file
  - `valuename` - the name of the value corresponding to the file
  - `data` - the data of the file
*/
function getFormDataForPost(fields, files) {
  function encodeFieldPart(boundary,name,value) {
    var return_part = "--" + boundary + "\r\n";
    return_part += "Content-Disposition: form-data; name=\"" + name + "\"\r\n\r\n";
    return_part += value + "\r\n";
    return return_part;
  }
  function encodeFilePart(boundary,type,name,filename) {
    var return_part = "--" + boundary + "\r\n";
    return_part += "Content-Disposition: form-data; name=\"" + name + "\"; filename=\"" + filename + "\"\r\n";
    return_part += "Content-Type: " + type + "\r\n\r\n";
    return return_part;
  }
  var boundary = Math.random();
  var post_data = [];

  if (fields) {
    for (var key in fields) {
      var value = fields[key];
      post_data.push(new Buffer(encodeFieldPart(boundary, key, value), 'ascii'));
    }
  }
  if (files) {
    for (var key in files) {
      var value = files[key];
      post_data.push(new Buffer(encodeFilePart(boundary, value.type, value.keyname, value.valuename), 'ascii'));

      post_data.push(new Buffer(value.data, 'utf8'))
    }
  }
  post_data.push(new Buffer("\r\n--" + boundary + "--"), 'ascii');
  var length = 0;

  for(var i = 0; i < post_data.length; i++) {
    length += post_data[i].length;
  }
  var params = {
    postdata : post_data,
    headers : {
      'Content-Type': 'multipart/form-data; boundary=' + boundary,
      'Content-Length': length
    }
  };
  return params;
}

/**
Sends a post form request via http
- `fields` - a property map of key value pairs
- `files` - a list of property maps of content
  - `type` - the type of file data
  - `keyname` - the name of the key corresponding to the file
  - `valuename` - the name of the value corresponding to the file
  - `data` - the data of the file
- `options` is a set of options
  - host
  - port
  - path
  - method
  - encoding
- `headers` headers to be sent with the request
- `callback` - callback to handle the response
*/
function postData(fields, files, options, headers, callback) {
  var headerparams = getFormDataForPost(fields, files);
  var totalheaders = headerparams.headers;
  for (var key in headers) totalheaders[key] = headers[key];

  var post_options = {
    host: options.host,
    port: options.port,
    path: options.path,
    method: options.method || 'POST',
    headers: totalheaders
  };
  var request = Http.request(post_options, function(response) {
    response.body = '';
    response.setEncoding(options.encoding);
    response.on('data', function(chunk){
      console.log(chunk);
      response.body += chunk;
    });
    response.on('end', function() {
      callback(null, response)
    });
  });

  for (var i = 0; i < headerparams.postdata.length; i++) {
    request.write(headerparams.postdata[i]);
  }
  request.end();
}

/**
Sends a post form request via http
- `options` is a set of options
  - host
  - port
  - path
  - method
  - encoding
- `filename` filename being uploaded
- `headers` headers to be sent with the request
*/
function postImage(options, filename, headers) {
  Step(
    function readImage()
    {
      fs.readFile(filename, this);
    },
    function(err, filecontents) {
      if (err) {
        Console.log('Unable to read file');
        return;
      }
      postData(null, [{type: 'image/jpeg', keyname: 'image', valuename: 'image.jpg', data: filecontents}], options, headers, this);
    },
    function(err, response) {
      console.log("response code " + response.statusCode);
    }
  );
}

//===== PUBLIC ================================================================

var interface = {
  getFormDataForPost : getFormDataForPost,
  postData : postData,
  postImage : postImage
};

module.exports = interface;

//======= USAGE ============================================================
var options = {
  host : 'localhost',
  port : 8080,
  path : 'imageupload',
  method : 'PUT',
  encoding : 'utf8'
};

postImage(options, "ap.jpg", {'Cookie': 'cookiename=cookievalue'});

4 thoughts on “Server side multipart/form-data uploads from nodejs

  1. kevin

    just a heads up, this isn’t particularly working for me right now..

    the anonymous function at line 131 should probably have error checking, and line 126 needs to s/Console/console

    after all that’s done, i’m getting 400 – Bad Request from a server that works quite nicely when i curl -F it.

  2. John

    Kevin, did you or anyone else ever get this working? I made the same two fixes, then read the comments ;) Someone should definitely create a package for this as well.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>