使用ajax请求下载文件


95

我想在单击按钮时发送“ ajax下载请求”,所以我尝试了这种方式:

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

但是没有按预期工作,我该怎么办?先感谢您


2
这将不起作用,请参阅[此问题] [1]。[1]:stackoverflow.com/questions/8771342/…–
olegsv

2
location.href='download.php';
穆萨

Answers:


92

2015年4月27日更新

进入HTML5场景的是download属性。它支持在Firefox和Chrome,并很快来到IE11。根据您的需求,window.location只要您要下载的文件与您的网站位于同一来源,就可以使用它代替AJAX请求(或使用)。

您始终可以window.location通过使用一些JavaScript来测试是否download支持AJAX请求/ 回退,如果不支持,则将其切换为call window.location

原始答案

您无法让AJAX请求打开下载提示,因为您实际上必须导航到文件以提示下载。相反,您可以使用成功函数来导航到download.php。这将打开下载提示,但不会更改当前页面。

$.ajax({
    url: 'download.php',
    type: 'POST',
    success: function() {
        window.location = 'download.php';
    }
});

即使这回答了问题,还是最好只使用window.location并完全避免AJAX请求。


39
这不是两次调用链接吗?我在类似的船上……我在标头中传递了很多安全信息,并且能够在成功函数中解析文件对象,但不知道如何触发下载提示。
user1447679

3
它确实会两次调用该页面,因此,如果您要查询该页面中的数据库,则意味着有2次前往DB。
mmmmmm

1
@ user1447679,请参见替代解决方案:stackoverflow.com/questions/38665947/…–
约翰

1
让我解释一下这对我有何帮助……示例可以更加完整。与“ download.php?get_file = true”之类的东西...我有一个ajax函数,该函数会对表单提交进行一些错误检查,然后创建一个csv文件。如果错误检查失败,则必须返回失败原因。如果它创建了CSV,则会告诉父级“继续并获取文件”。我这样做是通过使用form变量将其发布到ajax文件中,然后将DIFFERENT参数发布到同一文件中并说“将刚创建的文件交给我”(路径/名称被硬编码到ajax文件中)。
Scott

4
但是它将发送请求2次,这是不正确的
Dharmendrasinh Chudasama

43

实际上,您根本不需要ajax。如果您仅将“ download.php”设置为按钮上的href,或者如果它不是链接,则使用:

window.location = 'download.php';

浏览器应该识别二进制下载文件,而不是加载实际页面,而只是将文件作为下载文件。


3
您用来更改的编程语言window.location JavaScript。
mikemaccana 2014年

1
你说得对@mikemaccana,我实际上是指ajax :)。
Jelle Kralt 2014年

2
一直在寻找解决方案,这是如此的优雅和完美。非常感谢。
Yangshun Tay

2
当然,此解决方案仅在它是已存在的静态文件时才有效。
krillgar's

1
如果服务器响应错误,但是没有任何方法可以保留在您的主页上,而不会被浏览器重定向到错误页面。当window.location的结果返回404时,Chrome至少会这样做。–
Ian

43

要使浏览器下载文件,您需要发出如下请求:

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

7
这对我有用,但是在firefox中,我需要首先在DOM中放置一个<a>标记,并将其引用为我的链接,而不是动态创建一个链接,以便文件自动下载。
Erik Donohoo '17

可以,但是如果在执行过程中创建文件会怎样?它不能像已经创建文件时那样工作。
迭戈

@Taha我在Edge上测试了它,它似乎起作用了。虽然不了解IE。我的客户没有针对IE用户;-)我很幸运吗?:d
Sнаđошƒаӽ

1
@ErikDonohoo您也可以<a>“即时”使用JS 创建标签,但必须将其附加到document.body。您当然想同时隐藏它。
顿·塔马斯

感谢@João,我一直在寻找这种解决方案。
Abhishek Gharai

20

跨浏览器解决方案,已在Chrome,Firefox,Edge,IE11上进行了测试。

在DOM中,添加一个隐藏的链接标记:

<a id="target" style="display: none"></a>

然后:

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

良好的通用代码。@leo您可以通过添加自定义标头来改进此代码Authorization吗?
vibs2006

1
谢谢@leo。它很有帮助。您还建议window.URL.revokeObjectURL(el.href);在之后添加el.click()什么?
vibs2006

如果内容处置指定了非UTF8文件名,则文件名将错误。
Mathew Alden

14

有可能的。您可以从ajax函数内部开始下载,例如,在创建.csv文件之后。

我有一个ajax函数,可将联系人数据库导出到.csv文件,完成后,它会自动开始下载.csv文件。所以,当我得到responseText并且一切正常之后,我就这样重定向了浏览器:

window.location="download.php?filename=export.csv";

我的download.php文件如下所示:

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

?>

没有页面刷新,该文件将自动开始下载。

注意 -在以下浏览器中测试:

Chrome v37.0.2062.120 
Firefox v32.0.1
Opera v12.17
Internet Explorer v11

1
这不是危险的安全明智的做法吗?
Mickael BergeronNéron16年

@MickaelBergeronNéron为什么?
Pedro Sousa

5
我想是这样,因为任何人都可以调用download.php?filename = [something]并尝试一些路径和文件名,尤其是常用的路径和文件名,而且甚至可以在程序或脚本内的循环内完成。
Mickael BergeronNéron'16

.htaccess会避免这种情况吗?
Pedro Sousa

9
@PedroSousa ..没有 htaccess通过Apache控制对文件结构的访问。由于访问已到达PHP脚本,因此htaccess现在停止其职责。这确实是一个安全漏洞,因为确实,PHP(及其运行用户)的任何文件都可以读取,因此可以将其传送到读取文件中……应该始终清理请求读取的文件
Prof83


0

从头文件中解码文件名会有点复杂...

    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, '');
    }

3
请格式化您的整个代码块,并为您的过程提供一些其他说明,以使将来的读者受益。
mickmackusa

0

这个解决方案与上面的解决方案并没有太大的区别,但是对我来说,它很好用,我认为它很干净。

我建议对文件服务器端进行base64编码(如果使用的是PHP,则为base64_encode()),然后将base64编码的数据发送给客户端

在客户端上,您可以执行以下操作:

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

此代码将编码后的数据放在链接中,并模拟对链接的单击,然后将其删除。


您可以隐藏标签并动态填充href。无需添加和删除
BPeela '19

0

满足您的需求, window.location('download.php');
但是我认为您需要传递要下载的文件,而不总是下载相同的文件,这就是您使用请求的原因,一种选择是创建一个像showfile.php这样简单的php文件,然后做一个像

var myfile = filetodownload.txt
var url = "shofile.php?file=" + myfile ;
ajaxRequest.open("GET", url, true);

showfile.php

<?php
$file = $_GET["file"] 
echo $file;

其中file是在请求中通过Get或Post传递的文件名,然后简单地在函数中捕获响应

if(ajaxRequest.readyState == 4){
                        var file = ajaxRequest.responseText;
                        window.location = 'downfile.php?file=' + file;  
                    }
                }

0

还有另一种解决方案,可以用ajax下载网页。但是我指的是必须首先处理然后下载的页面。

首先,您需要将页面处理与结果下载分开。

1)在ajax调用中仅进行页面计算。

$ .post(“ CalculusPage.php”,{calculusFunction:true,ID:29,data1:“ a”,data2:“ b”},

       功能(数据,状态) 
       {
            如果(状态==“成功”) 
            {
                / * 2)在答案中,下载了使用先前计算的页面。例如,这可以是打印ajax调用中计算出的表的结果的页面。* /
                window.location.href = DownloadPage.php +“?ID =” + 29;
            }               
       }
);

//例如:在CalculusPage.php中

    如果(!empty($ _ POST [“ calculusFunction”])) 
    {
        $ ID = $ _POST [“ ID”];

        $ query =“ INSERT INTO ExamplePage(data1,data2)VALUES('”。$ _ POST [“ data1”]。“','” $ _POST [“ data2”]。“')WHERE id =”。$ ID;
        ...
    }

//例如:在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”);

    ...

我希望这个解决方案对我来说对许​​多人都有用。


0

对于寻求更现代方法的用户,可以使用fetch API。以下示例显示了如何下载电子表格文件。使用以下代码即可轻松完成。

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

我相信这种方法比其他XMLHttpRequest解决方案更容易理解。而且,它具有与该jQuery方法类似的语法,而无需添加任何其他库。

当然,我建议您检查一下要开发的浏览器,因为这种新方法不适用于IE。您可以在以下链接中找到完整的浏览器兼容性列表。

重要提示:在此示例中,我向服务器侦听给定的JSON请求url。这是url必须设置的,在我的示例中,我假设您知道这一部分。另外,请考虑您的请求正常工作所需的标题。由于我正在发送JSON,因此必须添加Content-Type标头并将其设置为application/json; charset=utf-8,以使服务器知道它将接收的请求的类型。


0

@Joao Marcos解决方案适用于我,但我必须修改代码以使其在IE上正常工作,下面的代码是什么样的

       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();
    },
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.