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

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

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

83
demandé sur BalusC 2012-02-22 13:07:11

4 réponses

Introduction

Vous pouvez tout faire passer ExternalContext. Dans JSF 1.x, vous pouvez obtenir l'objet HttpServletResponse brut en ExternalContext#getResponse(). Dans JSF 2.x, vous pouvez utiliser le tas de nouvelles méthodes de délégué comme ExternalContext#getResponseOutputStream() sans avoir besoin de saisir le HttpServletResponse sous les hottes JSF.

Sur la réponse, vous devez définir l'en-tête Content-Type afin que le client sache quelle application associer au fichier fourni. Et, vous devez définir l'en-tête Content-Length afin 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 voulez une boîte de dialogue Enregistrer sous, sinon le client tentera de l'afficher en ligne. Enfin, écrivez simplement le contenu du fichier dans le flux de sortie de réponse.

La partie la plus importante est d'appeler FacesContext#responseComplete() pour informer JSF qu'il ne doit pas effectuer de navigation et de rendu 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 JSF, vous obtiendrez un IllegalStateException avec un message comme getoutputstream() has already been called for this response lorsque l'implémentation JSF appelle getWriter() pour rendre HTML.

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

Vous avez seulement besoin de s'assurer que la méthode d'action est pas, appelé par une requête ajax, mais qu'il est appelé par une requête normale comme vous le feu avec <h:commandLink> et <h:commandButton>. Les requêtes Ajax et les commandes à distance sont gérées par JavaScript qui à son tour a, pour des raisons de sécurité, aucune possibilité de forcer un dialogue Save As avec le contenu de la réponse ajax.

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

JSF Générique 2.x exemple

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.
}

JSF Générique 1.x exemple

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

Au cas où vous devez diffuser un fichier statique à partir du système de fichiers de disque local, remplacez le code comme suit:

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

Si vous avez besoin de diffuser un fichier généré dynamiquement, tel que PDF ou XLS, fournissez 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. Si vous devez supprimer la ligne pour définir la réponse la longueur du contenu. Ceci est techniquement pas de problème, le seul inconvénient est que l'utilisateur final sera présenté une progression de téléchargement inconnu. Dans le cas où 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 D'utilité

Si vous utilisez la bibliothèque utilitaire JSF OmniFaces , Vous pouvez utiliser l'un des trois Faces#sendFile() méthodes prenant soit un File, soit un InputStream, soit un byte[], et spécifiant si le fichier doit être téléchargé en pièce jointe (true) ou en ligne (false).

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

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

207
répondu BalusC 2018-09-26 13:08:44
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();

}
3
répondu John Mendes 2016-10-03 10:01:56

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();
}
2
répondu Koray Tugay 2017-04-28 09:06:43

Voici l'extrait de code complet http://bharatonjava.wordpress.com/2013/02/01/downloading-file-in-jsf-2/

 @ManagedBean(name = "formBean")
 @SessionScoped
 public class FormBean implements Serializable
 {
   private static final long serialVersionUID = 1L;

   /**
    * Download file.
    */
   public void downloadFile() throws IOException
   {
      File file = new File("C:\\docs\\instructions.txt");
      InputStream fis = new FileInputStream(file);
      byte[] buf = new byte[1024];
      int offset = 0;
      int numRead = 0;
      while ((offset < buf.length) && ((numRead = fis.read(buf, offset, buf.length -offset)) >= 0)) 
      {
        offset += numRead;
      }
      fis.close();
      HttpServletResponse response =
         (HttpServletResponse) FacesContext.getCurrentInstance()
        .getExternalContext().getResponse();

     response.setContentType("application/octet-stream");
     response.setHeader("Content-Disposition", "attachment;filename=instructions.txt");
     response.getOutputStream().write(buf);
     response.getOutputStream().flush();
     response.getOutputStream().close();
     FacesContext.getCurrentInstance().responseComplete();
   }
 }

Vous pouvez modifier la logique de lecture du fichier au cas où vous souhaiteriez que le fichier soit généré lors de l'exécution.

-2
répondu Bharat Sharma 2013-02-05 10:57:14