I've been developing a Flask/Vue webapp and I've run into an issue regarding downloading of files. I'm serving a userupload from the backend using flask, which returns a file through send_from_directory(). This is called through a downloadFile method in Vue
downloadFile(key, name){
const path = '/api/ticket/filedownload'
this.$ajax.post(path, {address: key}, {responseType: 'arraybuffer',})
.then((response) => {
// Create response donwload link
const url = window.URL.createObjectURL(new Blob([response.data], response.headers['contentType']));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', name);
document.body.appendChild(link);
link.click();
})
.catch(error => {
console.log(error)
window.alert("File not found")
})
}
This method I found online in many places and seems to work for most people.
Yet the file that gets served at the front end "contains errors" or seems corrupt.
In my network tab I can clearly see that the post request was successful and the response is the actual image (e.g.) that was sent! When copying the response from the post result directly in my network tab (Mozilla, right mouse>copy>copy response) I was able to retrieve that the response was base64 encoded and could be decoded easily to the same image that was uploaded. However! When printing the response.data, the output is completely different and not in base64:
base64 from network tab: R0lGODlhWAJYAvcAABAQEL2cUntSG(etc...)
console.log(response.data):GIF89aX�X���������R{R�)�1)�1�j ���������ޔ��{)��j
I have tried supplying the response.data as a blob directly or by parsing it in a filereader to no avail. Our this.$ajax.post is a workaround for axios' post, which can be supplied with returnTypes such as blob, arraybuffer, text, anything, all with the same result.
My question is: How would I go ahead and access the response such as it was displayed in the network tab as I am capable of converting from base64, not an unknown format. Or how would I parse this response in any successful way.
Related
I am trying to send a POST request to upload a file to my IPFS server. However I am unsure how can I upload a file into the body of the request. I have tried looking at the examples from Fetch API, but their example shows files uploaded from a form. While in my case, the files are already within my directory.
Update:
I have revised my code for sending a POST request to my IPFS server based on the documentation on Fetch API and I am now able to successfully send a request. However I am still stuck on how can I get the "fileInput" variable to reference a file from my directory. The current example only works if I have a html form.
I have tried nodejs fs library but I ran into issues where some of the functions are not available. It appears that using fs may pose certain security concerns from what I read. Would appreciate if I can have some advice on this.
var formdata = new FormData();
const fileInput = document.querySelector('input[type="file"]');
formdata.append(
"file",
fileInput.files[0],
`../../ipfs_files/${item._id}.png`
);
var requestOptions = {
method: "POST",
body: formdata,
redirect: "follow",
};
fetch("http://localhost:5001/api/v0/add", requestOptions)
.then((response) => response.text())
.then((result) => console.log(result))
.catch((error) => console.log("error", error));
you can also send data as base64 string and store it into the database then while accessing convert back to the original format.
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.)
The file that I am generating is being returned in the response as a string instead of prompting the download.
I've seen and tried a couple approaches to it from stackoverflow / other websites and they ultimately all resulted in the exact same problem.
I've tried:
Making a temp file, writing to it, then moving it to a new location with Storage::put
and downloading from that location using response()->download.
Writing to a temp file and echoing/reading it in the streamDownload closure.
Writing to a temp file and reading it after finishing (while setting the Headers beforehand)
I am POSTing a form payload using Axios with the headers
'content-type': 'multipart/form-data',
'X-CSRF-TOKEN': 'csrfToken'
The headers I am using for the response are:
'Content-Type: text/csv',
'Content-Disposition: attachment; filename=myFile.csv'
I've tried setting the headers using header() and by creating an array then passing it to streamDownload/download as header arguments.
The file download works/prompts normally if I simply create a form and submit it with form.submit(). I am only experiencing this problem when I try to do it asynchronously via a direct post request with Axios.
I am at a complete loss for what is causing this discrepancy, since submitting the form normally prompts the download just fine.
I managed to solve this by using the returned response and creating a BLOB with it and prompting the download using the BLOB.
let config = {
headers: {
'content-type': 'multipart/form-data',
'responseType': 'blob',
'X-CSRF-TOKEN': csrfToken
}
};
axios.post(downloadRoute, payload, config).then(response => {
const downloadUrl = window.URL.createObjectURL(new Blob([response.data.fileOutput]));
const link = document.createElement('a');
link.href = downloadUrl;
link.setAttribute('download', 'myfile.csv');
document.body.appendChild(link);
link.click();
link.remove();
});
Hope this helps anyone in the same situation!
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.
});
I have an API endpoint which on a PUT serves a PDF with Content-Type "application/pdf" (you might ask "why a PUT and not a GET?" and the answer to that is "it’s complicated and I can’t change it").
I want to (client side), request that PDF and display the result in a window.open() call (ie display the PDF in a new window). Previously I used a data URI to do this, ie:
window.open('data:application/pdf,<the data payload from the response>');
but in Chrome 60 this no longer works (you can’t use a data URI in a window.open, see https://bugs.chromium.org/p/chromium/issues/detail?id=751135 ).
I have now changed the client side code to do a fetch() of the same endpoint, create a blob from the response, call URL.createObjectURL() on the blob to get a url, and pass that url to the window.open() call. Ie I have something like:
fetch('http://somedomain.com/rest/getMyPdf', {
method: 'PUT',
... some headers & the body ...
}).then(function(response) {
return response.blob();
}).then(function(blob) {
window.open(URL.createObjectURL(blob));
});
This works in Firefox (I get a PDF in a new window), but in Chrome you just see the raw ASCII of the PDF contents (ie %PDF-1.3.....).
I’m guessing this is some sort of content-type issue, ie if I could set the content type on the url, then Chrome would interpret the response as a PDF. Any suggestions on how to do that?
Try
var data = response.arraybuffer();
Then create a Blob from there with the right content type
var = new Blob(data, 'application/pdf');