处理从ajax文件下载文件
我有一个JavaScript应用程序发送一个HTTP请求到一个特定的URL。 响应可能是一个JSONstring,也可能是一个文件(作为附件)。 我可以在我的ajax调用中轻松检测Content-Type和Content-Disposition,但是一旦我检测到响应包含文件,我该如何提供客户端来下载它? 我已经阅读了许多类似的线程,但没有一个提供了我正在寻找的答案。
请,请不要发布答案,build议我不应该使用Ajax的,或者我应该redirect浏览器,因为这是没有一个选项。 使用纯HTML格式也不是一个选项。 我需要的是向客户端显示一个下载对话框。 可以这样做,怎么样?
编辑:
显然,这是不能做到的,但是有一个简单的解决方法,正如接受的答案所build议的那样。 对于将来遇到这个问题的人来说,我是这样解决的:
$.ajax({ type: "POST", url: url, data: params, success: function(response, status, request) { var disp = request.getResponseHeader('Content-Disposition'); if (disp && disp.search('attachment') != -1) { var form = $('<form method="POST" action="' + url + '">'); $.each(params, function(k, v) { form.append($('<input type="hidden" name="' + k + '" value="' + v + '">')); }); $('body').append(form); form.submit(); } } });
所以基本上,只需要生成一个HTML表单,其中使用了AJAX请求中使用的相同的参数并提交。
创build一个表单,使用POST方法,提交表单 – 不需要iframe。 当服务器页面响应请求时,为该文件的MIMEtypes编写一个响应头,它将显示一个下载对话框 – 我已经做了很多次。
您需要内容types的应用程序/下载 – 只需search如何为您使用的任何语言提供下载。
不要放弃这么快,因为这可以使用FileAPI的一部分完成(在现代浏览器中):
编辑2017-09-28:更新到使用文件构造函数时,所以它在Safari> = 10.1。
编辑2015-10-16:jQuery ajax无法正确处理二进制响应(无法设置responseType),所以最好使用纯XMLHttpRequest调用。
var xhr = new XMLHttpRequest(); xhr.open('POST', url, true); xhr.responseType = 'arraybuffer'; xhr.onload = function () { if (this.status === 200) { 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 = typeof File === 'function' ? new File([this.response], filename, { type: type }) : new Blob([this.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 } } }; xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); xhr.send($.param(params));
这是使用jQuery.ajax的旧版本。 当响应被转换成一些string时,它可能会破坏二进制数据。
$.ajax({ type: "POST", url: url, data: params, success: function(response, status, xhr) { // check for a filename 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 } } });
你使用什么服务器端语言? 在我的应用程序中,我可以通过在PHP响应中设置正确的标头,轻松地从AJAX调用中下载文件:
设置标题服务器端
header("HTTP/1.1 200 OK"); header("Pragma: public"); header("Cache-Control: must-revalidate, post-check=0, pre-check=0"); // The optional second 'replace' parameter indicates whether the header // should replace a previous similar header, or add a second header of // the same type. By default it will replace, but if you pass in FALSE // as the second argument you can force multiple headers of the same type. header("Cache-Control: private", false); header("Content-type: " . $mimeType); // $strFileName is, of course, the filename of the file being downloaded. // This won't have to be the same name as the actual file. header("Content-Disposition: attachment; filename=\"{$strFileName}\""); header("Content-Transfer-Encoding: binary"); header("Content-Length: " . mb_strlen($strFile)); // $strFile is a binary representation of the file that is being downloaded. echo $strFile;
这实际上是将浏览器“redirect”到这个下载页面,但是@ahren alread在他的评论中表示,它不会离开当前页面。
这是关于设置正确的标题,所以我相信你会find一个合适的解决scheme,你正在使用的服务器端语言,如果它不是PHP。
处理响应客户端
假设你已经知道如何进行AJAX调用,在客户端你执行一个AJAX请求到服务器。 服务器然后生成一个链接,从这个文件可以下载的地方,例如,你要指向的“前进”的URL。 例如,服务器响应:
{ status: 1, // ok // unique one-time download token, not required of course message: 'http://yourwebsite.com/getdownload/ska08912dsa' }
当处理响应时,你在你的body中注入一个iframe
,并将iframe
的SRC设置为你刚才收到的URL(为了方便本例,使用jQuery):
$("body").append("<iframe src='" + data.message + "' style='display: none;' ></iframe>");
如果您已经如上所示设置了正确的标题,则iframe将强制下载对话框,而无需浏览器离开当前页面。
注意
关于你的问题额外增加; 我认为最好总是返回JSON时,请求与AJAX技术的东西。 在收到JSON响应之后,您可以决定客户端如何处理它。 也许,例如,稍后您希望用户单击URL的下载链接,而不是直接强制下载,在当前的设置中,您必须更新客户端和服务器端才能这样做。
我面临同样的问题,并成功解决了这个问题。 我的用例是这样的。
“将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”链接,然后单击它。
这里我们需要在服务器端仔细设置一些东西。 我在Python Django HttpResponse中设置了一些头文件。 如果您使用其他编程语言,则需要相应地设置它们。
# In python django code response = HttpResponse(file_content, content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
由于我在这里下载了xls(excel),所以我将contentType调整到了一个以上。 您需要根据您的文件types进行设置。 您可以使用这种技术来下载任何types的文件。
我看到你已经find了一个解决scheme,但是我只是想添加一些信息,可能会帮助有人试图用大的POST请求来实现同样的事情。
几周前我也遇到了同样的问题,事实上,通过AJAX无法实现“干净”的下载,Filament Group创build了一个jQuery插件,它的工作原理就是您已经知道的,它被称为jQuery File下载但是这个技术有一个缺点。
如果您通过AJAX发送大量请求(比如文件+ 1MB),则会对响应速度产生负面影响。 在缓慢的互联网连接中,您必须等待很长时间直到发送请求,并等待文件下载。 它不是像即时的“点击”=>“popup”=>“下载开始”。 这更像是“点击”=>“等到数据发送”=>“等待响应”=>“下载开始”,这使得它出现文件的两倍,因为你必须等待请求发送通过AJAX并将其作为可下载文件取回。
如果您正在处理小于1MB的小文件,则不会注意到这一点。 但正如我在我自己的应用程序中发现的,对于更大的文件大小,它几乎是无法忍受的。
我的应用程序允许用户导出dynamic生成的图像,这些图像通过POST请求以base64格式发送到服务器(这是唯一可能的方式),然后处理并以.png,.jpg文件,base64forms发回给用户图像+ 1MB的string是巨大的,这迫使用户等待超过必要的文件开始下载。 在缓慢的互联网连接,它可以真的很烦人。
我的解决scheme是临时写入文件到服务器,一旦准备就绪,dynamic生成一个buttonforms的文件链接,在“Please wait …”和“Download”状态之间进行切换时间,在预览popup窗口中打印base64图像,以便用户可以“右键单击”并保存。 这使得所有的等待时间对于用户来说都是可以忍受的,并且也加快了速度。
更新2014年9月30日:
几个月过去了,我发布了这个,终于find了一个更好的方法来加速大的base64string的工作。 我现在将base64string存储到数据库中(使用longtext或longblog字段),然后通过jQuery File Download传递其loggingID,最后在下载脚本文件中,使用此ID查询数据库以拉取base64string并将其传递下载function。
下载脚本示例:
<?php // Record ID $downloadID = (int)$_POST['id']; // Query Data (this example uses CodeIgniter) $data = $CI->MyQueries->GetDownload( $downloadID ); // base64 tags are replaced by [removed], so we strip them out $base64 = base64_decode( preg_replace('#\[removed\]#', '', $data[0]->image) ); // This example is for base64 images $imgsize = getimagesize( $base64 ); // Set content headers header('Content-Disposition: attachment; filename="my-file.png"'); header('Content-type: '.$imgsize['mime']); // Force download echo $base64; ?>
我知道这是远远超出OP所要求的,但是我觉得用我的发现来更新我的答案会很好。 当我在为我的问题寻找解决scheme时,我阅读了很多“从AJAX POST数据下载”的线程,这些线程并没有给我我正在寻找的答案,我希望这些信息可以帮助那些希望实现这样的目标的人。
对于那些从Angularangular度寻找解决scheme的人来说,这对我来说很合适:
$http.post( 'url', {}, {responseType: 'arraybuffer'} ).then(function (response) { var headers = response.headers(); var blob = new Blob([response.data],{type:headers['content-type']}); var link = document.createElement('a'); link.href = window.URL.createObjectURL(blob); link.download = "Filename"; link.click(); });
我想指出在接受的答案中使用该技术时出现的一些困难,即使用表格post:
-
您不能在请求上设置标题。 如果您的身份validation模式涉及到头,Json-Web-Token传递到授权头中,则必须find其他方式将其发送,例如作为查询参数。
-
您无法确定请求何时完成。 那么,你可以使用一个cookie来响应,就像jquery.fileDownload一样 ,但是它是完美的。 它不适用于并发请求,如果响应永远不会到来,将会中断。
-
如果服务器响应错误,用户将被redirect到错误页面。
-
您只能使用表单支持的内容types。 这意味着你不能使用JSON。
我最终使用了在S3上保存文件的方法,并发送一个预先签名的URL来获取文件。
正如其他人所说,您可以创build并提交表单,通过POST请求下载。 但是,您不必手动执行此操作。
一个真正简单的库正是为了这个jquery.redirect 。 它提供了一个类似于标准jQuery.post
方法的API:
$.redirect(url, [values, [method, [target]]])
这是一个3岁的问题,但今天我也有同样的问题。 我看了你编辑的解决scheme,但我认为它可以牺牲性能,因为它必须提出双重要求。 所以,如果任何人需要另一个解决scheme,并不意味着两次调用服务,那么这是我做到的:
<form id="export-csv-form" method="POST" action="/the/path/to/file"> <input type="hidden" name="anyValueToPassTheServer" value=""> </form>
这种forms只是用来调用服务,并避免使用window.location()。 之后,你只需要从jquery提交表单提交,以便调用服务并获取文件。 这很简单,但这样你可以使用POST进行下载。 我现在说,如果你打电话的服务是一个GET ,这可能会更容易,但这不是我的情况。
这是我如何得到这个工作https://stackoverflow.com/a/27563953/2845977
$.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(); } });
我用这个FileSaver.js 。 在我的情况下与csv文件,我做了这个(在coffescript):
$.ajax url: "url-to-server" data: "data-to-send" success: (csvData)-> blob = new Blob([csvData], { type: 'text/csv' }) saveAs(blob, "filename.csv")
我认为对于最复杂的情况,数据必须正确处理。 在FileSaver.js的引擎下实现了Jonathan Amend的答案相同的方法。
见: http : //www.henryalgus.com/reading-binary-files-using-jquery-ajax/它会返回一个blob作为响应,然后可以放入文件pipe理器
这是我的解决scheme,使用临时隐藏的窗体。
//Create an hidden form var form = $('<form>', {'method': 'POST', 'action': this.href}).hide(); //Add params var params = { ...your params... }; $.each(params, function (k, v) { form.append($('<input>', {'type': 'hidden', 'name': k, 'value': v})); }); //Make it part of the document and submit $('body').append(form); form.submit(); //Clean up form.remove();
请注意,我大量使用JQuery,但是您也可以使用本机JS。