Загрузите файл с помощью jQuery.Аякс
У меня есть действие 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 я вижу, что данные извлекаются с помощью двоичного потока . Интересно, как открыть окно загрузки файла , с помощью которого пользователь может сохранить файл локально?
16 ответов:
Bluish абсолютно прав, вы не можете сделать это через Ajax, потому что JavaScript не может сохранять файлы непосредственно на компьютер пользователя (из соображений безопасности). К сожалению, указание URL-адресаглавного окна При загрузке файла означает, что у вас мало контроля над тем, что происходит при загрузке файла.
Я создал jQuery File Download, который позволяет использовать "Ajax-подобный" опыт загрузки файлов в комплекте с OnSuccess и OnFailure обратные вызовы для обеспечения лучшего пользовательского опыта. Взгляните на мой пост в блоге об общей проблеме, которую решает плагин, и о некоторых способах ее использования, а также на демонстрацию загрузки файла jQuery в действии. Вот источник
Вот простой пример использования демо - версии с использованием плагинаsource с обещаниями. Демонстрационная страница включает также множество других примеров "лучшего UX".
$.fileDownload('some/file.pdf') .done(function () { alert('File download a success!'); }) .fail(function () { alert('File download failed!'); });В зависимости от того, какие браузеры вам нужны для поддержки возможно, удастся использовать https://github.com/eligrey/FileSaver.js/, который позволяет более явное управление, чем метод IFRAME, используемый jQuery File Download.
никто не опубликовал это решение @Pekka ... так что я его опубликую. Это может кому-то помочь.
Вы не можете и не должны делать это через Ajax. Просто используйте
window.location="download.action?para1=value1...."
Вы можете использовать HTML5
NB: возвращаемые файловые данные должны быть закодированы в 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
1. Фреймворк agnostic: сервлет загружает файл как вложение
<!-- with JS --> <a href="javascript:window.location='downloadServlet?param1=value1'"> download </a> <!-- without JS --> <a href="downloadServlet?param1=value1" >download</a>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>Было бы лучше использовать
<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>В приведенных выше случаях вам необходимо записать заголовок Content-Disposition в ответ , указав, что файл должен быть загружен (
attachment) и не открывается браузером (inline). Вам Нужно также указать тип контента , и вы можете добавить имя файла и длину (чтобы помочь браузеру нарисовать реалистичную панель прогресса).Например, при загрузке 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 (если вы не используете действие в качестве сервлета, например, hack для прямой потоковой передачи ), вам не нужно ничего напрямую записывать в ответ; просто используйте поток тип результата и настройка его в стойках.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. Фреймворк agnostic (/Struts2 framework): сервлет (/Action) открывающий файл внутри браузера
Если вы хотите открыть файл в браузере, вместо того, чтобы загружать его, Content-disposition должен быть установлен в inline , но целевым объектом не может быть текущее местоположение окна; вы должны выбрать новое окно, созданное javascript,
<iframe>на странице или новое окно, созданное на лету с" обсуждаемой "целью= "_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>
Я создал небольшую функцию в качестве обходного решения (вдохновленный плагином @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(); }Демонстрация с событием click:
$('#someid').on('click', function() { ajax_download('/download.action', {'para1': 1, 'para2': 2}, 'dataname'); });
ОК, на основе кода 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}); });Парамы передаются как правильные post-парамы, как если бы они исходили из входных данных, а не как кодированная строка json, как в предыдущем примере.
Предостережение: будьте осторожны с потенциалом для переменной инъекции в эти формы. Возможно, существует более безопасный способ кодирования этих переменных. В качестве альтернативы подумайте о побеге от них.
Я столкнулся с той же проблемой и успешно ее решил. Мой пример использования таков.
"отправьте данные 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)); });Приведенный выше фрагмент просто делает следующее
- отправка массива в виде JSON на сервер с помощью XMLHttpRequest.
- после извлечения содержимого в виде двоичного объекта(blob), мы вы создаете загружаемый URL-адрес и прикрепляете его к невидимой ссылке "а", а затем нажимаете на нее. Я сделал запрос на почту здесь. Вместо этого, вы можете пойти на простой получить тоже. Мы не можем загрузить файл через Ajax, должны использовать XMLHttpRequest.
Здесь нам нужно тщательно установить несколько вещей на стороне сервера. Я установил несколько заголовков в Python Django HttpResponse. Вы должны установить их соответствующим образом, если вы используете другие языки программирования.
# In python django code response = HttpResponse(file_content, content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")Поскольку я загружаю xls (excel) здесь, я настроил contentType в выше. Вы должны установить его в соответствии с вашим типом файла. Вы можете использовать эту технику для загрузки любых файлов.
Простой способ заставить браузер загрузить файл - это сделать запрос следующим образом:
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(); }Откроется всплывающее окно загрузки браузера.
Вот что я сделал, чистый 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"); });Использование только тех компонентов, которые поддерживаются во всех браузерах без дополнительных библиотеки.
Вот мой серверный код Java Spring controller.
@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 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); }Теперь в коде javascript с помощью FileSaver.js, может скачать файл с кодом ниже
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);Выше будет загружен файл
function downloadURI(uri, name) { var link = document.createElement("a"); link.download = name; link.href = uri; link.click(); }
В 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)); } }); }Фокус в окне .место Часть. Метод контроллера выглядит следующим образом:
# GET /files/{:id}/download_file/ def download_file send_file(@file.file, :disposition => 'attachment', :url_based_filename => false) end
Итак, вот рабочий код при использовании MVC, и вы получаете файл от контроллера
Допустим, у вас есть свой байтовый массив declare и populate, единственное, что вам нужно сделать, это использовать функцию File (using System.Сеть.Mvc)
byte[] bytes = .... insert your bytes in the array return File(bytes, System.Net.Mime.MediaTypeNames.Application.Octet, "nameoffile.exe");И затем, в том же контроллере, добавьте thoses 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); }И тогда вы сможете вызвать свой контроллер для загрузки и получить обратный вызов" success "или" failure "
$.fileDownload(mvcUrl('name of the controller'), { httpMethod: 'POST', successCallback: function (url) { //insert success code }, failCallback: function (html, url) { //insert fail code } });
Если вы хотите использовать загрузку файлов 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()
Я нашел исправление, которое, хотя на самом деле не использует 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>Я думаю, что есть способ читать get data с помощью js, так что никакой php не понадобится. но я не знаю его с руки, и сервер, который я использую, поддерживает php, так что это работает для меня. я подумал, что поделюсь им, если это кому-то поможет.
Несомненно, что вы не можете сделать это через вызов Ajax.
Однако существует обходной путь.
Шаги:
Если вы используете форму.submit () для загрузки файла вы можете сделать следующее:
- создайте вызов ajax от клиента к серверу и сохраните файловый поток внутри сеанса.
- после того, как" success " будет возвращен с сервера, вызовите свою форму.submit() просто передает поток файлов, сохраненный в сеансе.
Это полезно в случае, когда вы хотите решить, нужно ли загружать файл после создания формы.submit (), например: может быть случай, когда на форме.submit (), исключение происходит на стороне сервера, и вместо сбоя может потребоваться показать пользовательское сообщение на стороне клиента, в таком случае эта реализация может помочь.


Comments