Обрабатывать загрузку файлов из ajax post

У меня есть приложение javascript, которое отправляет запросы ajax POST на определенный URL. Ответ может быть строкой JSON или файлом (в виде вложения). Я могу легко определить тип контента и расположение контента в моем вызове ajax, но как только я обнаружу, что ответ содержит файл, как я могу предложить клиенту загрузить его? Я прочитал несколько подобных тем здесь, но ни один из них не дает ответа, который я ищу.

пожалуйста, пожалуйста, пожалуйста, не публикуйте ответы, предлагая что я не должен использовать AJAX для этого, или что я должен перенаправить, потому что это вариант. Использование простой формы HTML также не вариант. Что мне нужно, так это показать клиенту диалог загрузки. Можно ли это сделать и как?

изменить:

по-видимому, это невозможно сделать, но есть простой обходной путь, как предложено принятым ответом. Для тех, кто сталкивается с этой проблемой в будущем, вот как я ее решил:

$.ajax({
    type: "POST",
    url: url,
    data: params,
    success: function(response, status, request) {
        var disp = request.getResponseHeader('Content-Disposition');
        if (disp && disp.search('attachment') != -1) {
            var form = $('<form method="POST" action="' + url + '">');
            $.each(params, function(k, v) {
                form.append($('<input type="hidden" name="' + k +
                        '" value="' + v + '">'));
            });
            $('body').append(form);
            form.submit();
        }
    }
});

Так в принципе, просто создайте HTML-форму с теми же параметрами, которые использовались в запросе AJAX, и отправьте ее.

14 ответов


создайте форму, используйте метод POST, отправьте форму - нет необходимости в iframe. Когда страница сервера ответит на запрос, напишите заголовок ответа для типа MIME файла, и он представит диалоговое окно загрузки - я делал это несколько раз.

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


Не сдавайся так быстро, потому что это можно сделать (в современных браузерах), используя части FileAPI:

Edit 2017-09-28: обновлено для использования конструктора файлов, когда он доступен, поэтому он работает в Safari >= 10.1.

Edit 2015-10-16: jQuery ajax не может правильно обрабатывать двоичные ответы (не может установить тип ответа), поэтому лучше использовать простой вызов XMLHttpRequest.

var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.responseType = 'arraybuffer';
xhr.onload = function () {
    if (this.status === 200) {
        var filename = "";
        var disposition = xhr.getResponseHeader('Content-Disposition');
        if (disposition && disposition.indexOf('attachment') !== -1) {
            var filenameRegex = /filename[^;=\n]*=((['"]).*?|[^;\n]*)/;
            var matches = filenameRegex.exec(disposition);
            if (matches != null && matches[1]) filename = matches[1].replace(/['"]/g, '');
        }
        var type = xhr.getResponseHeader('Content-Type');

        var blob = typeof File === 'function'
            ? new File([this.response], filename, { type: type })
            : new Blob([this.response], { type: type });
        if (typeof window.navigator.msSaveBlob !== 'undefined') {
            // IE workaround for "HTML7007: One or more blob URLs were revoked by closing the blob for which they were created. These URLs will no longer resolve as the data backing the URL has been freed."
            window.navigator.msSaveBlob(blob, filename);
        } else {
            var URL = window.URL || window.webkitURL;
            var downloadUrl = URL.createObjectURL(blob);

            if (filename) {
                // use HTML5 a[download] attribute to specify filename
                var a = document.createElement("a");
                // safari doesn't support this yet
                if (typeof a.download === 'undefined') {
                    window.location = downloadUrl;
                } else {
                    a.href = downloadUrl;
                    a.download = filename;
                    document.body.appendChild(a);
                    a.click();
                }
            } else {
                window.location = downloadUrl;
            }

            setTimeout(function () { URL.revokeObjectURL(downloadUrl); }, 100); // cleanup
        }
    }
};
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.send($.param(params));

вот старая версия с использованием jQuery.Аякс. Он может искажать двоичные данные, когда ответ преобразуется в строку некоторой кодировки.

$.ajax({
    type: "POST",
    url: url,
    data: params,
    success: function(response, status, xhr) {
        // check for a filename
        var filename = "";
        var disposition = xhr.getResponseHeader('Content-Disposition');
        if (disposition && disposition.indexOf('attachment') !== -1) {
            var filenameRegex = /filename[^;=\n]*=((['"]).*?|[^;\n]*)/;
            var matches = filenameRegex.exec(disposition);
            if (matches != null && matches[1]) filename = matches[1].replace(/['"]/g, '');
        }

        var type = xhr.getResponseHeader('Content-Type');
        var blob = new Blob([response], { type: type });

        if (typeof window.navigator.msSaveBlob !== 'undefined') {
            // IE workaround for "HTML7007: One or more blob URLs were revoked by closing the blob for which they were created. These URLs will no longer resolve as the data backing the URL has been freed."
            window.navigator.msSaveBlob(blob, filename);
        } else {
            var URL = window.URL || window.webkitURL;
            var downloadUrl = URL.createObjectURL(blob);

            if (filename) {
                // use HTML5 a[download] attribute to specify filename
                var a = document.createElement("a");
                // safari doesn't support this yet
                if (typeof a.download === 'undefined') {
                    window.location = downloadUrl;
                } else {
                    a.href = downloadUrl;
                    a.download = filename;
                    document.body.appendChild(a);
                    a.click();
                }
            } else {
                window.location = downloadUrl;
            }

            setTimeout(function () { URL.revokeObjectURL(downloadUrl); }, 100); // cleanup
        }
    }
});

какой серверный язык вы используете? В моем приложении я могу легко загрузить файл из вызова AJAX, установив правильные заголовки в ответе PHP:

настройка заголовков на стороне сервера

header("HTTP/1.1 200 OK");
header("Pragma: public");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");

// The optional second 'replace' parameter indicates whether the header
// should replace a previous similar header, or add a second header of
// the same type. By default it will replace, but if you pass in FALSE
// as the second argument you can force multiple headers of the same type.
header("Cache-Control: private", false);

header("Content-type: " . $mimeType);

// $strFileName is, of course, the filename of the file being downloaded. 
// This won't have to be the same name as the actual file.
header("Content-Disposition: attachment; filename=\"{$strFileName}\""); 

header("Content-Transfer-Encoding: binary");
header("Content-Length: " . mb_strlen($strFile));

// $strFile is a binary representation of the file that is being downloaded.
echo $strFile;

это фактически "перенаправит" браузер на эту страницу загрузки, но, как сказал @ahren alread в своем комментарии, он не будет перемещаться от текущей страницы.

речь идет о настройке правильных заголовков, поэтому я уверен, что вы найдете подходящее решение для серверный язык вы используете, если это не PHP.

обработка клиентских ответ

предполагая, что вы уже знаете, как сделать вызов AJAX, на стороне клиента вы выполняете запрос AJAX на сервер. Затем сервер генерирует ссылку, из которой можно загрузить этот файл, например URL-адрес "вперед", на который вы хотите указать. Например, сервер отвечает:

{
    status: 1, // ok
    // unique one-time download token, not required of course
    message: 'http://yourwebsite.com/getdownload/ska08912dsa'
}

при обработке ответа вы вводите iframe в свое тело, the iframeSRC для URL, который вы только что получили (используя jQuery для удобства этого примера):

$("body").append("<iframe src='" + data.message +
  "' style='display: none;' ></iframe>");

если вы установили правильные заголовки, как показано выше, iframe заставит диалоговое окно загрузки без перемещения браузера от текущей страницы.

Примечание

дополнительное дополнение по отношению к вашему вопросу; я думаю, что лучше всегда возвращать JSON при запросе материала с технологией AJAX. После получения ответа JSON вы можете затем решать на стороне клиента, что с ним делать. Возможно, например, позже вы хотите, чтобы пользователь щелкнул ссылку для загрузки на URL-адрес, а не принудительно загружал напрямую, в вашей текущей настройке вам придется обновить как клиент, так и серверную часть.


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

"опубликуйте данные 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", а затем щелкаем по ней.

здесь нам нужно тщательно установить несколько вещей на стороне сервера. Я установил несколько заголовков в Python Django HttpResponse. Вам нужно установить их соответственно, если вы используете другие языки программирования.

# In python django code
response = HttpResponse(file_content, content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")

поскольку я загружаю xls (excel) здесь, я настроил contentType выше одного. Вы должны установить его в соответствии с типом файла. Вы можете использовать это техника для загрузки любых файлов.


для тех, кто ищет решение с угловой точки зрения, это сработало для меня:

$http.post(
  'url',
  {},
  {responseType: 'arraybuffer'}
).then(function (response) {
  var headers = response.headers();
  var blob = new Blob([response.data],{type:headers['content-type']});
  var link = document.createElement('a');
  link.href = window.URL.createObjectURL(blob);
  link.download = "Filename";
  link.click();
});

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

У меня была такая же проблема пару недель назад, действительно, невозможно добиться "чистой" загрузки через AJAX, группа накаливания создала плагин jQuery, который работает именно так, как вы уже узнали, он называется в jQuery скачать однако есть и обратная сторона этого метод.

Если вы отправляете большие запросы через AJAX (скажем, файлы +1 МБ), это негативно повлияет на отзывчивость. В медленных подключениях к Интернету вам придется подождать большое пока запрос не будет отправлен, а также дождитесь загрузки файла. Это не похоже на мгновенный "click" = > "popup" = > "download start". Это больше похоже на "click" = > "wait until data is sent" = > "wait for response" = > "download start", что делает его вдвое больше, потому что вам придется дождитесь отправки запроса через AJAX и получите его обратно в виде загружаемого файла.

Если вы работаете с небольшими размерами файлов

мое приложение позволяет пользователям экспортировать динамически генерируемые изображения, эти изображения отправляются через почтовые запросы в формате base64 на сервер (это единственный возможный способ), затем обрабатываются и отправляются обратно пользователям в виде .формат PNG. ,формат jpg файлы, base64 строки для изображений +1 МБ огромны, это заставляет пользователей ждать больше, чем необходимо для начала загрузки файла. В медленных интернет-соединениях это может быть очень раздражающим.

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

Обновление 30 Сентября 2014:

прошли месяцы с тех пор, как я опубликовал это, наконец, я нашел лучший подход к ускорению при работе с большими строками base64. Теперь я храню строки base64 в базе данных (используя поля longtext или longblog), затем я передаю его идентификатор записи через загрузку файла jQuery, наконец, при загрузке файл сценария я запрашиваю базу данных с помощью этого идентификатора, чтобы вытащить строку base64 и передать ее через функцию загрузки.

Скачать Скрипт:

<?php
// Record ID
$downloadID = (int)$_POST['id'];
// Query Data (this example uses CodeIgniter)
$data       = $CI->MyQueries->GetDownload( $downloadID );
// base64 tags are replaced by [removed], so we strip them out
$base64     = base64_decode( preg_replace('#\[removed\]#', '', $data[0]->image) );
// This example is for base64 images
$imgsize    = getimagesize( $base64 );
// Set content headers
header('Content-Disposition: attachment; filename="my-file.png"');
header('Content-type: '.$imgsize['mime']);
// Force download
echo $base64;
?>

Я знаю, что это далеко за пределами того, что спросил ОП, однако я чувствовал, что было бы хорошо обновить мой ответ с моими выводами. Когда я искал решения своей проблемы, я читал много "загрузить из данных AJAX POST" темы, которые не дали мне ответа, я был в поисках, я надеюсь, что эта информация поможет кому-то, кто хочет достичь чего-то подобного.


вот как я получил эту работу https://stackoverflow.com/a/27563953/2845977

$.ajax({
  url: '<URL_TO_FILE>',
  success: function(data) {
    var blob=new Blob([data]);
    var link=document.createElement('a');
    link.href=window.URL.createObjectURL(blob);
    link.download="<FILENAME_TO_SAVE_WITH_EXTENSION>";
    link.click();
  }
});

обновленный ответ с помощью скачать.js

$.ajax({
  url: '<URL_TO_FILE>',
  success: download.bind(true, "<FILENAME_TO_SAVE_WITH_EXTENSION>", "<FILE_MIME_TYPE>")
});

Я хочу указать на некоторые трудности, которые возникают при использовании техники в принятом ответе, т. е. при использовании формы post:

  1. вы не можете установить заголовки запроса. Если ваша схема аутентификации включает заголовки, JSON-Web-токен, переданный в заголовке авторизации, вам придется найти другой способ отправить его, например, в качестве параметра запроса.

  2. вы не можете сказать, когда запрос закончился. Ну, вы можете использовать cookie, который получает набор на ответ, как это сделано С помощью jQuery.fileDownload, но это далеко не идеально. Он не будет работать для параллельных запросов, и он сломается, если ответ никогда не придет.

  3. Если сервер отвечает с ошибкой, пользователь будет перенаправлен на страницу ошибки.

  4. вы можете использовать только типы контента, поддерживаемые форма. Это означает, что вы не можете использовать JSON.

Я закончил использование метода сохранения файла на S3 и отправки предварительно подписанного URL-адреса для получения файла.


Как заявили другие, вы можете создать и отправить форму для загрузки с помощью запроса POST. Однако это не обязательно делать вручную.

одна действительно простая библиотека для выполнения именно этого -С помощью jQuery.перенаправить. Он предоставляет API, похожий на стандартный jQuery.post способ:

$.redirect(url, [values, [method, [target]]])

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

<form id="export-csv-form" method="POST" action="/the/path/to/file">
    <input type="hidden" name="anyValueToPassTheServer" value="">
</form>

эта форма используется только для вызова службы и избежать использования окна.расположение.)( После этого вам просто нужно сделать форму отправки из jquery, чтобы позвоните в службу и получите файл. Это довольно просто, но таким образом вы можете сделать загрузку с помощью в должности. Я теперь, что это может быть проще, если служба, которую вы вызываете, является GET, но это не мой случай.


вот мое решение, использующее временную скрытую форму.

//Create an hidden form
var form = $('<form>', {'method': 'POST', 'action': this.href}).hide();

//Add params
var params = { ...your params... };
$.each(params, function (k, v) {
    form.append($('<input>', {'type': 'hidden', 'name': k, 'value': v}));
});

//Make it part of the document and submit
$('body').append(form);
form.submit();

//Clean up
form.remove();

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


я использовал этот FileSaver.js. В моем случае с csv-файлами я сделал это (в coffescript):

  $.ajax
    url: "url-to-server"
    data: "data-to-send"
    success: (csvData)->
      blob = new Blob([csvData], { type: 'text/csv' })
      saveAs(blob, "filename.csv")

Я думаю, что для самого сложного случая данные должны быть обработаны правильно. Под капотом FileSaver.js реализует тот же подход ответа Джонатан Изменить.


см.:http://www.henryalgus.com/reading-binary-files-using-jquery-ajax/ он вернет blob в качестве ответа, который затем можно поместить в filesaver


и Джонатан Вину ответ для работы в Edge я внес следующие изменения:

var blob = typeof File === 'function'
    ? new File([this.response], filename, { type: type })
    : new Blob([this.response], { type: type });

этой

var f = typeof File+"";
var blob = f === 'function' && Modernizr.fileapi
    ? new File([this.response], filename, { type: type })
    : new Blob([this.response], { type: type });

Я бы написал это как комментарий, но у меня не хватает репутации для этого