завантажити файл за допомогою запиту ajax


95

Я хочу надіслати "запит на завантаження 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

Відповіді:


92

Оновлення 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 поїздки до БД.
мммммм

1
@ user1447679 див. альтернативне рішення: stackoverflow.com/questions/38665947/…
Джон,

1
Дозвольте пояснити, як мені це допомогло ... приклад міг бути більш повним. з "download.php? get_file = true" або щось інше ... У мене є функція ajax, яка виконує перевірку помилок при поданні форми, а потім створює файл CSV. Якщо перевірка помилки не вдається, вона повинна повернутися з повідомленням про те, чому не вдалося. Якщо він створює CSV, він повідомляє батькові, що "продовжуйте і заберіть файл". Я роблю це, публікуючи у файлі ajax із змінною форми, а потім публікуючи РІЗНИЙ параметр у тому самому файлі, кажучи: "передай мені файл, який ти щойно створив" (шлях / ім'я жорстко закодовано у файл ajax).
Скотт

4
Але він надішле запит 2 рази, що не є належним чином
Dharmendrasinh Chudasama

44

Щоб браузер завантажив файл, потрібно зробити такий запит:

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

працює, але що станеться, якщо файл створюється під час виконання? це не працює, як коли файл вже створений.
Дієго,

@Taha Я протестував це на Edge, і, здавалося, це спрацювало. Не знаю про IE, хоча. Мій клієнт не націлений на користувачів IE ;-) Чи мені пощастило? : D
Sнаđошƒаӽ

1
@ErikDonohoo Ви можете створити <a>тег за допомогою JS, також "на льоту", але його потрібно додати document.body. Ви, звичайно, хочете приховати це одночасно.
Мартон Тамас,

Дякую @ João, я дуже довго шукав це рішення.
Абхішек Гарай

43

Для цього вам насправді взагалі не потрібен ajax. Якщо ви просто встановили "download.php" як href на кнопці, або, якщо це не посилання, використовуйте:

window.location = 'download.php';

Браузер повинен розпізнати двійкове завантаження і не завантажувати фактичну сторінку, а просто подавати файл як завантаження.


3
Мовою програмування, яку ви використовуєте для зміни, window.location є JavaScript.
mikemaccana

1
Ви маєте рацію @mikemaccana, я насправді мав на увазі ajax :).
Jelle Kralt,

2
Полювали високо і низько, щоб знайти рішення, і це настільки елегантно та ідеально. Дуже дякую.
Yangshun Tay,

2
Звичайно, це рішення буде працювати, лише якщо це вже існує статичний файл.
krillgar

1
Якщо сервер відповідає помилкою, хоча не буде жодного способу залишитися на вашій головній сторінці без перенаправлення браузером на сторінку помилок. Принаймні це те, що робить Chrome, коли результат window.location повертає 404.
Ян,

20

Рішення для перехресного браузера, тестоване на 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
Хіба це не небезпечно з точки зору безпеки?
Мікаель Бержерон Нерон

@ MickaelBergeronNéron Чому?
Педро Соуза

5
Я думаю, що так, бо кожен може зателефонувати download.php? Filename = [щось] і спробувати деякі шляхи та імена файлів, особливо загальноприйняті, і це навіть можна зробити всередині циклу в межах програми чи сценарію.
Мікаель Бержерон Нерон

не вдалося б .htaccess цього уникнути?
Педро Соуза

9
@PedroSousa .. ні. htaccess контролює доступ до файлової структури через Apache. Оскільки доступ досягнув PHP-скрипту, htaccess тепер припиняє свою роботу. Це дуже багато діра в безпеці , тому що на самому справі, будь-який файл , який PHP (і користувач його запускаються під) можуть читати, так що він може поставити в ReadFile ... Завжди слід дезінфікувати потрібний файл для читання
Prof


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

Цей код розміщує закодовані дані у посиланні та імітує клацання на посиланні, а потім видаляє їх.


Ви можете просто зробити тег прихованим і динамічно заповнювати href. не потрібно додавати та видаляти
BPeela

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;

де файл - це назва файлу, передана в запиті за допомогою 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

    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;
    ...

    $ filename = "Export_Data.xls";
    заголовок ("Тип вмісту: application / vnd.ms-excel");
    заголовок ("Розміщення вмісту: вбудований; ім'я файлу = $ ім'я файлу");

    ...

Сподіваюсь, це рішення може бути корисним для багатьох, як і для мене.


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();
    },
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.