Express GraphicsMagick - javascript

I'm currently building a MEAN app and the section I'm currently working on involves image uploads. I'm trying to use GraphicsMagick for Node but I'm not really having any success. Below is my POST request for image uploads (as is):
app.post('/api/users/upload_image', function (req, res) {
var fstream;
req.pipe(req.busboy);
req.busboy.on('file', function (fieldname, file, filename) {
console.log('\n\nUploading file: '.underline.bold +filename .underline.bold);
// var readStream = fs.createReadStream(filename);
// gm(readStream, readStream.path)
// .resize('200','200')
// .stream(function (err, stdout, stderr) {
// var writeStream = fs.createWriteStream('www/uploads/' + readStream.path);
// stdout.pipe(writeStream);
// });
fstream = fs.createWriteStream('www/uploads/' + filename);
file.pipe(fstream);
});
req.busboy.on('finish', function () {
res.writeHead(303, { Connection: 'close', Location: '/' });
res.end();
});
});
The commented out section is my attempt at using GM but that throws back the error: Error: ENOENT, open '[filename].jpg'
Where am I going wrong? This is my first try at using GM so I'm a newb to this library!

var readStream = fs.createReadStream(filename);
at this line file named filename is not actually there yet you write that file below the commented lines. What you have is read steam named file you get from busyboy. so get rid of this line and pass file to gm directly
gm(file,....
Code below does exactly what you want, note the gm's write function's parameters.
app.post('/api/users/upload_image', function (req, res) {
req.pipe(req.busboy);
req.busboy.on('file', function (fieldname, file, filename) {
console.log('\n\nUploading file: '.underline.bold +filename .underline.bold);
console.log('Resizing file...');
gm(file,'www/uploads/' + filename)
.resize(200,200)
.write('www/uploads/' + filename, function (err) {
console.log("finished");
});
});
req.busboy.on('finish', function () {
res.writeHead(303, { Connection: 'close', Location: '/' });
res.end();
});
});
Note that gm's resize when used this way, simply fit's image in 200x200 box, does not exactly rescale/stretch it to 200x200.
If this still give you an error like EPIPE, your node process might not have sufficent permissions to write that file.

The problem is that you're trying to read the file before it's written. If you don't need to save the original image, you could just stream the file directly to gm instead:
req.busboy.on('file', function (fieldname, file, filename, encoding, mimetype) {
console.log('\n\nUploading file: '.underline.bold +filename .underline.bold);
console.log('Resizing file...');
gm(file, filename)
.resize('200','200')
.write('www/uploads/' + filename, function(err) {
if (err) throw err; // handle better
console.log('Success!'.bold);
});
});
You should probably also check that the given mime type of the file matches what you expect (e.g. image/jpeg, image/png, etc). You could also take that a step farther by using something like mmmagic to check the file type. However with mmmagic, you would have to forgo the direct streaming to gm option and save the file to disk first.

Related

Adding local file to zip

I'm trying to add a local file to the zip so when the user downloads and unzips, he'll get a folder with a .dll and a config.json file:
var zip = new JSZip();
options.forEach(option => {
zip.folder("REST." + option + ".Connector")
.file("config.json", "//config for " + option)
// I want this file to be from a local directory within my project
// eg. {dir}\custom_rest_connector_repository\src\dlls\Connectors.RestConnector.dll
.file('../dlls/Connectors.RestConnector.dll', null);
});
zip.generateAsync({type:"blob"}).then(function (blob) {
FileSaver.saveAs(blob, "REST_Connectors_"
+ dateStr
+ ".zip");
});
I read through the JSZip documentation but couldn't find an example or any information whether this can actually be done.
If it can't, is there any other more robust library that does support this operation?
Found the answer to my own question using the jszip-utils
JSZipUtils.getBinaryContent("../dlls/Connectors.RestConnector.dll", function (err, data) {
if(err) {
throw err; // or handle the error
}
zip.file("../dlls/Connectors.RestConnector.dll", data, {binary:true});
});

busboy "file" to gm to ftp upload

I want to re size a uploaded image on nodejs and send it on via ftp.
Using nodejs, busboy, GraphicsMagick, and jsftp.
var uploadFile = function(dir, req, cb) {
req.busboy.on('file', function(fieldname, file, filename, encoding, mimetype) {
var imageMagick = gm.subClass({
imageMagick: true
});
console.log('Resizing file...');
console.log(filename);
imageMagick(file)
.resize(150, 150)
.stream(function(err, stdout, stderr) {
if (err)
console.log(err);
var i = [];
stdout.on('data', function(data) {
console.log('data');
i.push(data);
});
stdout.on('close', function() {
console.log('close');
var image = Buffer.concat(i);
console.log(image.length);
console.log(image);
ftp.put(image, filepath, function(hadError) {
if (!hadError) {
filename = config.one.filepath + dir + "/" + filename;
cb(null, filename);
} else {
console.log(hadError);
cb(hadError, 'Error');
}
});
});
});
});
req.pipe(req.busboy);
};
The output is now:
Resizing file...
100-0001_IMG.JPG
close
0
<Buffer >
On ftp server side I get a 0 bytes file and also never doing a cb.
I found this two questions but couln't make it work for me:
Question 1
Question 2
I guess there must be something wrong with my file I gave to gm because "data" is never written to the console.
File it self is fine since I managed to upload a unresized file to the ftp server.
I appreciate every help!
Thx
Firstly you can check your stream for errors:
stdout.on('error', function(err) {
console.log(err);
});
So if there is an error related to imageMagick it can be a mismatching of imageMagic binary and node-imagemagick library

Reading file to disk error, "name and value are required for setHeader()"

Trying to allow users to upload image files to the Node.js server in a MEAN Stack application. I am using ng-file-upload for the client side angular directive. That seems to be working good enough. I run into an error when I pass the image to the server.
I use an API route to handle the work on the server side. The server will be responsible for saving the file to disk with node-multiparty module. It seems to hit route but when it tries to emit a close event I get the error. throw new Error('"name" and "value" are required for setHeader().'
The file I want is in my temp folder but it doesn't get saved to the target directory on my server plus I get the header error after the file should have been saved. So I need to stop the error and save the file with fs.rename() to the target image directory.
Here is the code that is breaking.
file api.js
// router to save images
router.route('/img/upload')
.post(function (req, res) {
console.log("image upload hits the router")
var options = {};
var count = 0;
var form = new multiparty.Form(options);
//save file to disk
form.on('file', function (name, file) {
var uploadDirectory = 'img/user/profile/';
var oldPath = file.path;
var newPath = uploadDirectory + file.originalFilename;
fs.rename(oldPath, newPath, function (err) {
if (err) throw err;
console.log('renamed complete');
});
});
// Close emitted after form parsed
form.on('close', function () {
console.log('Upload completed!');
res.setHeader('text/plain'); // Here is the line that gives an error.
res.end('Received ' + count + ' files');
});
// Parse req
form.parse(req);
});
So this is what I got to work for me
The actual line that gave me an error was setHeaders. It appears I needed to put the name and value as strings separated by a comma. This works perfectly for me now. I hope it saves everyone time coding.
// post
.post(function (req, res) {
var options = {};
var count = 0;
var form = new multiparty.Form(options);
form.on('error', function (err) {
console.log('Error parsing form: ' + err.stack);
});
//save file to disk
form.on('file', function (name, file) {
var uploadDirectory = '/img/user/profile/';
var oldPath = file.path;
var newPath = uploadDirectory + file.originalFilename;
fs.rename(oldPath, newPath, function (err) {
if (err) throw err;
console.log('renamed complete');
});
});
// Close emitted after form parsed
form.on('close', function () {
console.log('Upload completed!');
res.setHeader('Content-Type', 'text/plain');
res.end('Received ' + count + ' files');
});
// Parse req
form.parse(req);
});

Blocking or not blocking (Express.js)

Blocking or not blocking, the question is now:
Here is simple route exposing, a folder that server stores temp images. This method just returns image, and thats it.
app.get('/uploads/fullsize/:file',function (req, res){
var file = req.params.file;
console.log("Crap comign from passport file: " + file)
var img = fs.readFileSync(myPath + "/uploads/fullsize/" + file);
res.writeHead(200, {'Content-Type': 'image/jpg' });
res.end(img, 'binary');
} );
I am concerned with the following line:
var img = fs.readFileSync(myPath + "/uploads/fullsize/" + file);
That appears to be sync call. Shall I change that to async?
fs.readFile(req.files.file.path, function (err, imageBinaryData) {
//read code here
});
Is this a valid concern or I am over reacting? Am I going to have block say if I have 1000 concurrent users doing the same thing?
Yes - we should make async what we can.
"readFile" is fine ! But this might not be the most important part:
Additionally the path says "fullsize" and so you should think about streaming the files.
You spoke about 1000 concurrent users and it depends how big the images are :
An async readFile will load the whole file to memory. And what if you have 1000 users, each loading >8MB at the same time. Your servers memory might be "full".
For "streaming" I can recommend this video:
Node.js - streaming 25GB text file
Yes, you should change that to an async call. I recommend using the Q library to make this call, as well as other async calls.
Example (straight from Q docs):
var readFile = Q.denodeify(FS.readFile);
Then use it as such:
readFile("foo.txt", "utf-8")
.then(function(data) {
//other processing
}
Or adapted to your example:
app.get('/uploads/fullsize/:file',function (req, res){
var readFile = Q.denodeify(FS.readFile);
var file = req.params.file;
console.log("Crap comign from passport file: " + file)
readFile(myPath + "/uploads/fullsize/" + file)
.then(function(img) {
res.writeHead(200, {'Content-Type': 'image/jpg' });
res.end(img, 'binary');
})
.fail(function(err) {
res.send(500, {message:err});
}
} );

Node/Express Generate a one time route / link / download?

How would I go about creating a one time download link in nodeJS or Express?
I'm trying to find the simplest way to accomplish this. My ideas so far are:
Use fs stream to read and then delete the file
or
Somehow generate a link/route that gets removed once the download button is clicked
Are any of these implementations possible?
Is there a simpler way?
Any help or example code would be greatly appreciated!
-Thanks
Check this simple implementation:
You store the information of the download in a file. The filename is the download session id. The file content is the real path of the file to be downloaded.
Use these three functions to manage the lifecycle of the download sessions:
var fs = require('fs');
var crypto = require('crypto');
var path = require('path');
// Path where we store the download sessions
const DL_SESSION_FOLDER = '/var/download_sessions';
/* Creates a download session */
function createDownload(filePath, callback) {
// Check the existence of DL_SESSION_FOLDER
if (!fs.existsSync(DL_SESSION_FOLDER)) return callback(new Error('Session directory does not exist'));
// Check the existence of the file
if (!fs.existsSync(filePath)) return callback(new Error('File doest not exist'));
// Generate the download sid (session id)
var downloadSid = crypto.createHash('md5').update(Math.random().toString()).digest('hex');
// Generate the download session filename
var dlSessionFileName = path.join(DL_SESSION_FOLDER, downloadSid + '.download');
// Write the link of the file to the download session file
fs.writeFile(dlSessionFileName, filePath, function(err) {
if (err) return callback(err);
// If succeeded, return the new download sid
callback(null, downloadSid);
});
}
/* Gets the download file path related to a download sid */
function getDownloadFilePath(downloadSid, callback) {
// Get the download session file name
var dlSessionFileName = path.join(DL_SESSION_FOLDER, downloadSid + '.download');
// Check if the download session exists
if (!fs.existsSync(dlSessionFileName)) return callback(new Error('Download does not exist'));
// Get the file path
fs.readFile(dlSessionFileName, function(err, data) {
if (err) return callback(err);
// Return the file path
callback(null, data);
});
}
/* Deletes a download session */
function deleteDownload(downloadSid, callback) {
// Get the download session file name
var dlSessionFileName = path.join(DL_SESSION_FOLDER, downloadSid + '.download');
// Check if the download session exists
if (!fs.existsSync(dlSessionFileName)) return callback(new Error('Download does not exist'));
// Delete the download session
fs.unlink(dlSessionFileName, function(err) {
if (err) return callback(err);
// Return success (no error)
callback();
});
}
Use createDownload() to create download sessions wherever you need to. It returns the download sid, then you can use it to build your download URL like: http://your.server.com/download?sid=<RETURNED SID>.
Finally you can add a simple handler to your /download route:
app.get('/download', function(req, res, next) {
// Get the download sid
var downloadSid = req.query.sid;
// Get the download file path
getDownloadFilePath(downloadSid, function(err, path) {
if (err) return res.end('Error');
// Read and send the file here...
// Finally, delete the download session to invalidate the link
deleteDownload(downloadSid, function(err) {
// ...
});
});
});
With this method, you don't have to create/move/delete big download files, which could cause slow responses and unnecessary resource consumption.
You can delete routes from the app.routes object. See Remove route mappings in NodeJS Express for more info.
Here is my quick and not very well tested way of doing what you ask:
var express = require('express');
var app = express();
app.get('/download', function(req,res,next){
res.download('./path/to/your.file');
//find this route and delete it.
for(i = 0; i < app.routes.get.length; i++){
if(app.routes.get[i].path === '/download'){
app.routes.get.splice(i,1);
}
}
});
app.listen(80);
I'd probably map a single route to manage downloads, and then upon downloading the file, move or delete it. That way I can prevent a lot of cashing of routes, or a lot of small temp files from the other two answers, but YMMV. Something like this:
// say your downloads are in /downloads
app.get('/dl/:filename', function(req, res) {
var fileStream = fs.createReadStream('/downloads' + req.params.filename);
// error handler, ie. file not there...
fileStream.on('error', function(err) {
if(err) {
res.status(404); // or something
return res.end();
}
});
// here you ow pipe that stream to the response,
fileStream.on('data', downloadHandler);
// and here delete the file or move it to other folder or whatever, do cleanup
fileStream.on('end', deleteFileHandler);
}
Note: This is a possible security vulnerability, it could let the adversary download files outside your downloads location. That filename param is passed directly to fs.

Categories

Resources