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

Я хочу отправить "запрос на загрузку 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");
?>

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

14 ответов

Решение

Обновление 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.

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

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

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

window.location = 'download.php';

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

Кросс-браузерное решение, протестировано на 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.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();

Это возможно. Вы можете начать загрузку из 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

Я предпочитаю Location.assign()

https://developer.mozilla.org/en-US/docs/Web/API/Location.assign

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

Решение @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();
    },

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

Я предлагаю кодировать 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);

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

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

    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, '');
    }

это работает для меня

      var dataObj = {
somekey:"someValue"
}
     $.ajax({
        method: "POST",
        url: "/someController/someMethod",
        data: dataObj,
        success: function (response) {
            const blob = new Blob([response], { type: 'text/csv' });
            const downloadUrl = URL.createObjectURL(blob);
            const a = document.createElement("a");
            a.href = downloadUrl;
            a.download = "file.csv";
            document.body.appendChild(a);
            a.click();
        }
    });
        var req = $.ajax({
  type: 'GET',
  cache: false,
  url: "ScaricaXmlbyLink",
  data: data,
  success: function (response) {
      const blob = new Blob([response], { type: 'text/xml' });
      const downloadUrl = URL.createObjectURL(blob);
      const a = document.createElement("a");
      a.href = downloadUrl;
      a.download = "file.xml";
      document.body.appendChild(a);
      a.click();
  }

});

Ваши потребности покрыты 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;  
                    }
                }

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

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

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

$.post ("CalculusPage.php", {calculusFunction: true, ID: 29, данные1: "a", данные2: "b" },

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

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

    if (!empty($_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 = "SELECT * FROM ExamplePage WHERE id = ".$ID;
    ...

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

    ...

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

Другие вопросы по тегам