30

I'm glad to see AWS now supports multipart/form-data on AWS Lambda, but now that the raw data is in my lambda function how do I process it?

I see multiparty is a good multipart library in Node for multipart processing, but its constructor expects a request, not a raw string.

The input message I am receiving on my Lambda function (after the body mapping template has been applied) is:

{ "rawBody": "--ce0741b2-93d4-4865-a7d6-20ca51fe2689\r\nContent-Disposition: form-data; name=\"Content-Type\"\r\n\r\nmultipart/mixed; boundary=\"------------020601070403020003080006\"\r\n--ce0741b2-93d4-4865-a7d6-20ca51fe2689\r\nContent-Disposition: form-data; name=\"Date\"\r\n\r\nFri, 26 Apr 2013 11:50:29 -0700\r\n--ce0741b2-93d4-4865-a7d6-20ca51fe2689\r\nContent-Disposition: form-data; name=\"From\"\r\n\r\nBob <bob@mg.mydomain.io>\r\n--ce0741b2-93d4-4865-a7d6-20ca51fe2689\r\nContent-Disposition: form-data; name=\"In-Reply-To\"\r... 

etc and some file data.

The body mapping template I'm using is

{
  "rawBody" : "$util.escapeJavaScript($input.body).replaceAll("\\'", "'")"
}

How can I parse this data to acecss the fields and files posted to my Lambda function?

Jedi
  • 3,088
  • 2
  • 28
  • 47
Diederik
  • 5,536
  • 3
  • 44
  • 60

4 Answers4

33

busboy doesn't work for me in the "file" case. It didn't throw an exception so I couldn't handle exception in lambda at all.

I'm using aws-lambda-multipart-parser lib wasn't hard like so. It just parses data from event.body and returns data as Buffer or text.

Usage:

const multipart = require('aws-lambda-multipart-parser');

const result = multipart.parse(event, spotText) // spotText === true response file will be Buffer and spotText === false: String

Response data:

{
    "file": {
        "type": "file",
        "filename": "lorem.txt",
        "contentType": "text/plain",
        "content": {
            "type": "Buffer",
            "data": [ ... byte array ... ]
        } or String
    },
    "field": "value"
}
Long Nguyen
  • 9,898
  • 5
  • 53
  • 52
  • 1
    I tried `multiparty` and was about to try `busboy` (it looks like they are kind of doing the same stuff) but found your reply and `aws-lambda-multipart-parser` saved my day, thanks – Marius B Mar 16 '20 at 13:39
  • This saved my day as well. Bounty started is just for this answer - hopefully it'll get some additional upvotes along the way. – Adam Rackis Apr 13 '20 at 19:52
  • I'm getting an empty response: `{}`. Did you guys deal with the same issue? – neuquen May 14 '20 at 22:03
  • I'm running into the same issue @Keven was anyone else able to fix it? – Destiny Faith Jun 17 '20 at 18:56
  • @SupunPraneeth It may be a known issue of this library. https://github.com/myshenin/aws-lambda-multipart-parser#8-issue-with-malformed-media-files-uploaded-to-s3 – Long Nguyen Aug 26 '20 at 01:38
  • 4
    If anybody is getting response:{}, try this: ````if (event.isBase64Encoded) { event.body = Buffer.from(event.body, 'base64').toString(); } let result = multipart.parse(event, true); ```` – JasonD Sep 07 '20 at 09:44
  • 7
    "The project is currently closed, and no further updates will be conducted. Please, consider using other libraries." - Anybody know if there is more stable support? Would be nice if AWS added support in their SDK. – drew kroft Jan 30 '21 at 18:19
24

This worked for me - using busboy

credits owed to Parse multipart/form-data from Buffer in Node.js which I copied most of this from.

const busboy = require('busboy');

const headers = {
  'Content-Type': 'application/json',
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Methods': 'OPTIONS, POST',
  'Access-Control-Allow-Headers': 'Content-Type'
};

function handler(event, context) {
  var contentType = event.headers['Content-Type'] || event.headers['content-type'];
  var bb = new busboy({ headers: { 'content-type': contentType }});

  bb.on('file', function (fieldname, file, filename, encoding, mimetype) {
    console.log('File [%s]: filename=%j; encoding=%j; mimetype=%j', fieldname, filename, encoding, mimetype);

    file
    .on('data', data => console.log('File [%s] got %d bytes', fieldname, data.length))
    .on('end', () => console.log('File [%s] Finished', fieldname));
  })
  .on('field', (fieldname, val) =>console.log('Field [%s]: value: %j', fieldname, val))
  .on('finish', () => {
    console.log('Done parsing form!');
    context.succeed({ statusCode: 200, body: 'all done', headers });
  })
  .on('error', err => {
    console.log('failed', err);
    context.fail({ statusCode: 500, body: err, headers });
  });

  bb.end(event.body);
}

module.exports = { handler };
Diederik
  • 5,536
  • 3
  • 44
  • 60
AvnerSo
  • 1,609
  • 1
  • 16
  • 23
3

Building on @AvnerSo :s answer, here's a simpler version of a function that gets the request body and headers as parameters and returns a promise of an object containing the form fields and values (skipping files):

const parseForm = (body, headers) => new Promise((resolve, reject) => {
  const contentType = headers['Content-Type'] || headers['content-type'];
  const bb = new busboy({ headers: { 'content-type': contentType }});

  var data = {};

  bb.on('field', (fieldname, val) => {
    data[fieldname] = val;
  }).on('finish', () => {
    resolve(data);
  }).on('error', err => {
    reject(err);
  });

  bb.end(body);
});
jlaitio
  • 1,878
  • 12
  • 13
1

If you want to get a ready to use object, here is the function I use. It returns a promise of it and handle errors:

import Busboy from 'busboy';
import YError from 'yerror';
import getRawBody from 'raw-body';

const getBody = (content, headers) =>
    new Promise((resolve, reject) => {
      const filePromises = [];
      const data = {};
      const parser = new Busboy({
        headers,
        },
      });

      parser.on('field', (name, value) => {
        data[name] = value;
      });
      parser.on('file', (name, file, filename, encoding, mimetype) => {
        data[name] = {
          filename,
          encoding,
          mimetype,
        };
        filePromises.push(
          getRawBody(file).then(rawFile => (data[name].content = rawFile))
        );
      });
      parser.on('error', err => reject(YError.wrap(err)));
      parser.on('finish', () =>
        resolve(Promise.all(filePromises).then(() => data))
      );
      parser.write(content);
      parser.end();
    })
nfroidure
  • 1,531
  • 12
  • 20