使用 Ajax 请求下载文件

117

当我点击按钮时,我想发送一个 "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");
?>

但是它没有像预期的那样工作,我该怎么办?提前谢谢。


2
这个方法行不通,参见这个问题 - olegsv
2
执行 location.href='download.php'; - Musa
尝试这个:http://stackoverflow.com/a/42235655/2282880 - Meloman
当你需要这个时,它似乎是一个常见的要求,但可悲的是没有优雅的解决方案。 - 0xc0de
14个回答

104

2015年4月27日更新

在HTML5领域中正在崭露头角的是下载属性(download attribute)。它已经在Firefox和Chrome中得到了支持,并且很快将会出现在IE11中。根据您的需要,只要所需下载的文件与您的站点具有相同的来源,您可以使用它来代替AJAX请求(或使用window.location)。

您始终可以通过使用一些JavaScript来测试是否支持download属性,如果不支持,则切换到调用window.location作为备选方案。

原始回答

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

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

尽管这个答案回答了问题,但更好的方法是只使用 window.location ,避免整个 AJAX 请求。

45
这个会不会调用两次链接?我也有类似的问题......我正在头信息中传递大量安全信息,并能够在成功函数中解析文件对象,但不知道如何触发下载提示。 - user1447679
4
它确实会调用页面两次,因此如果您在该页面中查询数据库,则意味着要进行两次数据库访问。 - mmmmmm
1
@user1447679 可以看看这个替代方案:http://stackoverflow.com/questions/38665947/in-javascript-how-can-i-redirect-an-ajax-response-in-window-location - John
2
让我解释一下这是如何帮助我的...例子可以更完整一些。使用“download.php?get_file=true”或类似的东西...我有一个ajax函数,它对表单提交进行一些错误检查,然后创建一个csv文件。如果错误检查失败,它必须返回失败原因。如果它创建了CSV,它会告诉父级“继续获取文件”。我通过将表单变量发布到ajax文件,然后发布一个不同的参数到同一个文件,说“把你刚刚创建的文件交给我”(路径/名称硬编码到ajax文件中)来实现这一点。 - Scott
8
但它会发送两次请求,这不是合适的。 - Dharmendrasinh Chudasama
显示剩余4条评论

61

要让浏览器下载文件,您需要按照以下方式进行请求:

 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 中,我需要首先将一个 <a> 标签放入 DOM 中,并将其用作我的链接,而不是动态创建一个链接,以便文件可以自动下载。 - Erik Donohoo
可以正常工作,但是如果文件在执行期间被创建会发生什么呢?它的工作原理就不像文件已经存在时那样了。 - Diego
1
@ErikDonohoo 你可以使用JS“即时”创建<a>标签,但必须将其附加到document.body上。同时,您肯定希望隐藏它。 - Márton Tamás
记得在 link.click() 后添加 link.remove() - Alberto Manuel
如果你想下载文本文件,请记住执行以下操作:var blob = new Blob([req.responseText], { type: 'text/csv', }) - Alberto Manuel
显示剩余3条评论

48
您其实并不需要使用AJAX来实现此功能。如果您只是将"download.php"设置为按钮的href属性,或者如果它不是一个链接,则可以使用以下代码:
window.location = 'download.php';

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

3
你正在使用的编程语言来更改 window.location 的是 JavaScript。 - mikemaccana
2
你说得对@mikemaccana,我实际上是指ajax :)。 - Jelle Kralt
2
我一直在寻找解决方案,这个方法非常优雅和完美。非常感谢你。 - Yangshun Tay
2
当然,这个解决方案只适用于已经存在的静态文件。 - krillgar
2
如果服务器响应错误,那么没有办法停留在主页面而不被浏览器重定向到错误页面。至少这是当window.location的结果返回404时Chrome所做的。 - Ian
哇,我想太多了。我只需将我的文件名作为GET参数添加,就可以了。谢谢! - CJ Broersma

22

跨浏览器解决方案,已在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,这很有帮助。另外,您建议在el.click()之后添加window.URL.revokeObjectURL(el.href);吗? - vibs2006
如果内容描述指定了非UTF8文件名,则文件名将不正确。 - Mathew Alden

15

这是可能的。例如,在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 Bergeron Néron
5
我认为是这样,因为任何人都可以调用download.php?filename=[something]并尝试使用一些路径和文件名(特别是常见的文件名),这甚至可以在程序或脚本中的循环内完成。 - Mickael Bergeron Néron
9
@PedroSousa .. no. htaccess控制着通过Apache访问文件结构的权限。由于访问已经到达了一个PHP脚本,htaccess现在停止了其任务。这是非常大的安全隐患,因为实际上,任何PHP(以及它正在运行的用户)可以读取的文件,它都可以传递到readfile中... 必须始终对要读取的请求文件进行净化处理。 - Prof
@prof83 我想你是对的。为了安全起见,我们应该始终对要请求的文件进行清理。感谢您指出这一点,我将来一定会考虑到这个问题。谢谢 :) - Pedro Sousa
下载期间存在风险。为了保证所有答案的安全,需要使用令牌系统或某种下载管理方式。对于没有安全/管理措施的每一个人来说,这些担忧都是合理的。 - JSG
显示剩余2条评论

4

4

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

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


2

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

0

您的需求可以通过以下代码实现:window.location('download.php');
但我认为您需要传递要下载的文件,而不是总是下载相同的文件,这就是为什么您正在使用请求的原因之一。一个选项是创建一个简单的php文件,例如showfile.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" },
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; } } );
// 例如:在 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");
...

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接