首页 文章

通过jQuery.Ajax下载文件

提问于
浏览
347

我在服务器端有一个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>

但是当我使用jQuery调用动作时:

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

在Firebug中,我看到使用 Binary stream 检索数据 . 我想知道如何打开用户可以在本地保存文件的 file downloading window

16 回答

  • 598

    Bluish对此完全正确,你可以't do it through Ajax because JavaScript cannot save files directly to a user'的电脑(出于安全考虑) . 不幸的是,将主窗口的URL指向文件下载意味着您几乎无法控制文件下载时的用户体验 .

    我创建了jQuery File Download,它允许使用OnSuccess和OnFailure回调完成文件下载,以提供更好的用户体验 . 看看我的blog post关于插件解决的常见问题以及使用它的一些方法以及demo of jQuery File Download in action . 这是source

    这是一个简单的用例演示,使用带有promises的插件source . demo page还包括许多其他的'better UX'示例 .

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

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

  • 188

    没有人发布这个@Pekka's solution ...所以我会发布它 . 它可以帮助某人 .

    您不能也不需要通过Ajax执行此操作 . 只是用

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

    You can with HTML5

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

    在我的 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)
        }
    }
    

    这意味着我可以通过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);
    }
    

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

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

    如果您的服务器正在转储要保存的filedata,这很好 . 但是,我还没有弄清楚如何实现HTML4后备

  • 24

    1. Framework agnostic: Servlet downloading file as attachment

    <!-- 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

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

    最好将指向OGNL的 <s:a> 标记用于使用 <s:url> 标记创建的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>
    

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

    例如,下载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...
    

    使用Struts2(除非您使用Action作为Servlet,例如hack for direct streaming),您不需要直接向响应写入任何内容;只需使用Stream result type并在struts.xml中配置它就可以了:EXAMPLE

    <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

    如果要在浏览器中打开文件而不是下载它,则必须将Content-disposition设置为inline,但目标不能是当前窗口位置;你必须定位一个由javascript创建的新窗口,页面中的 <iframe> ,或者使用"discussed" target = "_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>
    
  • 0
    function downloadURI(uri, name) 
    {
        var link = document.createElement("a");
        link.download = name;
        link.href = uri;
        link.click();
    }
    
  • 21

    我创建了一个小功能作为解决方案解决方案(受@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();
    }
    

    点击事件演示:

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

    好的,基于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();
    }
    

    像这样使用这个; -

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

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

    CAVEAT:对这些形式的可变注射潜力持谨慎态度 . 可能有一种更安全的方法来编码这些变量 . 或者考虑逃避它们 .

  • 1

    使浏览器下载文件的简单方法是发出如下请求:

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

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

  • 15

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

    “将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));
    });
    

    上面的代码片段正在做以下事情

    • 使用XMLHttpRequest将数组作为JSON发布到服务器 .

    • 在将内容作为blob(二进制)获取后,我们创建了一个可下载的URL并将其附加到不可见的"a"链接然后单击它 . 我在这里做了一个POST请求 . 相反,你也可以进行简单的GET . 我们无法通过Ajax下载文件,必须使用XMLHttpRequest .

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

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

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

  • 4

    这是我做的,纯粹的javascript和HTML . 没有测试它,但这应该适用于所有浏览器 .

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

    仅使用所有浏览器支持的组件,不使用其他库 .

    enter image description here

    enter image description here

    这是我的服务器端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();
            }
        }
        }
    
  • 12

    在上面的答案中添加更多内容以下载文件

    下面是一些生成字节数组的java spring代码

    @RequestMapping(value = "/downloadReport", method = { RequestMethod.POST })
        public ResponseEntity<byte[]> downloadReport(
                @RequestBody final SomeObejct obj, HttpServletResponse response) throws Exception {
    
            OutputStream out = new ByteArrayOutputStream();
            // write something to output stream
            HttpHeaders respHeaders = new HttpHeaders();
            respHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            respHeaders.add("X-File-Name", name);
            ByteArrayOutputStream bos = (ByteArrayOutputStream) out;
            return new ResponseEntity<byte[]>(bos.toByteArray(), respHeaders, HttpStatus.CREATED);
        }
    

    现在使用FileSaver.js的javascript代码,可以下载带有以下代码的文件

    var json=angular.toJson("somejsobject");
    var url=apiEndPoint+'some url';
    var xhr = new XMLHttpRequest();
    //headers('X-File-Name')
    xhr.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 201) {
            var res = this.response;
            var fileName=this.getResponseHeader('X-File-Name');
            var data = new Blob([res]);
            saveAs(data, fileName); //this from FileSaver.js
        }
    }    
    xhr.open('POST', url);
    xhr.setRequestHeader('Authorization','Bearer ' + token);
    xhr.setRequestHeader('Content-Type', 'application/json');
    xhr.responseType = 'arraybuffer';
    xhr.send(json);
    

    以上将下载文件

  • 13

    在Rails中,我这样做:

    function download_file(file_id) {
      let url       = '/files/' + file_id + '/download_file';
        $.ajax({
        type: 'GET',
        url: url,
        processData: false,
        success: function (data) {
           window.location = url;
        },
        error: function (xhr) {
         console.log(' Error:  >>>> ' + JSON.stringify(xhr));
        }
       });
     }
    

    诀窍是window.location部分 . 控制器的方法如下:

    # GET /files/{:id}/download_file/
    def download_file
        send_file(@file.file,
              :disposition => 'attachment',
              :url_based_filename => false)
    end
    
  • 0

    好的,这是使用MVC时的工作代码,你从控制器获取文件

    假设你有你的字节数组声明和填充,你唯一需要做的就是使用File函数(使用System.Web.Mvc)

    byte[] bytes = .... insert your bytes in the array
    return File(bytes, System.Net.Mime.MediaTypeNames.Application.Octet, "nameoffile.exe");
    

    然后,在同一个控制器中,添加2个函数

    protected override void OnResultExecuting(ResultExecutingContext context)
        {
            CheckAndHandleFileResult(context);
    
            base.OnResultExecuting(context);
        }
    
        private const string FILE_DOWNLOAD_COOKIE_NAME = "fileDownload";
    
        /// <summary>
        /// If the current response is a FileResult (an MVC base class for files) then write a
        /// cookie to inform jquery.fileDownload that a successful file download has occured
        /// </summary>
        /// <param name="context"></param>
        private void CheckAndHandleFileResult(ResultExecutingContext context)
        {
            if (context.Result is FileResult)
                //jquery.fileDownload uses this cookie to determine that a file download has completed successfully
                Response.SetCookie(new HttpCookie(FILE_DOWNLOAD_COOKIE_NAME, "true") { Path = "/" });
            else
                //ensure that the cookie is removed in case someone did a file download without using jquery.fileDownload
                if (Request.Cookies[FILE_DOWNLOAD_COOKIE_NAME] != null)
                    Response.Cookies[FILE_DOWNLOAD_COOKIE_NAME].Expires = DateTime.Now.AddYears(-1);
        }
    

    然后你就可以打电话给你的控制器下载并获得“成功”或“失败”的回调

    $.fileDownload(mvcUrl('name of the controller'), {
                httpMethod: 'POST',
                successCallback: function (url) {
                //insert success code
    
                },
                failCallback: function (html, url) {
                //insert fail code
                }
            });
    
  • 3

    如果你想使用jQuery文件下载,请注意这个IE . 您需要重置响应,否则将无法下载

    //The IE will only work if you reset response
        getServletResponse().reset();
        //The jquery.fileDownload needs a cookie be set
        getServletResponse().setHeader("Set-Cookie", "fileDownload=true; path=/");
        //Do the reset of your action create InputStream and return
    

    您的操作可以实现 ServletResponseAware 来访问 getServletResponse()

  • 8

    我找到了一个修复,虽然它实际上并没有使用ajax,但它确实允许你使用javascript调用来请求下载,然后在下载实际开始时获得回调 . 我发现这有用,如果链接运行服务器端脚本,在发送之前需要一点点来编写文件 . 因此,您可以提醒他们正在处理,然后当它最终发送文件时删除该处理通知 . 这就是为什么我想尝试通过ajax加载文件开始,以便我可以在请求文件时发生事件,而在实际开始下载时发生另一个事件 .

    首页上的js

    function expdone()
    {
        document.getElementById('exportdiv').style.display='none';
    }
    function expgo()
    {
       document.getElementById('exportdiv').style.display='block';
       document.getElementById('exportif').src='test2.php?arguments=data';
    }
    

    iframe

    <div id="exportdiv" style="display:none;">
    <img src="loader.gif"><br><h1>Generating Report</h1>
    <iframe id="exportif" src="" style="width: 1px;height: 1px; border:0px;"></iframe>
    </div>
    

    那么另一个文件:

    <!DOCTYPE html>
    <html>
    <head>
    <script>
    function expdone()
    {
        window.parent.expdone();
    }
    </script>
    </head>
    <body>
    <iframe id="exportif" src="<?php echo "http://10.192.37.211/npdtracker/exportthismonth.php?arguments=".$_GET["arguments"]; ?>"></iframe>
    <script>document.getElementById('exportif').onload= expdone;</script>
    </body></html>
    

    我认为有一种方法可以使用js读取数据,因此不需要php . 但我不知道它和我正在使用的服务器支持PHP所以这对我有用 . 以为我会分享它,以防它帮助任何人 .

  • 0

    可以肯定的是,你无法通过Ajax调用来实现 .

    但是,有一种解决方法 .

    脚步 :

    如果您使用form.submit()下载文件,您可以做的是:

    • 从客户端到服务器创建ajax调用,并将文件流存储在会话中 .

    • 在从服务器返回"success"时,调用form.submit()以仅流式传输存储在会话中的文件流 .

    如果你想在make.submit()之后决定是否需要下载文件,这很有用,例如:在form.submit()上可能存在一种情况,在服务器端发生异常而是崩溃时,您可能需要在客户端显示自定义消息,在这种情况下,此实现可能会有所帮助 .

相关问题