How to make browser download file from xhr request - javascript

I'm sending xhr request to server to download a file. I'm including authorization token into the request so I can't download a file without using xhr. What steps should I take to make browser download a file when response from the server is received? And what headers should the server include?

This is a piece of code that works for me. Im using it for testing so its not the cleanest way I guess. But it can show a picture.
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var downloadUrl = URL.createObjectURL(xhttp.response);
var a = document.createElement("a");
document.body.appendChild(a);
a.style = "display: none";
a.href = downloadUrl;
a.download = "";
a.click();
}
};
xhttp.open("GET", fileUrl, true);
xhttp.responseType = "blob";
xhttp.setRequestHeader('Authorization', token);
xhttp.send();
This piece is not crucial, I was just need it in my case:
xhttp.setRequestHeader('Authorization', token);
This link can be usefull as well: Sending and Receiving Binary Data

Related

How to display a message after downloading a file using XMLHttpRequest?

Hello community I hope you can help me since I could not show a message to the user after downloading an excel file.
I am using httpRequest for sending data to the server and everything works correctly the file is downloaded but what I also want is to show the message.
Thank you very much for your help.
This is my code javaScript.
function download_excel_file() {
var file_name; //Example Test.xlsx
var parameter = '{file_name:"' + file_name + '"}';
var url = "Download.aspx/Download_File";
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
var a;
if (xhr.readyState === 4 && xhr.status === 200) {
a = document.createElement('a');
a.href = window.URL.createObjectURL(xhr.response);
a.download = file_name;
a.style.display = 'none';
document.body.appendChild(a);
a.click();
// Here I want to show the message with the legend = File downloaded successfully but it does not work.
$("[id*=message_download]").css("display","block");
$("[id*=message_download]").text(xhr.response.Text);
}
};
xhr.open("POST", url, true);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.responseType = 'blob';
xhr.send(parameter);
}
<input id="btn_download_file" type="button" value="Download file" class="btn btn-success btn-block" onclick="return download_excel_file();"/>
<div id="message_download" class="p-3 mb-1 bg-secondary text-white text-center" style="display:none"> </div>
This is my code from server.
[WebMethod]
public static void Download_File(string file_name)
{
if (file_name != null || file_name != "")
{
string path = HttpContext.Current.Server.MapPath("~/Folder_Excel/" + file_name);
if (File.Exists(path))
{
// This is the message I want to show in the div $("[id*=message_download]")
HttpContext.Current.Response.Write("File downloaded successfully");
System.IO.FileStream fs = null;
fs = System.IO.File.Open(path, System.IO.FileMode.Open);
byte[] btFile = new byte[fs.Length];
fs.Read(btFile, 0, Convert.ToInt32(fs.Length));
fs.Close();
HttpContext.Current.Response.AddHeader("Content-disposition", "attachment; filename=" + file_name);
HttpContext.Current.Response.ContentType = "application/octet-stream";
HttpContext.Current.Response.BinaryWrite(btFile);
HttpContext.Current.Response.Flush();
HttpContext.Current.Response.End();
}
else
{
HttpContext.Current.Response.Write("No files");
}
}
}
Add an ‘alert’ at the end of the script:
<script>
function myFunction() {
alert("Success! File downloaded!");
}
</script>
added 2019 10 04
Use InnerHTML along with getElementById to set the message back from the server.
from https://www.w3schools.com/xml/xml_http.asp
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
// Typical action to be performed when the document is ready:
document.getElementById("demo").innerHTML = xhttp.responseText;
}
};
xhttp.open("GET", "filename", true);
xhttp.send();
You should display your message at first, then make the browser "click" on the download button. Otherwise the browser may just fire an unload event and stop executing any scripts at this time - like on redirection.
I don't think you're using the Attribute Contains Selector correctly.
$("[id*=message_download]").css("display","block");
$("[id*=message_download]").text(xhr.response.Text);
Try this instead:
$("[id*='message_download']").css("display","block");
$("[id*='message_download']").text(xhr.responseText);
Or better yet, use $("#message_download") Also notice that I changed xhr.response.Text to xhr.responseText

How to receive files in Javascript sent from Flask using send_file()

I am developing a web application using HTML + plain Javascript in the frontend, and Flask as backend. The application sends some ID to the server, the server should generate a report as PDF file and send it back to the client.
I am using Flask for the backend and I have created the following endpoint:
#app.route("/download_pdf", methods=['POST'])
def download_pdf():
if request.method == 'POST':
report_id = request.form['reportid']
print(report_id) //Testing purposes.
// do some stuff with report_id and generate a pdf file.
return send_file('/home/user/report.pdf', mimetype='application/pdf', as_attachment=True)
// I already tried different values for mimetype and as_attachment=False
From the command line I can test the endpoint and I get the right file, and the server console prints the 123 report_id as expected:
curl --form reportid=123 http://localhost:5000/download_pdf >> output.pdf
For the frontend side I created a button that calls a Javascript function:
<button id=pdf_button onclick="generatePdf()"> PDF </button>
The Javascript function looks like this:
function generatePdf(){
var report_list = document.getElementById("report_list")
if (report_list.selectedIndex < 0){
alert("Please, select a report.");
}else{
var req = new XMLHttpRequest();
req.open("POST", "/download_pdf", true);
req.responseType = "document";
req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
req.onreadystatechange = function(){
console.log(req.readyState)
console.log(req.status)
console.log(req.response)
var link = document.createElement('a')
link.href = req.response;
link.download="report.pdf"
link.click()
}
var selected_value = report_list.options[report_list.selectedIndex].value;
var params="reportid="+selected_value;
req.send(params);
}
};
req.response is null in this case. However, the call to the endpoint has been done correctly, as the backend console prints the report_id as expected.
Already tried:
Using "blob" and "arraybuffer" as responseType as in https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Sending_and_Receiving_Binary_Data#Receiving_binary_data_using_JavaScript_typed_arrays
Checking for the HTTP return code, which is always 0.
Calling req.onload instead of req.onreadystatechange. The console shows nothing.
Lastly, the Firefox console shows these 6 messages after pressing the related button (please, observe the console.log() calls in the previous code):
2
0
null
4
0
null
It seems that the Javascript function has been called twice when the button is pressed.
My goal is to get the PDF downloaded. I don't know if what am I doing wrong; I'd thank any help on this.
Finally, I found what the problem was and I post this for the record.
I thought it was unrelated, but the <button> calling the Javascript function was inside a <form>. I checked that the form was updated before the call to the endpoint finished, causing the call to finish prepaturely.
If somebody else needs this as example, a snipet of the final code is as follows:
HTML (both the select and button are not part of a <form>):
<select id="report_list" size=20> ... </select>
...
<button id="pdf_button" onclick="generatePdf()"> PDF </button>
Javascript:
function generatePdf(){
var report_list = document.getElementById("report_list");
var req = XMLHttpRequest();
var selected_value = report_list.options[report_list.selectedIndex].value;
req.open("POST", "/reports/"+selected_value+"/pdf", true);
req.responseType = "blob";
req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
req.onreadystatechange = function(){
if (this.readyState == 4 && this.status == 200){
var blob = new Blob([this.response], {type: "application/pdf"});
var url = window.URL.createObjectURL(blob);
var link = document.createElement('a');
document.body.appendChild(link);
link.style = "display: none";
link.href = url;
link.download = "report.pdf";
link.click();
setTimeout(() => {
window.URL.revokeObjectURL(url);
link.remove(); } , 100);
}
};
req.send();
}
Flask:
#app.route("/reports/<id>/pdf", methods=['POST'])
def get_pdf(id):
if request.method == 'POST':
return send_file(get_pdf_path(id), mimetype='application/pdf')
I am not sure if this is the best or more elegant way to get this done, but so far it works for me.
Your ajax settings are wrong, they should be like these
req.open("POST", "/download_pdf", true);
req.responseType = "blob";
req.onreadystatechange = function() {
console.log(req.readyState)
console.log(req.status)
const blob = new Blob([req.response]);
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a')
link.href = url
link.download = "report.pdf"
link.click()
}
The response type should be blob and when you get the response, parse it as a blob. After some time, remove the link
setTimeout(() => {
window.URL.revokeObjectURL(url);
link.remove();
}, 100);

How to download large file with JavaScript

I need to download a large file with JavaScript using XMLHttpRequest or fetch without saving the file first in the RAM-Memory.
Normal link download doesn't work for me, because I need to send a Bearer Token in the header of the request.
I could manage to download a file, but this "solution", it's saving the file first in the RAM-Memory, before I get a save dialog, so that the Browser will brake if the file is larger then the available RAM-Memory.
Here is my "solution" with fetch:
var myHeaders = new Headers();
myHeaders.append('Authorization', `Bearer ${token}`);
var myInit = { method: 'GET',
headers: myHeaders,
mode: 'cors',
cache: 'default' };
var a = document.createElement('a');
fetch(url,myInit)
.then((response)=> {
return response.blob();
})
.then((myBlob)=> {
a.href = window.URL.createObjectURL(myBlob);
var attr = document.createAttribute("download");
a.setAttributeNode(attr);
a.style.display = 'none';
document.body.appendChild(a);
a.click();
a.remove();
});
And here is my "solution" with XMLHttpRequest:
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = ()=>{
if (xhttp.readyState == 4){
if ((xhttp.status == 200) || (xhttp.status == 0)){
var a = document.createElement('a');
a.href = window.URL.createObjectURL(xhttp.response); // xhr.response is a blob
var attr = document.createAttribute("download");
a.setAttributeNode(attr);
a.style.display = 'none';
document.body.appendChild(a);
a.click();
a.remove();
}
}
};
xhttp.open("GET", url);
xhttp.responseType = "blob";
xhttp.setRequestHeader('Authorization', `Bearer ${token}`);
xhttp.send();
The question is how can I download files larger then the available RAM-Memory and in the same time setting the headers?
As found in StreamSaver.js (link below), you could work with streams to workaround this issue.
You can try StreamSaver.js (Disclaimer: I am not the owner of that repo). Seems to solve what you want to the extent that it is not cross-browser compatible. Currently it is only supported by Chrome +52 and Opera +39.
Alternatively, there is FileSaver.js (Disclaimer: I am not the owner of that repo) but you'd run into the same problems you are currently running into.

Firebase Download Image using Download URL (Without Calling Storage)

Firebase's documentation covers downloading an image if you call storage and getDownloadURL, and I have this working fine (straight from the docs):
storageRef.child('images/stars.jpg').getDownloadURL().then(function(url) {
// `url` is the download URL for 'images/stars.jpg'
// This can be downloaded directly:
var xhr = new XMLHttpRequest();
xhr.responseType = 'blob';
xhr.onload = function(event) {
var blob = xhr.response;
};
xhr.open('GET', url);
xhr.send();
// Or inserted into an <img> element:
var img = document.getElementById('myimg');
img.src = url;
}).catch(function(error) {
// Handle any errors
});
However, I already have a URL and want to download an image without calling firebase storage. This is my attempt:
var url = "https://firebasestorage.googleapis.com/v0/b/somerandombucketname..."
console.log(url);
// This can be downloaded directly:
var xhr = new XMLHttpRequest();
xhr.responseType = 'blob';
xhr.onload = function(event) {
var blob = xhr.response;
};
xhr.open('GET', url);
xhr.send();
But, no file is downloaded and no error is shown in the browser's development tools.
Note: I do know the URL is correct because if I put URL directly into my browser search bar, I am able to access the file and download it.
Does anyone know how to download an image using a Download URL that you already have (without calling Firebase Storage as they do in the docs)?
This ended up working for me:
var url = "https://firebasestorage.googleapis.com/v0/b/somerandombucketname..."
var filename = url.substring(url.lastIndexOf("/") + 1).split("?")[0];
var xhr = new XMLHttpRequest();
xhr.responseType = 'blob';
xhr.onload = function() {
var a = document.createElement('a');
a.href = window.URL.createObjectURL(xhr.response);
a.download = "fileDownloaded.filetype"; // Name the file anything you'd like.
a.style.display = 'none';
document.body.appendChild(a);
a.click();
};
xhr.open('GET', url);
xhr.send();
This is essentially creating an a href to the URL I have and then clicking the a href programmatically when the xhr response is received.
It is not clear to me why the first way doesn't work as well, but hopefully this helps others that face the same issue.

ajax -> save as dialog before downloading

I am programming an embedded Device in C with a webserver. One Task is to download files from this devices. I want to Download serveral files at once, so i created an ajax-request, which using POST-Request and a bunch of filenames to return a zip-file (i create these zip-file on my own on the device). Everything works fine, but the dialog save as appears after the whole zip-file was transmitted.
At server-side the device is sending the 200 OK-, Content-Type: application/octet-stream- and Content-Disposition: attachment; filename="testzip.zip"-headers.
At client-side i using this javascript-code(got this from stackoverlfow: Handle file download from ajax post):
function downloadFiles(filenames) {
var xhr = new XMLHttpRequest();
xhr.open('POST', /file-save/, true);
xhr.responseType = 'arraybuffer';
xhr.onload = function () {
if (this.status === 200) {
var filename = "test.zip";
var type = xhr.getResponseHeader('Content-Type');
var blob = new Blob([this.response], { type: type });
var URL = window.URL || window.webkitURL;
var downloadUrl = URL.createObjectURL(blob);
// use HTML5 a[download] attribute to specify filename
var a = document.createElement("a");
a.href = downloadUrl;
a.download = filename;
document.body.appendChild(a);
a.click();
setTimeout(function () { URL.revokeObjectURL(downloadUrl); }, 100); // cleanup
}
};
xhr.send(filenames);
}
The if-statement if (this.status === 200) is reached, when the whole file is transmitted. If the size of the file is small, there is not a problem, because the user isn't recognizing the lack of time. But is the file about 50MB the user can't see any download although the file is downloading. In my opinion the reason is a.click(), because the click-method imitades the begin of the download.
Is there sombody who can help me out with a solution or some hints?
By the way, jquery isn't an option!.
Thanks for any help
EDIT: my goal is to download a file like on every webpage with large files, where i get a dialog with the location to save and i can see the download-progress.
SOLUTION(Hint from Herr Derb):
function downloadFiles(filenames) {
var xhr = new XMLHttpRequest();
xhr.open('POST', /file_save/, true);
xhr.onload = function () {
if (this.status === 200) {
var mydisp = xhr.getResponseHeader('Content-Disposition');
var save_response = xhr.responseText;
var var_json_format = JSON.parse(save_response);
/* check for errors */
if(var_json_format["error"]) {
return;
} else {
status = _.findWhere(var_json_format["link"], {id : 'status'}).value;
download_id = _.findWhere(var_json_format["link"], {id : 'download_id'}).value;
}
if(status != "active") {
return;
}
var filename = "test.zip";
var downloadUrl = "/file_save/" + download_id;
var a = document.createElement("a");
a.href = downloadUrl;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
setTimeout(function () { URL.revokeObjectURL(downloadUrl); }, 100); // cleanup
}
};
xhr.send(filenames);
return;
}
Your first request should only create the zip file on your server and return a link to reach it. After you received that link on the client site, simply execute it. This way, everything will happen as you desire, as it will be a regular file download.
And ss soon the download is finished, your free to delete the file again.

Categories

Resources