Загрузите файл с помощью jQuery.Аякс

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

16 ответов


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

Я создал в jQuery скачать что позволяет" Ajax как " опыт загрузки файлов в комплекте с onsuccess и onfailure обратные вызовы для обеспечения лучшего пользовательского опыта. Взгляните на мой блоге об общей проблеме, которую решает плагин и некоторые способы ее использования, а также демонстрация загрузки файла jQuery в действии. Вот это источник

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


никто не опубликовал это решение@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


1. Framework agnostic: сервлет загрузки файла в виде вложения

<!-- 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> tag:

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

в вышеуказанных случаях вы нужно написать Содержание-Диспозиция к ответ, указав, что файл необходимо загрузить (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 (если вы не используете действие в качестве сервлета, hack для прямого потоковое, например), вам не нужно ничего писать в ответ, просто используя тип результата потока и настройка его в стойках.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. Framework agnostic(/Struts2 framework): сервлет (/Action) открытие файла внутри браузера

если вы хотите открыть файл в браузере, не загружая его,содержание-диспозиция должно быть установлено значение inline, но целью не может быть текущее местоположение окна; вы должны нацелиться на новое окно, созданное javascript,<iframe> на странице или в новом окне, созданном на лету с" обсуждаемой "целью= "_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();
}

демо с событием щелчка:

$('#someid').on('click', function() {
    ajax_download('/download.action', {'para1': 1, 'para2': 2}, 'dataname');
});

Ok, на основе кода 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});
});

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

предостережение: будьте осторожны с потенциалом для переменной инъекции в этих формах. Возможно, есть более безопасный способ кодирования этих переменных. В качестве альтернативы подумайте о том, чтобы убежать от них.


я столкнулся с той же проблемой и успешно ее решил. Мой случай это.

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


простой способ сделать браузер загружает файл, чтобы сделать запрос, как это:

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

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

enter image description hereenter image description here

вот мой серверный код Java Spring controller.

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

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

Ниже приведен некоторый код Java spring, который генерирует массив байтов

@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);

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


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

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

фокус в


Ok Итак, вот рабочий код при использовании MVC, и вы получаете свой файл от контроллера

допустим, у вас есть Ваш массив байтов, объявите и заполните, единственное, что вам нужно сделать, это использовать функцию File (используя System.Сеть.В 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
            }
        });

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


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

iframe и

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


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

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

действия :

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

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

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