bir ajax isteği kullanarak dosya indirme


96

Bir düğmeye tıkladığımda bir "ajax indirme isteği" göndermek istiyorum, bu yüzden şu şekilde denedim:

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

ama beklendiği gibi çalışmıyor, nasıl yapabilirim? Şimdiden teşekkür ederim


2
Bu işe yaramayacak, [bu soruya] [1] bakın. [1]: stackoverflow.com/questions/8771342/…
olegsv

2
Dolocation.href='download.php';
Musa

Yanıtlar:


94

27 Nisan 2015 Güncellemesi

HTML5 sahnesine gelen ve gelen, indirme özelliğidir . Bu oluyor desteklenen Firefox ve Chrome'da ve yakında IE11 gelmek için. İhtiyaçlarınıza bağlı window.locationolarak, indirmek istediğiniz dosya sitenizle aynı kaynakta olduğu sürece AJAX isteği yerine (veya kullanmak ) kullanabilirsiniz.

Desteklenip desteklenmediğini test etmek window.locationiçin bazı JavaScript kullanarak downloadve desteklenmiyorsa, çağrıya geçirerek AJAX isteğini / geri dönüşünü her zaman yapabilirsiniz window.location.

Orijinal cevap

İndirmeyi istemek için fiziksel olarak dosyaya gitmeniz gerektiğinden, bir AJAX isteğinin indirme istemini açmasına izin veremezsiniz. Bunun yerine, download.php'ye gitmek için bir başarı işlevi kullanabilirsiniz. Bu, indirme istemini açacak ancak mevcut sayfayı değiştirmeyecektir.

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

Bu soruyu window.locationyanıtlasa da, AJAX isteğini tamamen kullanmak ve önlemek daha iyidir .


39
Bu bağlantıyı iki kez aramıyor mu? Benzer bir teknedeyim ... Başlıklarda çok sayıda güvenlik bilgisi aktarıyorum ve başarı işlevinde dosya nesnesini ayrıştırabiliyorum, ancak bir indirme istemini nasıl tetikleyeceğimi bilmiyorum.
user1447679

3
Sayfayı iki kez çağırır, bu nedenle bu sayfadaki bir veritabanını sorguluyorsanız, bu DB'ye 2 gezi anlamına gelir.
mmmmmm

1
@ user1447679 alternatif bir çözüm için bakın: stackoverflow.com/questions/38665947/…
John

1
Bunun bana nasıl yardımcı olduğunu açıklayayım ... örnek daha eksiksiz olabilirdi. "download.php? get_file = true" veya başka bir şeyle ... Bir form gönderimi üzerinde bazı hataları kontrol eden ve ardından bir csv dosyası oluşturan bir ajax işlevim var. Hata kontrolü başarısız olursa, neden başarısız olduğunu bildirmek zorundadır. CSV'yi oluşturursa, üst öğeye "devam et ve dosyayı getir" diyor demektir. Bunu ajax dosyasına form değişkeniyle göndererek ve ardından aynı dosyaya "az önce oluşturduğunuz dosyayı bana ver" diyerek FARKLI bir parametre göndererek yapıyorum (yol / ad ajax dosyasına sabit kodlanmıştır).
Scott

4
Ancak 2 kez istek gönderecek, bu uygun değil
Dharmendrasinh Chudasama

48

Tarayıcının bir dosya indirmesini sağlamak için aşağıdaki gibi istekte bulunmanız gerekir:

 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
Bu benim için işe yarıyor, ancak firefox'ta, dosyanın otomatik olarak indirilmesi için önce DOM'a bir <a> etiketi koymam ve anında bir tane oluşturmak yerine ona bağlantım olarak başvurmam gerekiyordu.
Erik Donohoo

çalışıyor ancak dosya yürütme sırasında oluşturuluyorsa ne olur? dosya zaten oluşturulduğu zamanki gibi çalışmaz.
Diego

@Taha Bunu Edge'de test ettim ve işe yaradı. Yine de IE hakkında bilmiyorum. Müşterim IE kullanıcılarını hedeflemiyor ;-) Şanslı mıyım? : D
Sнаđошƒаӽ

1
@ErikDonohoo <a>JS ile etiketi "anında" da oluşturabilirsiniz, ancak eklemelisiniz document.body. Kesinlikle aynı anda saklamak istiyorsun.
Márton Tamás

Teşekkürler @ João, bu çözümü çok uzun zamandır arıyordum.
Abhishek Gharai

44

Aslında bunun için ajax'a hiç ihtiyacınız yok. "Download.php" yi düğme üzerinde href olarak ayarlarsanız veya bir bağlantı değilse:

window.location = 'download.php';

Tarayıcı ikili indirmeyi tanımalı ve asıl sayfayı yüklememeli, sadece dosyayı bir indirme olarak sunmalıdır.


3
Eğer değişim için kullandığınız programlama dili window.location olan JavaScript.
mikemaccana

1
Haklısın @mikemaccana, aslında ajax'ı kastetmiştim :).
Jelle Kralt

2
Bir çözüm için yüksek ve alçak bir arayış içindeyim ve bu çok zarif ve mükemmel. Çok teşekkür ederim.
Yangshun Tay

2
Elbette, bu çözüm yalnızca zaten var olan statik bir dosyaysa işe yarayacaktır.
krillgar

1
Sunucu bir hatayla yanıt verirse, tarayıcı tarafından bir hata sayfasına yönlendirilmeden ana sayfanızda kalmanın bir yolu yoktur. En azından window.location sonucu 404'ü döndürdüğünde Chrome'un yaptığı şey budur.
Ian,

21

Çapraz tarayıcı çözümü, Chrome, Firefox, Edge, IE11'de test edildi.

DOM'da gizli bir bağlantı etiketi ekleyin:

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

Sonra:

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

İyi Genel Kod. @leo gibi özel başlıklar ekleyerek bu kodu geliştirebilir misiniz Authorization?
vibs2006

1
Teşekkürler @leo. Yararlı.Ayrıca, window.URL.revokeObjectURL(el.href);sonrasına ne eklemeyi önerirsiniz el.click()?
vibs2006

İçerik düzeni UTF8 olmayan bir dosya adı belirtiyorsa dosya adı yanlış olacaktır.
Mathew Alden

14

Bu mümkün. İndirmenin bir ajax işlevi içinden, örneğin .csv dosyası oluşturulduktan hemen sonra başlamasını sağlayabilirsiniz.

Bir kişi veritabanını bir .csv dosyasına dışa aktaran bir ajax işlevim var ve tamamlandıktan hemen sonra .csv dosyası indirmeyi otomatik olarak başlatıyor. Bu yüzden, responseText'i aldıktan ve her şey yolunda olduktan sonra, tarayıcıyı şu şekilde yeniden yönlendiriyorum:

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

Benim indir.php böyle dosya görünüyor:

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

?>

Herhangi bir sayfa yenilemesi yoktur ve dosya otomatik olarak indirilmeye başlar.

NOT - Aşağıdaki tarayıcılarda test edilmiştir:

Chrome v37.0.2062.120 
Firefox v32.0.1
Opera v12.17
Internet Explorer v11

1
Bu, güvenlik açısından tehlikeli değil mi?
Mickael Bergeron Néron

@ MickaelBergeronNéron Neden?
Pedro Sousa

5
Böyle düşünüyorum çünkü herkes download.php? Dosyaadı = [bir şey] 'i çağırabilir ve bazı yol ve dosya adlarını deneyebilir, özellikle de yaygın olanları ve bu bir program veya komut dosyası içindeki bir döngü içinde bile yapılabilir.
Mickael Bergeron Néron

.htaccess bundan kaçınmaz mı?
Pedro Sousa

9
@PedroSousa .. hayır. htaccess, Apache aracılığıyla dosya yapısına erişimi kontrol eder. Erişim bir PHP betiğine ulaştığından, htaccess artık görevini durduruyor. Bu ÇOK ÇOK bir güvenlik açığıdır çünkü PHP'nin (ve altında çalıştırıldığı kullanıcının) okuyabileceği herhangi bir dosya, böylece okuma dosyasına teslim edilebilir ... Okunmak için istenen dosyayı her zaman sterilize etmek gerekir
Prof


0

Başlıktan bir dosya adını çözmek biraz daha karmaşıktır ...

    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
Lütfen kod bloğunuzun tamamını biçimlendirin ve ileride okuyucuya yarar sağlamak için sürecinize bazı ek açıklamalar sağlayın.
mickmackusa

0

Bu çözüm yukarıdakilerden çok farklı değil ama benim için çok iyi çalışıyor ve bence temiz.

Dosya sunucusu tarafını base64 kodlamanızı (PHP kullanıyorsanız base64_encode ()) ve base64 kodlu verileri istemciye göndermenizi öneririm

İstemcide şunu yaparsınız:

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

Bu kod, kodlanmış verileri bir bağlantıya yerleştirir ve bağlantıya yapılan tıklamayı simüle eder, ardından kaldırır.


Sadece a etiketini gizleyebilir ve href'i dinamik olarak doldurabilirsiniz. eklemeye ve kaldırmaya gerek yok
BPeela

0

İhtiyaçlarınız karşılanıyor window.location('download.php');
Ancak, indirilecek dosyayı iletmeniz gerektiğini düşünüyorum, her zaman aynı dosyayı indirmemelisiniz ve bu yüzden bir istek kullanıyorsunuz, bir seçenek showfile.php kadar basit bir php dosyası oluşturmaktır ve gibi bir istek yap

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

showfile.php

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

burada dosya, istekte Al veya Gönder aracılığıyla iletilen dosya adıdır ve ardından yanıtı bir işlevde yakalar

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

0

ajax'ta bir web sayfası indirmek için başka bir çözüm var. Ama önce işlenmesi ve sonra indirilmesi gereken bir sayfadan bahsediyorum.

İlk önce sayfa işlemeyi sonuç indirmesinden ayırmanız gerekir.

1) Ajax çağrısında sadece sayfa hesaplamaları yapılır.

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

       işlev (veriler, durum) 
       {
            eğer (status == "başarılı") 
            {
                / * 2) Cevapta önceki hesaplamaları kullanan sayfa indirilir. Örneğin bu, ajax çağrısında hesaplanan bir tablonun sonuçlarını yazdıran bir sayfa olabilir. * /
                window.location.href = DownloadPage.php + "? ID =" + 29;
            }               
       }
);

// Örneğin: CalculusPage.php içinde

    eğer (! boş ($ _ POST ["hesapFonksiyonu"])) 
    {
        $ ID = $ _POST ["ID"];

        $ sorgu = "INSERT INTO ExamplePage (data1, data2) DEĞERLER ('". $ _ POST ["data1"]. "', '". $ _ POST ["data2"]. "') WHERE id =". $ ID;
        ...
    }

// Örneğin: DownloadPage.php'de

    $ ID = $ _GET ["ID"];

    $ sede = "SELECT * FROM ExamplePage WHERE id =". $ ID;
    ...

    $ dosyaadı = "Export_Data.xls";
    başlık ("İçerik Türü: uygulama / vnd.ms-excel");
    başlık ("Content-Disposition: inline; dosyaadı = $ dosyaadı");

    ...

Umarım bu çözüm benim için olduğu gibi birçokları için de yararlı olabilir.


0

Daha modern bir yaklaşım arayanlar için fetch API. Aşağıdaki örnek, bir elektronik tablo dosyasının nasıl indirileceğini gösterir. Aşağıdaki kod ile kolaylıkla yapılır.

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

Bu yaklaşımın diğer XMLHttpRequestçözümlerden çok daha kolay anlaşılacağına inanıyorum . Ayrıca, jQueryherhangi bir ek kitaplık eklemeye gerek kalmadan yaklaşıma benzer bir sözdizimi vardır .

Elbette, hangi tarayıcıyı geliştirdiğinizi kontrol etmenizi tavsiye ederim, çünkü bu yeni yaklaşım IE'de çalışmayacaktır. Tam tarayıcı uyumluluk listesini aşağıdaki bağlantıda bulabilirsiniz .

Önemli : Bu örnekte, verileni dinleyen bir sunucuya JSON isteği gönderiyorum url. Bu urlayarlanmalıdır, örneğimde bu bölümü bildiğinizi varsayıyorum. Ayrıca, isteğinizin çalışması için gereken başlıkları da göz önünde bulundurun. Bir JSON gönderdiğim için, Content-Typebaşlığı eklemeli ve şu şekilde ayarlamalıyım:application/json; charset=utf-8 , sunucunun alacağı istek türünü bilmesini sağlamak .


0

@Joao Marcos çözümü benim için çalışıyor ancak IE'de çalışması için kodu değiştirmem gerekiyordu, eğer kod neye benziyorsa aşağıda

       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();
    },
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.