introduction
Vous pouvez tout faire passer ExternalContext
. Dans JSF 1.x, vous pouvez obtenir l' HttpServletResponse
objet 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 HttpServletResponse
sous les hottes JSF.
Sur la réponse, vous devez définir l'en- Content-Type
tête de sorte que le client sache quelle application associer au fichier fourni. Et, vous devez définir l'en- Content-Length
tê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-Disposition
tête sur attachment
si 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 IllegalStateException
avec un message comme getoutputstream() has already been called for this response
lorsque 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 output
là 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 .
InputStream
infrastructure pourp:fileDownload
, et je n'ai pas réussi à convertirOutputStream
enInputStream
. 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!