Hokie2014
Hokie2014

Reputation: 47

Unable to upload multiple images to AWS S3 if I don't first upload one image through a AWS NodeJS Lambda endpoint using Promises

I have the code below on AWS Lambda as an endpoint exposed through API Gateway. The point of this endpoint is to upload images to an S3 bucket. I've been experiencing an interesting bug and could use some help. This code is unable to upload multiple images to S3 if it does not first upload one image. I've listed the scenarios below. The reason I want to use Promises is because I intend to insert data into a mysql table in the same endpoint. Any advice or feedback will be greatly appreciated!

Code Successfully uploads multiple images:

  1. Pass one image to the endpoint to upload to S3 first
  2. Pass several images to the endpoint to upload to S3 after uploading one image first

Code fails to upload images:

  1. Pass several images to the endpoint to upload to s3 first. A random amount of images might be uploaded, but it consistently fails to upload all of them. A 502 error code is returned because it failed to upload all images.

Code

const AWS = require('aws-sdk');
const s3 = new AWS.S3({});

function uploadAllImagesToS3(imageMap) {
    console.log('in uploadAllImagesToS3')
    return new Promise((resolve, reject) => {
        awaitAll(imageMap, uploadToS3)
            .then(results => {
                console.log('awaitAllFinished. results: ' + results)
                resolve(results)
            })
            .catch(e => {
                console.log("awaitAllFinished error: " + e)
                reject(e)
            })
   })
}

function awaitAll(imageMap, asyncFn) {
    const promises = [];
    imageMap.forEach((value, key) => {
        promises.push(asyncFn(key, value)); 
    })
    console.log('promises length: ' + promises.length)
    return Promise.all(promises)
}

function uploadToS3(key, value) {
    return new Promise((resolve, reject) => {
        console.log('Promise uploadToS3 | key: ' + key)
        // [key, value] = [filePath, Image]
        var params = {
            "Body": value,
            "Bucket": "userpicturebucket",
            "Key": key
        };
        s3.upload(params, function (err, data) {
            console.log('uploadToS3.  s3.upload. data: ' + JSON.stringify(data))
            if (err) {
                console.log('error when uploading to s3 | error: ' + err)
                reject(JSON.stringify(["Error when uploading data to S3", err]))
            } else {
                let response = {
                    "statusCode": 200,
                    "headers": {
                        "Access-Control-Allow-Origin": "http://localhost:3000"
                    },
                    "body": JSON.stringify(data),
                    "isBase64Encoded": false
                };
                resolve(JSON.stringify(["Successfully Uploaded data to S3", response]))
            }
        });
    })
}

exports.handler = (event, context, callback) => {
    if (event !== undefined) {
        let jsonObject = JSON.parse(event.body)
        let pictures = jsonObject.pictures
        let location = jsonObject.pictureLocation
        let imageMap = new Map()

        for (let i = 0; i < pictures.length; i++) {
            let base64Image = pictures[i].split('base64,', 2)
            let decodedImage = Buffer.from(base64Image[1], 'base64'); // image string is after 'base64'
            let base64Metadata = base64Image[0].split(';', 3) // data:image/jpeg,name=coffee.jpg,
            let imageNameData = base64Metadata[1].split('=', 2)
            let imageName = imageNameData[1]
            var filePath = "test/" + imageName
            imageMap.set(filePath, decodedImage)
        }

        const promises = [uploadAllImagesToS3(imageMap)]
        Promise.all(promises)
            .then(([uploadS3Response]) => {
                console.log('return promise!! | uploadS3Response: ' + JSON.stringify([uploadS3Response]))
                let res = {
                    body: JSON.stringify(uploadS3Response),
                    headers: {
                        "Access-Control-Allow-Origin": "http://localhost:3000"
                    }
                };
                callback(null, res);
            })
            .catch((err) => {
                callback(err);
            });
    } else {
        callback("No pictures were uploaded")
    }
};

Upvotes: 1

Views: 479

Answers (1)

Hokie2014
Hokie2014

Reputation: 47

Reason for problem and solution :

After several hours of debugging this issue I realized what the error was! My Lambda endpoint was timing out early. The reason I was able to upload multiple images after first uploading one image was because my the lambda endpoint was being executed from a warm start - as it was already up and running. The scenario where I was unable to upload multiple images was actually only occurring when I would try to do so after not executing the endpoint in 10+ minutes - therefore a cold start. Therefore, the solution was to increase the Timeout from the default of 3 seconds. I increased it to 20 seconds, but might need to play around with that time.

How to increase the lambda timeout?

  1. Open Lambda function
  2. Scroll down to Basic Settings and select Edit
  3. Increase time in Timeout

TLDR

This error was occurring because Lambda would timeout. Solution is to increase lambda timeout.

Upvotes: 1

Related Questions