Download a file by jQuery.Ajax
我在服务器端有一个Struts2动作用于文件下载。
1 2 3 4 5 6 7 | <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调用动作时:
1 2 3 4 5 6 7 8 9 | $.post( "/download.action",{ para1:value1, para2:value2 .... },function(data){ console.info(data); } ); |
在Firebug中我看到使用二进制流检索数据。 我想知道如何打开用户可以在本地保存文件的文件下载窗口?
2019年现代浏览器更新
这是我现在推荐的方法,但有一些注意事项:
- 需要一个相对现代的浏览器
- 如果文件预计会非常大,您应该做一些类似于原始方法(iframe和cookie)的事情,因为以下某些操作可能会消耗系统内存,至少与正在下载的文件和/或其他有趣的CPU一样大副作用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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原始jQuery / iframe / Cookie方法
Bluish完全正确,你无法通过Ajax来实现,因为JavaScript无法将文件直接保存到用户的计算机上(出于安全考虑)。不幸的是,将主窗口的URL指向文件下载意味着您??几乎无法控制文件下载时的用户体验。
我创建了jQuery文件下载,它允许使用OnSuccess和OnFailure回调完成文件下载的"类似Ajax"体验,以提供更好的用户体验。看看我的博客文章,了解插件解决的常见问题以及使用它的一些方法,以及jQuery文件下载的演示。这是来源
这是一个使用带有promises的插件源的简单用例演示。该演示页面还包含许多其他"更好的用户体验"示例。
1 2 3 | $.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方法更明确的控制。
没有人发布这个@Pekka的解决方案......所以我会发布它。它可以帮助某人。
您不需要通过Ajax执行此操作。只是用
1 | window.location="download.action?para1=value1...." |
你可以使用HTML5
注意:返回的文件数据必须是base64编码的,因为你不能JSON编码二进制数据
在我的
1 2 3 4 5 6 7 8 | { 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保存文件
1 2 3 4 5 6 7 8 9 10 | 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取自此处,必须按照此函数使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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后备
1.框架不可知:Servlet下载文件作为附件
1 2 3 4 5 6 7 | <!-- with JS --> download <!-- without JS --> download |
2. Struts2 Framework:动作下载文件作为附件
1 2 3 4 5 6 7 | <!-- with JS --> download <!-- without JS --> download |
最好使用带有OGNL的
1 2 3 4 5 | <!-- 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> |
在上述情况下,您需要将Content-Disposition标头写入响应,指定需要下载文件(
例如,下载ZIP时:
1 2 3 4 | 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,例如直接流式传输),您不需要直接向响应写入任何内容;只需使用Stream结果类型并在struts.xml中配置它就可以了:例子
1 2 3 4 5 | <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.框架不可知(/ Struts2框架):Servlet(/ Action)在浏览器内打开文件
如果要在浏览器中打开文件而不是下载它,则必须将Content-disposition设置为inline,但目标不能是当前窗口位置;你必须定位一个由javascript创建的新窗口,页面中的
1 2 3 4 5 6 7 8 9 10 11 12 13 | <!-- From a parent page into an IFrame without javascript --> download <!-- In a new window without javascript --> download <!-- In a new window with javascript --> download |
我创建了一个小功能作为解决方案解决方案(受@JohnCulviner插件启发):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | // 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(); } |
点击事件演示:
1 2 3 | $('#someid').on('click', function() { ajax_download('/download.action', {'para1': 1, 'para2': 2}, 'dataname'); }); |
使浏览器下载文件的简单方法是发出如下请求:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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(); } |
这将打开浏览器下载弹出窗口。
好的,基于ndpu的代码继承了ajax_download的改进版(我认为); -
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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(); } |
像这样用它; -
1 2 3 | $('#someid').on('click', function() { ajax_download('/download.action', {'para1': 1, 'para2': 2}); }); |
params作为正确的post params发送,好像来自输入而不是像前面的例子那样作为json编码的字符串。
CAVEAT:对这些形式的可变注射潜力保持警惕。可能有一种更安全的方法来编码这些变量。或者考虑逃避它们。
我遇到了同样的问题并成功解决了它。我的用例是这样的。
"将JSON数据发布到服务器并接收excel文件。
该excel文件由服务器创建,并作为对客户端的响应返回。在浏览器中将该响应下载为具有自定义名称的文件"
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | $("#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中设置了几个标题。如果使用其他编程语言,则需要相应地设置它们。
1 2 | # In python django code response = HttpResponse(file_content, content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") |
由于我在这里下载xls(excel),我将contentType调整为高于1。您需要根据文件类型进行设置。您可以使用此技术下载任何类型的文件。
这是我做的,纯粹的javascript和HTML。没有测试它,但这应该适用于所有浏览器。
Javascript Function
1 2 3 4 5 6 7 8 | 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.
Here is my server side JAVA Spring controller code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | @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(); } } } |
1 2 3 4 5 6 7 | function downloadURI(uri, name) { var link = document.createElement("a"); link.download = name; link.href = uri; link.click(); } |
在Rails中,我这样做:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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部分。控制器的方法如下:
1 2 3 4 5 6 | # GET /files/{:id}/download_file/ def download_file send_file(@file.file, :disposition => 'attachment', :url_based_filename => false) end |
在上面的答案中添加更多内容以下载文件
下面是一些生成字节数组的java spring代码
1 2 3 4 5 6 7 8 9 10 11 12 | @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代码中,可以下载带有以下代码的文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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); |
以上将下载文件
好的,这是使用MVC时的工作代码,你从控制器获取文件
假设你有你的字节数组声明和填充,你唯一需要做的就是使用File函数(使用System.Web.Mvc)
1 2 | byte[] bytes = .... insert your bytes in the array return File(bytes, System.Net.Mime.MediaTypeNames.Application.Octet,"nameoffile.exe"); |
然后,在同一个控制器中添加2个函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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); } |
然后你就可以打电话给你的控制器下载并获得"成功"或"失败"的回调
1 2 3 4 5 6 7 8 9 10 | $.fileDownload(mvcUrl('name of the controller'), { httpMethod: 'POST', successCallback: function (url) { //insert success code }, failCallback: function (html, url) { //insert fail code } }); |
还有另一种在ajax中下载网页的解决方案。但我指的是必须首先处理然后下载的页面。
首先,您需要将页面处理与结果下载分开。
1)仅在ajax调用中进行页面计算。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | $.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"); ... |
我希望这个解决方案可以对许多人有用,就像我一样。
如果你想使用jQuery文件下载,请注意这个IE。
您需要重置响应,否则将无法下载
1 2 3 4 5 | //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 |
您的操作可以实现
我找到了一个修复,虽然它实际上并没有使用ajax,但它确实允许你使用javascript调用来请求下载,然后在下载实际开始时获得回调。我发现这有用,如果链接运行服务器端脚本,在发送之前需要一点点来编写文件。所以你可以提醒他们正在处理它,然后当它最终发送文件时删除该处理通知。这就是为什么我想尝试通过ajax加载文件开始,以便我可以在请求文件时发生事件,而在实际开始下载时发生另一个事件。
首页上的js
1 2 3 4 5 6 7 8 9 | 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
1 2 | <img src="loader.gif">Generating Report <iframe id="exportif" src="" style="width: 1px;height: 1px; border:0px;"></iframe> |
那么另一个文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <!DOCTYPE html> <html> <head> function expdone() { window.parent.expdone(); } </head> <body> <iframe id="exportif" src="<?php echo"http://10.192.37.211/npdtracker/exportthismonth.php?arguments=".$_GET["arguments"]; ?>"></iframe> document.getElementById('exportif').onload= expdone; </body></html> |
我认为有一种方法可以使用js读取数据,因此不需要php。但我不知道它和我正在使用的服务器支持PHP,所以这对我有用。以为我会分享它,以防它帮助任何人。
可以肯定的是,你无法通过Ajax调用来实现。
但是,有一种解决方法。
脚步 :
如果您使用form.submit()下载文件,您可以做的是:
当你想要决定在生成form.submit()之后是否需要下载文件时,这很有用,例如:在form.submit()上可能存在一种情况,在服务器端发生异常而是崩溃时,您可能需要在客户端显示自定义消息,在这种情况下,此实现可能会有所帮助。
使用
例如,您可以将此行代码放在单击处理程序中:
1 | window.open('/file.txt', '_blank'); |
它将打开一个新选项卡(因为'_blank'窗口名称),该选项卡将打开URL。
你的服务器端代码也应该是这样的:
1 | res.set('Content-Disposition', 'attachment; filename=file.txt'); |
这样,浏览器应该提示用户将文件保存到磁盘,而不是只显示文件。它还会自动关闭刚刚打开的选项卡。
我很长一段时间都在努力解决这个问题。最后,这里建议的优雅外部图书馆帮助了我。