I'm working on something that grabs a PDF from my Google Cloud Storage. The PDF URL is in the entry in MongoDB. Actually pulling the file down works fine. But I'm having an issue with actually getting it to send to the client. I think the problem lies with the data it's reading somehow ending up empty, so I assume it has to do with async or callbacks or promises or something of the like. However, I can't figure out where the error is happening. Any advice as to what I'm missing would be greatly appreciated. Here is the main code:
router.get('/:slug', async function(req, res) {
const slug = req.params.slug;
let fullPath;
const find = await Entry.findOne({slug: slug}, (err, data) => {
if(err) {
res.render('error.ejs');
}
else {
console.log(data.fileURL);
getFile(data.fileURL).then(async (fileName) => {
fullPath = dir.APP_DIR fileName;
console.log(fullPath);
fs.readFile(fullPath, async (err, data) => {
if(err || data.length == 0) {
res.render('error.ejs', {session: req.session});
}
else {
console.log('Full path is: ' fullPath);
console.log(data.length);
res.writeHead(200, {
'Content-Type': 'application/pdf',
'Content-disposition': 'inline',
'Content-Length': data.length
}).end(Buffer.from(data, 'binary'));
}
})
});
}
});
});
The getFile function is exported, and it is as follows:
async function getFile(fileURL) {
const url = fileURL;
console.log(url);
const newFileName = '/pdf_files/' genID() '.pdf';
const file = fs.createWriteStream('.' newFileName);
https.get(url, function(response) {
response.pipe(file);
file.on('finish', function() {
file.close();
});
});
return newFileName;
}
CodePudding user response:
So in theory your code seems fine. I have some legibility optimizations so that the flow of data might appear a bit clearer. These changes are reflected below.
Obliviously I do not know if this will fix your issue, but to me it seems, like you have a race condition in your getFile() function. You return the new file name, but are not waiting for the file to actually be written. Then you immediately try and read it which will most likely not work. The writing is async and thus might not be done.
Here are my proposed changes:
Your main code:
router.get('/:slug', async function (req, res) {
const slug = req.params.slug;
let fullPath;
try {
const data = await Entry.findOne({slug});
console.log(data.fileURL);
fileName = await getFile(data.fileURL);
fullPath = dir.APP_DIR fileName;
console.log(fullPath);
fs.readFile(fullPath, async (err, data) => {
if (err || data.length == 0) {
res.render('error.ejs', {
session: req.session
});
} else {
console.log('Full path is: ' fullPath);
console.log(data.length);
res.writeHead(200, {
'Content-Type': 'application/pdf',
'Content-disposition': 'inline',
'Content-Length': data.length
}).end(Buffer.from(data, 'binary'));
}
});
} catch (e) {
console.error(e);
res.render('error.ejs');
}
});
Your get file method
async function getFile(fileURL) {
const url = fileURL;
console.log(url);
const newFileName = '/pdf_files/' genID() '.pdf';
const file = fs.createWriteStream('.' newFileName);
// this await changes it all. This way you are waiting for the http request to be done. Then you return the name.
await https.get(url, function (response) {
response.pipe(file);
file.on('finish', function () {
file.close();
});
});
return newFileName;
}
Let me know if that helps :)
CodePudding user response:
You have to wait for writing file done before return (using a Promise to wrap functions that have callback like code below), you are not waiting so in the next flow content of file may be empty.
async function getFile(fileURL) {
return new Promise(
(resolve, reject) => {
const url = fileURL;
console.log(url);
const newFileName = '/pdf_files/' genID() '.pdf';
const file = fs.createWriteStream('.' newFileName);
https.get(url, function (response) {
file.on('finish', function () {
file.close();
resolve(newFileName);
});
response.pipe(file);
});
});
}