How to download file with post method? [duplicate] - javascript

I want to send an "ajax download request" when I click on a button, so I tried in this way:
javascript:
var xhr = new XMLHttpRequest();
xhr.open("GET", "download.php");
xhr.send();
download.php:
<?
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-Disposition: attachment; filename= file.txt");
header("Content-Transfer-Encoding: binary");
readfile("file.txt");
?>
but doesn't work as expected, how can I do ? Thank you in advance

Update April 27, 2015
Up and coming to the HTML5 scene is the download attribute. It's supported in Firefox and Chrome, and soon to come to IE11. Depending on your needs, you could use it instead of an AJAX request (or using window.location) so long as the file you want to download is on the same origin as your site.
You could always make the AJAX request/window.location a fallback by using some JavaScript to test if download is supported and if not, switching it to call window.location.
Original answer
You can't have an AJAX request open the download prompt since you physically have to navigate to the file to prompt for download. Instead, you could use a success function to navigate to download.php. This will open the download prompt but won't change the current page.
$.ajax({
url: 'download.php',
type: 'POST',
success: function() {
window.location = 'download.php';
}
});
Even though this answers the question, it's better to just use window.location and avoid the AJAX request entirely.

To make the browser downloads a file you need to make the request like that:
function downloadFile(urlToSend) {
var req = new XMLHttpRequest();
req.open("GET", urlToSend, true);
req.responseType = "blob";
req.onload = function (event) {
var blob = req.response;
var fileName = req.getResponseHeader("fileName") //if you have the fileName header available
var link=document.createElement('a');
link.href=window.URL.createObjectURL(blob);
link.download=fileName;
link.click();
};
req.send();
}

You actually don't need ajax at all for this. If you just set "download.php" as the href on the button, or, if it's not a link use:
window.location = 'download.php';
The browser should recognise the binary download and not load the actual page but just serve the file as a download.

Cross browser solution, tested on Chrome, Firefox, Edge, IE11.
In the DOM, add an hidden link tag:
<a id="target" style="display: none"></a>
Then:
var req = new XMLHttpRequest();
req.open("GET", downloadUrl, true);
req.responseType = "blob";
req.setRequestHeader('my-custom-header', 'custom-value'); // adding some headers (if needed)
req.onload = function (event) {
var blob = req.response;
var fileName = null;
var contentType = req.getResponseHeader("content-type");
// IE/EDGE seems not returning some response header
if (req.getResponseHeader("content-disposition")) {
var contentDisposition = req.getResponseHeader("content-disposition");
fileName = contentDisposition.substring(contentDisposition.indexOf("=")+1);
} else {
fileName = "unnamed." + contentType.substring(contentType.indexOf("/")+1);
}
if (window.navigator.msSaveOrOpenBlob) {
// Internet Explorer
window.navigator.msSaveOrOpenBlob(new Blob([blob], {type: contentType}), fileName);
} else {
var el = document.getElementById("target");
el.href = window.URL.createObjectURL(blob);
el.download = fileName;
el.click();
}
};
req.send();

It is possible. You can have the download started from inside an ajax function, for example, just after the .csv file is created.
I have an ajax function that exports a database of contacts to a .csv file, and just after it finishes, it automatically starts the .csv file download. So, after I get the responseText and everything is Ok, I redirect browser like this:
window.location="download.php?filename=export.csv";
My download.php file looks like this:
<?php
$file = $_GET['filename'];
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-Disposition: attachment; filename=".$file."");
header("Content-Transfer-Encoding: binary");
header("Content-Type: binary/octet-stream");
readfile($file);
?>
There is no page refresh whatsoever and the file automatically starts downloading.
NOTE - Tested in the following browsers:
Chrome v37.0.2062.120
Firefox v32.0.1
Opera v12.17
Internet Explorer v11

I prefer location.assign(url);
Complete syntax example:
document.location.assign('https://www.urltodocument.com/document.pdf');
developer.mozilla.org/en-US/docs/Web/API/Location.assign

For those looking a more modern approach, you can use the fetch API. The following example shows how to download a spreadsheet file. It is easily done with the following code.
fetch(url, {
body: JSON.stringify(data),
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=utf-8'
},
})
.then(response => response.blob())
.then(response => {
const blob = new Blob([response], {type: 'application/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});
const downloadUrl = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = downloadUrl;
a.download = "file.xlsx";
document.body.appendChild(a);
a.click();
})
I believe this approach to be much easier to understand than other XMLHttpRequest solutions. Also, it has a similar syntax to the jQuery approach, without the need to add any additional libraries.
Of course, I would advise checking to which browser you are developing, since this new approach won't work on IE. You can find the full browser compatibility list on the following link.
Important: In this example I am sending a JSON request to a server listening on the given url. This url must be set, on my example I am assuming you know this part. Also, consider the headers needed for your request to work. Since I am sending a JSON, I must add the Content-Type header and set it to application/json; charset=utf-8, as to let the server know the type of request it will receive.

#Joao Marcos solution works for me but I had to modify the code to make it work on IE, below if what the code looks like
downloadFile(url,filename) {
var that = this;
const extension = url.split('/').pop().split('?')[0].split('.').pop();
var req = new XMLHttpRequest();
req.open("GET", url, true);
req.responseType = "blob";
req.onload = function (event) {
const fileName = `${filename}.${extension}`;
const blob = req.response;
if (window.navigator.msSaveBlob) { // IE
window.navigator.msSaveOrOpenBlob(blob, fileName);
}
const link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = fileName;
link.click();
URL.revokeObjectURL(link.href);
};
req.send();
},

Decoding a filename from the header is a little bit more complex...
var filename = "default.pdf";
var disposition = req.getResponseHeader('Content-Disposition');
if (disposition && disposition.indexOf('attachment') !== -1)
{
var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
var matches = filenameRegex.exec(disposition);
if (matches != null && matches[1])
filename = matches[1].replace(/['"]/g, '');
}

This solution is not very different from those above, but for me it works very well and i think it's clean.
I suggest to base64 encode the file server side (base64_encode(), if you are using PHP) and send the base64 encoded data to the client
On the client you do this:
let blob = this.dataURItoBlob(THE_MIME_TYPE + "," + response.file);
let uri = URL.createObjectURL(blob);
let link = document.createElement("a");
link.download = THE_FILE_NAME,
link.href = uri;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
This code puts the encoded data in a link and simulates a click on the link, then it removes it.

Your needs are covered by
window.location('download.php');
But I think that you need to pass the file to be downloaded, not always download the same file, and that's why you are using a request, one option is to create a php file as simple as showfile.php and do a request like
var myfile = filetodownload.txt
var url = "shofile.php?file=" + myfile ;
ajaxRequest.open("GET", url, true);
showfile.php
<?php
$file = $_GET["file"]
echo $file;
where file is the file name passed via Get or Post in the request and then catch the response in a function simply
if(ajaxRequest.readyState == 4){
var file = ajaxRequest.responseText;
window.location = 'downfile.php?file=' + file;
}
}

there is another solution to download a web page in ajax. But I am referring to a page that must first be processed and then downloaded.
First you need to separate the page processing from the results download.
1) Only the page calculations are made in the ajax call.
$.post("CalculusPage.php", { calculusFunction: true, ID: 29, data1: "a", data2: "b" },
function(data, status)
{
if (status == "success")
{
/* 2) In the answer the page that uses the previous calculations is downloaded. For example, this can be a page that prints the results of a table calculated in the ajax call. */
window.location.href = DownloadPage.php+"?ID="+29;
}
}
);
// For example: in the CalculusPage.php
if ( !empty($_POST["calculusFunction"]) )
{
$ID = $_POST["ID"];
$query = "INSERT INTO ExamplePage (data1, data2) VALUES ('".$_POST["data1"]."', '".$_POST["data2"]."') WHERE id = ".$ID;
...
}
// For example: in the DownloadPage.php
$ID = $_GET["ID"];
$sede = "SELECT * FROM ExamplePage WHERE id = ".$ID;
...
$filename="Export_Data.xls";
header("Content-Type: application/vnd.ms-excel");
header("Content-Disposition: inline; filename=$filename");
...
I hope this solution can be useful for many, as it was for me.

this works for me
var dataObj = {
somekey:"someValue"
}
$.ajax({
method: "POST",
url: "/someController/someMethod",
data: dataObj,
success: function (response) {
const blob = new Blob([response], { type: 'text/csv' });
const downloadUrl = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = downloadUrl;
a.download = "file.csv";
document.body.appendChild(a);
a.click();
}
});

Related

How to serve file from REST api backend to frontend [duplicate]

I want to send an "ajax download request" when I click on a button, so I tried in this way:
javascript:
var xhr = new XMLHttpRequest();
xhr.open("GET", "download.php");
xhr.send();
download.php:
<?
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-Disposition: attachment; filename= file.txt");
header("Content-Transfer-Encoding: binary");
readfile("file.txt");
?>
but doesn't work as expected, how can I do ? Thank you in advance
Update April 27, 2015
Up and coming to the HTML5 scene is the download attribute. It's supported in Firefox and Chrome, and soon to come to IE11. Depending on your needs, you could use it instead of an AJAX request (or using window.location) so long as the file you want to download is on the same origin as your site.
You could always make the AJAX request/window.location a fallback by using some JavaScript to test if download is supported and if not, switching it to call window.location.
Original answer
You can't have an AJAX request open the download prompt since you physically have to navigate to the file to prompt for download. Instead, you could use a success function to navigate to download.php. This will open the download prompt but won't change the current page.
$.ajax({
url: 'download.php',
type: 'POST',
success: function() {
window.location = 'download.php';
}
});
Even though this answers the question, it's better to just use window.location and avoid the AJAX request entirely.
To make the browser downloads a file you need to make the request like that:
function downloadFile(urlToSend) {
var req = new XMLHttpRequest();
req.open("GET", urlToSend, true);
req.responseType = "blob";
req.onload = function (event) {
var blob = req.response;
var fileName = req.getResponseHeader("fileName") //if you have the fileName header available
var link=document.createElement('a');
link.href=window.URL.createObjectURL(blob);
link.download=fileName;
link.click();
};
req.send();
}
You actually don't need ajax at all for this. If you just set "download.php" as the href on the button, or, if it's not a link use:
window.location = 'download.php';
The browser should recognise the binary download and not load the actual page but just serve the file as a download.
Cross browser solution, tested on Chrome, Firefox, Edge, IE11.
In the DOM, add an hidden link tag:
<a id="target" style="display: none"></a>
Then:
var req = new XMLHttpRequest();
req.open("GET", downloadUrl, true);
req.responseType = "blob";
req.setRequestHeader('my-custom-header', 'custom-value'); // adding some headers (if needed)
req.onload = function (event) {
var blob = req.response;
var fileName = null;
var contentType = req.getResponseHeader("content-type");
// IE/EDGE seems not returning some response header
if (req.getResponseHeader("content-disposition")) {
var contentDisposition = req.getResponseHeader("content-disposition");
fileName = contentDisposition.substring(contentDisposition.indexOf("=")+1);
} else {
fileName = "unnamed." + contentType.substring(contentType.indexOf("/")+1);
}
if (window.navigator.msSaveOrOpenBlob) {
// Internet Explorer
window.navigator.msSaveOrOpenBlob(new Blob([blob], {type: contentType}), fileName);
} else {
var el = document.getElementById("target");
el.href = window.URL.createObjectURL(blob);
el.download = fileName;
el.click();
}
};
req.send();
It is possible. You can have the download started from inside an ajax function, for example, just after the .csv file is created.
I have an ajax function that exports a database of contacts to a .csv file, and just after it finishes, it automatically starts the .csv file download. So, after I get the responseText and everything is Ok, I redirect browser like this:
window.location="download.php?filename=export.csv";
My download.php file looks like this:
<?php
$file = $_GET['filename'];
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-Disposition: attachment; filename=".$file."");
header("Content-Transfer-Encoding: binary");
header("Content-Type: binary/octet-stream");
readfile($file);
?>
There is no page refresh whatsoever and the file automatically starts downloading.
NOTE - Tested in the following browsers:
Chrome v37.0.2062.120
Firefox v32.0.1
Opera v12.17
Internet Explorer v11
I prefer location.assign(url);
Complete syntax example:
document.location.assign('https://www.urltodocument.com/document.pdf');
developer.mozilla.org/en-US/docs/Web/API/Location.assign
For those looking a more modern approach, you can use the fetch API. The following example shows how to download a spreadsheet file. It is easily done with the following code.
fetch(url, {
body: JSON.stringify(data),
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=utf-8'
},
})
.then(response => response.blob())
.then(response => {
const blob = new Blob([response], {type: 'application/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});
const downloadUrl = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = downloadUrl;
a.download = "file.xlsx";
document.body.appendChild(a);
a.click();
})
I believe this approach to be much easier to understand than other XMLHttpRequest solutions. Also, it has a similar syntax to the jQuery approach, without the need to add any additional libraries.
Of course, I would advise checking to which browser you are developing, since this new approach won't work on IE. You can find the full browser compatibility list on the following link.
Important: In this example I am sending a JSON request to a server listening on the given url. This url must be set, on my example I am assuming you know this part. Also, consider the headers needed for your request to work. Since I am sending a JSON, I must add the Content-Type header and set it to application/json; charset=utf-8, as to let the server know the type of request it will receive.
#Joao Marcos solution works for me but I had to modify the code to make it work on IE, below if what the code looks like
downloadFile(url,filename) {
var that = this;
const extension = url.split('/').pop().split('?')[0].split('.').pop();
var req = new XMLHttpRequest();
req.open("GET", url, true);
req.responseType = "blob";
req.onload = function (event) {
const fileName = `${filename}.${extension}`;
const blob = req.response;
if (window.navigator.msSaveBlob) { // IE
window.navigator.msSaveOrOpenBlob(blob, fileName);
}
const link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = fileName;
link.click();
URL.revokeObjectURL(link.href);
};
req.send();
},
Decoding a filename from the header is a little bit more complex...
var filename = "default.pdf";
var disposition = req.getResponseHeader('Content-Disposition');
if (disposition && disposition.indexOf('attachment') !== -1)
{
var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
var matches = filenameRegex.exec(disposition);
if (matches != null && matches[1])
filename = matches[1].replace(/['"]/g, '');
}
This solution is not very different from those above, but for me it works very well and i think it's clean.
I suggest to base64 encode the file server side (base64_encode(), if you are using PHP) and send the base64 encoded data to the client
On the client you do this:
let blob = this.dataURItoBlob(THE_MIME_TYPE + "," + response.file);
let uri = URL.createObjectURL(blob);
let link = document.createElement("a");
link.download = THE_FILE_NAME,
link.href = uri;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
This code puts the encoded data in a link and simulates a click on the link, then it removes it.
Your needs are covered by
window.location('download.php');
But I think that you need to pass the file to be downloaded, not always download the same file, and that's why you are using a request, one option is to create a php file as simple as showfile.php and do a request like
var myfile = filetodownload.txt
var url = "shofile.php?file=" + myfile ;
ajaxRequest.open("GET", url, true);
showfile.php
<?php
$file = $_GET["file"]
echo $file;
where file is the file name passed via Get or Post in the request and then catch the response in a function simply
if(ajaxRequest.readyState == 4){
var file = ajaxRequest.responseText;
window.location = 'downfile.php?file=' + file;
}
}
there is another solution to download a web page in ajax. But I am referring to a page that must first be processed and then downloaded.
First you need to separate the page processing from the results download.
1) Only the page calculations are made in the ajax call.
$.post("CalculusPage.php", { calculusFunction: true, ID: 29, data1: "a", data2: "b" },
function(data, status)
{
if (status == "success")
{
/* 2) In the answer the page that uses the previous calculations is downloaded. For example, this can be a page that prints the results of a table calculated in the ajax call. */
window.location.href = DownloadPage.php+"?ID="+29;
}
}
);
// For example: in the CalculusPage.php
if ( !empty($_POST["calculusFunction"]) )
{
$ID = $_POST["ID"];
$query = "INSERT INTO ExamplePage (data1, data2) VALUES ('".$_POST["data1"]."', '".$_POST["data2"]."') WHERE id = ".$ID;
...
}
// For example: in the DownloadPage.php
$ID = $_GET["ID"];
$sede = "SELECT * FROM ExamplePage WHERE id = ".$ID;
...
$filename="Export_Data.xls";
header("Content-Type: application/vnd.ms-excel");
header("Content-Disposition: inline; filename=$filename");
...
I hope this solution can be useful for many, as it was for me.
this works for me
var dataObj = {
somekey:"someValue"
}
$.ajax({
method: "POST",
url: "/someController/someMethod",
data: dataObj,
success: function (response) {
const blob = new Blob([response], { type: 'text/csv' });
const downloadUrl = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = downloadUrl;
a.download = "file.csv";
document.body.appendChild(a);
a.click();
}
});

Transfering pdf from backend to frontend on request jQuery

as mentioned in the title I am trying to pass pdf from backend php to frontend using jquery.
Sample php code.
public function printPdf(){
header('Content-Disposition: attachment; filename="kainos.pdf"');
$html = $this->request->post['pdf'];
$mpdf = new \Mpdf\Mpdf();
$mpdf->WriteHTML('<div>Section 1 text</div>');
return $mpdf->Output();
}
function(response){
var blob=new Blob([response], { type: 'application/pdf' });
var link=document.createElement('a');
link.href=window.URL.createObjectURL(blob);
link.download="Kainos.pdf";
link.click();
}
What i get is empty pdf.
What i know :
Data is being transfered correctly, since plain html can be transfered and seen using javascript console.log
Pdf is being generated, since i can just move the contents of the function to other function and get the pdf in other page, but i don't need it there.
I suspect that pdf is getting destroyed while being transfered, but can't seem to find any information
on how to fix it. Any help is appreciated
EDIT
Problem was not in the method I am sending but in jquery, it handles blobs wierd, so I am in the middle of rewriting everything in vanila js.
var xhr = new XMLHttpRequest();
xhr.open('POST', document.location.origin + '/somelocation.php', true);
//Send data to server
xhr.send({$("#pdf").html()});
//Set response type to file
xhr.responseType = "blob";
//On response do...
xhr.onload = () => {
//Create file in client side from xhr response
var blob=new Blob([xhr.response], { type: 'application/pdf' });
//create clickable element to download file and click it.
var link=document.createElement('a');
link.href=window.URL.createObjectURL(blob);
link.download="kainos.pdf";
link.click();
}
});
Try sending some more headers with your php script (And modifying your current one):
public function printPdf(){
ob_clean();
header('Content-type: application/pdf');
header('Content-Disposition: inline; filename="kainos.pdf"');
header('Content-Transfer-Encoding: binary');
header('Accept-Ranges: bytes');
$html = $this->request->post['pdf'];
$mpdf = new \Mpdf\Mpdf();
$mpdf->WriteHTML('<div>Section 1 text</div>');
return $mpdf->Output();
}

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);

PHP to download zip file works when called directly but not from web application

I have a PHP code which produces a zip file and make it downloadable from browser. The download part looks like:
download.php
// force client download
if (headers_sent()) {
echo 'HTTP header already sent';
} else {
if (!is_file($zipFile)) {
header($_SERVER['SERVER_PROTOCOL'].' 404 Not Found');
echo $zipFile . ' not found';
} else if (!is_readable($zipFile)) {
header($_SERVER['SERVER_PROTOCOL'].' 403 Forbidden');
echo $zipFile . ' not readable';
} else {
ob_start();
// http headers for zip downloads
header('Content-Description: File Transfer');
header('Content-Type: application/zip');
header('Content-Disposition: attachment; filename="' . $zipName . '"');
header('Content-Transfer-Encoding: binary');
header('Connection: Keep-Alive');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
set_time_limit(0);
ob_flush();
ob_clean();
readfile($zipFile);
}
}
When called from the browser directly like localhost/download.php it works and makes me download the zipped file properly.
However, I need to call it from my JS web application.
The way I am invoking it is via a POST AJAX request that look like this:
var xhr;
if (window.XMLHttpRequest) xhr = new XMLHttpRequest(); // all browsers
else xhr = new ActiveXObject("Microsoft.XMLHTTP"); // for IE
var php_url = '/localhost/download.php' // ?wfs_url=' + url + 'format=' + format_list[0];
// (https://stackoverflow.com/a/53982364/1979665)
var formData = new FormData();
formData.append('wfs_url', url);
formData.append('format', format_list[0]);
xhr.open('POST', php_url);
xhr.onreadystatechange = function () {
if (xhr.readyState===4 && xhr.status===200) {
alert('Server reply: ' + xhr.responseText);
}
}
xhr.send(formData);
return false;
The code is triggered when I click on a button.
Apparently it's doing "something". The PHP intermediate outputs (some folders) are correctly created, but the download does not begin.
The alert('Server reply: ' + xhr.responseText); part shows a strange message with some messy symbols, which I guess derives from the files created and read as text somehow.
Here is a screenshot of the message:
The PHP script is sending the actual binary source of the zip file, while the AJAX request is trying to display it as text, that's the garbage you get.
If you really want to keep the AJAX (it would be more simple to just make a <form> that is sent to download.php), you could do one two things:
Just print the URL to $zipFile in PHP, and then when you get the response redirect to it with window.location.href=xhr.responseText;.
Save the response as a zip file. See How to save binary data of zip file in Javascript?
After following option 2 in #Gabriel accepted answer I was able to make my code working. Here is the modified version of the JS (I didn't make any change in the PHP code):
var php_url = '/localhost/download.php' // ?wfs_url=' + url + 'format=' + format_list[0];
// (https://stackoverflow.com/a/53982364/1979665)
var formData = new FormData();
formData.append('wfs_url', url);
formData.append('format', format_list[0]);
xhr.open('POST', php_url);
xhr.onreadystatechange = function () {
if (xhr.readyState===4 && xhr.status===200) {
var blob = new Blob([xhr.response], { // SOLUTION
type: "application/zip", // SOLUTION
}); // SOLUTION
var filename = "test.zip"; // SOLUTION
if (navigator.msSaveOrOpenBlob) { // SOLUTION
navigator.msSaveOrOpenBlob(blob, filename); // SOLUTION
} else { // SOLUTION
var a = document.createElement("a"); // SOLUTION
document.body.appendChild(a); // SOLUTION
a.style = "display:none"; // SOLUTION
var url = window.URL.createObjectURL(blob); // SOLUTION
a.href = url; // SOLUTION
a.download = filename; // SOLUTION
a.click(); // SOLUTION
window.URL.revokeObjectURL(url); // SOLUTION
a.remove(); // SOLUTION
} // SOLUTION
}
}
xhr.responseType = "arraybuffer"; // SOLUTION
xhr.send(formData);

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