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

337

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

  • 0
    Возможный дубликат Как скачать файл при нажатии на имя файла с помощью PHP?
  • 1
    Я отметил его как дубликат, несмотря на разницу в платформе, потому что, насколько я вижу, решение одно и то же (вы не можете и не должны делать это через Ajax).
Показать ещё 3 комментария
Теги:
jsp
download

16 ответов

590
Лучший ответ

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

  • 64
    Мне нравится то, что вы создали, но я подозреваю, что для получения большего кредита от StackOverFlow ваш ответ должен содержать немного больше деталей. Конкретно о том, как вы решили проблему.
  • 0
    я люблю то, что ты сделал! Трудно было найти решение, которое позволило бы мне иметь удобство использования при загрузке, а когда загрузка не удалась. Отлично!
Показать ещё 15 комментариев
176

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

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

window.location="download.action?para1=value1...."
  • 4
    Хороший ... как я боролся с обработкой приглашения файла загрузки и использованием jquery ajax .. и это решение отлично работает для меня .. + 1
  • 43
    Обратите внимание, что для этого необходимо, чтобы сервер установил значение заголовка Content-Disposition «attachment», иначе браузер перенаправит (и отобразит) содержимое ответа
Показать ещё 5 комментариев
30

Вы можете использовать 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
    a.click() не работает в Firefox ... Есть идеи?
  • 0
    В некоторых браузерах вам может понадобиться добавить a в dom, чтобы этот код работал, и / или удалить часть revokeObjectURL : document.body.appendChild(a)
Показать ещё 2 комментария
25

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>
  • 1
    Сэр, Ваш вклад: "Content-Disposition", "inline; .... спас день плохого кодера :)
20

Я создал небольшую функцию в качестве решения обхода (вдохновленный плагином @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');
});
  • 0
    Это отправляет данные очень странным способом на сервер. Интересно, можно ли это изменить, чтобы создать совместимый POST?
  • 0
    @Shayne это может, но у меня все работает нормально с json
Показать ещё 1 комментарий
15

Хорошо, основываясь на коде 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: Будьте осторожны в отношении возможности переменной инъекции в этих формах. Может быть более безопасный способ кодирования этих переменных. Альтернативно, избегайте их.

  • 0
    Это рабочий пример. Благодарю. Можно ли сделать это без iframe, но без window.location?
  • 0
    Я полагаю, вы могли бы просто добавить скрытую форму в нижней части DOM. Также возможно стоит изучить использование Shadow dom, хотя это не всегда хорошо поддерживается в старых браузерах.
Показать ещё 3 комментария
12

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

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

  • 0
    «Мы не можем загрузить файл через Ajax, мы должны использовать XMLHttpRequest». XMLHttpRequest является AJAX по определению. В противном случае отличное решение для современных веб-браузеров. Для IE, который не поддерживает HTMLAnchorElement.download , я думаю объединить его с проприетарным методом msSaveOrOpenBlob .
11

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

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

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

  • 3
    Спасибо, я использовал это решение. Работал как шарм. Кроме того, если вы не получили блоб из ответа, просто создайте новый блоб.
  • 4
    Лучшая версия со ссылкой для обработки IE
8

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

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

Изображение 2269Изображение 2270

Вот код контроллера 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();
        }
    }
    }
  • 0
    кажется, что ваше событие загрузки не вызывается для содержимого вложения Content-disposition (потому что в iframe ничего не загружается), если оно работает для вас (вы получаете console.log), пожалуйста, опубликуйте образец
  • 0
    Вот быстрая скрипка jsfiddle.net/y2xezyoj, которая запускает задницу события загрузки, как только файл PDF загружается в iframe. Эта скрипка не загружается, потому что ключ для загрузки находится на стороне сервера "response.setHeader (" Content -disposition "," attachment ; filename = \ "" + fileName + ".xlsx \" ");"
Показать ещё 2 комментария
3
function downloadURI(uri, name) 
{
    var link = document.createElement("a");
    link.download = name;
    link.href = uri;
    link.click();
}
  • 0
    Не могли бы вы объяснить свой ответ? Это помогло бы другим понять, что вы сделали, чтобы они могли применять ваши методы в своих ситуациях.
  • 2
    Просто предупреждение: Safari и IE не поддерживают атрибут download , поэтому ваш файл будет иметь имя «Неизвестно»
3

Добавление еще нескольких вещей в ответ для загрузки файла

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

Вышеупомянутый файл будет загружен

2

В 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
  • 1
    Быстрый вопрос, не сгенерирует ли это файл дважды? После того как вы отправите запрос AJAX. Затем вы также перенаправляете страницу на тот же URL. Как мы можем устранить это?
  • 0
    Не в моем случае. Я только проверял это на Chrome все же.
Показать ещё 1 комментарий
1

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

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

Однако есть обходной путь.

Шаги:

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

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

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

0

Я нашел исправление, что, хотя на самом деле он не использует 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, поэтому это работает для меня. подумал, что я поделюсь им, если он кому-то поможет.

0

Если вы хотите использовать загрузку файла 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()

Ещё вопросы

Сообщество Overcoder
Наверх
Меню