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

91

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. Le problème principal est que je ne peux pas comprendre comment obtenir OutputStreamla réponse pour écrire le contenu du fichier. 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 OutputStreamla réponse du courant FacesContext?

zmeda
la source

Réponses:

237

introduction

Vous pouvez tout faire passer ExternalContext. Dans JSF 1.x, vous pouvez obtenir l' HttpServletResponseobjet brut par 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 récupérer le HttpServletResponsesous les hottes JSF.

Sur la réponse, vous devez définir l'en- Content-Typetête de sorte que le client sache quelle application associer au fichier fourni. Et, vous devez définir l'en- Content-Lengthtête pour que le client puisse calculer la progression du téléchargement, sinon il sera inconnu. Et, vous devez définir l'en- Content-Dispositiontête sur attachmentsi vous voulez un Enregistrer sous boîte de dialogue , sinon le client tentera 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 et de rendu après avoir écrit le fichier dans la réponse, sinon la fin de la réponse sera polluée par le contenu HTML de la page, ou dans les anciennes versions de JSF , vous obtiendrez un IllegalStateExceptionavec un message comme getoutputstream() has already been called for this responselorsque l'implémentation JSF appellegetWriter() pour rendre HTML.

Désactivez ajax / n'utilisez pas la commande à distance!

Vous devez seulement vous assurer que la méthode d'action n'est pas 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é, n'a pas la possibilité de forcer un enregistrement sous dialogue 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' ajax="false"attribut. Si vous utilisez ICEfaces, vous devez imbriquer un<f:ajax disabled="true" /> dans le composant de commande.

Exemple générique 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 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 courant

Si vous avez besoin de diffuser un fichier statique à partir du système de fichiers du disque local, remplacez le code comme 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

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

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 aucun 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 Faces#sendFile()méthodes pratiques en prenant un File, ou un InputStream, ou un byte[], et en 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. 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 .

BalusC
la source
1
Si facile! Je me suis demandé comment rendre le téléchargement disponible pour PrimeFaces en fonction de leur vitrine, car il nécessite une InputStreaminfrastructure pour p:fileDownload, et je n'ai pas réussi à convertir OutputStreamen InputStream. Il est maintenant clair que même un auditeur d'action peut changer le type de contenu de la réponse et que la réponse sera de toute façon respectée en tant que téléchargement de fichier du côté de l'agent utilisateur. Je vous remercie!
Lyubomyr Shaydariv
1
Existe-t-il un moyen de le faire en utilisant un HTTP GET au lieu de HTTP POST (h: commandButton et h: commandLink)?
Alfredo Osorio
@Alfredo: oui, en utilisant l' preRenderViewauditeur dans une vue sans marqueur. Une question similaire pour le téléchargement (enfin, le service) JSON est répondue ici: stackoverflow.com/questions/8358006/…
BalusC
Le lien w3schools.com/media/media_mimeref.asp est rompu. Peut-être que celui-ci convient: iana.org/assignments/media-types
Zakhar
2
@BalusC vous couvrez tous les sujets jsf - merci de me faciliter la vie monsieur!
Buttinger Xaver
5
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();

}
John Mendes
la source
3

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();
}
Koray Tugay
la source
1
Après 2 jours de travail, cela a résolu mon problème avec quelques changements :) merci beaucoup.
ÖMER TAŞCI
@ ÖMERTAŞCI: Quels changements,
Kukeltje
-3

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é au moment de l'exécution.

Bharat Sharma
la source
Cela ne vous donnera qu'une partie du fichier d'entrée, si sa taille est supérieure à 1024 octets!
hinneLiens