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

Ответ 1

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!'));

Ответ 2

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

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

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

Ответ 3

Вы можете использовать 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

Ответ 4

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>

Ответ 5

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

Ответ 6

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

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

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

Ответ 7

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

Ответ 8

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

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

Ответ 9

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

Ответ 10

function downloadURI(uri, name) 
{
    var link = document.createElement("a");
    link.download = name;
    link.href = uri;
    link.click();
}

Ответ 11

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

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

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

Ответ 12

В 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

Ответ 13

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

Ответ 14

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

Ответ 15

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

Ответ 16

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

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

Шаги:

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

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

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

Ответ 17

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

Ответ 18

Используйте window.open https://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');

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

Ответ 19

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

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

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

$.post("CalculusPage.php", { calculusFunction: true, ID: 29, data1: "a", data2: "b" },

       function(data, status) 
       {
            if (status == "success") 
            {
                /* 2) In the answer the page that uses the previous calculations is downloaded. For example, this can be a page that prints the results of a table calculated in the ajax call. */
                window.location.href = DownloadPage.php+"?ID="+29;
            }               
       }
);

// For example: in the CalculusPage.php

    if ( !empty($_POST["calculusFunction"]) ) 
    {
        $ID = $_POST["ID"];

        $query = "INSERT INTO ExamplePage (data1, data2) VALUES ('".$_POST["data1"]."', '".$_POST["data2"]."') WHERE id = ".$ID;
        ...
    }

// For example: in the DownloadPage.php

    $ID = $_GET["ID"];

    $sede = "SELECT * FROM ExamplePage WHERE id = ".$ID;
    ...

    $filename="Export_Data.xls";
    header("Content-Type: application/vnd.ms-excel");
    header("Content-Disposition: inline; filename=$filename");

    ...

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