Serving Temporary Files with NodeJs - node.js

I am building a NodeJs SOAP client. Originally, I imagined the server (ie the node SOAP client) would allow downloading documents through a REST API (the REST API is authenticated). After a good deal of time on Google and SO, looks like that is not possible.
That means when a document download is requested, I'll have to make a SOAP call for the document and return a URL to the REST client via AJAX.
In order to do this I'll need to:
Temporarily create a file in Node
get its URL and return to web client
When the file is requested and response is sent, delete the file (for security purposes)
Here are my questions:
Is there already a framework that does this? the temp module might be an option, but really I'd like to delete after every request, not after a time period.
If not, can I do this just using the NodeJs File System, and Express static module? Basically we would modify the static module to look like this:
return function static(req, res, next) {
if ('GET' != req.method && 'HEAD' != req.method) return next();
var path = parse(req).pathname;
var pause = utils.pause(req);
/* My Added Code Here */
res.on('end', function(){
// delete file based on req URL
})
/* end additions */
function resume() {
next();
pause.resume();
}
function directory() {
if (!redirect) return resume();
var pathname = url.parse(req.originalUrl).pathname;
res.statusCode = 301;
res.setHeader('Location', pathname + '/');
res.end('Redirecting to ' + utils.escape(pathname) + '/');
}
function error(err) {
if (404 == err.status) return resume();
next(err);
}
send(req, path)
.maxage(options.maxAge || 0)
.root(root)
.hidden(options.hidden)
.on('error', error)
.on('directory', directory)
.pipe(res);
};
Is res.on('end',... vaild? Alternatively,should I create some middleware that does this for URLs pointing to the temporary files?

Found two SO questions that answer my question. So apparently we don't need to use the express.static middleware. We just need the filesystem to download a file:
app.get('/download', function(req, res){
var file = __dirname + '/upload-folder/dramaticpenguin.MOV';
res.download(file); // Set disposition and send it.
});
If we want to stream and then delete follow:
app.get('/download', function(req, res){
var stream = fs.createReadStream('<filepath>/example.pdf', {bufferSize: 64 * 1024})
stream.pipe(res);
var had_error = false;
stream.on('error', function(err){
had_error = true;
});
stream.on('close', function(){
if (!had_error) fs.unlink('<filepath>/example.pdf');
});

If you are visiting this SO page after Dec 2015, you may find that the previous solution isn't working (At least it isn't working for me). I found a different solution so I thought I would provide it here for future readers.
app.get('/download', function(req, res){
res.download(pathToFile, 'fileNameForEndUser.pdf', function(err) {
if (!err) {
fs.unlink(path);
}
});
});

Related

How to respond without reloading my current page?

I have Express API which used to upload file to the files directory. Whenever I call my API res.send redirecting to a new page. How can I perform this API with reloading my current page?
app.post('/upload', function(req, res) {
let sampleFile;
let uploadPath;
if (Object.keys(req.files).length == 0) {
res.status(400).send('No files were uploaded.');
return;
}
console.log('req.files >>>', req.files); // eslint-disable-line
sampleFile = req.files.sampleFile;
console.log('lusu', sampleFile); // eslint-disable-line
uploadPath = __dirname + '/uploads/' + sampleFile.name;
sampleFile.mv(uploadPath, function(err) {
if (err) {
return res.status(500).send(err);
}
res.send('File uploaded to ' + uploadPath);
});
});
First of all, I would like to recommend multer package for file upload in node js.
instead of res.send(), try res.status(200).json({message:"successfully uploaded"})
try debugging at front end, suppose you have a function for file upload like below,
function fileUpload(){
http.post('url',{headers:headers}).then(res){
// Try to handle the response here. Do not write anything that reloads the page.
}
}

res.sendFile does not work

I am using Express 4.13, and I met some problems when I tried to serve the files:
function doServeFile(name, fullpath, res, download) {
if (fullpath) {
if (download)
res.download(fullpath, name);
else {
res.sendFile(fullpath, {}, function (err) {
console.info(err);
});
}
}
else {
res.status(404);
}
}
function serveFile(id, res, download) {
Model.findById(id).then(file=> {
var fullPath = Config.uploadDest + "/" + file.path;
doServeFile(file.filename, fullPath, res, download)
}).catch(Util.dbErrorHandler);
}
router.get("/:id", function (req, res, next) {
serveFile(req.params.id, res);
});
router.get("/download/:id", function (req, res, next) {
serveFile(req.params.id, res, true);
});
As the codes shown, once I send request /1 to the server, it will retrieve the file whose id is 1 to get the file path, and then use the res.sendFile to serve the file to client.
However when I run the application, I found that the request will hang on too long to have the response. But I can see the logs like this:
---try send file:D:/file_upload/1464578330791_user.jpg
It seems that the file has been fetched, but the res.sendFile does not complete its job.
Also, when I tried /download/1, the file can be downloaded.
What's going on ?
I had this same issue the other day and found that sendFile doesn't actually acknowledge absolute paths correctly and hangs. Setting the root option solved the issue. Hopefully it might work for you.
res.sendFile(fullpath, {root: '/'});
After dig into the source codes of express, I found the answer.
When the res.sendFile is called, express will to make sure that the root of the path is set or the path must be an absolute path, check this, it is just the isAbsolutepath which cause this problem,codes:
exports.isAbsolute = function(path){
if ('/' == path[0]) return true;
if (':' == path[1] && '\\' == path[2]) return true;
if ('\\\\' == path.substring(0, 2)) return true; // Microsoft Azure absolute path
};
So a path like
D:/a/a.txt will not be treated as an absolute path!
D:\\a\\a.txt will be. And so does linux-like path /home/a/a.txt.
And this is how I build the path(manually), which is not an absolute path from express's perspective:
var fullPath = Config.uploadDest + "/" + file.path;
So I change it to:
var fullPath = path.join(Config.uploadDest,file.path);
It worked.
If you are using windows operating system, use its full path such as
D:/folder1/folder2/index.html

How to stop upload and redirect in busboy if mime type is invalid?

I am fairly new to Node.js, and I am using Express and Busboy-Connect to create a simple file upload form, for wav files only.
Here is what I am trying to do :
- start the upload
- if the mimetype is not wav, redirect to an error page
- else : write the file on the server and redirect back.
If the mimetype is valid, everything works fine, but if it isn't I cannot redirect and the browser is just hanging and eventually times out.
My understanding of it is that the browser doesn't want to redirect because it is waiting for the upload to finish, but how can I cancel the upload then within my js code ?
I could work around the issue and write the file then delete it if it's not the right mimetype, but I think it's a bit stupid to do that, I'd rather find a way to trigger an event that will stop it and redirect immediately.
Here is (a snippet of) my app code :
app.get('/', function (req, res) {
res.render(__dirname + '/public/index.ejs', {error: 0});
});
app.get('/error', function (req, res) {
res.render(__dirname + '/public/index.ejs', {error: 1});
});
app.post('/upload', function (req, res) {
var timestamp = new Date().getTime().toString();
//console.log(timestamp);
var fstream;
req.pipe(req.busboy);
req.busboy.on('file', function (fieldname, file, filename, encoding, mimetype) {
if ("audio/wav" != mimetype)
{
console.log("invalid mimetype"); // that prints ok
// req.busboy.end(); // I tried that but it doesn't work
res.redirect('/error');
}
else
{
console.log("Uploading: " + mimetype);
fstream = fs.createWriteStream(__dirname + '/tmp/' + timestamp + filename);
file.pipe(fstream);
fstream.on('close', function () {
res.redirect('back');
});
}
});
});
Can anyone point me in the right direction?
Thank you for your help !
Alright I found it in the docs of npm, if you think anyone could be interested in finding this answer from a google search you can leave it resolved, otherwise feel free to close/remove this post.
Basically there is a function on the filestream that need to be called to unblock busboy, so all I had to do to make it work is to add
file.resume();
before redirecting to the error page.

Flowjs file upload - AngularJS and Node

I am using Flowjs, and its ng-flow directive to upload file with NodeJS as backend. When i try to upload file, only file in tem folder is uploaded, but it's note any type of file like JPG or PNG. (flow-135601-juicy_gustinektar02l_10185jpg.1). Here is code:
ANGULARJS
app.config(['flowFactoryProvider', function (flowFactoryProvider) {
flowFactoryProvider.defaults = {
target: 'http://localhost:8086/api/upload/',
permanentErrors: [500, 501],
maxChunkRetries: 1,
chunkRetryInterval: 5000,
simultaneousUploads: 1
};
flowFactoryProvider.on('catchAll', function (event) {
console.log('catchAll', arguments);
});
// Can be used with different implementations of Flow.js
//flowFactoryProvider.factory = fustyFlowFactory;
}]);
NODEJS
// Handle uploads through Flow.js
app.post('/api/upload', function(req, res){
flow.post(req, function(status, filename, original_filename, identifier){
console.log('POST', status, original_filename, identifier);
res.send(200, {
// NOTE: Uncomment this funciton to enable cross-domain request.
//'Access-Control-Allow-Origin': '*'
});
});
});
// Handle cross-domain requests
// NOTE: Uncomment this funciton to enable cross-domain request.
/*
app.options('/upload', function(req, res){
console.log('OPTIONS');
res.send(true, {
'Access-Control-Allow-Origin': '*'
}, 200);
});
*/
// Handle status checks on chunks through Flow.js
app.get('/api/upload', function(req, res){
flow.get(req, function(status, filename, original_filename, identifier){
console.log('GET', status);
res.send(200, (status == 'found' ? 200 : 404));
});
});
Reassembling all chunks is easy, just call this:
var stream = fs.createWriteStream(filename);
r.write(identifier, stream);
And that is it!
But other question is, when this method should be called?
Maybe when all chunks are uploaded and present at tmp folder.
But there is another issue with duplicate calls of the done.
This can be solved by creating and locking the file, once all chunks exists.
Then call
r.write(identifier, stream);
Then clean all chunks, release the lock and close the file.
Same approuch is done in php server side library: https://github.com/flowjs/flow-php-server/blob/master/src/Flow/File.php#L102
Hope this helps, and I hope someone could collaborate and update node.js sample with these fixes.

Response headers of previous request affecting current request

The following code is the user-facing part of a new node app we are building:
var loadInvoice = function(req, res, next) {
Invoice.findById(req.params.invoiceId, function (err, invoice) {
if (err) {
res.send(404, 'Page not found');
} else {
req.invoice = invoice;
next();
}
});
};
app.namespace('/invoices/:invoiceId', loadInvoice, function () {
app.get('', function(req, res){
var templateVals = {
//some template data
};
res.render('paymentselection', templateVals);
});
app.post('', function(req, res){
var data = {
// some data for the apiCall
};
someAPI.someRequest(data, function(err, data) {
console.log(res.status());
res.redirect(data.url);
});
});
});
The first method returns a confirmation page where the user presses a button to post to the same url, which triggers a redirect to an external website.
This all works exactly once. Every second request will crash the app with the message Cant set headers after they are sent. After carefull inspection of the code I could find no reason for this to happen so I added the console.log line which indeed confirms the location header has been set. But it is set to the value i got from someAPI on the previous request not the current one.
This makes absolutely no sense to me. I do not store this value anywhere nor do I do caching or persistence of this data in any way.
Does anybody know what could be causing this?
I use express, express-namespace, mogoose and swig
I found out the problem was being caused bij the 'Restler' libaray used within 'someAPI'. I have no idea how this is possible but swapping it out with something else fixed the problem.

Resources