Uploading and reading an image from s3 via postman and lambda - node.js

I am trying to write a code where I am sending an image or array of images via multipart/form-data as in the below images:
Click here for the postman Screenshot
I am trying to store these images to an S3 bucket and then trigger another lambda to read the images to perform text extraction using AWS rekognition.
Code for the lambda that is storing the images is as below:
var AWS = require('aws-sdk')
var express = require('express')
var multer = require('multer')
var multerS3 = require('multer-s3')
var bodyParser = require('body-parser')
const cors = require('cors')
var app = express()
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cors())
const env = process.env.NODE_ENV || 'prod'
AWS.config.update({
accessKeyId: 'ACCESS_KEY',
secretAccessKey: 'SECRET_KEY',
region: 'ap-south-1'
});
var s3 = new AWS.S3({
region: "ap-south-1"
})
var lambda = new AWS.Lambda({
region: "ap-south-1"
});
var upload = multer({
storage: multerS3({
s3: s3,
bucket: 'MY_BUCKET_NAME',
metadata: function (req, file, cb) {
cb(null, { fieldName: file.fieldname });
},
key: function (req, file, cb) {
req.originalname = file.originalname
cb(null, req.originalname)
}
})
})
app.post('/', upload.array('file', 2), function (req, res, next) {
const params = {
FunctionName: "ANOTHER_LAMBDA_NAME",
Payload: JSON.stringify({ "fileName": req.originalname })
};
return lambda.invoke(params, (err, data) => {
if (err) console.log("err", err.stack);
else {
res.json(data.Payload)
}
});
})
if (env === "dev") {
const port = process.env.port || 4000
app.listen(port, () => console.log(`server is running on ${port}`))
}
else {
module.exports = app
}
Code for another lambda is as below:
var AWS = require('aws-sdk')
var s3 = new AWS.S3()
const rekognition = new AWS.Rekognition()
exports.handler = function(event, context) {
var params = {
Image: {
S3Object: {
Bucket: 'MY_BUCKET_NAME',
Name: event.filename,
}
}
};
rekognition.detectText(params, function(err, data) {
if (err) console.log(err, err.stack);
else context.succeed(data); // returns "null"
});
};
After this, I created an API Gateway which triggers the first lambda to upload the files in s3 as in the picture above. The response I get is "null"
The main problem seems to be that the image is getting stored in S3 Bucket. But when I download that image and try to open it, it gives the following error:
The file “EPICared.jpg” could not be opened. It may be damaged or use a file format that Preview doesn’t recognize.
So what do you guys suggest I should be doing? Where am I going wrong? I cannot figure out why the image cannot be opened or read by the other lambda.

Related

How do I dynamically specify a file path I would like to upload to in my s3 bucket using multer

So, I'm quite new to Nodejs and I'm trying to implement a API to upload file to an s3 bucket. But, I'd like to specify the file path to upload each file to as an optional parameter. What's the simplest way to achieve this? Here is the original code for uploading a file
require("dotenv/config");
const express = require("express");
const multer = require("multer")
const AWS = require("aws-sdk")
const app = express();
const port = process.env.PORT || 3000;
const s3 = new AWS.S3({
accessKeyId: process.env.AWS_ID,
secretAccessKey: process.env.AWS_SECRET,
region: process.env.AWS_REGION
})
const storage = multer.memoryStorage({
destination: (req, file, callback) => {
callback(null, "")
}
})
const upload = multer({storage}).single("file");
app.post("/upload", upload, (req, res) => {
let myFile = req.file.originalname
const params = {
Bucket: process.env.AWS_BUCKET_NAME,
Key: myFile,
Body: req.file.buffer,
}
s3.upload(params, (error, data) => {
if(error) {
res.status(500).send(error)
}
res.status(200).send(data)
})
});
app.listen(port, () => {
console.log(`Server is up at ${port}`)
});
If you are trying to upload files to a folder, AWS s3 simulates this by adding prefixes. You can change the Key parameter by adding the folder name.
app.post("/upload/:directory", upload, (req, res) => {
let myFile = req.file.originalname
let path = req.params.directory
const params = {
Bucket: process.env.AWS_BUCKET_NAME,
Key: `${path}/${myFile}`,
Body: req.file.buffer,
}
s3.upload(params, (error, data) => {
if(error) {
res.status(500).send(error)
}
res.status(200).send(data)
})
});

Getting SignatureDoesNotMatch: null error while uploading images to DigitalOcean Spaces using multers3 node.js

Error: code:'SignatureDoesNotMatch'
extendedRequestId:undefined
message:null
name:'SignatureDoesNotMatch'
region:null
// Load dependencies
const aws = require('aws-sdk');
const express = require('express');
const multer = require('multer');
const multerS3 = require('multer-s3');
const app = express();
// Set S3 endpoint to DigitalOcean Spaces
const spacesEndpoint = new aws.Endpoint('fra1.digitaloceanspaces.com');
const s3 = new aws.S3({
endpoint: spacesEndpoint,
accessKeyId: '*************',
secretAccessKey: '**********************************'
});
// Change bucket property to your Space name
const upload = multer({
storage: multerS3({
s3: s3,
bucket: 'bucket',
acl: 'public-read',
key: function (request, file, cb) {
console.log(file);
cb(null, file.originalname);
}
})
}).array('upload', 1);
// Views in public directory
app.use(express.static('public'));
// Main, error and success views
app.get('/', function (request, response) {
response.sendFile(__dirname + '/public/index.html');
});
app.get("/success", function (request, response) {
response.sendFile(__dirname + '/public/success.html');
});
app.get("/error", function (request, response) {
response.sendFile(__dirname + '/public/error.html');
});
app.post('/upload', function (request, response, next) {
upload(request, response, function (error) {
if (error) {
console.log(error);
return response.redirect("/error");
}
console.log('File uploaded successfully.');
response.redirect("/success");
});
});
app.listen(3001, function () {
console.log('Server listening on port 3001.');
});
Trying this blog for digital ocean spaces example
I had the same problem, the only thing that helped was to reset the keys
I can confirm Livius answer. I had the same issue. Without any changes to the code, I created new access keys and it started working. For your reference, here my setup code snipped:
// --------------------------------------------------------
....
var aws = require('aws-sdk');
// --------- AWS SETUP -------------
aws.config.update({
accessKeyId: '..........',
secretAccessKey: '.......',
region: "eu-central-1",
signatureVersion: 'v4',
signatureCache: false
});
let s3 = new aws.S3();
.. and so on...
// --------------------------------------------------------

node js multer s3 file Uploading Issue

I am working a on nodejs restful api and trying to upload file on S3 using multer but its not working also i am not getting any error .
here is a code from my Controller
var aws = require('aws-sdk')
var express = require('express')
var multer = require('multer')
var multerS3 = require('multer-s3')
var bodyParser = require('body-parser')
var uuid = require('uuid').v4;
aws.config.update({
secretAccessKey: '',
accessKeyId: '' ,
region: 'us-west-2'
});
var app = express();
var s3 = new aws.S3();
app.use(bodyParser.json());
var upload = multer({
storage: multerS3({
s3: s3,
bucket: 'stack',
key: function (req, file, cb) {
console.log(file);
cb(null, req.s3key)
}
})
})
var fileUpload = upload.array('attachments',1);
function uploadToS3(req, res){
req.s3key = uuid();
let downloadUrl = 'https://s3-us-west-2.amazonaws.com/stack/'+req.s3key;
return new Promise((resolve,reject) => {
return fileUpload(req, res, err=> {
if(err) return reject(err);
return resolve(downloadUrl)
})
})
}
exports.uploadImagetoS3 = (req, res) => {
uploadToS3(req,res).then(downloadUrl=> {
console.log(downloadUrl);
});
}
What am i missing here??
Can you try catch block for error log if there is any missing then it will display in log.
`exports.uploadImagetoS3 = (req, res) => {
uploadToS3(req,res).then(downloadUrl=> {
console.log(downloadUrl);
}).catch(error=>{
console.log(error)
});`
}

Image is getting corrupted after uploading it from lambda to S3 using serverless framework and connect-multiparty module

When i am uploading image from local its working fine i am able to view the image from browser but when i upload it from lambda using serverless framework the image is displayed as empty white square box the body data for s3bucket.upload is in <Buffer format i have tried converting to base64 and buffering even then no luck.
const express = require('express');
const bodyParser = require('body-parser');
var Request = require("request");
const AWS = require('aws-sdk');
var app = express();
var fs = require('fs');
const serverless = require('serverless-http');
var multipart = require('connect-multiparty');
var multipartMiddleware = multipart({ uploadDir: '/tmp' })
app.post('/uploadImageToS3',multipartMiddleware, (req, res) => {
console.log(req.files);
console.log(req.files.uImage.type);
if(typeof req.files.uImage !== 'undefined' && req.files.uImage !== null)
{
AWS.config.update({
accessKeyId: '.................',
secretAccessKey: '................................',
region: 'ap-south-1'
});
var file = req.files.uImage;
var s3bucket = new AWS.S3();
fs.readFile(file.path, function (err, data) {
var params = {
Bucket: "..........",
Key:file.name,
Body:data,
ContentType: req.files.uImage.type,
ContentEncoding: 'base64'
};
console.log(data);
s3bucket.upload(params, function(err, data) {
if (err) {
return res.status(400).json(err);
console.log('ERROR MSG: ', err);
} else {
console.log(data.Location);
res.status(200).json(data);
}
});
});
}else
{
return res.status(400).json('No files were uploaded.');
}
});

Simple file upload to S3 using aws-sdk and Node/Express

I am at a loss of what I am doing wrong, here is what I have:
HTML
<html>
<body>
<form method="POST" action="/upload" enctype="multipart/form-data">
<div class="field">
<label for="image">Image Upload</label>
<input type="file" name="image" id="image">
</div>
<input type="submit" class="btn" value="Save">
</form>
</body>
</html>
Port 5000 is my Node.js server's port.
In this example I am using POST to /upload, and it works fine.
module.exports = function(app, models) {
var fs = require('fs');
var AWS = require('aws-sdk');
var accessKeyId = process.env.AWS_ACCESS_KEY || "xxxxxx";
var secretAccessKey = process.env.AWS_SECRET_KEY || "+xxxxxx+B+xxxxxxx";
AWS.config.update({
accessKeyId: accessKeyId,
secretAccessKey: secretAccessKey
});
var s3 = new AWS.S3();
app.post('/upload', function(req, res){
var params = {
Bucket: 'makersquest',
Key: 'myKey1234.png',
Body: "Hello"
};
s3.putObject(params, function (perr, pres) {
if (perr) {
console.log("Error uploading data: ", perr);
} else {
console.log("Successfully uploaded data to myBucket/myKey");
}
});
});
}
Now I want to post the file that I am POSTing, which is where the problem arises.
module.exports = function(app, models) {
var fs = require('fs');
var AWS = require('aws-sdk');
var accessKeyId = process.env.AWS_ACCESS_KEY || "xxxxxx";
var secretAccessKey = process.env.AWS_SECRET_KEY || "+xxxxxx+B+xxxxxxx";
AWS.config.update({
accessKeyId: accessKeyId,
secretAccessKey: secretAccessKey
});
var s3 = new AWS.S3();
app.post('/upload', function(req, res){
var path = req.files.image.path;
fs.readFile(path, function(err, file_buffer){
var params = {
Bucket: 'makersquest',
Key: 'myKey1234.png',
Body: file_buffer
};
s3.putObject(params, function (perr, pres) {
if (perr) {
console.log("Error uploading data: ", perr);
} else {
console.log("Successfully uploaded data to myBucket/myKey");
}
});
});
});
}
The error I get is:
TypeError: Cannot read property 'path' of undefined
As a matter of fact files is completely empty.
I am assuming I am missing something pretty obvious but I can't seem to find it.
You will need something like multer to handle multipart uploading.
Here is an example streaming your file upload to s3 using aws-sdk.
var multer = require('multer');
var AWS = require('aws-sdk');
var accessKeyId = process.env.AWS_ACCESS_KEY || "xxxxxx";
var secretAccessKey = process.env.AWS_SECRET_KEY || "+xxxxxx+B+xxxxxxx";
AWS.config.update({
accessKeyId: accessKeyId,
secretAccessKey: secretAccessKey
});
var s3 = new AWS.S3();
app.use(multer({ // https://github.com/expressjs/multer
dest: './public/uploads/',
limits : { fileSize:100000 },
rename: function (fieldname, filename) {
return filename.replace(/\W+/g, '-').toLowerCase();
},
onFileUploadData: function (file, data, req, res) {
// file : { fieldname, originalname, name, encoding, mimetype, path, extension, size, truncated, buffer }
var params = {
Bucket: 'makersquest',
Key: file.name,
Body: data
};
s3.putObject(params, function (perr, pres) {
if (perr) {
console.log("Error uploading data: ", perr);
} else {
console.log("Successfully uploaded data to myBucket/myKey");
}
});
}
}));
app.post('/upload', function(req, res){
if(req.files.image !== undefined){ // `image` is the field name from your form
res.redirect("/uploads"); // success
}else{
res.send("error, no file chosen");
}
});
Simple S3 File Upload Without Multer
var express = require('express')
const fileUpload = require('express-fileupload');
const app = express();
app.use(fileUpload());
var AWS = require('aws-sdk');
app.post('/imageUpload', async (req, res) => {
AWS.config.update({
accessKeyId: "ACCESS-KEY", // Access key ID
secretAccesskey: "SECRET-ACCESS-KEY", // Secret access key
region: "us-east-1" //Region
})
const s3 = new AWS.S3();
// Binary data base64
const fileContent = Buffer.from(req.files.uploadedFileName.data, 'binary');
// Setting up S3 upload parameters
const params = {
Bucket: 'BUKET-NAME',
Key: "test.jpg", // File name you want to save as in S3
Body: fileContent
};
// Uploading files to the bucket
s3.upload(params, function(err, data) {
if (err) {
throw err;
}
res.send({
"response_code": 200,
"response_message": "Success",
"response_data": data
});
});
})
app.listen(3000, function () {
console.log('Example app listening on port 3000!');
});
[Update Mar 2022] Supports multiple file uploads at a time, and returns the uploaded file(s)' public URL(s) too.
Latest Answer # Dec-2016 [New]
Use multer-s3 for multipart uploading to s3 without saving on local disk as:
var express = require('express'),
aws = require('aws-sdk'),
bodyParser = require('body-parser'),
multer = require('multer'),
multerS3 = require('multer-s3');
aws.config.update({
secretAccessKey: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
accessKeyId: 'XXXXXXXXXXXXXXX',
region: 'us-east-1'
});
var app = express(),
s3 = new aws.S3();
app.use(bodyParser.json());
var upload = multer({
storage: multerS3({
s3: s3,
acl: 'public-read',
bucket: 'bucket-name',
key: function (req, file, cb) {
console.log(file);
cb(null, file.originalname); //use Date.now() for unique file keys
}
})
});
//open in browser to see upload form
app.get('/', function (req, res) {
res.sendFile(__dirname + '/index.html');
});
//use by upload form
app.post('/upload', upload.array('upl', 25), function (req, res, next) {
res.send({
message: "Uploaded!",
urls: req.files.map(function(file) {
return {url: file.location, name: file.key, type: file.mimetype, size: file.size};
})
});
});
app.listen(3000, function () {
console.log('Example app listening on port 3000!');
});
Latest Answer # Mar-2016 [Old-One]
Edited 1 use multer#1.1.0 and multer-s3#1.4.1 for following snippet:
var express = require('express'),
bodyParser = require('body-parser'),
multer = require('multer'),
s3 = require('multer-s3');
var app = express();
app.use(bodyParser.json());
var upload = multer({
storage: s3({
dirname: '/',
bucket: 'bucket-name',
secretAccessKey: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
accessKeyId: 'XXXXXXXXXXXXXXX',
region: 'us-east-1',
filename: function (req, file, cb) {
cb(null, file.originalname); //use Date.now() for unique file keys
}
})
});
//open in browser to see upload form
app.get('/', function (req, res) {
res.sendFile(__dirname + '/index.html');
});
//use by upload form
app.post('/upload', upload.array('upl'), function (req, res, next) {
res.send("Uploaded!");
});
app.listen(3000, function () {
console.log('Example app listening on port 3000!');
});
For complete running example clone express_multer_s3 repo and run node app.
You need something like multer in your set of middleware to handle multipart/form-data for you and populate req.files. From the doco:
var express = require('express')
var multer = require('multer')
var app = express()
app.use(multer({ dest: './uploads/'}))
Now req.files.image.path should be populated in your app.post function.
One of the easy ways to upload your image is to use an NPM package Multer
You can upload an image to S3 and then store its name in your database so every time you want to fetch it you can generate a signed URL for that image. This is one of the ways to secure access to your S3 bucket.
For uploading an image you can do something like this
const AWS = require("aws-sdk");
const express = require("express");
const multer = require("multer");
const crypto = require("crypto");
const cors = require("cors");
const {
S3Client,
PutObjectCommand
} = require("#aws-sdk/client-s3");
const app = express();
app.use(cors());
app.use(express.json());
const port = process.env.PORT || 3000
const storage = multer.memoryStorage();
const upload = multer({ storage: storage });
// Read the values from .env file
const bucketName = process.env.BUCKET_NAME;
const bucketRegion = process.env.BUCKET_REGION;
const accessId = process.env.ACCESS_ID;
const secretAccessKey = process.env.SECRET_ACCESS_KEY;
// Create a client
const s3 = new S3Client({
credentials: {
accessKeyId: accessId,
secretAccessKey: secretAccessKey,
},
region: bucketRegion,
});
// This function generates unique name for our files
const generateFileName = (bytes = 32) =>
crypto.randomBytes(bytes).toString("hex");
// Notice the upload middleware.
// "image" is the same name that you will pass form your UI request
app.post('/', upload.single("image"), (req, res) => {
# When you use multer the image can be accessed from req.file
let fileName = generateFileName()
let params = {
Bucket: bucketName,
Key: fileName,
Body: req.file.buffer ,
ContentType: req.file.mimetype,
ContentEncoding: 'base64',
};
const command = new PutObjectCommand(params);
await s3.send(command);
// before sending response you can save the 'fileName' in the DB of your choice
res.send('image uploaded')
})
app.listen(port, () => {
console.log(`app listening on port ${port}`)
})
Next, to get the signed URL for the image you can do as follows
// assuming other things are set as above snippet
const { GetObjectCommand } = require("#aws-sdk/client-s3");
const { getSignedUrl } = require("#aws-sdk/s3-request-presigner");
app.get('/', (req, res) => {
// First you will get the image name that was saved in DB
// lets say it was called user_image.
let obj_params = {
Bucket: bucketName,
Key: user_image,
};
let command = new GetObjectCommand(obj_params);
image_url = await getSignedUrl(
s3,
command,
{ expiresIn: 86400 } // seconds in a day
);
let response = {
success: true,
data: {
image_url
},
};
res.status(200).send(response);
})
Note:
Note that you might need to install some packages to make it work.
Make sure in your API requests you are setting 'content-type': 'multipart/form-data' in request headers
In your API gateway in S3, you might also need to set the Binary Media Type as multipart/form-data. More info on that in this link
This stack overflow was the best answer I found explaining exactly how to get Node to S3 working.
AWS Missing credentials when i try send something to my S3 Bucket (Node.js)
This in addition to some more stuff I had to hack on to get it all working. In my situation I was using a MEAN stack application so my Node file I was working with was a route file.
my aconfig.json file with the amazon credentials looks like this:
{ "accessKeyId": "*****YourAccessKey****", "secretAccessKey": "***YourSecretKey****" }
The final contents of the route file look like the file pasted below.
router.post('/sendToS3', function(req, res) {
var fs = require('fs');
var multer = require('multer');
var AWS = require('aws-sdk');
var path = require('path');
var awsCredFile = path.join(__dirname, '.', 'aconfig.json');
console.log('awsCredFile is');
console.log(awsCredFile);
AWS.config.loadFromPath(awsCredFile);
var s3 = new AWS.S3();
var photoBucket = new AWS.S3({params: {Bucket: 'myGreatBucketName'}});
var sampleFile = {
"_id" : 345345,
"fieldname" : "uploads[]",
"originalname" : "IMG_1030.JPG",
"encoding" : "7bit",
"mimetype" : "image/jpeg",
"destination" : "./public/images/uploads",
"filename" : "31a66c51883595e74ab7ae5e66fb2ab8",
"path" : "/images/uploads/31a66c51883595e74ab7ae5e66fb2ab8",
"size" : 251556,
"user" : "579fbe61adac4a8a73b6f508"
};
var filePathToSend = path.join(__dirname, '../public', sampleFile.path);
function uploadToS3(filepath, destFileName, callback) {
photoBucket
.upload({
ACL: 'public-read',
Body: fs.createReadStream(filepath),
Key: destFileName.toString(),
ContentType: 'application/octet-stream' // force download if it's accessed as a top location
})
// http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3/ManagedUpload.html#httpUploadProgress-event
.on('httpUploadProgress', function(evt) { console.log(evt); })
// http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3/ManagedUpload.html#send-property
.send(callback);
}
multer({limits: {fileSize:10*1024*1024}});
console.log('filePathToSend is ');
console.log(filePathToSend);
uploadToS3(filePathToSend, sampleFile.filename, function (err, data) {
if (err) {
console.error(err);
return res.status(500).send('failed to upload to s3').end();
}
res.status(200)
.send('File uploaded to S3: '
+ data.Location.replace(/</g, '<')
+ '<br/><img src="' + data.Location.replace(/"/g, '"') + '"/>')
.end();
});
console.log('uploading now...');
});
This took me a while to finally get working, but if you setup the route below, update the sampleFile JSON to point to a real file on your system and hit it with Postman it will publish a file to your S3 account.
Hope this helps

Resources