Datei mit einer Ajax-Anfrage herunterladen

95

Ich möchte eine "Ajax-Download-Anfrage" senden, wenn ich auf eine Schaltfläche klicke. Deshalb habe ich Folgendes versucht:

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

funktioniert aber nicht wie erwartet, wie kann ich das machen? Vielen Dank im Voraus

Manuel Di Iorio
quelle
2
Dies wird nicht funktionieren, siehe [diese Frage] [1]. [1]: stackoverflow.com/questions/8771342/…
olegsv
2
Dolocation.href='download.php';
Musa
versuchen Sie diese stackoverflow.com/a/42235655/2282880
Meloman

Antworten:

92

Update 27. April 2015

Das Herunterladen der HTML5-Szene ist das Download-Attribut . Es wird in Firefox und Chrome unterstützt und wird bald in IE11 verfügbar sein. Abhängig von Ihren Anforderungen können Sie es anstelle einer AJAX-Anfrage (oder Verwendung window.location) verwenden, solange sich die Datei, die Sie herunterladen möchten, auf demselben Ursprung wie Ihre Site befindet.

Sie können die AJAX-Anforderung / window.locationeinen Fallback jederzeit durchführen, indem Sie JavaScript verwenden , um zu testen, ob sie downloadunterstützt wird, und wenn nicht, sie auf Aufruf umzustellen window.location.

Ursprüngliche Antwort

Sie können die Download-Eingabeaufforderung nicht durch eine AJAX-Anforderung öffnen, da Sie physisch zu der Datei navigieren müssen, um zum Herunterladen aufzufordern. Stattdessen können Sie eine Erfolgsfunktion verwenden, um zu download.php zu navigieren. Dadurch wird die Download-Eingabeaufforderung geöffnet, die aktuelle Seite wird jedoch nicht geändert.

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

Obwohl dies die Frage beantwortet, ist es besser, window.locationdie AJAX-Anforderung nur zu verwenden und vollständig zu vermeiden.

Steven Lambert
quelle
39
Ruft der Link nicht zweimal auf? Ich bin in einem ähnlichen Boot ... Ich übergebe viele Sicherheitsinformationen in Headern und kann das Dateiobjekt in der Erfolgsfunktion analysieren, weiß aber nicht, wie ich eine Download-Eingabeaufforderung auslösen soll.
user1447679
3
Die Seite wird zweimal aufgerufen. Wenn Sie also eine Datenbank auf dieser Seite abfragen, bedeutet dies zwei Fahrten zur Datenbank.
mmmmmm
1
@ user1447679 siehe für eine alternative Lösung: stackoverflow.com/questions/38665947/…
John
1
Lassen Sie mich erklären, wie mir das geholfen hat ... das Beispiel hätte vollständiger sein können. mit "download.php? get_file = true" oder so ... Ich habe eine Ajax-Funktion, die eine Fehlerprüfung bei einer Formularübermittlung durchführt und dann eine CSV-Datei erstellt. Wenn die Fehlerprüfung fehlschlägt, muss erneut angegeben werden, warum sie fehlgeschlagen ist. Wenn die CSV erstellt wird, wird dem übergeordneten Element mitgeteilt, dass "die Datei abgerufen werden soll". Ich mache das, indem ich mit der Formularvariablen in die Ajax-Datei poste und dann einen DIFFERENT-Parameter in dieselbe Datei poste, der sagt "gib mir die gerade erstellte Datei" (Pfad / Name ist fest in der Ajax-Datei codiert).
Scott
4
Aber es wird Anfrage 2 mal senden, das ist nicht richtig
Dharmendrasinh Chudasama
43

Dafür brauchst du eigentlich gar keinen Ajax. Wenn Sie nur "download.php" als href auf der Schaltfläche festlegen oder wenn es sich nicht um einen Link handelt, verwenden Sie:

window.location = 'download.php';

Der Browser sollte den binären Download erkennen und nicht die eigentliche Seite laden, sondern nur die Datei als Download bereitstellen.

Jelle Kralt
quelle
3
Die Programmiersprache, die Sie zum Ändern verwenden, window.location ist JavaScript.
Mikemaccana
1
Du hast recht @mikemaccana, ich meinte eigentlich Ajax :).
Jelle Kralt
2
Ich habe hoch und niedrig nach einer Lösung gesucht und diese ist so elegant und perfekt. Ich danke dir sehr.
Yangshun Tay
2
Natürlich funktioniert diese Lösung nur, wenn es sich um eine statische Datei handelt, die bereits vorhanden ist.
Krillgar
1
Wenn der Server mit einem Fehler antwortet, gibt es keine Möglichkeit, auf Ihrer Hauptseite zu bleiben, ohne vom Browser auf eine Fehlerseite umgeleitet zu werden. Zumindest ist dies das, was Chrome tut, wenn das Ergebnis von window.location 404 zurückgibt.
Ian
43

Damit der Browser eine Datei herunterlädt, müssen Sie die Anfrage folgendermaßen stellen:

 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
quelle
7
Dies funktioniert für mich, aber in Firefox musste ich zuerst ein <a> -Tag in das DOM einfügen und es als meinen Link referenzieren, anstatt eines im laufenden Betrieb zu erstellen, damit die Datei automatisch heruntergeladen werden kann.
Erik Donohoo
funktioniert, aber was passiert, wenn die Datei während der Ausführung erstellt wird? Es funktioniert nicht so, als ob die Datei bereits erstellt wurde.
Diego
@Taha Ich habe dies auf Edge getestet und es schien zu funktionieren. Ich weiß aber nichts über IE. Mein Client richtet sich nicht an IE-Benutzer ;-) Habe ich Glück? : D
Sнаđошƒаӽ
1
@ErikDonohoo Sie können das <a>Tag auch "on the fly" mit JS erstellen , müssen es aber anhängen document.body. Sie möchten es sicherlich gleichzeitig verstecken.
Márton Tamás
Danke @ João, ich habe sehr lange nach dieser Lösung gesucht.
Abhishek Gharai
20

Browserübergreifende Lösung, getestet auf Chrome, Firefox, Edge, IE11.

Fügen Sie im DOM ein verstecktes Link-Tag hinzu:

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

Dann:

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();
Löwe
quelle
Guter generischer Code. @leo können Sie diesen Code verbessern, indem Sie benutzerdefinierte Header wie hinzufügen Authorization?
Vibs2006
1
Danke @leo. Es ist hilfreich. Auch was schlagen Sie window.URL.revokeObjectURL(el.href);nach dem Hinzufügen vor el.click()?
Vibs2006
Der Dateiname ist falsch, wenn die Inhaltsdisposition einen Nicht-UTF8-Dateinamen angibt.
Mathew Alden
14

Es ist möglich. Sie können den Download beispielsweise innerhalb einer Ajax-Funktion starten, unmittelbar nachdem die CSV-Datei erstellt wurde.

Ich habe eine Ajax-Funktion, die eine Datenbank mit Kontakten in eine CSV-Datei exportiert und unmittelbar nach deren Abschluss automatisch den Download der CSV-Datei startet. Nachdem ich den responseText erhalten habe und alles in Ordnung ist, leite ich den Browser folgendermaßen um:

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

Meine download.php- Datei sieht folgendermaßen aus:

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

?>

Es erfolgt keinerlei Seitenaktualisierung und die Datei wird automatisch heruntergeladen.

HINWEIS - In folgenden Browsern getestet:

Chrome v37.0.2062.120 
Firefox v32.0.1
Opera v12.17
Internet Explorer v11
Pedro Sousa
quelle
1
Ist das nicht gefährlich in Bezug auf die Sicherheit?
Mickael Bergeron Néron
@ MickaelBergeronNéron Warum?
Pedro Sousa
5
Ich würde es mir vorstellen, weil jeder download.php? Filename = [etwas] aufrufen und einige Pfad- und Dateinamen ausprobieren kann, insbesondere allgemeine, und dies könnte sogar innerhalb einer Schleife innerhalb eines Programms oder eines Skripts erfolgen.
Mickael Bergeron Néron
würde .htaccess das nicht vermeiden?
Pedro Sousa
9
@ PedroSousa .. nein. htaccess steuert den Zugriff auf die Dateistruktur über Apache. Da der Zugriff ein PHP-Skript erreicht hat, beendet htaccess nun seine Pflicht. Dies ist SEHR VIEL eine Sicherheitslücke, da tatsächlich jede Datei, die PHP (und der Benutzer, unter dem sie ausgeführt wird) lesen kann, in die Lesedatei übertragen werden kann ... Die angeforderte zu lesende Datei sollte immer
bereinigt
0

Das Dekodieren eines Dateinamens aus dem Header ist etwas komplexer ...

    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
quelle
3
Bitte formatieren Sie Ihren gesamten Codeblock und geben Sie eine zusätzliche Erklärung für Ihren Prozess, damit der zukünftige Leser davon profitieren kann.
Mickmackusa
0

Diese Lösung unterscheidet sich nicht sehr von den oben genannten, aber für mich funktioniert sie sehr gut und ich denke, sie ist sauber.

Ich empfehle, base64 die Dateiserverseite zu codieren (base64_encode (), wenn Sie PHP verwenden) und die base64-codierten Daten an den Client zu senden

Auf dem Client tun Sie dies:

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

Dieser Code fügt die codierten Daten in einen Link ein und simuliert einen Klick auf den Link. Anschließend wird er entfernt.

Martinethyl
quelle
Sie können das a-Tag einfach ausblenden und das href dynamisch füllen. Keine Notwendigkeit hinzuzufügen und zu entfernen
BPeela
0

Ihre Anforderungen werden von abgedeckt. window.location('download.php');
Ich denke jedoch, dass Sie die herunterzuladende Datei übergeben müssen und nicht immer dieselbe Datei herunterladen müssen. Aus diesem Grund verwenden Sie eine Anfrage. Eine Option besteht darin, eine PHP-Datei zu erstellen, die so einfach wie showfile.php und ist mache eine Anfrage wie

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

showfile.php

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

Dabei ist Datei der Dateiname, der in der Anforderung über Get oder Post übergeben wird, und fängt dann die Antwort in einer Funktion einfach ab

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

Es gibt eine andere Lösung zum Herunterladen einer Webseite in Ajax. Ich beziehe mich jedoch auf eine Seite, die zuerst verarbeitet und dann heruntergeladen werden muss.

Zuerst müssen Sie die Seitenverarbeitung vom Ergebnisdownload trennen.

1) Im Ajax-Aufruf werden nur die Seitenberechnungen durchgeführt.

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

       Funktion (Daten, Status) 
       {
            if (status == "success") 
            {
                / * 2) In der Antwort wird die Seite heruntergeladen, die die vorherigen Berechnungen verwendet. Dies kann beispielsweise eine Seite sein, auf der die Ergebnisse einer im Ajax-Aufruf berechneten Tabelle gedruckt werden. * /
                window.location.href = DownloadPage.php + "? ID =" + 29;
            }}               
       }}
);

// Zum Beispiel: in der CalculusPage.php

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

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

// Zum Beispiel: in der DownloadPage.php

    $ ID = $ _GET ["ID"];

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

    $ filename = "Export_Data.xls";
    Header ("Inhaltstyp: application / vnd.ms-excel");
    Header ("Content-Disposition: Inline; Dateiname = $ Dateiname");

    ...

Ich hoffe, diese Lösung kann für viele nützlich sein, so wie es für mich war.

Netluke
quelle
0

Für diejenigen, die einen moderneren Ansatz suchen, können Sie die verwenden fetch API. Das folgende Beispiel zeigt, wie Sie eine Tabellenkalkulationsdatei herunterladen. Mit dem folgenden Code ist dies problemlos möglich.

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

Ich glaube, dieser Ansatz ist viel einfacher zu verstehen als andere XMLHttpRequestLösungen. Außerdem hat es eine ähnliche Syntax wie der jQueryAnsatz, ohne dass zusätzliche Bibliotheken hinzugefügt werden müssen.

Natürlich würde ich empfehlen, zu überprüfen, in welchem ​​Browser Sie entwickeln, da dieser neue Ansatz im IE nicht funktioniert. Die vollständige Browserkompatibilitätsliste finden Sie unter folgendem Link .

Wichtig : In diesem Beispiel sende ich eine JSON-Anfrage an einen Server, der die angegebenen Daten abhört url. Dies urlmuss festgelegt werden. In meinem Beispiel gehe ich davon aus, dass Sie diesen Teil kennen. Berücksichtigen Sie auch die Header, die für Ihre Anfrage erforderlich sind. Da ich einen JSON sende, muss ich den Content-TypeHeader hinzufügen und auf setzen application/json; charset=utf-8, um dem Server den Typ der Anforderung mitzuteilen, die er erhalten wird.

Alain Cruz
quelle
0

Die @ Joao Marcos-Lösung funktioniert für mich, aber ich musste den Code ändern, damit er im IE funktioniert. Unten sehen Sie, wie der Code aussieht

       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
quelle