скачать файл с помощью запроса ajax

96

Я хочу отправить «запрос на загрузку ajax», когда нажимаю кнопку, поэтому попробовал вот так:

javascript:

var xhr = new XMLHttpRequest();
xhr.open("GET", "download.php");
xhr.send();

download.php:

<?
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-Disposition: attachment; filename= file.txt");
header("Content-Transfer-Encoding: binary");    
readfile("file.txt");
?>

но работает не так, как ожидалось, как мне это сделать? заранее спасибо

Мануэль Ди Иорио
источник
2
Это не сработает, см. [Этот вопрос] [1]. [1]: stackoverflow.com/questions/8771342/…
olegsv
2
Dolocation.href='download.php';
Musa
попробуйте этот stackoverflow.com/a/42235655/2282880
Meloman

Ответы:

94

Обновление 27 апреля 2015 г.

На сцену HTML5 приходит атрибут загрузки . Он поддерживается в Firefox и Chrome и скоро появится в IE11. В зависимости от ваших потребностей вы можете использовать его вместо запроса AJAX (или использования window.location), если файл, который вы хотите загрузить, находится в том же источнике, что и ваш сайт.

Вы всегда можете сделать запрос AJAX / window.locationоткат, используя некоторый JavaScript, чтобы проверить, downloadподдерживается ли он, а если нет, переключив его на вызов window.location.

Оригинальный ответ

Запрос AJAX не может открывать окно загрузки, так как вам физически нужно перейти к файлу, чтобы запросить загрузку. Вместо этого вы можете использовать функцию успеха для перехода к download.php. Откроется запрос на загрузку, но текущая страница не изменится.

$.ajax({
    url: 'download.php',
    type: 'POST',
    success: function() {
        window.location = 'download.php';
    }
});

Несмотря на то, что это отвечает на вопрос, лучше просто использовать window.locationи полностью избегать запроса AJAX.

Стивен Ламберт
источник
39
Разве это не вызывает ссылку дважды? Я нахожусь в подобной лодке ... Я передаю много информации о безопасности в заголовках и могу анализировать объект файла в функции успеха, но не знаю, как вызвать запрос на загрузку.
user1447679
3
Он вызывает страницу дважды, поэтому, если вы запрашиваете базу данных на этой странице, это означает 2 обращения к БД.
мммммм 05
1
@ user1447679 см. альтернативное решение: stackoverflow.com/questions/38665947/…
Джон
1
Позвольте мне объяснить, как это мне помогло ... пример можно было бы сделать более полным. с "download.php? get_file = true" или что-то в этом роде ... У меня есть функция ajax, которая выполняет некоторую проверку ошибок при отправке формы, а затем создает файл csv. Если проверка ошибок не удалась, она должна вернуться с объяснением, почему она не удалась. Если он создает CSV, он сообщает родителю, что «давай, получи файл». Я делаю это, отправляя в файл ajax с переменной формы, а затем отправляя РАЗНЫЙ параметр в тот же файл, говоря «передайте мне файл, который вы только что создали» (путь / имя жестко закодированы в файле ajax).
Скотт
4
Но он отправит запрос 2 раза, это неправильно
Дхармендрасин Чудасама
45

Чтобы браузер скачал файл, вам нужно сделать такой запрос:

 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
Это работает для меня, но в firefox мне нужно сначала поместить тег <a> в DOM и ссылаться на него как на мою ссылку, а не создавать ее на лету, чтобы файл загружался автоматически.
Эрик Доноху
работает, но что произойдет, если файл создается во время выполнения? это не работает, как когда файл уже создан.
Diego
@Taha Я тестировал это на Edge, и, похоже, это сработало. Хотя не знаю об IE. Мой клиент не нацелен на пользователей IE ;-) Мне повезло? : D
Sнаđошƒаӽ
1
@ErikDonohoo Вы также можете создать <a>тег с помощью JS "на лету", но нужно добавить его document.body. Вы, безусловно, хотите одновременно это скрыть.
Мартон Тамаш
Спасибо @ João, я очень долго искал это решение.
Абхишек Гараи,
44

На самом деле вам вообще не нужен ajax. Если вы просто установите "download.php" в качестве href на кнопке, или, если это не ссылка, используйте:

window.location = 'download.php';

Браузер должен распознавать двоичную загрузку и не загружать фактическую страницу, а просто использовать файл как загрузку.

Джелле Кралт
источник
3
Язык программирования вы используете для изменения window.location является JavaScript.
mikemaccana
1
Вы правы @mikemaccana, я действительно имел в виду ajax :).
Jelle Kralt
2
Мы долго искали решение, и оно так элегантно и идеально. Огромное спасибо.
Яншун Тай
2
Конечно, это решение будет работать только в том случае, если это уже существующий статический файл.
krillgar
1
Если сервер отвечает с ошибкой, хотя у вас не будет возможности оставаться на вашей главной странице без перенаправления браузера на страницу с ошибкой. По крайней мере, это то, что делает Chrome, когда результат window.location возвращает 404.
Ян
21

Кроссбраузерное решение, протестировано в Chrome, Firefox, Edge, IE11.

В DOM добавьте тег скрытой ссылки:

<a id="target" style="display: none"></a>

Затем:

var req = new XMLHttpRequest();
req.open("GET", downloadUrl, true);
req.responseType = "blob";
req.setRequestHeader('my-custom-header', 'custom-value'); // adding some headers (if needed)

req.onload = function (event) {
  var blob = req.response;
  var fileName = null;
  var contentType = req.getResponseHeader("content-type");

  // IE/EDGE seems not returning some response header
  if (req.getResponseHeader("content-disposition")) {
    var contentDisposition = req.getResponseHeader("content-disposition");
    fileName = contentDisposition.substring(contentDisposition.indexOf("=")+1);
  } else {
    fileName = "unnamed." + contentType.substring(contentType.indexOf("/")+1);
  }

  if (window.navigator.msSaveOrOpenBlob) {
    // Internet Explorer
    window.navigator.msSaveOrOpenBlob(new Blob([blob], {type: contentType}), fileName);
  } else {
    var el = document.getElementById("target");
    el.href = window.URL.createObjectURL(blob);
    el.download = fileName;
    el.click();
  }
};
req.send();
Лео
источник
Хороший общий код. @leo, можете ли вы улучшить этот код, добавив собственные заголовки вроде Authorization?
vibs2006
1
Спасибо @leo. Это полезно. И что вы предлагаете добавить window.URL.revokeObjectURL(el.href);после el.click()?
vibs2006
Имя файла будет неправильным, если в расположении содержимого указано имя файла, отличного от UTF8.
Мэтью Олден,
14

Это возможно. Вы можете начать загрузку изнутри функции ajax, например, сразу после создания файла .csv.

У меня есть функция ajax, которая экспортирует базу данных контактов в файл .csv, и сразу после ее завершения автоматически запускает загрузку файла .csv. Итак, после того, как я получил responseText и все в порядке, я перенаправляю браузер следующим образом:

window.location="download.php?filename=export.csv";

Мой файл download.php выглядит так:

<?php

    $file = $_GET['filename'];

    header("Cache-Control: public");
    header("Content-Description: File Transfer");
    header("Content-Disposition: attachment; filename=".$file."");
    header("Content-Transfer-Encoding: binary");
    header("Content-Type: binary/octet-stream");
    readfile($file);

?>

Никакого обновления страницы не происходит, и файл автоматически начинает скачиваться.

ПРИМЕЧАНИЕ. - Проверено в следующих браузерах:

Chrome v37.0.2062.120 
Firefox v32.0.1
Opera v12.17
Internet Explorer v11
Педро Соуза
источник
1
Разве это не опасно с точки зрения безопасности?
Mickael Bergeron Néron
@ MickaelBergeronNéron Почему?
Педро Соуза
5
Я так думаю, потому что любой может вызвать download.php? Filename = [something] и попробовать некоторые пути и имена файлов, особенно общие, и это можно даже сделать внутри цикла внутри программы или сценария.
Mickael Bergeron Néron
не сможет ли .htaccess этого избежать?
Педро Соуза
9
@PedroSousa .. нет. htaccess контролирует доступ к файловой структуре через Apache. Поскольку доступ достиг скрипта PHP, htaccess прекращает свою работу. Это ОЧЕНЬ БОЛЬШАЯ дыра в безопасности, потому что на самом деле любой файл, который PHP (и пользователь, под которым он запущен) может читать, поэтому он может доставить в файл для чтения ... Всегда следует дезинфицировать запрошенный файл для чтения
Проф
0

Расшифровка имени файла из заголовка немного сложнее ...

    var filename = "default.pdf";
    var disposition = req.getResponseHeader('Content-Disposition');

    if (disposition && disposition.indexOf('attachment') !== -1) 
    {
       var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
       var matches = filenameRegex.exec(disposition);

       if (matches != null && matches[1]) 
           filename = matches[1].replace(/['"]/g, '');
    }
Люмический
источник
3
Отформатируйте весь блок кода и предоставьте дополнительные пояснения к процессу для удобства читателей в будущем.
mickmackusa
0

Это решение не сильно отличается от приведенных выше, но для меня оно работает очень хорошо и чисто.

Я предлагаю base64 кодировать сторону файлового сервера (base64_encode (), если вы используете PHP) и отправлять данные в кодировке base64 клиенту.

На клиенте вы делаете это:

 let blob = this.dataURItoBlob(THE_MIME_TYPE + "," + response.file);
 let uri = URL.createObjectURL(blob);
 let link = document.createElement("a");
 link.download = THE_FILE_NAME,
 link.href = uri;
 document.body.appendChild(link);
 link.click();
 document.body.removeChild(link);

Этот код помещает закодированные данные в ссылку и имитирует щелчок по ссылке, а затем удаляет ее.

мартинэтил
источник
Вы можете просто скрыть тег a и динамически заполнять href. не нужно добавлять и удалять
BPeela 01
0

Ваши потребности покрываются window.location('download.php');
Но я думаю, что вам нужно передать файл для загрузки, а не всегда загружать тот же файл, и поэтому вы используете запрос, один из вариантов - создать файл php так же просто, как showfile.php и сделать запрос вроде

var myfile = filetodownload.txt
var url = "shofile.php?file=" + myfile ;
ajaxRequest.open("GET", url, true);

showfile.php

<?php
$file = $_GET["file"] 
echo $file;

где file - это имя файла, переданное через Get или Post в запросе, а затем просто перехватить ответ в функции.

if(ajaxRequest.readyState == 4){
                        var file = ajaxRequest.responseText;
                        window.location = 'downfile.php?file=' + file;  
                    }
                }
Лисандро
источник
0

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

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

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

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

       функция (данные, статус) 
       {
            если (статус == "успех") 
            {
                / * 2) В ответ загружается страница, использующая предыдущие вычисления. Например, это может быть страница, на которой печатаются результаты таблицы, вычисленной в вызове ajax. * /
                window.location.href = DownloadPage.php + "? ID =" + 29;
            }               
       }
);

// Например: в CalculusPage.php

    если (! пусто ($ _ POST ["CalculusFunction"])) 
    {
        $ ID = $ _POST ["ID"];

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

// Например: в DownloadPage.php

    $ ID = $ _GET ["ID"];

    $ sede = "ВЫБРАТЬ * ИЗ ExamplePage WHERE id =". $ ID;
    ...

    $ filename = "Export_Data.xls";
    заголовок ("Content-Type: application / vnd.ms-excel");
    заголовок ("Content-Disposition: inline; filename = $ filename");

    ...

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

netluke
источник
0

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

fetch(url, {
    body: JSON.stringify(data),
    method: 'POST',
    headers: {
        'Content-Type': 'application/json; charset=utf-8'
    },
})
.then(response => response.blob())
.then(response => {
    const blob = new Blob([response], {type: 'application/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});
    const downloadUrl = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = downloadUrl;
    a.download = "file.xlsx";
    document.body.appendChild(a);
    a.click();
})

Я считаю, что этот подход намного легче понять, чем другие XMLHttpRequestрешения. Кроме того, он имеет синтаксис, аналогичный jQueryподходу, без необходимости добавлять какие-либо дополнительные библиотеки.

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

Важно : в этом примере я отправляю запрос JSON на сервер, который прослушивает данный url. Это urlдолжно быть установлено, на моем примере я предполагаю, что вы знаете эту часть. Также обратите внимание на заголовки, необходимые для работы вашего запроса. Поскольку я отправляю JSON, я должен добавить Content-Typeзаголовок и установить его application/json; charset=utf-8, чтобы сервер знал, какой тип запроса он получит.

Ален Круз
источник
0

Решение @Joao Marcos работает для меня, но мне пришлось изменить код, чтобы он работал в IE, ниже, если как выглядит код

       downloadFile(url,filename) {
        var that = this;
        const extension =  url.split('/').pop().split('?')[0].split('.').pop();

        var req = new XMLHttpRequest();
        req.open("GET", url, true);
        req.responseType = "blob";
        req.onload = function (event) {
            const fileName = `${filename}.${extension}`;
            const blob = req.response;

            if (window.navigator.msSaveBlob) { // IE
                window.navigator.msSaveOrOpenBlob(blob, fileName);
            } 
            const link = document.createElement('a');
            link.href = window.URL.createObjectURL(blob);                
            link.download = fileName;
            link.click();
            URL.revokeObjectURL(link.href);

        };

        req.send();
    },
Джемил Ойебиси
источник