使用Ajax下载并打开PDF文件

115

我有一个生成PDF的动作类。 contentType 已经适当设置。

public class MyAction extends ActionSupport 
{
   public String execute() {
    ...
    ...
    File report = signedPdfExporter.generateReport(xyzData, props);

    inputStream = new FileInputStream(report);
    contentDisposition = "attachment=\"" + report.getName() + "\"";
    contentType = "application/pdf";
    return SUCCESS;
   }
}

我通过一个 Ajax 调用来调用这个 action。 我不知道将此流传递到浏览器的方法。 我尝试了一些方法,但什么都没有起作用。

$.ajax({
    type: "POST",
    url: url,
    data: wireIdList,
    cache: false,
    success: function(response)
    {
        alert('got response');
        window.open(response);
    },
    error: function (XMLHttpRequest, textStatus, errorThrown) 
    {
        alert('Error occurred while opening fax template' 
              + getAjaxErrorString(textStatus, errorThrown));
    }
});

上述代码会报错:

您的浏览器发送了一个请求,但是该服务器无法理解。

18个回答

138

以下是我如何使它工作的步骤

$.ajax({
  url: '<URL_TO_FILE>',
  success: function(data) {
    var blob=new Blob([data]);
    var link=document.createElement('a');
    link.href=window.URL.createObjectURL(blob);
    link.download="<FILENAME_TO_SAVE_WITH_EXTENSION>";
    link.click();
  }
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

使用download.js更新答案

$.ajax({
  url: '<URL_TO_FILE>',
  success: download.bind(true, "<FILENAME_TO_SAVE_WITH_EXTENSION>", "<FILE_MIME_TYPE>")
});


35
能在Chrome上使用吗?我只能看到一个空白的PDF。 - Tarun Gupta
5
在IE 11、Edge和Firefox中,这个(锚点元素)实际上对我没有起作用。将"success"更改为只使用"window.open(URL.createObjectURL(blob))"确实有效。 - JimiSweden
3
PDF文件已经下载,但是没有内容可用。我已经在服务器端保存了字节数组并且PDF内容是可用的。请给予建议。 - Awanish Kumar
8
下载了空白的PDF文件。 - Farukh
5
жҲ‘йҖҡиҝҮеңЁ$.ajax()зҡ„еҸӮж•°дёӯж·»еҠ xhrFields: { responseType: 'blob' }жҲҗеҠҹи§ЈеҶідәҶз©әзҷҪPDFй—®йўҳгҖӮ - Jesse Hogan
显示剩余7条评论

47
你并不一定需要使用 Ajax。如果在服务器端代码中设置 content-dispositionattachment,那么只要一个 <a> 链接就足够了。这样,父页面将保持打开状态,如果这是你的主要关注点(否则你为什么会不必要地选择 Ajax 呢?)。此外,没有办法很好地异步处理 PDF。PDF 不是字符数据,而是二进制数据。你不能像 $(element).load() 这样执行操作。你需要使用全新的请求来处理它。对于这个问题,<a href="pdfservlet/filename.pdf">pdf</a> 是完全适合的。
为了更好地帮助你处理服务器端代码,你需要告诉我所使用的语言,并发布一段代码尝试内容的摘录。

8
再强调一遍:你不需要使用Ajax完成这个任务,否则只会引起麻烦。PDF是二进制数据,不同于HTML或JSON的字符数据。 - BalusC
4
var url = contextPath + "/xyz/blahBlah.action"; url += url + "?" + params; try { var child = window.open(url); child.focus(); } catch (e) { } - Nayn
5
在某些浏览器中,window.open会保持打开但不显示内容,这可能会令最终用户感到烦恼。因此,也请不要使用window.open。如果将content-disposition设置为attachment,则只会弹出“另存为”对话框。父页面将保持不变。只需使用<a href="pdfservlet/filename.pdf">pdf</a>或者<form action="pdfservlet/filename.pdf"><input type="submit"></form>即可。 - BalusC
7
有一个有限的URL长度。作者在询问关于POST的内容。 - Edward Olamisan
3
同意@EdwardOlamisan的说法,这不是正确的答案,因为作者试图“POST”数据。 - adamj
显示剩余10条评论

34

我并不认为之前的回答中有任何一个人发现了原始问题。他们都假定是一个GET请求,而原始发布者试图通过POST数据并获得响应的下载。

在搜索更好的答案的过程中,我们找到了这个jQuery插件,用于请求类似Ajax的文件下载(如果链接在未来某个时候失效,请参见互联网档案馆)。

它的“核心”是创建一个“临时”HTML表单,其中包含给定的数据作为输入字段。该表单被附加到文档中并发布到所需的URL中。然后立即再次删除表单:

jQuery('<form action="'+ url +'" method="'+ (method||'post') +'">'+inputs+'</form>')
    .appendTo('body').submit().remove()

更新:Mayur的答案看起来非常有前途,与我所提到的jQuery插件相比非常简单。


9
这是我解决这个问题的方法。
Jonathan Amend在这篇文章中的答案对我帮助很大。
下面的示例已经简化了。

要了解更多细节,上述源代码能够使用JQuery Ajax请求(GET、POST、PUT等)下载文件。它还可以帮助上传JSON参数并将内容类型更改为application/json(默认值)

html源代码:

<form method="POST">
    <input type="text" name="startDate"/>
    <input type="text" name="endDate"/>
    <input type="text" name="startDate"/>
    <select name="reportTimeDetail">
        <option value="1">1</option>
    </select>
    <button type="submit"> Submit</button>
</form>  

一个简单的表单,包含两个输入框、一个下拉选择框和一个按钮元素。

Javascript页面源代码:

<script type="text/javascript" src="JQuery 1.11.0 link"></script>
<script type="text/javascript">
    // File Download on form submition.
    $(document).on("ready", function(){
        $("form button").on("click", function (event) {
            event.stopPropagation(); // Do not propagate the event.

            // Create an object that will manage to download the file.
            new AjaxDownloadFile({
                url: "url that returns a file",
                data: JSON.stringify($("form").serializeObject())
            });

            return false; // Do not submit the form.
        });
    });
</script>  

按钮点击时会触发一个简单的事件。它会创建一个AjaxDownloadFile对象。下面是AjaxDownloadFile类的源代码。

AjaxDownloadFile类源代码:

var AjaxDownloadFile = function (configurationSettings) {
    // Standard settings.
    this.settings = {
        // JQuery AJAX default attributes.
        url: "",
        type: "POST",
        headers: {
            "Content-Type": "application/json; charset=UTF-8"
        },
        data: {},
        // Custom events.
        onSuccessStart: function (response, status, xhr, self) {
        },
        onSuccessFinish: function (response, status, xhr, self, filename) {
        },
        onErrorOccured: function (response, status, xhr, self) {
        }
    };
    this.download = function () {
        var self = this;
        $.ajax({
            type: this.settings.type,
            url: this.settings.url,
            headers: this.settings.headers,
            data: this.settings.data,
            success: function (response, status, xhr) {
                // Start custom event.
                self.settings.onSuccessStart(response, status, xhr, self);

                // Check if a filename is existing on the response headers.
                var filename = "";
                var disposition = xhr.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, "");
                }

                var type = xhr.getResponseHeader("Content-Type");
                var blob = new Blob([response], {type: type});

                if (typeof window.navigator.msSaveBlob !== "undefined") {
                    // IE workaround for "HTML7007: One or more blob URLs were revoked by closing the blob for which they were created. These URLs will no longer resolve as the data backing the URL has been freed.
                    window.navigator.msSaveBlob(blob, filename);
                } else {
                    var URL = window.URL || window.webkitURL;
                    var downloadUrl = URL.createObjectURL(blob);

                    if (filename) {
                        // Use HTML5 a[download] attribute to specify filename.
                        var a = document.createElement("a");
                        // Safari doesn"t support this yet.
                        if (typeof a.download === "undefined") {
                            window.location = downloadUrl;
                        } else {
                            a.href = downloadUrl;
                            a.download = filename;
                            document.body.appendChild(a);
                            a.click();
                        }
                    } else {
                        window.location = downloadUrl;
                    }

                    setTimeout(function () {
                        URL.revokeObjectURL(downloadUrl);
                    }, 100); // Cleanup
                }

                // Final custom event.
                self.settings.onSuccessFinish(response, status, xhr, self, filename);
            },
            error: function (response, status, xhr) {
                // Custom event to handle the error.
                self.settings.onErrorOccured(response, status, xhr, self);
            }
        });
    };
    // Constructor.
    {
        // Merge settings.
        $.extend(this.settings, configurationSettings);
        // Make the request.
        this.download();
    }
};

我创建了这个类,以便将其添加到我的JS库中。它是可重复使用的。希望这有所帮助。


2
Blob 对象在 IE10+ 中得到支持。 - crush
我必须将xhr的responseType设置为arraybufferblob才能使其正常工作。(否则,这个很好用。) - tjklemz
我有完全相同的问题。所有回答“只需将其设置为链接”都没有帮助到OP。如果您的内容是动态的,而您要访问的链接也是动态的,那么您必须使用jquery来处理它...对我来说,答案是在页面上放置一个带有所有隐藏输入的表单(不会显示给用户),然后使用jquery填写并提交它。效果很好。 - Scott
这是一个很好的答案,但出于某种原因,我一直得到损坏的空PDF。无法弄清楚原因。当我通过API返回相同的字节集时 - 没问题,所以这与MVC响应有关。我使用FileResult响应类型:File(bytes,System.Net.Mime.MediaTypeNames.Application.Octet,fileName); - Jurijs Kastanovs
澄清:如果通过地址栏打开URL,则文件会正确打开。 如果我使用AJAX + blob获取文件,则文件格式不正确。 - Jurijs Kastanovs
更新:我通过使用这个答案(https://github.com/eligrey/FileSaver.js/issues/238#issuecomment-224572273)成功解决了它。 - Jurijs Kastanovs

8
我的建议是使用以下代码,因为服务器函数正在检索File(memoryStream.GetBuffer(), "application/pdf", "fileName.pdf");
$http.get( fullUrl, { responseType: 'arraybuffer' })
            .success(function (response) {
                var blob = new Blob([response], { type: 'application/pdf' });

                if (window.navigator && window.navigator.msSaveOrOpenBlob) {
                    window.navigator.msSaveOrOpenBlob(blob); // for IE
                }
                else {
                    var fileURL = URL.createObjectURL(blob);
                    var newWin = window.open(fileURL);
                    newWin.focus();
                    newWin.reload();
                }
});

这在我发表评论时和最新的Chrome上完美运行。 - Loredra L

6
您可以使用此插件,它会创建一个表单并提交它,然后将其从页面中删除。
jQuery.download = function(url, data, method) {
    //url and data options required
    if (url && data) {
        //data can be string of parameters or array/object
        data = typeof data == 'string' ? data : jQuery.param(data);
        //split params into form inputs
        var inputs = '';
        jQuery.each(data.split('&'), function() {
            var pair = this.split('=');
            inputs += '<input type="hidden" name="' + pair[0] +
                '" value="' + pair[1] + '" />';
        });
        //send request
        jQuery('<form action="' + url +
                '" method="' + (method || 'post') + '">' + inputs + '</form>')
            .appendTo('body').submit().remove();
    };
};


$.download(
    '/export.php',
    'filename=mySpreadsheet&format=xls&content=' + spreadsheetData
);

对我有用。在这里找到了这个插件 此页面


1
此插件只是创建一个表单,然后提交它,最后从页面中删除它。(如果有人想知道的话) - crush

6

为了解决在post请求中获取流数据(如PDF)时出现的空白PDF问题,我们需要在请求中添加响应类型为'arraybuffer'或'blob'

$.ajax({
  url: '<URL>',
  type: "POST",
  dataType: 'arraybuffer',
  success: function(data) {
    let blob = new Blob([data], {type: 'arraybuffer'});
    let link = document.createElement('a');
    let objectURL = window.URL.createObjectURL(blob);
    link.href = objectURL;
    link.target = '_self';
    link.download = "fileName.pdf";
    (document.body || document.documentElement).appendChild(link);
    link.click();
    setTimeout(()=>{
        window.URL.revokeObjectURL(objectURL);
        link.remove();
    }, 100);
  }
});

5
关于 Mayur Padshala 给出的答案,这是通过ajax下载pdf文件的正确逻辑,但正如其他人在评论中所报告的那样,此解决方案确实会下载一个空白的pdf文件。
这个问题的原因在这个问题的被接受的答案中有所解释:jQuery使用AJAX请求加载二进制数据时存在一些问题,因为它尚未实现一些HTML5 XHR v2功能,请参见这个请求和这个讨论
因此,使用HTMLHTTPRequest,代码应该像这样:
var req = new XMLHttpRequest();
req.open("POST", "URL", true);
req.responseType = "blob";
req.onload = function (event) {
    var blob = req.response;
    var link=document.createElement('a');
    link.href=window.URL.createObjectURL(blob);
    link.download="name_for_the_file_to_save_with_extention";
    link.click();
}

5
以下代码对我有效:
//Parameter to be passed
var data = 'reportid=R3823&isSQL=1&filter=[]';
var xhr = new XMLHttpRequest();
xhr.open("POST", "Reporting.jsp"); //url.It can pdf file path
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.responseType = "blob";
xhr.onload = function () {
    if (this.status === 200) {
        var blob = new Blob([xhr.response]);
        const url = window.URL.createObjectURL(blob);
        var a = document.createElement('a');
        a.href = url;
        a.download = 'myFile.pdf';
        a.click();
        setTimeout(function () {
            // For Firefox it is necessary to delay revoking the ObjectURL
            window.URL.revokeObjectURL(data)
                , 100
        })
    }
};
xhr.send(data);

3
希望这能为你节省几个小时,避免头痛的困扰。 我花了一些时间才弄清楚这个问题,使用常规的$.ajax()请求会破坏我的PDF文件,而通过地址栏请求则完美运行。 解决方案如下:
引入download.js:https://www.npmjs.com/package/downloadjs 然后使用XMLHttpRequest替代$.ajax()请求。
    var ajax = new XMLHttpRequest(); 
    
    ajax.open("GET", '/Admin/GetPdf' + id, true); 
    ajax.onreadystatechange = function(data) { 
        if (this.readyState == 4)
        {
            if (this.status == 200)
            {
                download(this.response, "report.pdf", "application/pdf");

            }
            else if (this.responseText != "")
            {
                alert(this.responseText);
            }
        }
        else if (this.readyState == 2)
        {
            if (this.status == 200)
            {
                this.responseType = "blob";
            }
            else
            {
                this.responseType = "text";
            }
        }
    };

    ajax.send(null);

1
我花了几个小时在同一个问题上。PDF字体无法正确加载。谢谢! - Blodhgard
链接已经损坏。 - BWhite

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