Saving a XLSX file from a HTTP API server, via Javascript - javascript

I have a http service which returns a HTTP response of type
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
and if I call this HTTP service from curl and redirect I get the .xlsx file recreated correctly. However trying to save the file from javascript fails. The code that I am using:
cURL:
$ curl -v -X POST <API_ENDPOINT> > a.xlsx ;# a.xlsx works fine
Javascript:
$http.post(<API_ENDPOINT>).then(function(response) {
console.log(response);
downloadFile(response.data, "b.xlsx")
});
var downloadFile = function(responseData, fileName) {
var blob = new Blob([responseData], {
type: 'application/vnd.ms-excel'
});
if (window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveBlob(blob, fileName);
} else {
var a = document.createElement('a');
var url = window.URL.createObjectURL(blob);
document.body.appendChild(a);
a.style = 'display: none';
a.href = url;
a.download = fileName;
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
}
};
The file is saved, however the contents seem to be different from the curl one, even though the server is sending the same file.
The content-length header value is the same for both the responses (curl and javascript (captured via chrome devtools)) but the filesize when redirected via curl is 5.1k (almost same as the content-length value) but the the filesize of file created via js, is 8.5k and is wrong.
I've tried setting application/octet-stream, octet/stream, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet while creating the blob and none of that helps.
I think I am messing up something in content-type and the blob creation but not able to figure out what is wrong. Any help ?

I have figured out the problem. The $http.post method needs another parameter to be set if the response type is going to be binary. The change that I had to make is:
$http.post(<API_ENDPOINT>, null, {responseType: 'arraybuffer'});

Related

Content-Disposition: download file automatically

The API call to the server is returning a zip file with Content-Disposition in format attachment, <filename>
I am using FileSaver's saveAs to save the file.
this.ajax.raw(requestUrl, {
dataType: 'binary',
xhr: () => {
const myXhr = $.ajaxSettings.xhr()
myXhr.responseType = 'blob'
return myXhr
}
}).then((response) => {
this.downloadSuccess(response, minTimeString, maxTimeString, downloadCompletedMessage)
}).catch((e) => {
this.downloadError(e)
})
downloadSuccess (response, minTime, maxTime, downloadCompletedMessage) {
const filename = (response.jqXHR.getResponseHeader('Content-Disposition').split('"')[1])
saveAs(response.payload, filename, 'application/zip')
This works fine for small files but fails if the file is more than 2Gb (The file is downloaded successfully but the saved file is of 1Kb only).
During my research, I saw that browser can download the file without FileSaver if the response has Content-Disposition which is true in my case. But I am not able to figure out how.
Do I need to use request differently?
From docs:
Content-Disposition attachment header is the best preferred way to
download files from the browser. It has better cross browser
compatibility, won't have any memory limit and it doesn't require any
JavaScript.
You don't need ajax request to download the file. Only ensure that server add Content-Disposition header and provide a link to download.
If you can also use the anchor download attribute from HTML5.
Causes the browser to treat the linked URL as a download.
const link = document.createElement('a');
link.href = '/xyz/abc.pdf';
link.download = "file.pdf";
link.dispatchEvent(new MouseEvent('click'));

Why my generated docx in javascript from a blob is filled with [Object Object]?

I'm sending data to my Django backend through a POST request, the server then creates a docx using this data and sends it back to me where I'm trying to download it using the following code :
axios.post(route, data).then((res) => {
const filename = "report.docx"
var blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
var link = document.createElement('a');
var URL = window.URL || window.webkitURL;
var downloadUrl = URL.createObjectURL(blob);
link.href = downloadUrl;
link.style = "display: none";
link.download = filename;
document.body.appendChild(link)
link.click()
link.remove()
});
The download of the file works but when I open it, it only contains [Object Object], what am I doing wrong here ? I checked the backend and the document is properly created as it has the right content when I'm saving it when it's generated in the server so it's either that i'm sending it badly or creating the file in the frontend badly.
Here is how I send it from the backend :
#Generating the doc properly before this...
response = HttpResponse(content_type='application/vnd.openxmlformats-officedocument.wordprocessingml.document')
report_filename = 'graph-report-'+now.strftime('%Y-%m-%d_%H-%M-%S')+'.docx'
response['Content-Disposition'] = 'attachment; filename=%s'%(report_filename)
document.save(response)
return response
Thanks for helping me.
With Axios, the res in axios.post(route, data).then((res) => is a response object.
The returned data is found in res.data.
Do console.log(res.data) to see that it truly is the binary data you expect.
Do new Blob([res.data], ... to use that data to get the blob. (If it already is a blob courtesy of Axios, you don't necessarily need to bother.)
(As an aside, you don't really need to use fetch() and createObjectURL for this at all since you're forming a download-like binary response; it would be enough to just have the browser POST your desired data e.g. as a form to the endpoint; it would download the response just the same.)

Unable to see Content-Disposition header in create-react-app [duplicate]

This question already has answers here:
Get a specific response header (e.g., Content-Disposition) in Angular from an ASP.NET Web API 2 response for a cross-origin http.get request
(2 answers)
Closed 1 year ago.
I've a create-react-app with which is served from express. Express is serving only as a proxy for the app. The application logic is in CRA.
I'm trying to call an API and to download a file. The api responds back with a "Content-Disposition" header which contains the file name. I'm unable to retrieve the header in my call.
When I call the API in Postman/chrome-dev-tools, I can see the header available.
Please see my code below. The filename code is commented out because res.headers is empty.
let filename = 'test-file-name'; // <--- default file name
const output = fetch(transactionReportPath(), {
headers: {
accept: 'text/csv',
Authorization: `Bearer ${getToken()}`
}
})
.then((res) => {
console.log(res.headers) // <---- empty object
// filename = res.headers.get('Content-Disposition').split(";")[1]
return res.blob()})
.then((blob) => {
const file = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = file;
a.download = filename;
document.body.appendChild(a);
a.click();
a.remove();
});
Was able to resolve this issue. There was a header missing from the server response.
response["Access-Control-Expose-Headers"] = "Content-Disposition"
Adding this header in the response started exposing the missing filename.

Download a .gz file using jQuery

I have to call a remote service to download a gzipped file (csv.gz).
I have to use jQuery because I have to set the Authentication HTTP header.
I tried with something like this:
$.ajax({
type: "GET",
url: url
}).done(function (res) {
const a = document.createElement('a');
a.style = 'display: none';
document.body.appendChild(a);
const blob = new Blob([res]);
const url = URL.createObjectURL(blob);
a.href = url;
a.download = _this.attributes.id + '.' + _DEFAULT_DOWNLOADED_FILE_EXTENTION;
a.click();
URL.revokeObjectURL(url);
}).fail(function (err) { });
I'm able to download the file but, after the download, I tried to unzip it on my pc but the file is not correct (corrupted). Probably the Blob creation that I used it's not correct.
You're trying to download binary data, you can fetch it using typed arrays in JavaScript, here's an example in the MDN.
In regards to setting the Authentication header, you don't need jQuery for that. Here's the XHR API that enables you to do it.

Dealing with encoding in Flask file uploads/downloads

I have a react client that takes in user input as a file and sends it to my remote Flask server for storage. I send the file in the form of a Werkzeug FileStorage object and in the remote server I store it with file.save(path). In the react client I'm trying to build a way to download the file from the server, however I'm running into problems. Currently my program is working for downloading .txt files. I'm able to do this though a fetch javascript request:
fetch(FETCH_URL, {
method: 'POST',
body: data,
headers: {
'Content-Type': 'application/json'
}
}).then((response) => {
var a = response.body.getReader();
a.read().then(({ done, value }) => {
saveAsFile(new TextDecoder("utf-8").decode(value), 'filename.txt');
}
);
});
function saveAsFile(text, filename) {
const type = 'application/text'; // modify or get it from response
const blob = new Blob([text], {type});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
}
Thanks to some help I got in this post: Download file in react client from flask remote server
I know this code is specifically made to work only with .txt files based on the type being passed in to Blob, but the front end is not the real problem.
The real problem is in my remote flask server, the following code is what is called in the flask server:
with open(filename, 'r') as f:
contents = f.read()
return contents
I tried returning the file itself but the server gives an error:
"ValueError: I/O operation on closed file."
So I decided to return the contents of the file as shown above.
The problem arises when I try to get a file for example "download.jpeg". Reading the file gives the following error:
"UnicodeDecodeError: 'utf-8' codec can't decode byte 0x89 in position 0: invalid start byte"
From what I understand Flask works exclusively with 'utf-8' and I assume this means the file in the server is on 'utf-8' encoded.
Does anyone have a suggestion or guidance on a solution or a workaround maybe a way to change the files encoding when I save it on the server or something else that could help me with what I'm trying to do?
Fetch's Response has blob() to convert the response directly to blob, so you don't have to read the stream, you don't have to find out it's content type or anything. Just try the below solution.
fetch(FETCH_URL, {
method: 'POST',
body: data,
headers: {
'Content-Type': 'application/json'
}
}).then((response) => {
response.blob().then((blob) => {
saveBlob(blob, 'filename');
});
});
function saveBlob(blob, filename) {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
}
Try this: make sure to install axios. Also you probably won't have to deal with content type like above said. Obviously changing the method type to POST and bring ur data in.
axios(FETCH_URL, {
method: 'GET',
responseType: 'blob', // important
}).then((response) => { //Creates an <a> tag hyperlink that links the excel sheet Blob object to a url for downloading.
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', `${Date.now()}.xlsx`); //set the attribute of the <a> link tag to be downloadable when clicked and name the sheet based on the date and time right now.
document.body.appendChild(link);
link.click(); //programmatically click the link so the user doesn't have to
document.body.removeChild(link);
URL.revokeObjectURL(url); //important for optimization and preventing memory leak even though link element has already been removed.
});

Categories

Resources