我有一个javascript应用程序,发送ajax POST请求到某个URL。Response可以是JSON字符串,也可以是文件(作为附件)。我可以很容易地在ajax调用中检测到Content-Type和Content-Disposition,但是一旦我检测到响应包含一个文件,我如何让客户端下载它呢?我在这里读过一些类似的帖子,但没有一个能提供我想要的答案。

请,请,请不要发布建议我不应该使用ajax或我应该重定向浏览器的答案,因为这些都不是一个选项。使用普通的HTML表单也是不可行的。我所需要的是向客户端显示一个下载对话框。这能做到吗?如何做到?


当前回答

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

重要提示:在本例中,我将发送一个JSON请求到侦听给定url的服务器。这个url必须设置,在我的例子中,我假设你知道这一部分。另外,考虑请求工作所需的头文件。因为我发送一个JSON,我必须添加内容类型头,并将其设置为application/ JSON;Charset =utf-8,以便让服务器知道它将接收的请求类型。

其他回答

您使用的服务器端语言是什么?在我的应用程序中,我可以通过在PHP的响应中设置正确的标题轻松地从AJAX调用下载文件:

设置报头服务器端

header("HTTP/1.1 200 OK");
header("Pragma: public");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");

// The optional second 'replace' parameter indicates whether the header
// should replace a previous similar header, or add a second header of
// the same type. By default it will replace, but if you pass in FALSE
// as the second argument you can force multiple headers of the same type.
header("Cache-Control: private", false);

header("Content-type: " . $mimeType);

// $strFileName is, of course, the filename of the file being downloaded. 
// This won't have to be the same name as the actual file.
header("Content-Disposition: attachment; filename=\"{$strFileName}\""); 

header("Content-Transfer-Encoding: binary");
header("Content-Length: " . mb_strlen($strFile));

// $strFile is a binary representation of the file that is being downloaded.
echo $strFile;

这实际上会将浏览器“重定向”到这个下载页面,但正如@ahren已经在他的评论中说的,它不会导航离开当前页面。

这一切都是关于设置正确的头文件,所以我相信您会找到适合您所使用的服务器端语言(如果不是PHP的话)的解决方案。

处理响应客户端

假设您已经知道如何进行AJAX调用,那么在客户端向服务器执行AJAX请求。然后服务器生成一个可以下载该文件的链接,例如你想要指向的“转发”URL。 例如,服务器响应如下:

{
    status: 1, // ok
    // unique one-time download token, not required of course
    message: 'http://yourwebsite.com/getdownload/ska08912dsa'
}

在处理响应时,你在你的主体中注入一个iframe,并将iframe的SRC设置为你刚刚接收到的URL,如下所示(使用jQuery来简化这个例子):

$("body").append("<iframe src='" + data.message +
  "' style='display: none;' ></iframe>");

如果您如上所示设置了正确的标题,iframe将强制下载对话框,而不需要将浏览器从当前页面导航出去。

Note

关于你的问题的额外补充;我认为使用AJAX技术请求东西时最好总是返回JSON。收到JSON响应后,就可以在客户端决定如何处理它。也许,例如,稍后您希望用户单击指向URL的下载链接,而不是强制直接下载,在您当前的设置中,您将不得不更新客户端和服务器端来实现这一点。

还有另一种解决方案下载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;
            }               
       }
);

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

    ...

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

我是这么做的 https://stackoverflow.com/a/27563953/2845977

. ajax({美元 url: ' < URL_TO_FILE >”, 成功:函数(数据){ var blob=new blob ([data]); var链接= document.createElement (' a '); link.href = window.URL.createObjectURL (blob); link.download = " < FILENAME_TO_SAVE_WITH_EXTENSION >”; link.click (); } });

使用download.js更新答案

. ajax({美元 url: ' < URL_TO_FILE >”, 成功:下载。绑定(true, "<FILENAME_TO_SAVE_WITH_EXTENSION>", "<FILE_MIME_TYPE>") });

以下是我从不同渠道收集的解决方案: 服务器端实现:

    String contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
    // Set headers
    response.setHeader("content-disposition", "attachment; filename =" + fileName);
    response.setContentType(contentType);
    // Copy file to output stream
    ServletOutputStream servletOutputStream = response.getOutputStream();
    try (InputStream inputStream = new FileInputStream(file)) {
        IOUtils.copy(inputStream, servletOutputStream);
    } finally {
        servletOutputStream.flush();
        Utils.closeQuitely(servletOutputStream);
        fileToDownload = null;
    }

客户端实现(使用jquery):

$.ajax({
type: 'POST',
contentType: 'application/json',
    url: <download file url>,
    data: JSON.stringify(postObject),
    error: function(XMLHttpRequest, textStatus, errorThrown) {
        alert(errorThrown);
    },
    success: function(message, textStatus, response) {
       var header = response.getResponseHeader('Content-Disposition');
       var fileName = header.split("=")[1];
       var blob = new Blob([message]);
       var link = document.createElement('a');
       link.href = window.URL.createObjectURL(blob);
       link.download = fileName;
       link.click();
    }
});   

这是一个3年前的问题,但我今天遇到了同样的问题。我看了你编辑的解决方案,但我认为它可以牺牲性能,因为它必须做出双重请求。因此,如果有人需要另一个解决方案,不暗示调用服务两次,那么这是我做的方式:

<form id="export-csv-form" method="POST" action="/the/path/to/file">
    <input type="hidden" name="anyValueToPassTheServer" value="">
</form>

此表单仅用于调用服务,避免使用window.location()。在此之后,您只需简单地使用jquery提交一个表单,以便调用服务并获取文件。这很简单,但是通过这种方式你可以使用POST进行下载。我现在知道,如果您调用的服务是GET,这可能会更容易,但我的情况不是这样。