Скачать файл от jQuery.Ajax - PullRequest
       14

Скачать файл от jQuery.Ajax

374 голосов
/ 28 декабря 2010

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

Ответы [ 19 ]

617 голосов
/ 02 апреля 2012

2019 обновление современных браузеров

Такой подход я бы сейчас рекомендовал с несколькими оговорками:

  • Требуется относительно современный браузер
  • Если файл, как ожидается, будет очень большой , вам, вероятно, следует сделать что-то похожее на оригинальный подход (iframe и cookie), поскольку некоторые из приведенных ниже операций могут, вероятно, потреблять системную память по крайней мере так же, как загружаемый файл и / или другие интересные побочные эффекты процессора.

fetch('https://jsonplaceholder.typicode.com/todos/1')
  .then(resp => resp.blob())
  .then(blob => {
    const url = window.URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.style.display = 'none';
    a.href = url;
    // the filename you want
    a.download = 'todo-1.json';
    document.body.appendChild(a);
    a.click();
    window.URL.revokeObjectURL(url);
    alert('your file has downloaded!'); // or you know, something with better UX...
  })
  .catch(() => alert('oh no!'));

2012 Оригинальный подход на основе jQuery / iframe / Cookie

Голубоватый совершенно прав насчет этого, вы не можете сделать это через Ajax, потому что JavaScript не может сохранять файлы напрямую на компьютер пользователя (из соображений безопасности). К сожалению, указание URL-адреса главного окна *1027* при загрузке файла означает, что вы мало контролируете действия пользователя при загрузке файла.

Я создал 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.

200 голосов
/ 05 октября 2011

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

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

window.location="download.action?para1=value1...."
33 голосов
/ 29 апреля 2015

Вы можете с помощью 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

26 голосов
/ 03 июня 2013

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 result type и настройка его в struts.xml будет работать: EXAMPLE

<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>
23 голосов
/ 16 января 2014

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

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

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

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

15 голосов
/ 20 января 2014

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

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

13 голосов
/ 09 августа 2016

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

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

8 голосов
/ 28 июня 2015

Вот что я сделал, чистый 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 here enter 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();
        }
    }
    }
4 голосов
/ 12 апреля 2016
function downloadURI(uri, name) 
{
    var link = document.createElement("a");
    link.download = name;
    link.href = uri;
    link.click();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...