Search code examples
node.jsaws-lambdadiscorddiscord.jsserverless-framework

AWS Lambda corrupts image buffer - Serverless Framework


I'm having an issue where AWS lambda is corrupting my image buffer when I try and send it to a discord webhook. It works locally with SLS Offline and I can see image in discord channel with no issues, but when I deploy it to AWS I get

enter image description here

instead of the image itself. Looking around at similar people with this issue I've tried adding to my serverless.yml

plugins:
   - serverless-apigw-binary

with apigwBinary in custom path.

apigwBinary:
    types: #list of mime-types
        - 'image/png'

I also saw another post about adding AwsApiGateway under provider in serverless.yml like so

provider:
   AwsApiGateway:
   binaryMediaTypes:
      - 'image/png'

When I console.log('Sending Buffer', buffer); the buffer to make sure it's actually there in cloudwatch I see

Sending Buffer <Buffer fd 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00 04 fd 00 00 02.... 162236 more bytes>

So the buffer is definitely making it to the lambda, but it gets corrupted when it makes it to discord. But again it does work locally with no issues. So Lambda is corrupting it somehow. I even tried adding a 3 second delay to see if that might fix it as if it was a race condition or something even though I can see buffer in console log, but nope.

webhook.send({
        files: [{
            attachment: buffer,
            name: newFileName
        }]
    })
    .then((res) => {
        console.log('FINISHED', res);
    });
}
catch (e) {
    console.log('ERROR', e);
}

UPDATE:

I also tried uploading the image to S3 to see if the image would be corrupted there and again the file is fine when uploading locally, but not when it's uploaded to S3 from Lambda. Same issue. Image is corrupted. I noticed also that the file size when uploading from SLS offline is a little bigger by like 50 bytes than the lambda version so there is some weird compression going on from cloudfront that could be causing the image corruption.

End to end code of sending the image from Angular to Lambda to Discord

component

     const table = document.getElementById('table');

     const canvas = await html2canvas(table);
     console.log('canvas', canvas);

     const image: any = await getCanvasBlob(canvas);

     this.angularService
         .postImageToDiscord(image)
         .pipe(takeUntil(this.destroy))
         .subscribe((res) => {
             this.isLoading = false;
         });

Angular Service

    postImageToDiscord(imageData: File) {
        let formData = new FormData();
        formData.append('file', imageData, 'file.png');

        const body = { file: imageData };

        const api = environment.baseUrl + '/post-image';

        const req = new HttpRequest('POST', api, formData);

        return this.http.request(req);
    }

Serverless function definition

functions:
    sendImageToDiscord:
        handler: src/handler.sendImageToDiscord
        events:
            - http:
                  path: /post-image
                  method: post
                  cors: true

handler.ts

import { parser } from './parser';

export const sendImageToDiscord = async (
    event: any,
    context: Context
): Promise<ProxyResult> => {
    const parsedEvent: any = await parser(event);

    try {
        await postImageTableToDiscord(
            parsedEvent.body.file,
            event.queryStringParameters.assetType
        );
    } catch (e) {
        console.log('ERROR HERE', e);
    }
    const response = {
        statusCode: 200,
        body: JSON.stringify('file sent successfully')
    };
    return response;
};

imported parser file from handler

const Busboy = require('busboy');

const getContentType = (event: any) => {
    const contentType = event.headers['content-type'];
    if (!contentType) {
        return event.headers['Content-Type'];
    }
    return contentType;
};

export const parser = (event: any) =>
    new Promise((resolve, reject) => {
        const busboy = new Busboy({
            headers: {
                'content-type': getContentType(event)
            }
        });

        let result: any = {
            file: '',
            filename: '',
            contentType: ''
        };

        busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
            file.on('data', (data) => {
                result.file = data;
            });

            file.on('end', () => {
                result.filename = filename;
                result.contentType = mimetype;
            });
        });

        busboy.on('field', (fieldname, value) => {
            result[fieldname] = value;
        });

        busboy.on('error', (error: any) => reject(error));
        busboy.on('finish', () => {
            event.body = result;
            resolve(event);
        });

        busboy.write(event.body, event.isBase64Encoded ? 'base64' : 'binary');
        busboy.end();
    });

I appreciate any help with this.


Solution

  • The issue was using busboy. There might be a way to get it working, but it was corrupting the image when parsing the blob to buffer. The way I got it working with a lambda function and serverless framework when deployed was with aws-multipart-parser. It was super straightforward and painless using this library with aws lambda. No corrupted image issues anymore.