Gérer le téléchargement de fichiers depuis ajax post

j'ai une application javascript qui envoie des requêtes ajax POST à une certaine URL. La réponse peut être une chaîne JSON ou un fichier (en pièce jointe). Je peux facilement détecter le Type et la Disposition de contenu dans mon appel ajax, mais une fois que je détecte que la réponse contient un fichier, Comment puis-je offrir au client de le télécharger? J'ai lu un certain nombre de fils similaires ici, mais aucun d'entre eux fournissent la réponse que je cherche.

s'il vous Plaît, veuillez, s'il vous plaît ne pas envoyer des réponses suggérant que je ne devrais pas utiliser ajax pour ceci ou que je devrais rediriger le navigateur, parce que rien de ceci n'est une option. L'utilisation d'un simple format HTML n'est pas non plus une option. Ce dont j'ai besoin, c'est d'afficher un dialogue de téléchargement sur le client. Cela peut-il être fait et comment le faire?

EDIT:

apparemment, cela ne peut pas être fait, mais il y a une solution de rechange simple, comme suggéré par la réponse acceptée. Pour tous ceux qui seront confrontés à ce problème dans le futur, voici comment je l'ai résolu:

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

donc fondamentalement, il suffit de générer un formulaire HTML avec les mêmes paramètres qui ont été utilisés dans la demande AJAX et de le soumettre.

319
demandé sur jwfearn 2013-04-18 18:48:45

14 réponses

créer un formulaire, utiliser la méthode POST, soumettre le formulaire - il n'y a pas besoin d'iframe. Quand la page du serveur répond à la requête, écrivez un en - tête de réponse pour le type mime du fichier, et il présentera un dialogue de téléchargement-j'ai fait cela un certain nombre de fois.

vous souhaitez contenu-type de demande/téléchargement - il suffit de rechercher comment fournir un téléchargement pour quelle que soit la langue que vous utilisez.

96
répondu 2013-04-18 15:00:21

ne pas abandonner si vite, parce que cela peut être fait (dans les navigateurs modernes) en utilisant des parties du FileAPI:

Modifier 2017-09-28: mis à Jour pour utiliser le Fichier de constructeur, lorsque disponibles, de sorte qu'il fonctionne dans Safari >= 10.1.

Edit 2015-10-16: jQuery ajax n'est pas capable de gérer correctement les réponses binaires (ne peut pas définir responsietype), il est donc préférable d'utiliser un appel simple XMLHttpRequest.

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]*=((['"]).*?|[^;\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));

Voici l'ancienne version avec jQuery.Ajax. Il peut coder les données binaires lorsque la réponse est convertie en une chaîne de certains charset.

$.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]*=((['"]).*?|[^;\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
        }
    }
});
449
répondu Jonathan Amend 2017-09-28 15:52:59

quel langage côté serveur utilisez-vous? Dans mon application, je peux facilement télécharger un fichier à partir D'un appel AJAX en définissant les en-têtes corrects dans la réponse de PHP:

réglage des en-têtes côté serveur

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;

cela va en fait 'rediriger' le navigateur vers cette page de téléchargement, mais comme @ahren a déjà dit dans son commentaire, il ne va pas naviguer loin de la page actuelle.

il s'agit de définir les headers corrects donc je suis sûr vous trouverez une solution appropriée pour le langage côté serveur que vous utilisez si ce N'est pas PHP.

la Manipulation de la réponse du côté client

en supposant que vous savez déjà faire un appel AJAX, du côté client vous exécutez une requête AJAX au serveur. Le serveur génère alors un lien à partir duquel ce fichier peut être téléchargé, par exemple L'URL "forward" vers laquelle vous voulez pointer. Par exemple, le serveur répond avec:

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

Lors du traitement de la réponse, vous injectez un iframe dans votre corps et définissez le iframe 's SRC à L'URL que vous venez de recevoir comme ceci (en utilisant jQuery pour la facilité de cet exemple):

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

si vous avez paramétré les en-têtes appropriés comme indiqué ci-dessus, l'iframe va forcer un dialogue de téléchargement sans naviguer le navigateur loin de la page actuelle.

Note

ajout Supplémentaire par rapport à votre question, je pense que c'est il est préférable de toujours retourner JSON lorsque vous demandez des choses avec la technologie AJAX. Une fois que vous avez reçu la réponse de JSON, vous pouvez décider du côté client ce qu'il faut en faire. Peut-être, par exemple, plus tard, vous voulez que l'utilisateur clique sur un lien de téléchargement vers L'URL au lieu de forcer le téléchargement directement, dans votre configuration actuelle, vous devriez mettre à jour à la fois le côté client et le côté serveur pour le faire.

28
répondu Robin van Baalen 2013-09-23 19:39:36

j'ai affronté le même problème et l'ai résolu avec succès. Mon cas d'utilisation est présent.

postez les données JSON au serveur et recevez un fichier excel. Ce fichier excel est créé par le serveur et retourné en réponse au client. Télécharger cette réponse comme un fichier avec nom personnalisé dans le navigateur

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

L'extrait ci-dessus ne fait que suivre

  • affichage d'un tableau comme JSON à la serveur utilisant XMLHttpRequest.
  • après avoir récupéré du contenu sous forme de blob(binaire), nous créons une URL téléchargeable et nous l'attachons au lien invisible" a", puis nous la cliquons.

ici, nous avons besoin de définir avec soin peu de choses du côté du serveur. J'ai mis quelques headers en Python Django HttpResponse. Vous devez définir en conséquence si vous utilisez d'autres langages de programmation.

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

depuis que je télécharge xls (excel) ici, je ContentType ajusté à au-dessus d'un. Vous devez le configurer en fonction de votre type de fichier. Vous pouvez utiliser cette technique pour télécharger toute sorte de fichiers.

26
répondu Naren Yellavula 2016-08-09 12:38:11

pour ceux qui cherchent une solution D'un point de vue angulaire, cela a fonctionné pour moi:

$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();
});
18
répondu Tim Hettler 2016-03-07 19:05:21

je vois que vous avez déjà trouvé une solution, mais je voulais juste ajouter quelques informations qui peuvent aider quelqu'un qui essaie d'atteindre la même chose avec de grandes requêtes POST.

j'ai eu le même problème il y a quelques semaines, en effet il n'est pas possible d'obtenir un téléchargement" propre " à travers AJAX, le groupe de filaments a créé un plugin jQuery qui fonctionne exactement comme vous l'avez déjà découvert, il s'appelle téléchargement de fichier jQuery cependant il y a un inconvénient de cette technique.

si vous envoyez de grosses requêtes via AJAX (par exemple des fichiers +1 Mo), cela aura un impact négatif sur la réactivité. Dans les connexions Internet lentes, vous aurez à attendre beaucoup jusqu'à ce que la demande est envoyée et aussi attendre le fichier à télécharger. Ce n'est pas comme en un "clic" => "popup" => "télécharger démarrer". Il ressemble plus à un "clic" => "attendez jusqu'à ce que les données sont envoyées" => "attendre pour la réponse" => "télécharger démarrer" qui le fait apparaître le fichier doublez sa taille car vous devrez attendre que la requête soit envoyée par AJAX et la récupérer sous forme de fichier téléchargeable.

si vous travaillez avec de petites tailles de fichier <1MB vous ne le remarquerez pas. Mais comme je l'ai découvert dans mon propre application, pour la taille du fichier il est presque insupportable.

mon application permet aux utilisateurs d'exporter des images générées dynamiquement, ces images sont envoyées au serveur via des requêtes POST au format base64 (c'est la seule façon possible), puis traité et renvoyé aux utilisateurs sous forme de .png,.les fichiers jpg, les chaînes de base64 pour les images +1MB sont énormes, ce qui oblige les utilisateurs à attendre plus que nécessaire pour que le fichier commence à être téléchargé. Dans les connexions Internet lentes, il peut être vraiment ennuyeux.

Ma solution était d'temporaire écrire le fichier sur le serveur, une fois qu'il est prêt, générer dynamiquement un lien vers le fichier sous la forme d'un bouton qui change entre "Veuillez patienter..."et "Télécharger" états et dans le même temps, Imprimez l'image base64 dans une fenêtre popup de prévisualisation pour que les utilisateurs puissent "cliquer avec le bouton droit de la souris" et la sauvegarder. Cela rend tout le temps d'attente plus supportable pour les utilisateurs, et accélère également les choses.

Mise À Jour 30 Sept. 2014:

des mois se sont écoulés depuis que j'ai posté ceci, finalement j'ai trouvé une meilleure approche pour accélérer les choses en travaillant avec de grandes chaînes de base64. Je stocke maintenant les chaînes base64 dans la base de données (en utilisant longtext ou longblog fields), puis je passe son ID d'enregistrement par le téléchargement du fichier jQuery, enfin sur le fichier de script de téléchargement j'interroge la base de données en utilisant ce ID pour tirer la chaîne de base64 et le passer par la fonction de téléchargement.

Télécharger Le Script Exemple:

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

je sais que c'est bien au-delà de ce que l'OP a demandé, mais j'ai pensé qu'il serait bon de mettre à jour ma réponse avec mes conclusions. Quand j'étais à la recherche d' solutions à mon problème, j'ai lu beaucoup de "télécharger à partir de données AJAX POST" fils qui ne m'ont pas donné la réponse que je cherchais, j'espère que cette information aide quelqu'un qui cherche à atteindre quelque chose comme cela.

11
répondu José SAYAGO 2014-09-30 22:00:52

Voici comment j'ai obtenu ce travail 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();
  }
});

mise à jour de la réponse en utilisant télécharger.js

$.ajax({
  url: '<URL_TO_FILE>',
  success: download.bind(true, "<FILENAME_TO_SAVE_WITH_EXTENSION>", "<FILE_MIME_TYPE>")
});
8
répondu Mayur Padshala 2017-08-30 19:06:06

je tiens à souligner quelques difficultés qui se posent lors de l'utilisation de la technique dans la réponse acceptée, c.-à-d. en utilisant un formulaire post:

  1. vous ne pouvez pas régler les en-têtes sur la demande. Si votre schéma d'authentification implique des en-têtes, un JSON-Web-Token passé dans l'en-tête D'autorisation, vous devrez trouver un autre moyen de l'envoyer, par exemple comme paramètre de requête.

  2. Vous ne pouvez pas vraiment dire lorsque la demande a terminer. Bien, vous pouvez utiliser un cookie qui est défini sur la réponse, comme fait par jquery.fileDownload , mais il est loin d'être parfait. Il ne fonctionnera pas pour les requêtes simultanées et il se brisera si une réponse n'arrive jamais.

  3. Si le serveur répond avec une erreur, l'utilisateur sera redirigé vers la page d'erreur.

  4. vous ne pouvez utiliser que les types de contenu pris en charge par un formulaire . Ce qui veut dire que vous ne pouvez pas utiliser JSON.

j'ai fini par utiliser la méthode d'enregistrer le fichier sur S3 et d'envoyer une URL pré-signée pour obtenir le fichier.

5
répondu tepez 2016-03-11 12:17:07

comme d'autres l'ont déclaré, Vous pouvez créer et soumettre un formulaire à télécharger via une demande de poste. Cependant, vous n'avez pas à le faire manuellement.

une bibliothèque vraiment simple pour faire exactement cela est jquery.rediriger . Il fournit une API similaire à la méthode standard jQuery.post :

$.redirect(url, [values, [method, [target]]])
2
répondu KurtPreston 2015-07-22 20:29:31

c'est une question vieille de 3 ans mais j'ai eu le même problème aujourd'hui. J'ai regardé votre solution éditée mais je pense qu'il peut sacrifier la performance parce qu'il doit faire une double demande. Donc, si quelqu'un a besoin d'une autre solution qui n'implique pas d'appeler le service deux fois, c'est la façon dont je l'ai fait:

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

Cette forme est utilisée pour appeler le service et éviter d'utiliser une fenêtre.emplacement.)( Après cela, vous avez tout simplement à faire un formulaire soumettre de jquery afin d'appeler le service et obtenir le fichier. C'est assez simple mais de cette façon vous pouvez faire un téléchargement en utilisant un POST . Je pense que cela pourrait être plus facile si le service que vous appelez est un obtenir , mais ce n'est pas mon cas.

2
répondu Jairo Miranda 2016-05-06 23:58:14

voici ma solution en utilisant une forme cachée temporaire.

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

notez que j'utilise massivement JQuery mais vous pouvez faire la même chose avec JS natif.

2
répondu Ludovic Martin 2017-09-01 09:28:56

j'ai utilisé ce FileSaver.js . Dans mon cas avec les fichiers csv, j'ai fait ceci (en coffescript):

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

je pense que pour les cas les plus compliqués, les données doivent être traitées correctement. Sous le capot FileSaver.js mettre en œuvre la même approche de la réponse de Jonathan modifier .

1
répondu Armando 2017-05-23 12:26:36

voir: http://www.henryalgus.com/reading-binary-files-using-jquery-ajax / il va retourner un blob comme une réponse, qui peut alors être mis dans filesaver

1
répondu Samantha Adrichem 2016-10-27 12:54:16

Pour obtenir "151930920 Jonathan" Modifie réponse travailler en Bord j'ai fait les modifications suivantes:

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

à ce

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

je préférerais avoir posté un commentaire mais je n'ai pas assez de réputation pour qui

1
répondu fstrandner 2018-04-13 08:24:50