it-swarm.com.de

Handle Datei-Download von Ajax-Post

Ich habe eine Javascript-App, die Ajax-Anfragen POST an eine bestimmte URL sendet. Die Antwort kann eine JSON-Zeichenfolge oder eine Datei (als Anhang) sein. Ich kann Content-Type und Content-Disposition in meinem Ajax-Aufruf leicht erkennen. Wenn ich jedoch feststelle, dass die Antwort eine Datei enthält, wie biete ich dem Client an, diese herunterzuladen? Ich habe hier eine Reihe ähnlicher Themen gelesen, aber keines liefert die Antwort, nach der ich suche.

Bitte, bitte, bitte posten Sie keine Antworten, die darauf hindeuten, dass ich dafür kein Ajax verwenden oder den Browser umleiten sollte, da dies keine Option ist. Die Verwendung eines einfachen HTML-Formulars ist ebenfalls keine Option. Ich muss dem Client einen Download-Dialog anzeigen. Kann das gemacht werden und wie?

BEARBEITEN:

Anscheinend ist dies nicht möglich, aber es gibt eine einfache Problemumgehung, wie in der akzeptierten Antwort vorgeschlagen. Für alle, die in Zukunft auf dieses Problem stoßen, habe ich es folgendermaßen gelöst:

$.ajax({
    type: "POST",
    url: url,
    data: params,
    success: function(response, status, request) {
        var disp = request.getResponseHeader('Content-Disposition');
        if (disp && disp.search('attachment') != -1) {
            var form = $('<form method="POST" action="' + url + '">');
            $.each(params, function(k, v) {
                form.append($('<input type="hidden" name="' + k +
                        '" value="' + v + '">'));
            });
            $('body').append(form);
            form.submit();
        }
    }
});

Generieren Sie einfach ein HTML-Formular mit denselben Parametern, die in der AJAX -Anforderung verwendet wurden, und senden Sie es ab.

367
Pavle Predic

Erstellen Sie ein Formular, verwenden Sie die POST -Methode, senden Sie das Formular ab - ein iframe ist nicht erforderlich. Wenn die Serverseite auf die Anforderung antwortet, schreiben Sie einen Antwortheader für den MIME-Typ der Datei. Daraufhin wird ein Dialogfeld zum Herunterladen angezeigt. Dies habe ich einige Male ausgeführt.

Sie möchten eine Anwendung/einen Download nach Inhaltstypen - suchen Sie einfach, wie Sie einen Download für die von Ihnen verwendete Sprache bereitstellen können.

108
user571545

Geben Sie nicht so schnell auf, denn dies kann (in modernen Browsern) mit Teilen der FileAPI erfolgen:

Edit 2017-09-28: Aktualisiert, um den Dateikonstruktor zu verwenden, wenn dieser verfügbar ist, sodass er in Safari> = 10.1 funktioniert.

Edit 2015-10-16: jQuery ajax kann Binärantworten nicht richtig verarbeiten (responseType kann nicht festgelegt werden), daher ist es besser, einen einfachen XMLHttpRequest-Aufruf zu verwenden.

var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.responseType = 'arraybuffer';
xhr.onload = function () {
    if (this.status === 200) {
        var filename = "";
        var disposition = xhr.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 type = xhr.getResponseHeader('Content-Type');

        var blob = typeof File === 'function'
            ? new File([this.response], filename, { type: type })
            : new Blob([this.response], { type: type });
        if (typeof window.navigator.msSaveBlob !== 'undefined') {
            // IE workaround for "HTML7007: One or more blob URLs were revoked by closing the blob for which they were created. These URLs will no longer resolve as the data backing the URL has been freed."
            window.navigator.msSaveBlob(blob, filename);
        } else {
            var URL = window.URL || window.webkitURL;
            var downloadUrl = URL.createObjectURL(blob);

            if (filename) {
                // use HTML5 a[download] attribute to specify filename
                var a = document.createElement("a");
                // safari doesn't support this yet
                if (typeof a.download === 'undefined') {
                    window.location = downloadUrl;
                } else {
                    a.href = downloadUrl;
                    a.download = filename;
                    document.body.appendChild(a);
                    a.click();
                }
            } else {
                window.location = downloadUrl;
            }

            setTimeout(function () { URL.revokeObjectURL(downloadUrl); }, 100); // cleanup
        }
    }
};
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.send($.param(params));

Hier ist die alte Version mit jQuery.ajax. Es kann zu einer Verwirrung von Binärdaten kommen, wenn die Antwort in eine Zeichenfolge aus einem Zeichensatz konvertiert wird.

$.ajax({
    type: "POST",
    url: url,
    data: params,
    success: function(response, status, xhr) {
        // check for a filename
        var filename = "";
        var disposition = xhr.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 type = xhr.getResponseHeader('Content-Type');
        var blob = new Blob([response], { type: type });

        if (typeof window.navigator.msSaveBlob !== 'undefined') {
            // IE workaround for "HTML7007: One or more blob URLs were revoked by closing the blob for which they were created. These URLs will no longer resolve as the data backing the URL has been freed."
            window.navigator.msSaveBlob(blob, filename);
        } else {
            var URL = window.URL || window.webkitURL;
            var downloadUrl = URL.createObjectURL(blob);

            if (filename) {
                // use HTML5 a[download] attribute to specify filename
                var a = document.createElement("a");
                // safari doesn't support this yet
                if (typeof a.download === 'undefined') {
                    window.location = downloadUrl;
                } else {
                    a.href = downloadUrl;
                    a.download = filename;
                    document.body.appendChild(a);
                    a.click();
                }
            } else {
                window.location = downloadUrl;
            }

            setTimeout(function () { URL.revokeObjectURL(downloadUrl); }, 100); // cleanup
        }
    }
});
495
Jonathan Amend

Ich sah mich dem gleichen Problem gegenüber und löste es erfolgreich. Mein Anwendungsfall ist dies.

"Stellen Sie JSON-Daten auf dem Server bereit und erhalten Sie eine Excel-Datei. Diese Excel-Datei wird vom Server erstellt und als Antwort an den Client zurückgegeben. Laden Sie diese Antwort als Datei mit einem benutzerdefinierten Namen im Browser herunter"

$("#my-button").on("click", function(){

// Data to post
data = {
    ids: [1, 2, 3, 4, 5]
};

// Use XMLHttpRequest instead of Jquery $ajax
xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
    var a;
    if (xhttp.readyState === 4 && xhttp.status === 200) {
        // Trick for making downloadable link
        a = document.createElement('a');
        a.href = window.URL.createObjectURL(xhttp.response);
        // Give filename you wish to download
        a.download = "test-file.xls";
        a.style.display = 'none';
        document.body.appendChild(a);
        a.click();
    }
};
// Post data to URL which handles post request
xhttp.open("POST", excelDownloadUrl);
xhttp.setRequestHeader("Content-Type", "application/json");
// You should set responseType as blob for binary responses
xhttp.responseType = 'blob';
xhttp.send(JSON.stringify(data));
});

Das obige Snippet ist nur das Folgende

  • Posten eines Arrays als JSON auf dem Server mithilfe von XMLHttpRequest.
  • Nachdem wir den Inhalt als Blob (binär) abgerufen haben, erstellen wir eine herunterladbare URL und hängen sie an einen unsichtbaren "A" -Link an. Klicken Sie dann darauf.

Hier müssen wir einige Dinge auf der Serverseite sorgfältig einstellen. Ich habe einige Header in Python Django HttpResponse gesetzt. Sie müssen sie entsprechend einstellen, wenn Sie andere Programmiersprachen verwenden.

# In python Django code
response = HttpResponse(file_content, content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")

Da ich hier xls (Excel) herunterlade, habe ich contentType auf über eins eingestellt. Sie müssen es entsprechend Ihrem Dateityp einstellen. Mit dieser Technik können Sie alle Arten von Dateien herunterladen.

32
Naren Yellavula

Welche serverseitige Sprache verwenden Sie? In meiner App kann ich einfach eine Datei aus einem AJAX -Aufruf herunterladen, indem ich die richtigen Header in der PHP-Antwort setze:

Headers serverseitig setzen

header("HTTP/1.1 200 OK");
header("Pragma: public");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");

// The optional second 'replace' parameter indicates whether the header
// should replace a previous similar header, or add a second header of
// the same type. By default it will replace, but if you pass in FALSE
// as the second argument you can force multiple headers of the same type.
header("Cache-Control: private", false);

header("Content-type: " . $mimeType);

// $strFileName is, of course, the filename of the file being downloaded. 
// This won't have to be the same name as the actual file.
header("Content-Disposition: attachment; filename=\"{$strFileName}\""); 

header("Content-Transfer-Encoding: binary");
header("Content-Length: " . mb_strlen($strFile));

// $strFile is a binary representation of the file that is being downloaded.
echo $strFile;

Dadurch wird der Browser in der Tat auf diese Download-Seite umgeleitet, aber wie @ahren bereits in seinem Kommentar sagte, wird er nicht von der aktuellen Seite weg navigieren.

Es geht nur darum, die richtigen Header zu setzen, damit Sie sicher eine geeignete Lösung für die von Ihnen verwendete serverseitige Sprache finden, wenn es sich nicht um PHP handelt.

Behandlung der Antwort Client-Seite

Vorausgesetzt, Sie wissen bereits, wie ein AJAX -Aufruf ausgeführt wird, führen Sie auf der Clientseite eine AJAX -Anforderung an den Server aus. Der Server erzeugt dann einen Link, von dem diese Datei heruntergeladen werden kann, z. Die Weiterleitungs-URL, auf die Sie verweisen möchten. Der Server antwortet beispielsweise mit:

{
    status: 1, // ok
    // unique one-time download token, not required of course
    message: 'http://yourwebsite.com/getdownload/ska08912dsa'
}

Bei der Verarbeitung der Antwort injizieren Sie ein iframe in Ihren Körper und setzen den SRC von iframe auf die soeben erhaltene URL (mit jQuery zur Vereinfachung dieses Beispiels):

$("body").append("<iframe src='" + data.message +
  "' style='display: none;' ></iframe>");

Wenn Sie die richtigen Überschriften wie oben angegeben festgelegt haben, erzwingt der Iframe einen Download-Dialog, ohne den Browser von der aktuellen Seite zu entfernen.

Hinweis

Zusätzlicher Zusatz in Bezug auf Ihre Frage; Ich denke, es ist am besten, immer JSON zurückzugeben, wenn Sie Dinge mit der AJAX -Technologie anfordern. Nachdem Sie die JSON-Antwort erhalten haben, können Sie clientseitig entscheiden, was damit geschehen soll. Vielleicht möchten Sie später, dass der Benutzer auf einen Download-Link zur URL klickt, anstatt den Download direkt zu erzwingen. In Ihrem aktuellen Setup müssten Sie dazu sowohl die Client- als auch die Serverseite aktualisieren.

31

Für diejenigen, die nach einer Lösung aus einer Angular Perspektive suchen, funktionierte dies für mich:

$http.post(
  'url',
  {},
  {responseType: 'arraybuffer'}
).then(function (response) {
  var headers = response.headers();
  var blob = new Blob([response.data],{type:headers['content-type']});
  var link = document.createElement('a');
  link.href = window.URL.createObjectURL(blob);
  link.download = "Filename";
  link.click();
});
22
Tim Hettler

Hier ist, wie ich das funktioniert habe https://stackoverflow.com/a/27563953/2845977

$.ajax({
  url: '<URL_TO_FILE>',
  success: function(data) {
    var blob=new Blob([data]);
    var link=document.createElement('a');
    link.href=window.URL.createObjectURL(blob);
    link.download="<FILENAME_TO_SAVE_WITH_EXTENSION>";
    link.click();
  }
});

Aktualisierte Antwort mit download.js

$.ajax({
  url: '<URL_TO_FILE>',
  success: download.bind(true, "<FILENAME_TO_SAVE_WITH_EXTENSION>", "<FILE_MIME_TYPE>")
});
14
Mayur Padshala

Wie ich sehe, haben Sie bereits eine Lösung gefunden. Ich wollte jedoch nur einige Informationen hinzufügen, die jemandem helfen können, mit großen POST Anfragen dasselbe zu erreichen.

Ich hatte vor ein paar Wochen das gleiche Problem. Tatsächlich ist es nicht möglich, über AJAX einen "sauberen" Download zu erzielen. Die Filament Group hat ein jQuery-Plugin erstellt, das genau so funktioniert, wie Sie es bereits herausgefunden haben. jQuery File Download Diese Technik hat jedoch einen Nachteil.

Wenn Sie große Anforderungen über AJAX senden (z. B. Dateien + 1 MB), wirkt sich dies negativ auf die Reaktionszeit aus. Bei langsamen Internetverbindungen müssen Sie eine Menge warten, bis die Anforderung gesendet wurde, und auch warten, bis die Datei heruntergeladen wurde. Es ist nicht wie ein sofortiger "Klick" => "Popup" => "Download-Start". Es ist eher so, als ob "click" => "warte, bis Daten gesendet werden" => "warte auf Antwort" => "download start", wodurch die Datei doppelt so groß erscheint, da Sie warten müssen, bis die Anfrage gesendet wird durch AJAX und erhalte es als herunterladbare Datei zurück.

Wenn Sie mit kleinen Dateigrößen <1 MB arbeiten, werden Sie dies nicht bemerken. Aber wie ich in meiner eigenen App herausgefunden habe, ist es für größere Dateien fast unerträglich.

Meine App ermöglicht es Benutzern, dynamisch generierte Bilder zu exportieren. Diese Bilder werden über POST -Anforderungen im Base64-Format an den Server gesendet (dies ist der einzige mögliche Weg), dann verarbeitet und in Form von .png an die Benutzer zurückgesendet , .jpg-Dateien, base64-Zeichenfolgen für Bilder + 1 MB sind riesig. Dadurch müssen Benutzer mehr als nötig warten, bis die Datei heruntergeladen wird. Bei langsamen Internetverbindungen kann es sehr ärgerlich sein.

Meine Lösung dafür war, die Datei temporär auf den Server zu schreiben, sobald sie fertig ist, dynamisch einen Link zu der Datei in Form einer Schaltfläche zu generieren, die zwischen den Zuständen "Bitte warten ..." und "Herunterladen" und gleichzeitig wechselt Drucken Sie das base64-Bild in einem Vorschau-Popup-Fenster, damit Benutzer mit der rechten Maustaste darauf klicken und es speichern können. Dies macht die Wartezeit für die Benutzer erträglicher und beschleunigt die Dinge.

Update 30. September 2014:

Es sind Monate vergangen, seit ich dies gepostet habe. Endlich habe ich einen besseren Ansatz gefunden, um die Arbeit mit großen base64-Strings zu beschleunigen. Ich speichere nun Base64-Strings in der Datenbank (unter Verwendung von Langtext- oder Longblog-Feldern), übergebe dann die Datensatz-ID über den jQuery-Dateidownload und frage schließlich die Datenbank mit dieser ID ab, um den Base64-String abzurufen und zu übergeben die Download-Funktion.

Beispiel für das Herunterladen eines Skripts:

<?php
// Record ID
$downloadID = (int)$_POST['id'];
// Query Data (this example uses CodeIgniter)
$data       = $CI->MyQueries->GetDownload( $downloadID );
// base64 tags are replaced by [removed], so we strip them out
$base64     = base64_decode( preg_replace('#\[removed\]#', '', $data[0]->image) );
// This example is for base64 images
$imgsize    = getimagesize( $base64 );
// Set content headers
header('Content-Disposition: attachment; filename="my-file.png"');
header('Content-type: '.$imgsize['mime']);
// Force download
echo $base64;
?>

Ich weiß, dass dies weit über das hinausgeht, worum das OP gebeten hat, aber ich hielt es für gut, meine Antwort mit meinen Erkenntnissen zu aktualisieren. Als ich nach Lösungen für mein Problem suchte, las ich viele "Herunterladen von AJAX POST Daten" Threads, die mir nicht die Antwort gaben, nach der ich gesucht habe. Ich hoffe, diese Informationen helfen jemandem, der nach einer solchen Lösung sucht.

12
José SAYAGO

Ich möchte auf einige Schwierigkeiten hinweisen, die bei der Verwendung der Technik in der akzeptierten Antwort auftreten, d. H. Bei Verwendung eines Formularposts:

  1. Sie können keine Header für die Anforderung festlegen. Wenn Ihr Authentifizierungsschema Header, ein im Authorization-Header übergebenes Json-Web-Token, enthält, müssen Sie eine andere Methode zum Senden finden, z. B. als Abfrageparameter.

  2. Sie können nicht wirklich sagen, wann die Anfrage abgeschlossen ist. Nun, Sie können ein Cookie verwenden, das wie jquery.fileDownload auf Antwort gesetzt wird, aber es ist bei weitem nicht perfekt. Es funktioniert nicht bei gleichzeitigen Anfragen und bricht ab, wenn keine Antwort eingeht.

  3. Wenn der Server mit einem Fehler antwortet, wird der Benutzer auf die Fehlerseite umgeleitet.

  4. Sie können nur die Inhaltstypen verwenden, die von einem Formular unterstützt werden. Das heißt, Sie können JSON nicht verwenden.

Am Ende habe ich die Methode zum Speichern der Datei in S3 verwendet und eine vorsignierte URL gesendet, um die Datei abzurufen.

5
tepez

Hier ist meine Lösung mit einem temporären versteckten Formular.

//Create an hidden form
var form = $('<form>', {'method': 'POST', 'action': this.href}).hide();

//Add params
var params = { ...your params... };
$.each(params, function (k, v) {
    form.append($('<input>', {'type': 'hidden', 'name': k, 'value': v}));
});

//Make it part of the document and submit
$('body').append(form);
form.submit();

//Clean up
form.remove();

Beachten Sie, dass ich JQuery massiv verwende, aber Sie können dasselbe mit nativem JS tun.

3
Ludovic Martin

Dies ist eine 3 Jahre alte Frage, aber ich hatte heute das gleiche Problem. Ich habe nach Ihrer bearbeiteten Lösung gesucht, aber ich denke, dass sie die Leistung opfern kann, weil sie eine doppelte Anforderung stellen muss. Wenn jemand also eine andere Lösung benötigt, die nicht bedeutet, dass er den Service zweimal anruft, dann habe ich das so gemacht:

<form id="export-csv-form" method="POST" action="/the/path/to/file">
    <input type="hidden" name="anyValueToPassTheServer" value="">
</form>

Dieses Formular wird nur verwendet, um den Dienst aufzurufen und die Verwendung einer window.location () zu vermeiden. Danach muss nur noch ein Formular von jquery gesendet werden, um den Service aufzurufen und die Datei abzurufen. Es ist ziemlich einfach, aber auf diese Weise können Sie einen Download mit einem POST durchführen. Ich weiß jetzt, dass dies einfacher sein könnte, wenn der Dienst, den Sie anrufen, ein GET ist, aber das ist nicht mein Fall.

3
Jairo Miranda

Wie bereits erwähnt, können Sie über eine POST -Anforderung ein Formular erstellen und zum Herunterladen einreichen. Sie müssen dies jedoch nicht manuell tun.

Eine wirklich einfache Bibliothek, um genau dies zu tun, ist jquery.redirect . Es bietet eine ähnliche API wie die Standardmethode jQuery.post:

$.redirect(url, [values, [method, [target]]])
2
KurtPreston

siehe: http://www.henryalgus.com/reading-binary-files-using-jquery-ajax/ Es wird ein Blob als Antwort zurückgegeben, das dann in den Dateisparer gestellt werden kann

1

Ich habe dies verwendet FileSaver.js . In meinem Fall mit CSV-Dateien habe ich Folgendes getan (in Coffescript):

  $.ajax
    url: "url-to-server"
    data: "data-to-send"
    success: (csvData)->
      blob = new Blob([csvData], { type: 'text/csv' })
      saveAs(blob, "filename.csv")

Ich denke für den kompliziertesten Fall müssen die Daten richtig verarbeitet werden. Unter der Haube FileSaver.js implementieren den gleichen Ansatz der Antwort von Jonathan Amend .

1
Armando

Damit Jonathan Amendsanswer in Edge funktioniert, habe ich folgende Änderungen vorgenommen:

var blob = typeof File === 'function'
    ? new File([this.response], filename, { type: type })
    : new Blob([this.response], { type: type });

dazu

var f = typeof File+"";
var blob = f === 'function' && Modernizr.fileapi
    ? new File([this.response], filename, { type: type })
    : new Blob([this.response], { type: type });

Ich hätte das lieber als Kommentar gepostet, aber ich habe nicht genug Ansehen dafür

1
fstrandner

es gibt eine andere Lösung, um eine Webseite in Ajax herunterzuladen. Aber ich beziehe mich auf eine Seite, die zuerst verarbeitet und dann heruntergeladen werden muss.

Zuerst müssen Sie die Seitenverarbeitung vom Ergebnis-Download trennen.

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

 $ .post ("CalculusPage.php", {calculusFunction: true, ID: 29, data1: "a", data2: "b"}, 
 
 Funktion (data , status) 
 {
 if (status == "success") 
 {
/* 2) In der Antwort wird die Seite heruntergeladen, die die vorherigen Berechnungen verwendet . Dies kann beispielsweise eine Seite sein, die die Ergebnisse einer im Ajax-Aufruf berechneten Tabelle druckt. */
 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 ("Content-Type: application/vnd.ms-Excel"); 
 Header ("Content-Disposition: Inline; Dateiname = $ Dateiname"); 
 
 ... 

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

0
netluke

Hier ist meine Lösung, die aus verschiedenen Quellen stammt: Serverseitige Implementierung:

    String contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
    // Set headers
    response.setHeader("content-disposition", "attachment; filename =" + fileName);
    response.setContentType(contentType);
    // Copy file to output stream
    ServletOutputStream servletOutputStream = response.getOutputStream();
    try (InputStream inputStream = new FileInputStream(file)) {
        IOUtils.copy(inputStream, servletOutputStream);
    } finally {
        servletOutputStream.flush();
        Utils.closeQuitely(servletOutputStream);
        fileToDownload = null;
    }

Clientseitige Implementierung (mit jquery):

$.ajax({
type: 'POST',
contentType: 'application/json',
    url: <download file url>,
    data: JSON.stringify(postObject),
    error: function(XMLHttpRequest, textStatus, errorThrown) {
        alert(errorThrown);
    },
    success: function(message, textStatus, response) {
       var header = response.getResponseHeader('Content-Disposition');
       var fileName = header.split("=")[1];
       var blob = new Blob([message]);
       var link = document.createElement('a');
       link.href = window.URL.createObjectURL(blob);
       link.download = fileName;
       link.click();
    }
});   
0
Dvs Prajapati