I think this should be a straight forward thing, but I can't fing a solution :s
I'm trying to figure out the best way to show images stored on amazon S3 on a website.
Currently I'm trying to get this to work (unsuccessful)
//app.js
app.get('/test', function (req, res) {
var file = fs.createWriteStream('slash-s3.jpg');
client.getFile('guitarists/cAtiPkr.jpg', function(err, res) {
res.on('data', function(data) { file.write(data); });
res.on('end', function(chunk) { file.end(); });
});
});
//index.html
<img src="/test" />
Isn't it maybe possible to show the images directly from amazon ?
I mean, the solution that lightens the load on my server would be the best.
This is a typical use case for streams. What you want to do is: request a file from Amazon S3 and redirect the answer of this request (ie. the image) directly to the client, without storing a temporary file. This can be done by using the .pipe() function of a stream.
I assume the library you use to query Amazon S3 returns a stream, as you already use .on('data') and .on('end'), which are standard events for a stream object.
Here is how you can do it:
app.get('/test', function (req, res) {
client.getFile('guitarists/cAtiPkr.jpg', function(err, imageStream) {
imageStream.pipe(res);
});
});
By using pipe, we redirect the output of the request to S3 directly to the client. When the request to S3 closes, this will automatically end the res of express.
For more information about streams, refer to substack's excellent Stream Handbook.
PS: Be careful, in your code snippet you have two variables named res: the inner variable will mask the outer variable which could lead to hard to find bugs.
if you set the access controls correctly (on a per-key basis. not on the whole Bucket.) then you can just use <img src="http://aws.amazon.com/myBucket/myKey/moreKey.jpg"> (or another appropriate domain if you're using something other than us-east-1) wherever you want to display the image in your html. remember to set the MIME type if you want browsers to display the image instead of downloading the image as attachment when someone opens it.
aws-sdk docs
s3: PUT Object docs
var AWS = require('aws-sdk');
AWS.config.update({
accessKeyId: "something",
secretAccessKey: "something else",
region:'us-east-1',
sslEnabled: true,
});
var fileStream = fs.createReadStream(filename);
s3.putObject({
Bucket:'myBucket',
Key: 'myKey/moreKey.jpg',
Body: fileStream,
ContentType:'image/jpeg',
ACL: 'public-read',
}, function(err, data){
if(err){ return callback(err) };
console.log('Uploaded '+key);
callback(null);
});
Related
Hey everyone so I am trying to make this type of request in nodejs. I assume you can do it with multer but there is one major catch I don't want to download the file or upload it from a form I want to pull it directly from s3, get the object and send it as a file along with the other data to my route. Is it possible to do that?
Yes it's completely possible. Assuming you know your way around the aws-sdk, you can create a method for retrieving the file and use this method to get the data in your route and do whatever you please with them.
Example: (Helper Method)
getDataFromS3(filename, bucket, callback) {
var params = {
Bucket: bucket,
Key: filename
};
s3.getObject(params, function(err, data) {
if (err) {
callback(true, err.stack); // an error occurred
}
else {
callback(false, data); //success in retrieving data.
}
});
}
Your Route:
app.post('/something', (req, res) => {
var s3Object = getDataFromS3('filename', 'bucket', (err, file) => {
if(err) {
return res.json({ message: 'File retrieval failed' });
}
var routeProperties = {};
routeProperties.file = file;
routeProperties.someOtherdata = req.body.someOtherData;
return res.json({routeProperties});
});
});
Of course, the code might not be totally correct. But this is an approach that you can use to get what you want. Hope this helps.
There are two ways that I see here, you can either:
pipe this request to user, it means that you still download it and pass it through but you don't save it anywhere, just stream it through your backend.
There is a very similar question asked here: Streaming file from S3 with Express including information on length and filetype
I'm just gonna copy & paste code snippet just for the reference how it could be done
function sendResponseStream(req, res){
const s3 = new AWS.S3();
s3.getObject({Bucket: myBucket, Key: myFile})
.createReadStream()
.pipe(res);
}
if the file gets too big for you to easily handle, create presigned URL in S3 and send it through. User then can download the file himself straight from S3 for a limited amount of time, more details here: https://docs.aws.amazon.com/AmazonS3/latest/dev/ShareObjectPreSignedURL.html
Hi I would like to store images in amazon s3. I am making a react application with node js and express at the back end. I have a code which is saving the images locally, in images folder as desired. I am using jimp library to convert the images into black and white. What i want is to store these black and white images directly to aws instead of saving to local hdd. I need to do this because in the end the app has to be deployed to heroku, and heroku is not able to read images from local hdd.
Here is the code through which i was able to store images in a particular directory as required.
const input = req.body.input;
google.list({
keyword: input,
num: 15,
detail: true,
})
.then(function (res) {
res.map((data,index)=>{
const url = data.url;
const extension = url.split('.')[url.split('.').length-1]
const foldername=input
Jimp.read(url, function (err, image) {
image.resize(250, 250)
.greyscale()
.write(path.join(__dirname,"../../public/images/"+foldername+"/"+foldername+index+"."+extension));
});
});
})
}).catch(function(err) {
res.send('There was some error')
})
I need to store images in the same path ie., awsbucketname/foldername/foldername.jpg. I tried converting the image to buffer but still i don't understand how to proceed with it. Some one please help me :(
(Disclaimer: I have no practical experience with Jimp!)
It seems like you are on the right track with writing the image to a buffer instead of a local file. Once you have initialized the AWS SDK and instantiated the S3 interface, it should be easy to pass the buffer to the upload function. Something along the lines of:
const s3 = new AWS.S3({ params: { Bucket: 'yourBucketName' } });
// ...
Jimp.read(url, (err, image) => {
const bucketPath = `/${foldername}/${index}.${extension}`;
image.resize(250, 250)
.greyscale()
.getBuffer(Jimp.AUTO).then(buffer => {
s3.upload({ Key: bucketPath, Body: buffer })
.then(() => console.log('yay!'));
});
}
);
This is just a sketch of course, missing error handling etc.
So, bit of an odd problem. I have a bunch of media files saved as base64 strings in mongo, some are images, some are videos.
I made an API for getting the media files:
app.get('/api/media/:media_id', function (req, res) {
media.findById(req.params.media_id)
.exec(function (err, media) {
if (err) {
res.send(err);
}
var file = new Buffer(media.file, 'base64');
res.writeHead(200, {'Content-Type': media.type, 'Content-Transfer-Encoding': 'BASE64', 'Content-Length': file.length});
res.end(file);
});
});
Now, images have no problems. They load just fine, both directly from the API, and when I call the API from a front-end (for example <img src="/api/media/23498423">)
THE PROBLEM
If I fetch a video from a front-end, like the images - but with a video- or object-tag:
<video src="/api/media/3424525" controls></video>
there's no problem, but if I load the video in a browser directly from the API:
http://localhost:8080/api/media/3424525
the server process crashes, no errors. It simply just freezes up. And we're not talking about huge video files - it's a 1.5MB video.
The media type in the header for all the videos I'm testing with is video/mp4. Oh, and just to be clear: if I do the same with images, everything works perfectly.
EDIT:
Okay, so as suggested by #idbehold and #zeeshan I took a look at gridfs and gridfs-stream, and for the purpose of my app, this certainly is what I should have used in the first place. However, after implementing gridfs in my app, the problem still persists.
app.get('/api/media/:media_id', function (req, res) {
gfs.findOne({ _id: req.params.media_id }, function (err, file) {
if (err) {
return res.status(400).send(err);
}
if (!file) {
return res.status(404).send('');
}
res.set('Content-Type', file.contentType);
res.set('Content-Disposition', 'inline; filename="' + file.filename + '"');
var readstream = gfs.createReadStream({
_id: file._id
});
readstream.on("error", function (err) {
console.log("Got an error while processing stream: ", err.message);
res.end();
});
readstream.pipe(res);
});
});
When I call the media file (be it image or video) from a front-end, within a HTML tag, everything works out fine. But if I load a video (again, smallish videos from 1.5mb to max 6mb total size) directly in the browser, the server process freezes. To be a bit more clear: I am testing on windows, and the server app (server.js) is run in console. The console and the process it is running is what freezes. I cannot load any more pages/views in the node app, and I cannot even stop/kill/shutdown the node app or the console.
Streaming videos directly to/from GridFS using gridfs-stream either with mongodb-native db instance or mongoose.
var mongo = require('mongodb'),
Grid = require('gridfs-stream'),
db = new mongo.Db('yourDatabaseName', new mongo.Server("127.0.0.1", 27017)),
gfs = Grid(db, mongo);
//store
app.post('/video', function (req, res) {
req.pipe(gfs.createWriteStream({
filename: 'file_name_here'
}));
res.send("Success!");
});
//get
app.get('/video/:vid', function (req, res) {
gfs.createReadStream({
_id: req.params.vid // or provide filename: 'file_name_here'
}).pipe(res);
});
for complete files and running project:
Clone node-cheat direct_upload_gridfs, run node app followed by npm install express mongodb gridfs-stream.
Truly an odd problem...
I could be way off, but it's worth a shot:
One of the differences when opening a url directly from the browser is that the browser will also try to fetch http://localhost:8080/favicon.ico (while trying to find the tab icon). Maybe the problem is not related to your video code, but rather to some other route, trying to handle the /favicon.ico request?
Have you tried using wget or curl?
I don't know the answer, maybe this is a dumb suggestion, but what is the browser you are using? Maybe something from Microsoft causes the problem...
So in my routes I have a post which lets me send a multipart post, which then gets saved to a database and sends the pictures attached in the post request to amazon s3.
Here's my code for the route
exports.post = function(req, res){
var pictureNames = [];
var s3Bucket = new AWS.S3({params: {Bucket: 'anonybox'}});
for(key in req.files){
tp = req.files[key].path;
fn = req.files[key].name;
ftype = req.files[key].type;
pictureUrls.push(fn);
fs.readFile(tp, function(err, fileBuffer){
var params = {
Key: fn,
Body: fileBuffer,
ACL: 'public-read',
ContentType: ftype
};
s3Bucket.putObject(params, function(err, data){
if(err){
console.log("error" + err);
}else{
console.log("worked, data: "+JSON.stringify(data));
}
});
});
}
messageObject = {
message: req.body.message,
staytime: req.body.staytime,
picturenames: pictureNames
};
var postMessage = new MessageModel(messageObject);
postMessage.save(function(err, doc){
if(err || !doc){
throw 'Error';
}else{
console.log("created");
console.log(doc);
// res.json(doc);
}
});
When I try to send two images in the post request In the console the putObject function returns:
worked, data: {"ETag":"\"24c1d19724ca10f40bc633aa29315931\""}
worked, data: {"ETag":"\"4201e2a997779c5595dc35925e954191\""}
But only one of the files shows up in my bucket on s3, the last file in the req.files object.
The problem was not with the aws-sdk It was that the for loop kept running before the first file was read in fs.readfile, the solution is detailed here
Asynchronously reading and caching multiple files in nodejs
I had a similar issue with two file uploads, I'm just mentioning it for posterity in case someone has a similiar issue in the future: my image uploads were not embedded in a for-loop, but just sequential calls to s3.putObject(). The first image would show up just fine on page refresh, but the second image upload was appearing to "fail silently" without working. The answer, it turns out, for some reason the first file upload would update the cache, but the second image upload would not update the cache, thus appearing to not work (even though the actual file transfer to s3 was actually working). If you are having an issue, try clearing your cache and see if the fresh upload is actually working but your browser isn't showing it properly.
I'm trying to take an image and upload it to an Amazon S3 bucket using Node.js. In the end, I want to be able to push the image up to S3, and then be able to access that S3 URL and see the image in a browser. I'm using a Curl query to do an HTTP POST request with the image as the body.
curl -kvX POST --data-binary "#test.jpg" 'http://localhost:3031/upload/image'
Then on the Node.js side, I do this:
exports.pushImage = function(req, res) {
var image = new Buffer(req.body);
var s3bucket = new AWS.S3();
s3bucket.createBucket(function() {
var params = {Bucket: 'My/bucket', Key: 'test.jpg', Body: image};
// Put the object into the bucket.
s3bucket.putObject(params, function(err) {
if (err) {
res.writeHead(403, {'Content-Type':'text/plain'});
res.write("Error uploading data");
res.end()
} else {
res.writeHead(200, {'Content-Type':'text/plain'});
res.write("Success");
res.end()
}
});
});
};
My file is 0 bytes, as shown on Amazon S3. How do I make it so that I can use Node.js to push the binary file up to S3? What am I doing wrong with binary data and buffers?
UPDATE:
I found out what I needed to do. The curl query is the first thing that should be changed. This is the working one:
curl -kvX POST -F foobar=#my_image_name.jpg 'http://localhost:3031/upload/image'
Then, I added a line to convert to a Stream. This is the working code:
exports.pushImage = function(req, res) {
var image = new Buffer(req.body);
var s3bucket = new AWS.S3();
s3bucket.createBucket(function() {
var bodyStream = fs.createReadStream(req.files.foobar.path);
var params = {Bucket: 'My/bucket', Key: 'test.jpg', Body: bodyStream};
// Put the object into the bucket.
s3bucket.putObject(params, function(err) {
if (err) {
res.writeHead(403, {'Content-Type':'text/plain'});
res.write("Error uploading data");
res.end()
} else {
res.writeHead(200, {'Content-Type':'text/plain'});
res.write("Success");
res.end()
}
});
});
};
So, in order to upload a file to an API endpoint (using Node.js and Express) and have the API push that file to Amazon S3, first you need to perform a POST request with the "files" field populated. The file ends up on the API side, where it resides probably in some tmp directory. Amazon's S3 putObject method requires a Stream, so you need to create a read stream by giving the 'fs' module the path where the uploaded file exists.
I don't know if this is the proper way to upload data, but it works. Does anyone know if there is a way to POST binary data inside the request body and have the API send that to S3? I don't quite know what the difference is between a multi-part upload vs a standard POST to body.
I believe you need to pass the content-length in the header as documented on the S3 docs: http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectPUT.html
After spending quite a bit of time working on pushing assets to S3, I ended up using the AwsSum library with excellent results in production:
https://github.com/awssum/awssum-amazon-s3/
(See the documentation on setting your AWS credentials)
Example:
var fs = require('fs');
var bucket_name = 'your-bucket name'; // AwsSum also has the API for this if you need to create the buckets
var img_path = 'path_to_file';
var filename = 'your_new_filename';
// using stat to get the size to set contentLength
fs.stat(img_path, function(err, file_info) {
var bodyStream = fs.createReadStream( img_path );
var params = {
BucketName : bucket_name,
ObjectName : filename,
ContentLength : file_info.size,
Body : bodyStream
};
s3.putObject(params, function(err, data) {
if(err) //handle
var aws_url = 'https://s3.amazonaws.com/' + DEFAULT_BUCKET + '/' + filename;
});
});
UPDATE
So, if you are using something like Express or Connect which are built on Formidable, then you don't have access to the file stream as Formidable writes files to disk. So depending on how you upload it on the client side the image will either be in req.body or req.files. In my case, I use Express and on the client side, I post other data as well so the image has it's own parameter and is accessed as req.files.img_data. However you access it, that param is what you pass in as img_path in the above example.
If you need to / want to Stream the file that is trickier, though certainly possible and if you aren't manipulating the image you may want to look at taking a CORS approach and uploading directly to S3 as discussed here: Stream that user uploads directly to Amazon s3