У меня есть действие 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 Я вижу, что данные извлекаются с помощью Двоичного потока. Интересно, как открыть окно загрузки файлов, с помощью которого пользователь может локально сохранить файл?
Синеватый абсолютно прав, вы не можете сделать это через Ajax, потому что JavaScript не может сохранять файлы непосредственно на компьютер пользователя (из соображений безопасности). К сожалению, указав URL главного окна на загрузку файла, вы не имеете никакого контроля над тем, что происходит с пользователем при загрузке файла.
Я создал jQuery File Download, который позволяет использовать опыт Ajax с загрузкой файлов в комплекте с обратными вызовами OnSuccess и OnFailure для обеспечения лучшего Пользовательский опыт. Взгляните на мой сообщение в блоге об общей проблеме, которую решает плагин, и о некоторых способах ее использования, а также демонстрация файла загрузки jQuery в действии. Вот источник
Вот пример демонстрационного примера использования с плагином источник с promises. В демонстрационной странице есть и другие примеры лучшего UX.
$.fileDownload('some/file.pdf')
.done(function () { alert('File download a success!'); })
.fail(function () { alert('File download failed!'); });
В зависимости от того, какие браузеры вам нужны для поддержки, вы можете использовать /FileSaver.js, который позволяет более явный контроль, чем метод IFRAME jQuery File Загрузить использует.
Никто не отправил это решение @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
a.click()
не работает в Firefox ... Есть идеи?
a
в dom, чтобы этот код работал, и / или удалить часть revokeObjectURL
: document.body.appendChild(a)
1. Агентский атрибут: файл загрузки сервлета в качестве вложения
<!-- 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 (если вы не используете действие как сервлет, например, взлом для прямой потоковой передачи), вам не нужно напрямую писать что-либо в ответ; просто используя тип результата Stream и его настройку в struts.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. Агентский агент (/Struts2 framework): файл открытия сервлета (/действия) внутри браузера
Если вы хотите открыть файл внутри браузера, вместо того, чтобы его загружать, для Content-disposition должно быть установлено значение inline, но цель не может быть текущим местоположением окна; вы должны настроить таргетинг на новое окно, созданное javascript, <iframe>
на странице, или новое окно, созданное "на лету" с "обсуждаемым" 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>
Я создал небольшую функцию в качестве решения обхода (вдохновленный плагином @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});
});
Парамеры отправляются как правильные пост-параметры, как если бы они поступали из ввода, а не как кодированная json строка в соответствии с предыдущим примером.
CAVEAT: Будьте осторожны в отношении возможности переменной инъекции в этих формах. Может быть более безопасный способ кодирования этих переменных. Альтернативно, избегайте их.
Я столкнулся с той же проблемой и успешно ее разрешил. Мой прецедент - это.
" Опубликовать данные 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));
});
Вышеприведенный фрагмент просто выполняет следующие
Здесь нам нужно тщательно задать несколько вещей на стороне сервера. Я установил несколько заголовков в Python Django HttpResponse. Вы должны установить их соответственно, если используете другие языки программирования.
# In python django code
response = HttpResponse(file_content, content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
Так как я загружаю xls (excel) здесь, я скорректировал contentType выше одного. Вам нужно установить его в соответствии с типом файла. Вы можете использовать эту технику для загрузки любых файлов.
HTMLAnchorElement.download
, я думаю объединить его с проприетарным методом msSaveOrOpenBlob .
Простой способ заставить браузер загружать файл - это сделать такой запрос:
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 на стороне сервера.
@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();
}
}
}
function downloadURI(uri, name)
{
var link = document.createElement("a");
link.download = name;
link.href = uri;
link.click();
}
download
, поэтому ваш файл будет иметь имя «Неизвестно»
Добавление еще нескольких вещей в ответ для загрузки файла
Ниже приведен код java spring, который генерирует байт Array
@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);
Вышеупомянутый файл будет загружен
В 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
Итак, вот рабочий код при использовании MVC и вы получаете свой файл с контроллера
позволяет сказать, что ваш массив байтов объявляется и заполняется, единственное, что вам нужно сделать, это использовать функцию File (используя System.Web.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);
}
а затем вы сможете вызвать своего контроллера для загрузки и получить обратный вызов "успех" или "отказ"
$.fileDownload(mvcUrl('name of the controller'), {
httpMethod: 'POST',
successCallback: function (url) {
//insert success code
},
failCallback: function (html, url) {
//insert fail code
}
});
Несомненно, вы не можете сделать это через вызов Ajax.
Однако есть обходной путь.
Шаги:
Если вы используете form.submit() для загрузки файла, то вы можете сделать следующее:
Это полезно в том случае, если вы хотите решить, нужно ли загружать файл после создания form.submit(), например: может быть случай, когда в form.submit() исключение возникает на стороне сервера и вместо этого сбоя, вам может потребоваться показать пользовательское сообщение на стороне клиента, в этом случае эта реализация может помочь.
Я нашел исправление, что, хотя на самом деле он не использует ajax, он позволяет вам использовать вызов javascript для запроса загрузки, а затем получить обратный вызов, когда загрузка начнется. Я нашел это полезным, если ссылка работает на стороне сервера script, которая занимает немного времени, чтобы составить файл перед его отправкой. поэтому вы можете предупредить их о том, что он обрабатывается, а затем, когда он наконец отправит файл, удалите это уведомление об обработке. поэтому я хотел попытаться загрузить файл через ajax, чтобы начать с того, чтобы я мог произойти событие, когда файл запрашивается, а другой, когда он действительно начинает загрузку.
js на первой странице
функция expdone()
{ . Document.getElementById( 'exportdiv') style.display = 'ни';
}
функция expgo()
{ . Document.getElementById( 'exportdiv') style.display = 'блок'; document.getElementById( 'exportif') src= 'test2.php аргументы = данные?.
}
Код>
iframe
< div id = "exportdiv" style = "display: none;" >
< img src= "loader.gif" > <br> <h1> Генерирующий отчет </h1>
< iframe id = "exportif" src= "style =" width: 1px; height: 1px; border: 0px;" > </iframe>
</дел >
Код>
то другой файл:
<! DOCTYPE html >
< & HTML GT;
< & головка GT;
< & сценарий GT;
функция expdone()
{ window.parent.expdone();
}
</сценарий >
</головка >
& Л; тело >
< iframe id = "exportif" src= "<? php echo" http://10.192.37.211/npdtracker/exportthismonth.php?arguments= ".$_GET[" arguments "];? >" > < lt;/IFRAME >
<script> document.getElementById('exportif'). onload = expdone; </script>
</тело > </HTML>
Код>
Я думаю, что есть способ читать данные с помощью js, поэтому php не понадобится. но я не знаю, это от руки, а сервер, который я использую, поддерживает php, поэтому это работает для меня. подумал, что я поделюсь им, если он кому-то поможет.
Если вы хотите использовать загрузку файла jQuery, обратите внимание на это для IE. Вам нужно reset ответ или он не будет загружать
//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()