通过jQuery.Ajax下载文件
我有一个服务器端的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中,我看到数据是与二进制stream检索。 我不知道如何打开文件下载窗口 ,用户可以将文件保存在本地?
Bluish是完全正确的,因为JavaScript无法将文件直接保存到用户计算机(出于安全考虑),所以无法通过Ajax来完成。 不幸的是,将主窗口的 URL指向您的文件下载意味着您无法控制文件下载时的用户体验。
我创build了jQuery文件下载 ,它允许在文件下载中使用OnSuccess和OnFailurecallback进行“类似Ajax”的体验,以提供更好的用户体验。 看看我的博客文章的插件解决这个常见问题,以及使用它的一些方法,也是一个演示jQuery文件下载的行动 。 这是来源
这是一个简单的用例演示,使用带有promise的插件源代码 。 演示页面还包括许多其他“更好的用户体验”示例。
$.fileDownload('some/file.pdf') .done(function () { alert('File download a success!'); }) .fail(function () { alert('File download failed!'); });
根据您需要支持的浏览器,您可以使用https://github.com/eligrey/FileSaver.js/ ,它允许比jQuery File Download使用的IFRAME方法更明确的控制。
没有人张贴这个@ Pekka的解决scheme …所以我会张贴它。 它可以帮助某人。
你不能也不需要通过Ajax来做到这一点。 只是使用
window.location="download.action?para1=value1...."
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框架:动作下载文件作为附件
<!-- with JS --> <a href="javascript:window.location='downloadAction.action?param1=value1'"> download </a> <!-- without JS --> <a href="downloadAction.action?param1=value1" >download</a>
使用<s:a>
标记指向OGNL到使用<s:url>
标记创build的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>
在上述情况下,您需要将Content-Disposition标头写入响应 ,指定文件需要下载( attachment
),而不是由浏览器打开( inline
)。 您还需要指定内容types ,并且您可能需要添加文件名称和长度(以帮助浏览器绘制逼真的进度条)。
例如,下载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使用,例如直接stream式传输 ),则无需直接向响应写入任何内容。 只需使用Stream结果types并在struts.xml中对其进行configuration即可: 示例
<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必须设置为内联 ,但目标不能是当前的窗口位置; 您必须定位一个由JavaScript创build的新窗口,页面中的<iframe>
,或与“讨论过的”target =“_ blank”实时创build的新窗口:
<!-- 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>
我创build了一个小function的解决scheme(受@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'); });
你可以用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从这里被拿走,并且必须按照这个函数来使用
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的后备
好的,基于ndpu的代码inheritance人改进(我认为)版本的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作为正确的后参数发送,就像来自input,而不是像前面的例子中的json编码string一样。
CAVEAT:对这些forms的可变注射潜力保持警惕。 对这些variables进行编码可能更安全。 或者考虑逃避他们。
我面临同样的问题,并成功解决了这个问题。 我的用例是这样的。
“将JSON数据发布到服务器并接收一个excel文件,这个excel文件是由服务器创build的,并作为对客户端的响应返回,将该响应作为一个带有自定义名称的文件在浏览器中下载 ”
$("#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(二进制)后,我们将创build一个可下载的URL并将其附加到不可见的“a”链接,然后单击它。 我在这里做了一个POST请求。 相反,你也可以去做一个简单的GET。 我们不能通过Ajax下载文件,必须使用XMLHttpRequest。
这里我们需要在服务器端仔细设置一些东西。 我在Python Django HttpResponse中设置了一些头文件。 如果您使用其他编程语言,则需要相应地设置它们。
# In python django code response = HttpResponse(file_content, content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
由于我在这里下载了xls(excel),所以我将contentType调整到了一个以上。 您需要根据您的文件types进行设置。 您可以使用这种技术来下载任何types的文件。
function downloadURI(uri, name) { var link = document.createElement("a"); link.download = name; link.href = uri; link.click(); }
这是我做的,纯JavaScript和HTML。 没有testing,但这应该适用于所有浏览器。
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"); });
只使用所有浏览器支持的组件,不需要额外的库。
这里是我的服务器端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(); } } }
在上面的答案中添加更多的东西来下载文件
下面是一些生成字节数组的java代码
@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);
以上将下载文件
好的,这里是使用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); }
然后你就可以打电话给你的控制器下载并获得“成功”或“失败”callback
$.fileDownload(mvcUrl('name of the controller'), { httpMethod: 'POST', successCallback: function (url) { //insert success code }, failCallback: function (html, url) { //insert fail code } });
让浏览器下载一个文件的简单方法就是这样做:
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(); }
这会打开浏览器下载popup。
在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
如果你想使用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()