NodeJs/Express display pdf file in browser - node.js

I am working on NodeJs/Express project and need to show in the browesr pdf file that is stored in
/public/images
Here is relevant router code:
router.post('/show_file', async (req,res)=>{
try {
let path = './public/images/1.pdf'
var data =fs.readFileSync(path);
res.contentType("application/pdf");
res.send(data);
} catch (err) {
res.status(500)
console.log(err)
res.send(err.message)
}
})
I don't get any errors but nothing is happening ie.browser is not opening etc.
Thanks in advance for any guidance.

The first change that I would do is to remove the async. It will just mess out the code with unneeded Promises.
Second, I removed the need to catch the exception, verifying the existence of the file with fs.existsSync(path). Try to not to rise exceptions as often as possible. If you know something can rise an exception, test it.
Last, and most important, I created a reading stream of the file and piped the result to the response with fs.createReadStream(path).pipe(res). This way, the client recieves the file as it is read and your memory is spared. Great for large files.
Reading a file can be memory intensive, so loading it all in memory is a bad practice. You just need a handfull of request to overload your machine.
You can read more on the pipe method here.
In this example, any GET call to /router/show_file will return the pdf.
const express = require('express')
const app = express()
const fs = require('fs')
const router = express.Router()
router.get('/show_file', (req, res) => {
const path = './public/images/1.pdf'
if (fs.existsSync(path)) {
res.contentType("application/pdf");
fs.createReadStream(path).pipe(res)
} else {
res.status(500)
console.log('File not found')
res.send('File not found')
}
})
app.use('/router', router) // Here we pass the router to the app with a path
app.listen(9999, () => console.log('Listening to port 9999'))

Related

Angular NodeJS Upload File and parameters together

I have been looking through multiple tutorials and stack overflow questions but for some reason I just cannot make this work. I have issues with uploading a file, so maybe fixing that first would solve the whole issue.
I tried a few options of sending a file from the front end to the back end, but it seems to always "get lost" before reaching the back end.
I have decided to use multer at the NodeJS backend to upload the file. Not sure if I am calling multer upload single right or not. Currently this is the code which I have for it:
const multer = require('multer');
const storage = multer.diskStorage({
destination: './uploadedImages',
filename: function(req,file,cb){
cb(null,file.originalname)
}
}) ;
const upload = multer({storage: storage})
exports.saveDrawing = async(req, res, next) => {
try
{
//save image
//tried a few different logs, but with FormData it seems like everything always empty
console.log("Image:");
console.log(req.body.drawingElement);
console.log(req.file);
upload.single('body.file');
return res.status(200).json({message: element});
}
}
catch (err)
{
console.log("Error at drawing save: " + err)
return res.status(500).json({message: "Error - Could not add/edit Drawing"});
}
}
And this is how it is sent from the Angular front end:
setDrawing(params, image): Observable<any> {
const formData = new FormData();
formData.append('file', image)
formData.append('data', params)
console.log("File: ");
console.log(formData.get('file'));
console.log("Data: ");
console.log(formData.get('data'));
return this.http.post<any>(`api/v1/structure/drawing/save`, formData);
}
At this stage printing out the data shows the right values. And the browser shows the right payload too:
At the back end I cannot see them in the req, req.body is empty, there is no req.form. For this api call before I have tried to include any files without the FromData I have accessed the data from req.body.
Am I looking for the data at the right place?
You're not using multer correctly, it's not doing anything.
To implement it as a middleware which you call from your handler, check the example from the docs
So, your handler should look something like this:
// setup multer middleware, set file field name
const upload = multer({storage: storage}).single('file');
exports.saveDrawing = async(req, res, next) => {
// now use the middleware, handle errors
upload(req, res, function (err) {
if (err instanceof multer.MulterError) {
// A Multer error occurred when uploading.
return res.status(500).json({message: "Error - Could not add/edit Drawing"});
} else if (err) {
// An unknown error occurred when uploading.
return res.status(500).json({message: "Error - Could not add/edit Drawing"});
}
// Everything went fine.
console.log("Image:");
console.log(req.body.drawingElement);
console.log(req.file);
return res.status(200).json({message: element});
});
});

Download file from /tmp to client in Google Cloud Function

I am trying to download a file created on an http Google Cloud function saved in the /tmp directory. Everything I try throws "Error: could not handle the request".
The file my code generates is saved at /tmp/output.wav and I can use fs.readdirSync('/tmp') to see the file. But, if I try res.download('/tmp/output.wav', 'output.wav') it throws the "Error: could not handle the request".
If I do res.send( fs.readdirSync('/tmp')[0] ); I can see the file. So I know it's there and readable. Why can't I get it to download to the client?
I checked the logs and there's nothing additional. This code also executes locally on my machine.
I am wondering if this is due to a quota limitation? Would I get a better error if so?
Full code:
exports.master = async (req, res ) => {
const fs = require('fs');
// Some code to make the file at /tmp/output.wav
fs.readdirSync('/tmp').forEach(file => {
console.log(file);
files.push( file );
});
// Code will fire up to this point
res.download('/tmp/output.wav', 'output.wav');
});
Your code is working on my end:
exports.master = async (req, res) => {
const fs = require('fs')
// Some code to make the file at /tmp directory
fs.writeFileSync('/tmp/output.txt','testing output')
var files = [{}];
fs.readdirSync('/tmp','utf8').forEach(file => {
console.log(file);
files.push(file);
});
res.download('/tmp/output.txt','output.txt')
}
I believe there's a problem in your async function or creation of files or on your file/s.

Download file in Express route, serve to the client, then remove

I have an internal HTTP Post API that generates files, size 5-10mb each. This service is not modifiable.
I want to "proxy" this file download through the public API, which is based on Node.js+Express. However, I can't figure out the best way of doing so.
I guess I can download this file with Axios into a temporary file in the Node.js API container, but that seems to be prone to issues with these temporary files potentially piling up and requiring later cleanup. Is there a way to achieve such file download -> send further to a client without creating a temporary file?
Or what would be the most efficient and "clean" way of doing so if temporary files are unavoidable?
router.post('/route/:someid',
[someRequestVerificationMiddleware],
(req, res, next) => {
const myFileId = req.params.someid;
const downloadRequestParams= {
"id": myFileId
};
let dlPromise = axios.post(`http://myinternalservice:80`,
downloadRequestParams, {responseType: "stream"});
dlPromise.then(response => {
try {
let filename = response.headers["x-result-filename"];
//
// What would be the most efficient way to return the received file
// from response data to the client calling this route without creating
// too much garbage?
//
} catch (e) {
console.error(e);
}
})
.catch(e=>{
console.error(e);
res.status(500);
})
.finally(() => {
next();
})
});
module.exports = router;
res is a stream. You can simply pipe your axios stream to the response.
res.setHeader("content-type", "...");
return dlPromise.then((response) => response.data.pipe(res));

Pipe data chunks as a Response to a clients terminal

so my question is on Node js piping. So my backend looks like this -- there is a simple route, the route calls function and passes to it a file path for an executable type file. This file is then run with the childProcess.spawn and there is a data output that I can console.log
const express = require("express");
const app = express();
etc...
const runExecutable = (executableFile) => {
const runFile = childProcess.spawn(executableFile);
runFile.stdout.on('data', function(data){
console.log("DATA", data);
})
runFile.on('exit', function(code, signal){
[some code here]
})
}
app.get('/example', (req, res) => {
var file = "./testFile.exe";
runExecutable(file);
})
The question I have is how can I pipe this output of data/a.k.a chunks in real time to the client, it's important for them to get the data as it comes out and not for me to write it to a file and send them the whole thing. One more thing to note, the client is accessing my route through a curl curl 123.45.678.901/example in their terminal and I want to pipe the data to their terminal.
On reading around, I know that for example the request module does a request.get(url).pipe(res) /[Express res] and so I'm wondering if this is similar to what I might need to be doing.
Thanks all!
Found the answer: Any stream can be piped - readable.pipe(destination[, options]) - childProcess.spawn(executableFile) is not a stream, but once the file starts being executed it does emit a "data" event which is another way of saying there is a stream being emitted from the running of the file. So if you are looking at these chunks of "data" coming out - like I am - like this:
runFile.stdout.on('data', function(data){
console.log("DATA", data);
})
then that's the stream that you use and that's the where you attach the pipe
Node documentation basically says - to the stream attach .pipe and then just send it to it's destination. Since I wanted to send these chunks of data to my client I also had to pass res around, so my code now looks like this:
const express = require("express");
const app = express();
etc...
const runExecutable = (executableFile, res) => {
const runFile = childProcess.spawn(executableFile);
runFile.stdout.on('data', function(data){
console.log("DATA", data);
}).pipe(res)
runFile.on('exit', function(code, signal){
[some code here]
})
}
app.get('/example', (req, res) => {
var file = "./testFile.exe";
runExecutable(file, res);
})
and it works! I hope this is helpful to others - Thanks for the help Lee!

HTTP2 push for Express

I'm trying to set up HTTP2 for an Express app I've built. As I understand, Express does not support the NPM http2 module, so I'm using SPDY. Here's how I'm thinking to go about it-I'd appreciate advice from people who've implemented something similar.
1) Server setup-I want to wrap my existing app with SPDY, to keep existing routes. Options are just an object with a key and a cert for SSL.
const app = express();
...all existing Express stuff, followed by:
spdy
.createServer(options, app)
.listen(CONFIG.port, (error) => {
if (error) {
console.error(error);
return process.exit(1)
} else {
console.log('Listening on port: ' + port + '.')
}
});
2) At this point, I want to enhance some of my existing routes with a conditional PUSH response. I want to check to see if there are any updates for the client making a request to the route (the client is called an endpoint, and the updates are an array of JSON objects called endpoint changes,) and if so, push to the client.
My idea is that I will write a function which takes res as one of its parameters, save the endpoint changes as a file (I haven't found a way to push non-file data,) and then add them to a push stream, then delete the file. Is this the right approach? I also notice that there is a second parameter that the stream takes, which is a req/res object-am I formatting it properly here?
const checkUpdates = async (obj, res) => {
if(res.push){
const endpointChanges = await updateEndpoint(obj).endpointChanges;
if (endpointChanges) {
const changePath = `../../cache/endpoint-updates${new Date().toISOString()}.json`;
const savedChanges = await jsonfile(changePath, endpointChanges);
if (savedChanges) {
let stream = res.push(changePath, {req: {'accept': '**/*'}, res: {'content-type': 'application/json'}});
stream.on('error', function (err) {
console.log(err);
});
stream.end();
res.end();
fs.unlinkSync(changePath);
}
}
}
};
3) Then, within my routes, I want to call the checkUpdates method with the relevant parameters, like this:
router.get('/somePath', async (req, res) => {
await checkUpdates({someInfo}, res);
ReS(res, {
message: 'keepalive succeeded'
}, 200);
}
);
Is this the right way to implement HTTP2?

Resources