Brian Shamblen Brian Shamblen - 4 years ago 176
Javascript Question

Set node request header based on encoded formData value

I'm attempting to send an HTTP POST, using the

request
npm package, to an API that requires a special header, consisting of a base64 encoded MD5 hash of the request body's string representation.

Code to generate the MD5 hash:

function md5(val) {
val = val || '';
return crypto.createHash('md5').update(val).digest('base64');
}


When there's no formData, for a GET or DELETE request, the value I'm supposed to use is an empty string, which works great. The API accepts the header and returns the requested data.

Unfortunately, when posting a file, using the
formData
option, the object is being encoded by the
request
module. So, when the server compares my MD5 hash with the body that's received on their end, it doesn't match and throws an error.

Simplified request of what I need:

var formData = {
left: 0,
top: 0,
width: 0,
height: 0,
profileImage: fs.readFileSync(__dirname + '/test_image.jpg')
};

var reqOptions = {
url: 'https://example.com/user/1234/profile-image',
method: 'POST,
json: true,
headers: {
'Content-MD5': md5(formData)
},
formData: formData
}

request(reqOptions, function(err, response, body) {
//process the response...
});


The example above will throw an error, because the formData variable is an object and the
crypto
module is expecting a string. I started to manually write code to convert the formData object to a string, but it seems a little ridiculous to rewrite all of the logic that encodes all of the form values when it's already being done by the
request
module.

I'm looking for a reliable way to get the exact encoded form contents, after they've been processed by the
request
module, but before the request is actually sent, so I can build the hash value and add the header.

Answer Source

You could do this manually with something like:

var crypto = require('crypto');
var FormData = require('form-data');

var form = new FormData();
form.append('left', 0);
form.append('top', 0);
form.append('width', 0);
form.append('height', 0);
form.append('profileImage', fs.readFileSync(__dirname + '/test_image.jpg'));

var rawChunks = [];
var hash = crypto.createHash('md5');
form.on('data', function(chunk) {
  rawChunks.push(chunk);
  hash.update(chunk);
}).on('end', function() {
  var headers = form.getHeaders();
  headers['Content-MD5'] = hash.digest('base64');
  var req = request({
    url: 'https://example.com/user/1234/profile-image',
    method: 'POST',
    headers: headers
  }, function(err, res, body) {
    // Do something with response
  });
  for (var i = 0; i < rawChunks.length; ++i)
    req.write(rawChunks[i]);
  req.end();
});

Another possible alternative may be to use chunked encoding and pass Content-MD5 as an HTTP trailer header (a header that appears after the body). This would allow you to prevent buffering the generated form data in memory (and possibly even buffering the file field in memory if you decide to change fs.readFileSync() to fs.createReadStream()). However it will all depend on whether or not the destination server supports trailer headers (whether parsing them or actually doing something with them).

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download