Javascript 通过 jQuery.Ajax 下载文件

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4545311/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 12:53:37  来源:igfitidea点击:

Download a file by jQuery.Ajax

javascriptjqueryajaxjspdownload

提问by hguser

I have a Struts2 action in the server side for file downloading.

我在服务器端有一个用于文件下载的 Struts2 操作。

<action name="download" class="com.xxx.DownAction">
    <result name="success" type="stream">
        <param name="contentType">text/plain</param>
        <param name="inputName">imageStream</param>
        <param name="contentDisposition">attachment;filename={fileName}</param>
        <param name="bufferSize">1024</param>
    </result>
</action>

However when I call the action using the jQuery:

但是,当我使用 jQuery 调用操作时:

$.post(
  "/download.action",{
    para1:value1,
    para2:value2
    ....
  },function(data){
      console.info(data);
   }
);

in Firebug I see the data is retrieved with the Binary stream. I wonder how to open the file downloading windowwith which the user can save the file locally?

在 Firebug 中,我看到使用二进制流检索数据。我想知道如何打开用户可以在本地保存文件的文件下载窗口

回答by John Culviner

2019 modern browsers update

2019 现代浏览器更新

This is the approach I'd now recommend with a few caveats:

这是我现在推荐的方法,但有一些注意事项:

  • A relatively modern browser is required
  • If the file is expected to be very largeyou should likely do something similar to the original approach (iframe and cookie) because some of the below operations could likely consume system memory at least as large as the file being downloaded and/or other interesting CPU side effects.
  • 需要一个相对现代的浏览器
  • 如果预计文件非常大,您可能应该执行类似于原始方法(iframe 和 cookie)的操作,因为以下某些操作可能消耗的系统内存至少与正在下载的文件和/或其他有趣的 CPU 一样大副作用。

fetch('https://jsonplaceholder.typicode.com/todos/1')
  .then(resp => resp.blob())
  .then(blob => {
    const url = window.URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.style.display = 'none';
    a.href = url;
    // the filename you want
    a.download = 'todo-1.json';
    document.body.appendChild(a);
    a.click();
    window.URL.revokeObjectURL(url);
    alert('your file has downloaded!'); // or you know, something with better UX...
  })
  .catch(() => alert('oh no!'));

2012 Original jQuery/iframe/Cookie based approach

2012 原始的基于 jQuery/iframe/Cookie 的方法

Bluishis completely right about this, you can't do it through Ajax because JavaScript cannot save files directly to a user's computer (out of security concerns). Unfortunately pointing the main window'sURL at your file download means you have little control over what the user experience is when a file download occurs.

Blueish在这方面完全正确,您不能通过 Ajax 做到这一点,因为 JavaScript 无法将文件直接保存到用户的计算机上(出于安全考虑)。不幸的是,将主窗口的URL指向您的文件下载意味着您​​几乎无法控制文件下载时的用户体验。

I created jQuery File Downloadwhich allows for an "Ajax like" experience with file downloads complete with OnSuccess and OnFailure callbacks to provide for a better user experience. Take a look at my blog poston the common problem that the plugin solves and some ways to use it and also a demo of jQuery File Download in action. Here is the source

我创建了jQuery 文件下载,它允许通过 OnSuccess 和 OnFailure 回调完成文件下载的“类似 Ajax”的体验,以提供更好的用户体验。看看我的博客文章,了解插件解决的常见问题和使用它的一些方法,以及jQuery 文件下载演示。这是来源

Here is a simple use case demo using the plugin sourcewith promises. The demo pageincludes many other, 'better UX' examples as well.

这是一个使用带有 promise的插件的简单用例演示。该演示页包含了许多其他“更好的UX”的例子也是如此。

$.fileDownload('some/file.pdf')
    .done(function () { alert('File download a success!'); })
    .fail(function () { alert('File download failed!'); });

Depending on what browsers you need to support you may be able to use https://github.com/eligrey/FileSaver.js/which allows more explicit control than the IFRAME method jQuery File Download uses.

根据您需要支持的浏览器,您可以使用https://github.com/eligrey/FileSaver.js/,它允许比 jQuery File Download 使用的 IFRAME 方法更明确的控制。

回答by bluish

Noone posted this @Pekka's solution... so I'll post it. It can help someone.

没有人发布这个@Pekka 的解决方案......所以我会发布它。它可以帮助某人。

You don't need to do this through Ajax. Just use

您不需要通过 Ajax 执行此操作。只需使用

window.location="download.action?para1=value1...."

回答by Luke Madhanga

You can with HTML5

你可以用 HTML5

NB: The file data returned MUST be base64 encoded because you cannot JSON encode binary data

注意:返回的文件数据必须是 base64 编码的,因为您不能对二进制数据进行 JSON 编码

In my AJAXresponse I have a data structure that looks like this:

在我的AJAX回复中,我有一个如下所示的数据结构:

{
    result: 'OK',
    download: {
        mimetype: string(mimetype in the form 'major/minor'),
        filename: string(the name of the file to download),
        data: base64(the binary data as base64 to download)
    }
}

That means that I can do the following to save a file via AJAX

这意味着我可以执行以下操作以通过 AJAX 保存文件

var a = document.createElement('a');
if (window.URL && window.Blob && ('download' in a) && window.atob) {
    // Do it the HTML5 compliant way
    var blob = base64ToBlob(result.download.data, result.download.mimetype);
    var url = window.URL.createObjectURL(blob);
    a.href = url;
    a.download = result.download.filename;
    a.click();
    window.URL.revokeObjectURL(url);
}

The function base64ToBlob was taken from hereand must be used in compliance with this function

函数 base64ToBlob 取自此处,必须按照此函数使用

function base64ToBlob(base64, mimetype, slicesize) {
    if (!window.atob || !window.Uint8Array) {
        // The current browser doesn't have the atob function. Cannot continue
        return null;
    }
    mimetype = mimetype || '';
    slicesize = slicesize || 512;
    var bytechars = atob(base64);
    var bytearrays = [];
    for (var offset = 0; offset < bytechars.length; offset += slicesize) {
        var slice = bytechars.slice(offset, offset + slicesize);
        var bytenums = new Array(slice.length);
        for (var i = 0; i < slice.length; i++) {
            bytenums[i] = slice.charCodeAt(i);
        }
        var bytearray = new Uint8Array(bytenums);
        bytearrays[bytearrays.length] = bytearray;
    }
    return new Blob(bytearrays, {type: mimetype});
};

This is good if your server is dumping filedata to be saved. However, I've not quite worked out how one would implement a HTML4 fallback

如果您的服务器正在转储要保存的文件数据,这很好。但是,我还没有完全弄清楚如何实现 HTML4 回退

回答by Andrea Ligios

1. Framework agnostic: Servlet downloading file as attachment

1. 框架不可知:Servlet 下载文件作为附件

<!-- with JS -->
<a href="javascript:window.location='downloadServlet?param1=value1'">
    download
</a>

<!-- without JS -->
<a href="downloadServlet?param1=value1" >download</a>

2. Struts2 Framework: Action downloading file as attachment

2. Struts2 Framework:动作下载文件作为附件

<!-- with JS -->
<a href="javascript:window.location='downloadAction.action?param1=value1'">
    download
</a>

<!-- without JS -->
<a href="downloadAction.action?param1=value1" >download</a>

It would be better to use <s:a>tag pointing with OGNLto an URLcreated with <s:url>tag:

使用<s:a>带有OGNL 的标记指向使用标记创建的URL会更好<s:url>

<!-- without JS, with Struts tags: THE RIGHT WAY -->    
<s:url action="downloadAction.action" var="url">
    <s:param name="param1">value1</s:param>
</s:ulr>
<s:a href="%{url}" >download</s:a>

In the above cases, you needto write the Content-Dispositionheader to the response, specifying that the file needs to be downloaded (attachment) and not opened by the browser (inline). You needto specify the Content Typetoo, and you may want to add the file name and length (to help the browser drawing a realistic progressbar).

在上述情况下,您需要Content-Disposition标头写入响应,指定文件需要下载 ( attachment) 而不是由浏览器打开 ( inline)。您还需要指定内容类型,并且您可能想要添加文件名和长度(以帮助浏览器绘制逼真的进度条)。

For example, when downloading a ZIP:

例如,下载 ZIP 时:

response.setContentType("application/zip");
response.addHeader("Content-Disposition", 
                   "attachment; filename=\"name of my file.zip\"");
response.setHeader("Content-Length", myFile.length()); // or myByte[].length...

With Struts2 (unless you are using the Action as a Servlet, an hack for direct streaming, for example), you don't need to directly write anything to the response; simply using the Stream result typeand configuring it in struts.xml will work: EXAMPLE

使用 Struts2(除非您将 Action 用作 Servlet,例如直接流式传输的 hack ),您不需要直接向响应写入任何内容;只需使用Stream 结果类型并在 struts.xml 中对其进行配置即可:示例

<result name="success" type="stream">
   <param name="contentType">application/zip</param>
   <param name="contentDisposition">attachment;filename="${fileName}"</param>
   <param name="contentLength">${fileLength}</param>
</result>

3. Framework agnostic (/ Struts2 framework): Servlet(/Action) opening file inside the browser

3.Framework agnostic(/Struts2框架):Servlet(/Action)在浏览器内打开文件

If you want to open the file inside the browser, instead of downloading it, the Content-dispositionmust be set to inline, but the target can't be the current window location; you must target a new window created by javascript, an <iframe>in the page, or a new window created on-the-fly with the "discussed" target="_blank":

如果要在浏览器中打开文件,而不是下载它,必须将Content-disposition设置为inline,但目标不能是当前窗口位置;您必须定位由 javascript 创建的新窗口、<iframe>页面中的an或使用“讨论”目标 =“_blank”即时创建的新窗口:

<!-- From a parent page into an IFrame without javascript -->   
<a href="downloadServlet?param1=value1" target="iFrameName">
    download
</a>

<!-- In a new window without javascript --> 
<a href="downloadServlet?param1=value1" target="_blank">
    download
</a>

<!-- In a new window with javascript -->    
<a href="javascript:window.open('downloadServlet?param1=value1');" >
    download
</a>

回答by Jo?o Marcos

The simple way to make the browser downloads a file is to make the request like that:

让浏览器下载文件的简单方法是发出这样的请求:

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

This opens the browser download pop up.

这将打开浏览器下载弹出窗口。

回答by ndpu

I have created little function as workaround solution (inspired by @JohnCulviner plugin):

我创建了很少的功能作为解决方案(灵感来自@JohnCulviner 插件):

// creates iframe and form in it with hidden field,
// then submit form with provided data
// url - form url
// data - data to form field
// input_name - form hidden input name

function ajax_download(url, data, input_name) {
    var $iframe,
        iframe_doc,
        iframe_html;

    if (($iframe = $('#download_iframe')).length === 0) {
        $iframe = $("<iframe id='download_iframe'" +
                    " style='display: none' src='about:blank'></iframe>"
                   ).appendTo("body");
    }

    iframe_doc = $iframe[0].contentWindow || $iframe[0].contentDocument;
    if (iframe_doc.document) {
        iframe_doc = iframe_doc.document;
    }

    iframe_html = "<html><head></head><body><form method='POST' action='" +
                  url +"'>" +
                  "<input type=hidden name='" + input_name + "' value='" +
                  JSON.stringify(data) +"'/></form>" +
                  "</body></html>";

    iframe_doc.open();
    iframe_doc.write(iframe_html);
    $(iframe_doc).find('form').submit();
}

Demo with click event:

带有点击事件的演示:

$('#someid').on('click', function() {
    ajax_download('/download.action', {'para1': 1, 'para2': 2}, 'dataname');
});

回答by Naren Yellavula

I faced the same issue and successfully solved it. My use-case is this.

我遇到了同样的问题并成功解决了它。我的用例是这样的。

"Post JSON data to the server and receive an excel file. That excel file is created by the server and returned as a response to the client. Download that response as a file with custom name in browser"

“ 将JSON 数据发布到服务器并接收一个 excel 文件。该 excel 文件由服务器创建并作为对客户端的响应返回。在浏览器中将该响应下载为具有自定义名称的文件

$("#my-button").on("click", function(){

// Data to post
data = {
    ids: [1, 2, 3, 4, 5]
};

// Use XMLHttpRequest instead of Jquery $ajax
xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
    var a;
    if (xhttp.readyState === 4 && xhttp.status === 200) {
        // Trick for making downloadable link
        a = document.createElement('a');
        a.href = window.URL.createObjectURL(xhttp.response);
        // Give filename you wish to download
        a.download = "test-file.xls";
        a.style.display = 'none';
        document.body.appendChild(a);
        a.click();
    }
};
// Post data to URL which handles post request
xhttp.open("POST", excelDownloadUrl);
xhttp.setRequestHeader("Content-Type", "application/json");
// You should set responseType as blob for binary responses
xhttp.responseType = 'blob';
xhttp.send(JSON.stringify(data));
});

The above snippet is just doing following

上面的代码片段只是在执行以下操作

  • Posting an array as JSON to the server using XMLHttpRequest.
  • After fetching content as a blob(binary), we are creating a downloadable URL and attaching it to invisible "a" link then clicking it. I did a POST request here. Instead, you can go for a simple GET too. We cannot download the file through Ajax, must use XMLHttpRequest.
  • 使用 XMLHttpRequest 将数组作为 JSON 发布到服务器。
  • 在将内容作为 blob(二进制)获取后,我们正在创建一个可下载的 URL 并将其附加到不可见的“a”链接,然后单击它。我在这里做了一个 POST 请求。相反,您也可以使用简单的 GET。我们不能通过 Ajax 下载文件,必须使用 XMLHttpRequest。

Here we need to carefully set few things on the server side. I set few headers in Python Django HttpResponse. You need to set them accordingly if you use other programming languages.

这里我们需要在服务器端仔细设置一些东西。我在 Python Django HttpResponse 中设置了几个标头。如果您使用其他编程语言,则需要相应地设置它们。

# In python django code
response = HttpResponse(file_content, content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")

Since I download xls(excel) here, I adjusted contentType to above one. You need to set it according to your file type. You can use this technique to download any kind of files.

由于我在这里下载了 xls(excel),因此我将 contentType 调整为高于 1。您需要根据您的文件类型进行设置。您可以使用此技术下载任何类型的文件。

回答by Shayne

Ok, based on ndpu's code heres an improved (I think) version of ajax_download;-

好的,基于 ndpu 的代码,这是 ajax_download 的改进(我认为)版本;-

function ajax_download(url, data) {
    var $iframe,
        iframe_doc,
        iframe_html;

    if (($iframe = $('#download_iframe')).length === 0) {
        $iframe = $("<iframe id='download_iframe'" +
                    " style='display: none' src='about:blank'></iframe>"
                   ).appendTo("body");
    }

    iframe_doc = $iframe[0].contentWindow || $iframe[0].contentDocument;
    if (iframe_doc.document) {
        iframe_doc = iframe_doc.document;
    }

    iframe_html = "<html><head></head><body><form method='POST' action='" +
                  url +"'>" 

    Object.keys(data).forEach(function(key){
        iframe_html += "<input type='hidden' name='"+key+"' value='"+data[key]+"'>";

    });

        iframe_html +="</form></body></html>";

    iframe_doc.open();
    iframe_doc.write(iframe_html);
    $(iframe_doc).find('form').submit();
}

Use this like this;-

像这样使用它;-

$('#someid').on('click', function() {
    ajax_download('/download.action', {'para1': 1, 'para2': 2});
});

The params are sent as proper post params as if coming from an input rather than as a json encoded string as per the previous example.

params 作为正确的 post params 发送,就好像来自输入一样,而不是像前面的例子那样作为 json 编码的字符串。

CAVEAT: Be wary about the potential for variable injection on those forms. There might be a safer way to encode those variables. Alternatively contemplate escaping them.

警告:警惕这些表格上可变注射的可能性。可能有一种更安全的方法来对这些变量进行编码。或者考虑逃避他们。

回答by manukyanv07

Here is what I did, pure javascript and html. Did not test it but this should work in all browsers.

这是我所做的,纯 javascript 和 html。没有测试,但这应该适用于所有浏览器。

Javascript Function

Javascript 函数

var iframe = document.createElement('iframe');
iframe.id = "IFRAMEID";
iframe.style.display = 'none';
document.body.appendChild(iframe);
iframe.src = 'SERVERURL'+'?' + $.param($scope.filtro);
iframe.addEventListener("load", function () {
     console.log("FILE LOAD DONE.. Download should start now");
});

Using just components that is supported in all browsers no additional libraries.

仅使用所有浏览器支持的组件,无需额外的库。

enter image description hereenter image description here

enter image description hereenter image description here

Here is my server side JAVA Spring controller code.

这是我的服务器端 JAVA Spring 控制器代码。

@RequestMapping(value = "/rootto/my/xlsx", method = RequestMethod.GET)
public void downloadExcelFile(@RequestParam(value = "param1", required = false) String param1,
    HttpServletRequest request, HttpServletResponse response)
            throws ParseException {

    Workbook wb = service.getWorkbook(param1);
    if (wb != null) {
        try {
            String fileName = "myfile_" + sdf.format(new Date());
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setHeader("Content-disposition", "attachment; filename=\"" + fileName + ".xlsx\"");
            wb.write(response.getOutputStream());
            response.getOutputStream().close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    }

回答by EL missaoui habib

function downloadURI(uri, name) 
{
    var link = document.createElement("a");
    link.download = name;
    link.href = uri;
    link.click();
}