web-dev-qa-db-fra.com

Comment fournir un téléchargement de fichier à partir d'un bean de sauvegarde JSF?

Existe-t-il un moyen de télécharger un fichier à partir d'une méthode d'action de bean de sauvegarde JSF? J'ai essayé beaucoup de choses. Le problème principal est que je ne peux pas comprendre comment obtenir le OutputStream de la réponse afin d'écrire le contenu du fichier. Je sais comment le faire avec un Servlet, mais cela ne peut pas être appelé depuis un formulaire JSF et nécessite une nouvelle requête.

Comment puis-je obtenir le OutputStream de la réponse du FacesContext actuel?

85
zmeda

Introduction

Vous pouvez tout obtenir à travers ExternalContext . Dans JSF 1.x, vous pouvez obtenir l’objet raw HttpServletResponse de ExternalContext#getResponse() . Dans JSF 2.x, vous pouvez utiliser le groupe de nouvelles méthodes de délégation telles que ExternalContext#getResponseOutputStream() sans qu'il soit nécessaire d'extraire le HttpServletResponse sous les capots JSF.

Sur la réponse, vous devez définir l'en-tête Content-Type Pour que le client sache quelle application associer au fichier fourni. Et, vous devez définir l'en-tête Content-Length De sorte que le client puisse calculer la progression du téléchargement, sinon il sera inconnu. Et, vous devez définir l'en-tête Content-Disposition Sur attachment si vous souhaitez une boîte de dialogue Enregistrer sous, sinon le client essaiera de l'afficher en ligne. Enfin, écrivez simplement le contenu du fichier dans le flux de sortie de la réponse.

La partie la plus importante est d’appeler FacesContext#responseComplete() pour informer JSF qu’il ne doit pas effectuer de navigation ni de restitution après avoir écrit le fichier dans la réponse, sinon la fin de la réponse sera pollué par le contenu HTML de la page, ou dans les anciennes versions de JSF, vous obtiendrez un IllegalStateException avec un message tel que getoutputstream() has already been called for this response lorsque l'implémentation JSF appelle getWriter() à rendre HTML.

Désactiver ajax/ne pas utiliser la commande à distance!

Vous devez simplement vous assurer que la méthode d'action est non appelée par une requête ajax, mais qu'elle est appelée par une requête normale lorsque vous déclenchez avec <h:commandLink> Et <h:commandButton>. Les requêtes Ajax et les commandes à distance sont gérées par JavaScript qui, pour des raisons de sécurité, ne dispose d'aucun moyen pour forcer un dialogue Enregistrer sous avec le contenu de la réponse ajax.

Si vous utilisez par exemple PrimeFaces <p:commandXxx>, Vous devez alors vous assurer que vous désactivez explicitement ajax via l'attribut ajax="false". Si vous utilisez ICEfaces, vous devez alors imbriquer un <f:ajax disabled="true" /> Dans le composant de commande.

Exemple générique de JSF 2.x

public void download() throws IOException {
    FacesContext fc = FacesContext.getCurrentInstance();
    ExternalContext ec = fc.getExternalContext();

    ec.responseReset(); // Some JSF component library or some Filter might have set some headers in the buffer beforehand. We want to get rid of them, else it may collide.
    ec.setResponseContentType(contentType); // Check http://www.iana.org/assignments/media-types for all types. Use if necessary ExternalContext#getMimeType() for auto-detection based on filename.
    ec.setResponseContentLength(contentLength); // Set it with the file size. This header is optional. It will work if it's omitted, but the download progress will be unknown.
    ec.setResponseHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); // The Save As popup magic is done here. You can give it any file name you want, this only won't work in MSIE, it will use current request URL as file name instead.

    OutputStream output = ec.getResponseOutputStream();
    // Now you can write the InputStream of the file to the above OutputStream the usual way.
    // ...

    fc.responseComplete(); // Important! Otherwise JSF will attempt to render the response which obviously will fail since it's already written with a file and closed.
}

Exemple générique de JSF 1.x

public void download() throws IOException {
    FacesContext fc = FacesContext.getCurrentInstance();
    HttpServletResponse response = (HttpServletResponse) fc.getExternalContext().getResponse();

    response.reset(); // Some JSF component library or some Filter might have set some headers in the buffer beforehand. We want to get rid of them, else it may collide.
    response.setContentType(contentType); // Check http://www.iana.org/assignments/media-types for all types. Use if necessary ServletContext#getMimeType() for auto-detection based on filename.
    response.setContentLength(contentLength); // Set it with the file size. This header is optional. It will work if it's omitted, but the download progress will be unknown.
    response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); // The Save As popup magic is done here. You can give it any file name you want, this only won't work in MSIE, it will use current request URL as file name instead.

    OutputStream output = response.getOutputStream();
    // Now you can write the InputStream of the file to the above OutputStream the usual way.
    // ...

    fc.responseComplete(); // Important! Otherwise JSF will attempt to render the response which obviously will fail since it's already written with a file and closed.
}

Exemple de fichier statique commun

Si vous avez besoin de diffuser un fichier statique à partir du système de fichiers du disque local, remplacez le code ci-dessous:

File file = new File("/path/to/file.ext");
String fileName = file.getName();
String contentType = ec.getMimeType(fileName); // JSF 1.x: ((ServletContext) ec.getContext()).getMimeType(fileName);
int contentLength = (int) file.length();

// ...

Files.copy(file.toPath(), output);

Exemple de fichier dynamique commun

Dans le cas où vous auriez besoin de diffuser un fichier généré dynamiquement, tel que PDF ou XLS, indiquez simplement output là où l'API utilisée attend un OutputStream.

Par exemple. iText PDF:

String fileName = "dynamic.pdf";
String contentType = "application/pdf";

// ...

Document document = new Document();
PdfWriter writer = PdfWriter.getInstance(document, output);
document.open();
// Build PDF content here.
document.close();

Par exemple. Apache POI HSSF:

String fileName = "dynamic.xls";
String contentType = "application/vnd.ms-Excel";

// ...

HSSFWorkbook workbook = new HSSFWorkbook();
// Build XLS content here.
workbook.write(output);
workbook.close();

Notez que vous ne pouvez pas définir la longueur du contenu ici. Vous devez donc supprimer la ligne pour définir la longueur du contenu de la réponse. Ce n'est techniquement pas un problème, le seul inconvénient est que l'utilisateur final se verra présenter une progression de téléchargement inconnue. Si cela est important, vous devez d'abord écrire dans un fichier local (temporaire), puis le fournir comme indiqué dans le chapitre précédent.

Méthode utilitaire

Si vous utilisez la bibliothèque d’utilitaires JSF OmniFaces , vous pouvez utiliser l’une des trois méthodes pratiques Faces#sendFile() utilisant soit File , ou InputStream, ou byte[], et en spécifiant si le fichier doit être téléchargé en tant que pièce jointe (true) ou inline (false).

public void download() throws IOException {
    Faces.sendFile(file, true);
}

Oui, ce code est complet tel quel. Vous n'avez pas besoin d'appeler responseComplete() et ainsi de suite. Cette méthode traite également correctement les en-têtes spécifiques à IE et les noms de fichiers UTF-8. Vous pouvez trouver code source ici .

216
BalusC
public void download() throws IOException
{

    File file = new File("file.txt");

    FacesContext facesContext = FacesContext.getCurrentInstance();

    HttpServletResponse response = 
            (HttpServletResponse) facesContext.getExternalContext().getResponse();

    response.reset();
    response.setHeader("Content-Type", "application/octet-stream");
    response.setHeader("Content-Disposition", "attachment;filename=file.txt");

    OutputStream responseOutputStream = response.getOutputStream();

    InputStream fileInputStream = new FileInputStream(file);

    byte[] bytesBuffer = new byte[2048];
    int bytesRead;
    while ((bytesRead = fileInputStream.read(bytesBuffer)) > 0) 
    {
        responseOutputStream.write(bytesBuffer, 0, bytesRead);
    }

    responseOutputStream.flush();

    fileInputStream.close();
    responseOutputStream.close();

    facesContext.responseComplete();

}
4
John Mendes

C'est ce qui a fonctionné pour moi:

public void downloadFile(String filename) throws IOException {
    final FacesContext fc = FacesContext.getCurrentInstance();
    final ExternalContext externalContext = fc.getExternalContext();

    final File file = new File(filename);

    externalContext.responseReset();
    externalContext.setResponseContentType(ContentType.APPLICATION_OCTET_STREAM.getMimeType());
    externalContext.setResponseContentLength(Long.valueOf(file.lastModified()).intValue());
    externalContext.setResponseHeader("Content-Disposition", "attachment;filename=" + file.getName());

    final HttpServletResponse response = (HttpServletResponse) externalContext.getResponse();

    FileInputStream input = new FileInputStream(file);
    byte[] buffer = new byte[1024];
    final ServletOutputStream out = response.getOutputStream();

    while ((input.read(buffer)) != -1) {
        out.write(buffer);
    }

    out.flush();
    fc.responseComplete();
}
3
Koray Tugay