web-dev-qa-db-fra.com

POST au serveur, recevoir un PDF, remettre à l'utilisateur avec jQuery

J'ai un lien sur lequel l'utilisateur clique pour obtenir un PDF. Dans jQuery, je crée un appel ajax POST au serveur pour obtenir le fichier PDF. Le PDF me parvient avec les en-têtes de contenu corrects, etc., qui obligeraient normalement le navigateur à ouvrir le plug-in Reader ou autoriseraient l'utilisateur à enregistrer le PDF.

Comme je reçois le PDF avec un appel ajax, je ne sais pas quoi faire des données que je reçois dans le rappel OnSuccess. Comment puis-je transmettre les données que je reçois au navigateur et lui permettre de faire ce qu'il a par défaut avec la réponse PDF?

47
bryanvick

Vous n'avez pas du tout besoin de jQuery. Il suffit de soumettre votre POST via un formulaire normalement, et côté serveur, ajoutez l'en-tête HTTP

Content-Disposition: attachment; filename="whatever.pdf"

Le navigateur fera sa chose par défaut.

Vous pouvez également effectuer cette opération si vous souhaitez faire plus attention en signalant les erreurs pouvant survenir lors de la génération PDF. POST vos paramètres sur votre serveur avec jQuery. Sur le serveur, générez le contenu binaire et mettez-le en cache quelque part pendant quelques minutes, accessible via une clé que vous avez insérée dans la session de l'utilisateur, puis renvoyez une réponse "succès" en Ajax à votre page "erreur" réponse). Si la page reçoit une réponse de succès, elle peut immédiatement faire quelque chose comme:

window.location = "/get/my/pdf";

Le serveur renvoie ensuite le contenu PDF mis en cache. Veillez à inclure l'en-tête Content-Disposition, comme ci-dessus.

31
Sean

Jetez un oeil à - jQuery Plugin pour demander des téléchargements de fichiers de type Ajax

La plugin entière ne compte qu'environ 30 lignes de code (y compris les commentaires).

L'appel est assez similaire à l'appel jquery ajax.

$.download('/export.php','filename=myPDF&format=pdf&content=' + pdfData );

Bien entendu, vous devez définir les en-têtes Content-Disposition et Content-Disposition côté serveur, comme vous le feriez pour tout téléchargement de ce type.

En Java, je ferais quelque chose comme ça

response.setContentType("application/pdf");
response.setHeader("Content-Disposition", "attachment; filename="exported.pdf");
50

La réponse mentionnant "jQuery Plugin pour demander des téléchargements de fichiers de type Ajax" m'a fait aller dans la bonne direction, mais cela ne fonctionnait pas entièrement pour ma situation car j'ai un objet complexe et un tableau d'objets à passer comme critères de recherche/filtrer les données. J'ai pensé partager mon code au cas où quelqu'un d'autre se retrouverait dans cette situation également.

$.download = function (url, data, method) {
    if (url && data) {
        //convert the data object into input HTML fields
        var inputs = '';
        var convertToInput = function (key, keyStr, obj) {
            if (typeof obj === 'undefined') {
                return;
            } else if (typeof obj === "object") {
                for (var innerKey in obj) {
                    if (obj.hasOwnProperty(innerKey)) {
                        var innerKeyStr = '';
                        if (keyStr === '') {
                            innerKeyStr = innerKey.toString();
                        } else {
                            innerKeyStr = keyStr + "[" + innerKey.toString() + "]";
                        }
                        convertToInput(innerKey, innerKeyStr, obj[innerKey]);
                    }
                }
                return;
            } else if ($.isArray(obj)) {
                obj.forEach(function (item) {
                    convertToInput(key, keyStr + "[]", item);
                });
                return;
            }

            inputs += "<input type='hidden' name='" + keyStr + "' value='" + obj + "' />";
        };
        convertToInput(null, '', data);

        //send request
        jQuery('<form action="' + url + '" method="' + (method || 'post') + '">' + inputs + '</form>').appendTo('body').submit().remove();
    };
};
$.download('/api/search?format=csv', searchData, 'POST');

Cela ne fait probablement pas une grande différence, mais pour vous situer dans le contexte, j'ai une interface utilisateur javascript et knock-out appelant WebAPI, MVC4 et nHibernate. La partie "format = csv" de la chaîne de requête déclenche un MediaTypeFormatter pour convertir les modèles retournés en un type de fichier CSV. Si je laisse cela de côté, je récupère les modèles à partir de l'API et je peux remplir une grille Slick pour l'affichage.

3
Mark Seefeldt

J'ai eu le même problème, mais sur le dessus, utilisez un RESTFUL webservice pour cela et un objet de données complexe que je dois poster.

Ma solution: Comme le plugin jQuery, je construis un formulaire temp et le soumets. Mais j’envoie l’objet de données en tant que paramètre avec un contenu json (j’utilise ici AngularJS mais cela devrait fonctionner aussi avec jQuery.param().)

Javascript:

$('<form target="_blank" action="' + appConstants.restbaseurl + '/print/pdf" method="POST">' + 
    "<input name='data' value='" + angular.toJson($scope.versicherung) + "' />" +
    '</form>').appendTo('body').submit().remove();

côté serveur, nous utilisons un CXF REST Service avec un JACKSON fournisseur:

Configuration de printemps:

<jaxrs:server id="masterdataService" address="/">
    <jaxrs:serviceBeans>
        <ref bean="printRestServiceBean" />
    </jaxrs:serviceBeans>
    <jaxrs:providers>
        <bean class="org.codehaus.jackson.jaxrs.JacksonJsonProvider" />
        <bean class="de.controller.ExceptionHandler" />
    </jaxrs:providers>
</jaxrs:server>

dans le contrôleur, j'ai extrait le paramètre et l'ai reconverti en un Java Pojo:

package de.controller;

import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;


@Path(Constants.PRINT_PATH)
@Consumes({ MediaType.APPLICATION_JSON, "application/x-www-form-urlencoded"})
@Produces("application/pdf; charset=UTF-8")
public class PrintRestController {

    @Autowired
    private PrintService printService;

    @POST
    @Produces("application/pdf")
    @Path("/pdf")
    public Response getPDF(@FormParam("data") String data) {
        return printService.getPDF(json2Versicherung(data));
    }

    private Versicherung json2Versicherung(String data) {
        Versicherung lVersicherung = null;
        try {
            ObjectMapper mapper = new ObjectMapper();
            lVersicherung = mapper.readValue(data, Versicherung.class);
        } catch(Exception e) {
            LOGGER.error("PrintRestController.json2Versicherung() error", e);
        }
        return lVersicherung;
    }
}

dans le PrintService, je construis le binaire pdf et la réponse:

@Override
public Response getPDF(Versicherung pVersicherung) {
    byte[] result = ... //build the pdf from what ever


    ResponseBuilder response = Response.ok((Object) result);
    response.header("Content-Disposition", "inline; filename=mypdf.pdf");
    return response.build();
}

Cette solution fonctionne pour tous les navigateurs (même pour IE9 qui ne peut pas gérer les URL de données) et sur les tablettes et les smartphones.

2
Anti-g

Le plug-in jQuery permettant de demander des téléchargements de fichiers de type Ajax consiste essentiellement à créer un formulaire, à ajouter les données de publication sous forme de champ caché, à le corps de la page, à le soumettre et à le supprimer. 

Dans mon cas, je n'avais pas de formulaire, seulement un bloc de données à afficher tel quel. Cela fait pour la solution suivante. Du côté du serveur, je peux obtenir les données en lisant simplement le paramètre "data" de la requête et en le décodant par URI. 

function postAndDownload(url, data) {

    encodedData = encodeURIComponent(data);

    $("<form>")
        .attr("action", url)
        .attr("method", "post")
        .append(
            $("input")
                .attr("type", "hidden")
                .attr("name", "data")
                .attr("value", encodedData)
        )
        .appendTo("body")
        .submit()
        .remove();
};
1
MichaelK

Je ne comprends pas pourquoi vous voulez une demande ajax vers une URL de téléchargement de fichier! Mais si cela ressemble plus au client lui-même génère du contenu à télécharger - utilisez un URI de données. Fonctionne parfaitement pour Chrome et Firefox 20+. Safari et IE NOT! Si Flash est autorisé, vous pouvez utiliser le programme de téléchargement.

Ah, après avoir lu votre code, je vois que vous voulez envoyer un tas de paramètres. Eh bien, à moins que la chaîne de requête ne soit trop longue (IE8- a une limite de 2083), pourquoi ne pas simplement utiliser une ancre avec une URL correcte? 

    $('a.export-csv').click( function (evt){
      linkEl.attr('href','/export?' + encodeURIComponent(formQueryString()));
      return true;
    });

Ce qui précède vous permet de modifier l'URL avant que l'événement par défaut (le clic) ne se produise.

0
Ustaman Sangat