it-swarm.com.ru

Скачать файл от 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 я вижу, что данные извлекаются с помощью двоичный поток. Интересно, как открыть окно загрузки файла, с помощью которого пользователь может сохранить файл локально?

367
hguser

Синеватый совершенно прав насчет этого, вы не можете сделать это через 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.

612
John Culviner

Никто не опубликовал это решение @ Pekka ... поэтому я опубликую его. Это может помочь кому-то.

Вам не нужно делать это через Ajax. Просто используйте

window.location="download.action?para1=value1...."
199
bluish

Вы можете с 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});
};

Это хорошо, если ваш сервер создает дамп файловых данных для сохранения. Тем не менее, я не совсем понял, как можно реализовать запасной вариант HTML4

33
Luke Madhanga

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 (если вы не используете Действие в качестве сервлета, например, хак для прямой потоковой передачи ), вам не нужно напрямую что-либо записывать в ответ; просто используйте тип результата потока и настройте его в 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): файл открытия сервлета (/ Action) внутри браузера

Если вы хотите открыть файл в браузере, вместо загрузки его, 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>
26
Andrea Ligios

Я создал небольшую функцию как обходное решение (вдохновленный плагином @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');
});
22
ndpu

Простой способ заставить браузер загружать файл - сделать запрос следующим образом:

 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();
 }

Откроется всплывающее окно загрузки браузера.

16
João Marcos

Хорошо, на основе кода 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, как в предыдущем примере.

ПРЕДУПРЕЖДЕНИЕ: будьте осторожны с возможностью внесения переменных в эти формы. Там может быть более безопасный способ кодирования этих переменных. В качестве альтернативы созерцайте избежать их.

15
Shayne

Я столкнулся с той же проблемой и успешно решил ее. Мой сценарий использования это.

"Отправьте данные 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.
  • После извлечения содержимого в виде двоичного объекта (двоичного объекта) мы создаем загружаемый URL-адрес и прикрепляем его к невидимой ссылке "а", а затем нажимаем на нее. Я сделал 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 выше указанного. Вам нужно установить его в соответствии с вашим типом файла. Вы можете использовать эту технику для загрузки любых файлов.

13
Naren Yellavula

Вот что я сделал, чистый 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");
});

Использование только компонентов, которые поддерживаются во всех браузерах, без дополнительных библиотек.

enter image description hereenter image description here

Вот моя серверная часть 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();
        }
    }
    }
8
manukyanv07
function downloadURI(uri, name) 
{
    var link = document.createElement("a");
    link.download = name;
    link.href = uri;
    link.click();
}
4
EL missaoui habib

Добавив еще несколько вещей к ответу выше для скачивания файла

Ниже приведен некоторый пружинный код 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);
    }

Теперь в коде 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);

Выше будет скачать файл

3
dario nascimento

В 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
2
aarkerio

Итак, вот рабочий код при использовании 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);
    }

и тогда вы сможете позвонить своему контроллеру, чтобы загрузить и получить ответный звонок "успех" или "сбой"

$.fileDownload(mvcUrl('name of the controller'), {
            httpMethod: 'POST',
            successCallback: function (url) {
            //insert success code

            },
            failCallback: function (html, url) {
            //insert fail code
            }
        });
1
Yannick Richard

Я нашел исправление, которое хотя на самом деле не использует 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';
}

фрейм

<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>

Я думаю, что есть способ прочитать данные с использованием js, поэтому никакой php не понадобится. но я не знаю это от руки, и сервер, который я использую, поддерживает php, так что это работает для меня. думал, что поделюсь этим на случай, если это кому-нибудь поможет.

0
Kit Ramos

Я долго боролся с этой проблемой. Наконец, элегантная внешняя библиотека предложила здесь выручил меня.

0
Eerik Sven Puudist

Несомненно, что вы не можете сделать это через вызов Ajax.

Тем не менее, есть обходной путь.

Шаги:

Если вы используете form.submit () для загрузки файла, вы можете сделать следующее:

  1. Создайте ajax-вызов от клиента к серверу и сохраните поток файлов внутри сеанса.
  2. После "успеха", возвращаемого с сервера, вызовите вашу форму.submit (), чтобы просто передать поток файлов, сохраненный в сеансе.

Это полезно в том случае, если вы хотите решить, нужно ли загружать файл после выполнения form.submit (), например: может быть случай, когда в form.submit () возникает исключение на стороне сервера и вместо этого в случае сбоя может потребоваться показать настраиваемое сообщение на стороне клиента, в этом случае эта реализация может помочь.

0
Aman Srivastava

Используйте window.openhttps://developer.mozilla.org/en-US/docs/Web/API/Window/open

Например, вы можете поместить эту строку кода в обработчик кликов:

window.open('/file.txt', '_blank');

Откроется новая вкладка (из-за имени окна "_blank"), и эта вкладка откроет URL.

Ваш серверный код также должен иметь что-то вроде этого:

res.set('Content-Disposition', 'attachment; filename=file.txt');

Таким образом, браузер должен предлагать пользователю сохранить файл на диск, а не просто показывать им файл. Он также автоматически закроет вкладку, которую он только что открыл.

0
Andrew Koster

есть другое решение для загрузки веб-страницы в ajax. Но я имею в виду страницу, которая должна быть сначала обработана, а затем загружена.

Для начала нужно отделить обработку страницы от загрузки результатов.

1) В вызове ajax выполняются только расчеты страницы.

 $. post (функция CalculusPage.php, {calculusFunction: true, ID: 29, данные1: "a", данные2: "b"}, 
 
 функция (данные , status) 
 {
 if (status == "success") 
 {
/* 2) В ответ загружается страница, которая использует предыдущие вычисления , Например, это может быть страница, которая печатает результаты таблицы, вычисленной в вызове ajax. */
 window.location.href = DownloadPage.php + "? ID =" + 29; 
} 
} 
); 
 
 // Например: в CalculusPage.php 
 
 If (! Empty ($ _ POST ["calculusFunction"])) 
 {
 $ ID = $ _POST ["ID"]; 
 
 $ Query = "INSERT INTO ExamplePage (data1, data2) VALUES ('". $ _ POST ["data1"]. "', '". $ _POST ["data2"]. "') WHERE id =". $ ID; 
 ... 
} 
 
 // Например: в DownloadPage.php 
 
 $ ID = $ _GET ["ID"]; 
 
 $ Sede = "SELECT * FROM ExamplePage WHERE id =". $ ID; 
 ... 
 
 $ filename = "Export_Data.xls"; 
 заголовок ("Content-Type: application/vnd.ms-Excel"); 
 header ("Content-Disposition: inline; filename = $ filename"); 
 
 ... 

Я надеюсь, что это решение может быть полезным для многих, как и для меня.

0
netluke

Если вы хотите использовать jQuery File Download, обратите внимание на это для 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()

0
Alireza Fattahi