unduh file menggunakan permintaan ajax

96

Saya ingin mengirim "permintaan unduhan ajax" ketika saya mengklik tombol, jadi saya mencoba dengan cara ini:

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

tetapi tidak berfungsi seperti yang diharapkan, bagaimana saya dapat melakukannya? Terima kasih sebelumnya

Manuel Di Iorio
sumber
2
Ini tidak akan berhasil, lihat [pertanyaan ini] [1]. [1]: stackoverflow.com/questions/8771342/…
olegsv
2
Dolocation.href='download.php';
Musa

Jawaban:

94

Perbarui 27 April 2015

Yang akan datang ke adegan HTML5 adalah atribut unduhan . Ini didukung di Firefox dan Chrome, dan akan segera hadir di IE11. Bergantung pada kebutuhan Anda, Anda dapat menggunakannya sebagai ganti permintaan AJAX (atau menggunakan window.location) selama file yang ingin Anda unduh berasal dari sumber yang sama dengan situs Anda.

Anda selalu dapat membuat permintaan AJAX / window.locationfallback dengan menggunakan beberapa JavaScript untuk menguji apakah downloaddidukung dan jika tidak, mengalihkannya ke panggilan window.location.

Jawaban asli

Anda tidak dapat meminta AJAX membuka prompt unduhan karena Anda secara fisik harus menavigasi ke file untuk meminta unduhan. Sebagai gantinya, Anda dapat menggunakan fungsi sukses untuk menavigasi ke download.php. Ini akan membuka prompt unduhan tetapi tidak akan mengubah halaman saat ini.

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

Meskipun ini menjawab pertanyaannya, lebih baik menggunakan window.locationdan menghindari permintaan AJAX sepenuhnya.

Steven Lambert
sumber
39
Bukankah ini memanggil tautan dua kali? Saya berada dalam perahu yang sama ... Saya menyampaikan banyak informasi keamanan di header, dan dapat mengurai objek file dalam fungsi sukses, tetapi tidak tahu cara memicu permintaan unduhan.
pengguna1447679
3
Itu memanggil halaman dua kali, jadi jika Anda menanyakan database di halaman itu, ini berarti 2 perjalanan ke DB.
mmmmmm
1
@ user1447679 lihat untuk solusi alternatif: stackoverflow.com/questions/38665947/…
John
1
Izinkan saya menjelaskan bagaimana ini membantu saya ... contohnya bisa jadi lebih lengkap. dengan "download.php? get_file = true" atau sesuatu ... Saya memiliki fungsi ajax yang melakukan pemeriksaan kesalahan pada pengiriman formulir dan kemudian membuat file csv. Jika pemeriksaan kesalahan gagal, itu harus kembali dengan mengapa gagal. Jika itu membuat CSV, itu memberi tahu orang tua bahwa "lanjutkan dan ambil file". Saya melakukannya dengan memposting ke file ajax dengan variabel bentuk kemudian memposting parameter yang BERBEDA ke file yang sama dengan mengatakan "berikan saya file yang baru saja Anda buat" (jalur / nama dikodekan dengan keras ke dalam file ajax).
Scott
4
Tetapi itu akan mengirim permintaan 2 kali, itu tidak tepat
Dharmendrasinh Chudasama
46

Untuk membuat browser mengunduh file, Anda perlu membuat permintaan seperti itu:

 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();
 }
João Marcos
sumber
7
Ini berfungsi untuk saya, tetapi di firefox, pertama-tama saya harus meletakkan tag <a> di DOM, dan mereferensikannya sebagai tautan saya daripada membuatnya dengan cepat agar file diunduh secara otomatis.
Erik Donohoo
berfungsi tetapi apa yang terjadi jika file dibuat selama eksekusi? itu tidak berfungsi seperti ketika file sudah dibuat.
Diego
@Taha Saya menguji ini di Edge dan sepertinya berhasil. Tidak tahu tentang IE. Klien saya tidak menargetkan pengguna IE ;-) Apakah saya beruntung? : D
Sнаđошƒаӽ
1
@ErikDonohoo Anda dapat membuat <a>tag dengan JS, "dengan cepat" juga, tetapi harus menambahkannya ke document.body. Anda tentu ingin menyembunyikannya pada saat bersamaan.
Márton Tamás
Terima kasih @ João, saya mencari solusi ini dari waktu yang sangat lama.
Abhishek Gharai
44

Anda sebenarnya tidak membutuhkan ajax sama sekali untuk ini. Jika Anda baru saja menyetel "download.php" sebagai href pada tombol, atau, jika bukan tautan, gunakan:

window.location = 'download.php';

Browser harus mengenali unduhan biner dan tidak memuat halaman sebenarnya tetapi hanya menyajikan file sebagai unduhan.

Jelle Kralt
sumber
3
Bahasa pemrograman yang Anda gunakan untuk mengubah window.location adalah JavaScript.
mikemaccana
1
Anda benar @mikemaccana, maksud saya sebenarnya ajax :).
Jelle Kralt
2
Telah berburu tinggi dan rendah untuk solusi dan ini sangat elegan dan sempurna. Terima kasih banyak.
Yangshun Tay
2
Tentu saja, solusi ini hanya akan berfungsi jika itu adalah file statis yang sudah ada.
krillgar
1
Jika server merespons dengan kesalahan meskipun tidak akan ada cara untuk tetap berada di halaman utama Anda tanpa dialihkan ke halaman kesalahan oleh browser. Setidaknya inilah yang dilakukan Chrome ketika hasil window.location mengembalikan 404.
Ian
21

Solusi lintas browser, diuji di Chrome, Firefox, Edge, IE11.

Di DOM, tambahkan tag tautan tersembunyi:

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

Kemudian:

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
sumber
Kode Umum yang Baik. @leo dapatkah Anda meningkatkan kode ini dengan menambahkan tajuk khusus seperti Authorization?
vibs2006
1
Terima kasih @leo. Ini membantu. Juga apa yang Anda sarankan untuk ditambahkan window.URL.revokeObjectURL(el.href);setelahnya el.click()?
vibs2006
Nama file akan salah jika disposisi konten menentukan nama file non-UTF8.
Mathew Alden
14

Itu mungkin. Anda dapat memulai pengunduhan dari dalam fungsi ajax, misalnya, tepat setelah file .csv dibuat.

Saya memiliki fungsi ajax yang mengekspor database kontak ke file .csv, dan setelah selesai, secara otomatis memulai pengunduhan file .csv. Jadi, setelah saya mendapatkan responseText dan semuanya baik-baik saja, saya mengarahkan browser seperti ini:

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

File download.php saya terlihat seperti ini:

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

?>

Tidak ada penyegaran halaman apa pun dan file secara otomatis mulai mengunduh.

CATATAN - Diuji di browser berikut:

Chrome v37.0.2062.120 
Firefox v32.0.1
Opera v12.17
Internet Explorer v11
Pedro Sousa
sumber
1
Bukankah ini keamanan yang berbahaya?
Mickael Bergeron Néron
@ MickaelBergeronNéron Mengapa?
Pedro Sousa
5
Saya akan berpikir demikian karena siapa pun dapat memanggil download.php? Filename = [sesuatu] dan mencoba beberapa jalur dan nama file, terutama yang umum, dan ini bahkan dapat dilakukan di dalam loop dalam program atau skrip.
Mickael Bergeron Néron
bukankah .htaccess menghindarinya?
Pedro Sousa
9
@Pedousa .. tidak. htaccess mengontrol akses ke struktur file melalui Apache. Karena akses telah mencapai skrip PHP, htaccess sekarang menghentikan tugasnya. Ini SANGAT BANYAK lubang keamanan karena memang, file apa pun yang dapat dibaca oleh PHP (dan pengguna yang menjalankannya), sehingga dapat dikirim ke readfile ... Seseorang harus selalu membersihkan file yang diminta untuk dibaca
Prof
0

Mendekode nama file dari header sedikit lebih rumit ...

    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, '');
    }
Lumic
sumber
3
Harap format seluruh blok kode Anda dan berikan beberapa penjelasan tambahan pada proses Anda untuk keuntungan pembaca di masa mendatang.
mickmackusa
0

Solusi ini tidak jauh berbeda dengan yang di atas, tetapi bagi saya ini berfungsi dengan sangat baik dan menurut saya bersih.

Saya sarankan untuk menyandikan base64 sisi server file (base64_encode (), jika Anda menggunakan PHP) dan mengirim data yang disandikan base64 ke klien

Pada klien Anda melakukan ini:

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

Kode ini menempatkan data yang dikodekan dalam sebuah tautan dan mensimulasikan klik pada tautan tersebut, lalu menghapusnya.

martinetil
sumber
Anda bisa membuat tag tersembunyi dan mengisi href secara dinamis. tidak perlu menambah dan menghapus
BPeela
0

Kebutuhan Anda tercakup oleh window.location('download.php');
Tetapi saya pikir Anda harus meneruskan file untuk diunduh, tidak selalu mengunduh file yang sama, dan itulah mengapa Anda menggunakan permintaan, salah satu opsi adalah membuat file php sesederhana showfile.php dan melakukan permintaan seperti

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

showfile.php

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

di mana file adalah nama file yang diteruskan melalui Get atau Post dalam permintaan dan kemudian menangkap responnya dalam sebuah fungsi dengan mudah

if(ajaxRequest.readyState == 4){
                        var file = ajaxRequest.responseText;
                        window.location = 'downfile.php?file=' + file;  
                    }
                }
lisandro
sumber
0

ada solusi lain untuk mendownload halaman web di ajax. Tapi saya mengacu pada halaman yang harus diproses terlebih dahulu dan kemudian diunduh.

Pertama, Anda perlu memisahkan pemrosesan halaman dari hasil unduhan.

1) Hanya penghitungan halaman yang dilakukan dalam panggilan ajax.

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

       fungsi (data, status) 
       {
            if (status == "sukses") 
            {
                / * 2) Dalam jawaban halaman yang menggunakan perhitungan sebelumnya diunduh. Misalnya, ini bisa menjadi halaman yang mencetak hasil tabel yang dihitung dalam panggilan ajax. * /
                window.location.href = DownloadPage.php + "? ID =" + 29;
            }               
       }
);

// Misalnya: di CalculusPage.php

    if (! empty ($ _ POST ["calculusFunction"])) 
    {
        $ ID = $ _POST ["ID"];

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

// Misalnya: di DownloadPage.php

    $ ID = $ _GET ["ID"];

    $ sede = "PILIH * DARI ExamplePage WHERE id =". $ ID;
    ...

    $ filename = "Export_Data.xls";
    header ("Jenis-Konten: application / vnd.ms-excel");
    header ("Content-Disposition: inline; filename = $ filename");

    ...

Saya berharap solusi ini dapat bermanfaat bagi banyak orang, seperti bagi saya.

netluke
sumber
0

Bagi mereka yang mencari pendekatan yang lebih modern, Anda dapat menggunakan fetch API. Contoh berikut menunjukkan cara mendownload file spreadsheet. Itu mudah dilakukan dengan kode berikut.

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

Saya yakin pendekatan ini jauh lebih mudah dipahami daripada XMLHttpRequestsolusi lain . Juga, ia memiliki sintaks yang mirip dengan jQuerypendekatan tersebut, tanpa perlu menambahkan pustaka tambahan.

Tentu saja, saya akan menyarankan untuk memeriksa browser mana yang Anda kembangkan, karena pendekatan baru ini tidak akan berfungsi di IE. Anda dapat menemukan daftar kompatibilitas browser lengkap pada tautan berikut .

Penting : Dalam contoh ini saya mengirim permintaan JSON ke server yang mendengarkan yang diberikan url. Ini urlharus diatur, pada contoh saya, saya berasumsi Anda tahu bagian ini. Juga, pertimbangkan tajuk yang diperlukan agar permintaan Anda berfungsi. Karena saya mengirim JSON, saya harus menambahkan Content-Typeheader dan mengaturnya ke application/json; charset=utf-8, agar server mengetahui jenis permintaan yang akan diterimanya.

Alain Cruz
sumber
0

Solusi @ Joao Marcos berfungsi untuk saya tetapi saya harus memodifikasi kode agar berfungsi di IE, di bawah ini jika seperti apa kodenya

       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();
    },
Jemil Oyebisi
sumber