使用 ajax 请求下载文件

Posted

技术标签:

【中文标题】使用 ajax 请求下载文件【英文标题】:download file using an ajax request 【发布时间】:2014-01-16 19:03:04 【问题描述】:

我想在点击一个按钮的时候发送一个“ajax下载请求”,所以我这样尝试:

javascript

var xhr = new XMLHttpRequest();
xhr.open("GET", "download.php");
xhr.send();

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

但没有按预期工作,我该怎么办?提前谢谢你

【问题讨论】:

这不起作用,请参阅[this question][1]。 [1]:***.com/questions/8771342/… location.href='download.php'; 试试这个***.com/a/42235655/2282880 当你需要这个的时候,确实觉得这是一件很平常的事情,遗憾的是没有优雅的解决方案。 【参考方案1】:

2015 年 4 月 27 日更新

即将进入 html5 场景的是 download attribute。在 Firefox 和 Chrome 中是 supported,很快就会出现在 IE11 中。根据您的需要,您可以使用它而不是 AJAX 请求(或使用 window.location),只要您要下载的文件与您的网站位于同一来源。

您始终可以使用 some JavaScript 测试是否支持 AJAX 请求/window.location 以测试是否支持 download,如果不支持,则将其切换为调用 window.location

原答案

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

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

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

【讨论】:

这不是两次调用链接吗?我在一条类似的船上...我在标头中传递了很多安全信息,并且能够在成功函数中解析文件对象,但不知道如何触发下载提示。 它确实调用了该页面两次,因此如果您在该页面中查询数据库,这意味着两次访问数据库。 @user1447679 查看替代解决方案:***.com/questions/38665947/… 让我解释一下这对我有什么帮助......这个例子本来可以更完整。使用“download.php?get_file=true”之类的......我有一个 ajax 函数,它对表单提交进行一些错误检查,然后创建一个 csv 文件。如果错误检查失败,它必须返回失败的原因。如果它创建 CSV,它会告诉父级“继续获取文件”。我通过使用表单变量发布到 ajax 文件,然后将不同的参数发布到同一个文件,说“把你刚刚创建的文件交给我”(路径/名称被硬编码到 ajax 文件中)来做到这一点。 但是它会发送请求2次,这是不正确的【参考方案2】:

要让浏览器下载文件,您需要发出这样的请求:

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

【讨论】:

这对我有用,但在 Firefox 中,我需要先在 DOM 中放置一个 标记,并将其作为我的链接引用,而不是动态创建一个以便文件自动下载。 有效,但如果文件在执行期间创建会怎样?它不像文件已经创建时那样工作。 @Taha 我在 Edge 上进行了测试,它似乎有效。虽然不知道IE。我的客户不针对 IE 用户 ;-) 我幸运吗? :D @ErikDonohoo 你可以使用 JS 创建 &lt;a&gt; 标签,“在运行中”也是如此,但必须将它附加到 document.body。你当然想同时隐藏它。 感谢@João,我一直在寻找这个解决方案。【参考方案3】:

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

window.location = 'download.php';

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

【讨论】:

你用来改变window.location的编程语言 JavaScript。 你说得对@mikemaccana,我实际上是指ajax :)。 一直在寻找解决方案,这非常优雅和完美。非常感谢。 当然,这个方案只有在静态文件已经存在的情况下才有效。 如果服务器响应错误,但没有任何方法可以留在您的主页上而不会被浏览器重定向到错误页面。至少这是 Chrome 在 window.location 的结果返回 404 时所做的。【参考方案4】:

跨浏览器解决方案,在 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 这样的自定义标题来改进这段代码吗? 谢谢@leo。它很有帮助。另外你建议在el.click() 之后添加window.URL.revokeObjectURL(el.href); 吗? 如果内容配置指定了非 UTF8 文件名,则文件名将出错。【参考方案5】:

这是可能的。您可以从 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

【讨论】:

这在安全方面是不是很危险? @MickaelBergeronNéron 为什么? 我会这样认为,因为任何人都可以调用 download.php?filename=[something] 并尝试一些路径和文件名,尤其是常见的,这甚至可以在程序的循环中完成,或者一个脚本。 .htaccess 不会避免这种情况吗? @PedroSousa .. 没有。 htaccess 通过 Apache 控制对文件结构的访问。由于访问已到达 PHP 脚本,htaccess 现在停止其职责。这是一个非常大的安全漏洞,因为事实上,PHP(以及运行它的用户)可以读取的任何文件,因此它可以传递到 readfile 中......应该始终清理要读取的请求文件【参考方案6】:

我更喜欢location.assign(url);

完整的语法示例:

document.location.assign('https://www.urltodocument.com/document.pdf');

developer.mozilla.org/en-US/docs/Web/API/Location.assign

【讨论】:

【参考方案7】:

对于那些寻求更现代方法的人,您可以使用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。您可以在以下link 上找到完整的浏览器兼容性列表。

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

【讨论】:

【参考方案8】:

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

【讨论】:

【参考方案9】:

从标题中解码文件名有点复杂...

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

【讨论】:

请格式化您的整个代码块,并为您的流程提供一些额外的解释,以便将来读者受益。【参考方案10】:

此解决方案与上述解决方案没有太大区别,但对我来说效果很好,我认为它很干净。

我建议对文件服务器端进行base64编码(base64_encode(),如果您使用的是PHP)并将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);

此代码将编码数据放入链接中并模拟点击链接,然后将其删除。

【讨论】:

您可以只隐藏 a 标签并动态填充 href。无需添加和删除【参考方案11】:

满足您的需求 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;  
                    
                

【讨论】:

【参考方案12】:

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

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

1) ajax 调用只进行页面计算。

$.post("CalculusPage.php", calculusFunction: true, ID: 29, data1: "a", data2: "b" , 功能(数据,状态) 如果(状态==“成功”) /* 2) 在答案中,下载了使用先前计算的页面。例如,这可以是打印在 ajax 调用中计算的表的结果的页面。 */ window.location.href = 下载页面.php+"?ID="+29; ); // 例如:在 CalculusPage.php if ( !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"); ...

我希望这个解决方案对许多人有用,就像对我一样。

【讨论】:

【参考方案13】:

这对我有用

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

【讨论】:

以上是关于使用 ajax 请求下载文件的主要内容,如果未能解决你的问题,请参考以下文章

下载 csv 文件作为对 AJAX 请求的响应

ruby 使用带伪装ajax的jquery / POST请求文件下载请求

通过发送ajax请求下载Excel文件

使用Jquery Ajax请求 下载压缩文件

在不使用 Ajax 请求的情况下使用 ExtJS 4.1 将文件下载为 CSV 的替代方法?

下载 excel 文件的 ajax 请求显示我被截断的响应