When I use axios to download a PDF file to the client side it always ends up corrupted. I have a MERN stack webapp that will allow a user to click a button to download a PDF file. The button click sends an axios GET request to the Node.js web server. I am using express.js to handle the routes and requests. The express route will use a Google API to download a Google Doc as PDF to the Node.js server, and then I would like Node.js to send that PDF to the client's browser. Everything works, except the download is always corrupted on the client side. (The pdf downloads and opens perfectly to Node.js)
Here is the Axios get request that is sent to Node.js when the user clicks a button. (I am using react-redux for the dispatch function). I have tried setting the responseType to 'blob' and that does not work.
export const downloadDocument = (googleDocId) => (dispatch) => {
dispatch({
type: DOCUMENT_DOWNLOADING
})
axios.get('/api/documents/download?googleDocId=' googleDocId, {
responseType: 'arraybuffer'
})
.then(res => {
dispatch({
type: DOCUMENT_DOWNLOADED,
payload: res.data
})
const url = window.URL.createObjectURL(new Blob([res.data]), {
type: 'application/pdf'
})
const link = document.createElement('a')
link.href = url;
link.setAttribute('download', 'LOR.pdf')
document.body.appendChild(link)
link.click()
})
.catch(err => {
dispatch(returnErrors(err.response.data, err.response.status))
dispatch({
type: DOCUMENT_DOWNLOAD_FAIL
})
})
}
Here is the backend express.js code that is handling the download request:
erouter.get('/download', async (req, res) => {
const googleDocId = req.query.googleDocId
if (!googleDocId) {
return res.status(400).json({
msg: "googleDocId not specified"
})
}
//Use the Google API to download a PDF from Google Drive
const { google } = require("googleapis")
const fs = require('fs')
const auth = new google.auth.GoogleAuth({
keyFile: "credentials.json",
scopes: ["https://www.googleapis.com/auth/drive"]
})
//Create client instance for auth
const client = await auth.getClient()
// Instance of Google Drive API
const gDrive = google.drive({
version: "v3",
auth: client
})
//Download file as PDF from google Drive to node.js server
await gDrive.files.export({
fileId: googleDocId,
mimeType: 'application/pdf'
}, {
responseType: 'stream'
})
.then(response => {
return new Promise((resolve, reject) => {
const filePath = path.join(__dirname, "../../tmp")
console.log(`writing to ${filePath}`);
const dest = fs.createWriteStream(filePath);
let progress = 0
response.data
.on('end', () => {
console.log('Done downloading file.')
resolve(filePath)
})
.on('error', err => {
console.error('Error downloading file.')
reject(err)
})
.on('data', d => {
progress = d.length;
if (process.stdout.isTTY) {
process.stdout.clearLine()
process.stdout.cursorTo(0)
process.stdout.write(`Downloaded ${progress} bytes`)
}
})
.pipe(dest)
})
})
.catch(err => console.log(err))
//Download file to client browser
const options = {
root: path.join(__dirname, "../../tmp"),
headers: {
'Content-Type' : 'application/pdf'
}
}
var filename = "LOR.pdf"
res.sendFile(filename, options)
//res.download('./tmp/LOR.pdf')
})
What could be going wrong? At some point after using responseType: 'arraybuffer' as recommended by other axios threads online the download did work and downloaded a working PDF once, however the effect is inconsistent and almost always results in a corrupted PDF on the client side browser. Someone please help I am going insane. I have tried using download.js npm packages but that doesn't give me the desired result. I have also tried recommendations from these threads: https://gist.github.com/javilobo8/097c30a233786be52070986d8cdb1743 https://github.com/axios/axios/issues/448 <- this solution in this second link worked once, and then later kept resulting in corrupted PDF downloads
CodePudding user response:
I got it working exactly as I intended to! Thank you Phil for your guidance. The Google API streams the PDF, a connect a pipe to the express res object, and that streams the PDF directly into the res, and I can rebuild it as a blob on the other side. It works now without having to write the PDF to the node.js server at all!
//Stream file as PDF from google Drive and pipe it to express res
gDrive.files.export({fileId: googleDocId, mimeType: 'application/pdf'}, {responseType: 'stream'})
.then(response => {
return new Promise((resolve, reject) => {
console.log('Beginning download stream')
let progress = 0
response.data
.on('end', () => {
console.log('Done downloading file.')
resolve()
})
.on('error', err => {
console.error('Error downloading file.')
reject(err)
})
.on('data', d => {
progress = d.length;
if (process.stdout.isTTY) {
process.stdout.clearLine()
process.stdout.cursorTo(0)
process.stdout.write(`Downloaded ${progress} bytes`)
}
})
.pipe(res) //Pipe data to the express res object
})
})
.catch(err => console.log(err))
CodePudding user response:
try this, it should work
// Your code
const url = window.URL.createObjectURL(new File([new Blob([res.data])], "LOR.pdf", {type: 'application/pdf'}))
// Your code