I'm trying to download an Excel file using an axios POST request. The backend seems to be working fine as I can download the Excel file using Swagger UI and Postman. The problem happens when I try to download using axios and I only get an Excel file with headers but missing all the rows of data. Here is my axios request:
await axios.post(API.viewReport.reportsNotLoginExcel, { data: data },
{
responseType: 'arraybuffer', // Important
}
).then(async (response) => {
let blob = new Blob([response.data], {type:'application/vnd.ms-excel'});
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", "file.xlsx");
document.body.appendChild(link);
link.click();
link.remove();
});
I have tried both responseType: 'arraybuffer' and responseType: 'blob'
And here is my Spring controller:
#PostMapping(path = "/reports/not-login/excel", produces = "application/vnd.ms-excel")
public ResponseEntity<Resource> doDownloadNotYetLogin(#RequestHeader(value = "iv-user") String ivUser,
#Valid #RequestBody DownloadRequest downloadRequest) {
InputStreamResource file = new InputStreamResource(viewReportService.doDownloadNotYetLogin(downloadRequest));
String filename = "NotYetLoginReport.xlsx";
return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + filename)
.contentType(MediaType.parseMediaType("application/vnd.ms-excel")).body(file);
}
In both Swagger UI and Postman, I am able to download the file with the correct data, so I assume there is nothing wrong with my backend.
Excel file with correct data
But downloading the file using axios gives me a file with only the headers and no rows of data.
Excel file with missing rows of data
There are no error messages or warnings from either frontend or backend.
I've tried external libraries like js-file-download and solutions from other posts but so far with no luck. I'm confused because I am able to get the file but somehow part of it is missing, and I haven't found any post facing this problem either.
Do let me know if any other information would be useful to show.
Related
Goal: Try to download a pdf file from Amazon S3 to my local machine via a NodeJS/VueJS application without creating a file on the server's filesystem.
Server: NodeJs(v 18.9.0) Express (4.17.1)
Middleware function that retrieves the file from S3 and converts the stream into a base64 string and sends that string to the client:
const filename = 'lets_go_to_the_snackbar.pdf';
const s3 = new AWS.S3(some access parameters);
const params = {
Bucket: do_not_kick_this_bucket,
Key: `yellowbrickroad/${filename}`
}
try {
const data = await s3
.getObject(params)
.promise();
const byte_string = Buffer.from(data.Body).toString('base64');
res.send(byte_string);
} catch (err) {
console.log(err);
}
Client: VueJS( v 3.2.33)
Function in component receives byte string via an axios (v 0.26.1) GET call to the server. The code to download is as follows:
getPdfContent: async function (filename) {
const resp = await AxiosService.getPdf(filename) // Get request to server made here.
const uriContent = `data:application/pdf;base64,${resp.data}`
const link = document.createElement('a')
link.href = uriContent
link.download = filename
document.body.appendChild(link) // Also tried omitting this line along with...
link.click()
link.remove() // ...omitting this line
}
Expected Result(s):
Browser opens a window to allow a directory to be selected as the file's destination.
Directory Selected.
File is downloaded.
Ice cream and mooncakes are served.
Actual Results(s):
Browser opens a window to allow a directory to be selected as the file's destination
Directory Selected.
Receive Failed - Network Error message.
Lots of crying...
Browser: Chrome (Version 105.0.5195.125 (Official Build) (x86_64))
Read somewhere that Chrome will balk at files larger than 4MB, so I checked the S3 bucket and according to Amazon S3 the file size is a svelte 41.7KB.
After doing some reading, a possible solution was presented that I tried to implement. It involved making a change to the VueJs getPdfContent function as follows:
getPdfContent: async function (filename) {
const resp = await AxiosService.getPdf(filename) // Get request to server made here.
/**** This is the line that was changed ****/
const uriContent = window.URL.createObjectURL(new Blob([resp.data], { type: 'application/pdf' } ))
const link = document.createElement('a')
link.href = uriContent
link.download = filename
document.body.appendChild(link) // Also tried omitting this line along with...
link.click()
link.remove() // ...omitting this line
}
Actual Results(s) for updated code:
Browser opens a window to allow a directory to be selected as the file's destination
Directory Selected.
PDF file downloaded.
Trying to open the file produces the message:
The file “lets_go_to_the_snackbar.pdf” could not be opened.
It may be damaged or use a file format that Preview doesn’t recognize.
I am able to download the file directly from S3 using the AWS S3 console with no problems opening the file.
I've read through similar postings and tried implementing their solutions, but found no joy. I would be highly appreciative if someone can
Give me an idea of where I am going off the path towards reaching the goal
Point me towards the correct path.
Thank you in advance for your help.
After doing some more research I found the problem was how I was returning the data from the server back to the client. I did not need to modify the data received from the S3 service.
Server Code:
let filename = req.params.filename;
const params = {
Bucket: do_not_kick_this_bucket,
Key: `yellowbrickroad/${filename}`
}
try {
const data = await s3
.getObject(params)
.promise();
/* Here I did not modify the information returned */
res.send(data.Body);
res.end();
} catch (err) {
console.log(err);
}
On the client side my VueJS component receives a Blob object as the response
Client Code:
async getFile (filename) {
let response = await AuthenticationService.downloadFile(filename)
const uriContent = window.URL.createObjectURL(new Blob([response.data]))
const link = document.createElement('a')
link.setAttribute('href', uriContent)
link.setAttribute('download', filename)
document.body.appendChild(link)
link.click()
link.remove()
}
In the end the goal was achieved; a file on S3 can be downloaded directly to a user's local machine without the application storing a file on the server.
I would like to mention Sunpun Sandaruwan's answer which gave me the final clue I needed to reach my goal.
I've read through alot of similiar questions, tried alot of suggested solutions, and none worked for me. So, i send the file from the backend using "res.download('directory/' + filename)", and judging from the response headers, i do get the correct file. There are no other files in the folder i'm sending from, and the original file is 14KB. However the 'data' part of the response is around 21KB. This is what i do with the response on the web app to get the file:
await axios.get(`api` + file.id,
{headers: {'x-access-token': token}}, { responseType: 'arraybuffer' }
).then((response) => {
const url = window.URL.createObjectURL(new Blob([response.data])); //specifying the type here doesn't help
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', `${filename}`); //filename = the name of the file i'm trying to download
document.body.appendChild(link);
link.click();
link.parentNode.removeChild(link);
})
And the file i get in result is also around 21KB, and does not open in word due to it being corrupt.
Fiddled a bit with the axios.get syntax, put the "responseType" togather with the config which has headers. And now the file i get is not corrupted o_o
axios.get(`api/` + file.id,
{
headers:
{
'x-access-token': token
},
responseType: 'arraybuffer'
}
)
At first i thought that the file size was different, but it's not. So that definately fixed it lol. That's what i get from being a noob in js.
You may need to add the file extension at the end of the filename like this:
link.setAttribute('download', `${filename}.docx`);
How to download a excel file from server on button click in Angular 8. I am new to Angular and I read through different posts which said to file-saver library. Do we need to install a library to work out download option or not. Can anyone please share the code and insights.
You can use this for example:
const resp = await this.httpClient.get(`{yourendpoint}`, { responseType: 'blob' }).toPromise();
const url = window.URL.createObjectURL(resp);
window.open(url);
If your getting blob from you backend then you can do something like this:
let a = document.createElement('a');
a.href = URL.createObjectURL(data);
a.download = 'FILE_NAME';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
Where 'data' is response from your api.
PS: Don't forget to add header responseType: 'blob'
I want to upload pdf file created by pdfmake to remote server. For that I am using following code roughly.
const doc = printer.createPdfKitDocument(docDefinition);
doc.pipe(fs.createWriteStream(filename))
var form = new FormData();
form.append("file", fs.createReadStream(filename));
let response=await axios.post(url, form, {headers: {
...form.getHeaders(),
}});
But issue with above approach is it requires to create file locally. I want to avoid that and want to take output from pdfmake and send it directly to server. How can I do that ?
I have an API written in expressjs, that sends a file when provided with a fileID.
The backend is working fine, as it sends the correct file (uncorrupted) when the route url is typed directly int the browser.
ex. http://localhost:8080/download?id=1234 (downloads all files just fine ie.txt, xlsx, jpg, zip)
My express route actually uses res.download, which is really jsut a wrapper for sendFile.
When I try calling these route urls from a Vue http get request, it only returns txt files uncorrupted. All other files download, but they can be opened, due to corruption.
Can anyone please point me in the right direction as to why its not working in Vue?
For clarity, "item" is passed as an argument to this function.
this.$http.get("http://localhost:8000/files/download", {params:{id:item._id}}, {responseType: 'arraybuffer'})
.then(response => {
var blob = new Blob([response.data], {type:response.headers.get('content-type')});
var link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = item.filename;
link.click();
})
For reference, this is my express route
Shout out to #Helpinghand for helping me troubleshoot this.
"I found the solution within the link you posted. The problem is that i was explicitly sending params in its own object before assigning "content-type". The example you posted concatenates the query params to the url. After making this switch, its working perfectly".
this.$http.get(`http://localhost:8000/files/download?id=${item._id}`, {responseType: 'arraybuffer'})
.then(response => {
console.log(response.headers.get('content-type'));
console.log(response);
var blob = new Blob([response.body], {type:response.headers.get('content-type')});
var link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = item.filename_version;
link.click();
})