Einführung
Sie können alles durchbringen ExternalContext
. In JSF 1.x können Sie das roh erhalten HttpServletResponse
Objekt durch ExternalContext#getResponse()
. In JSF 2.x können Sie eine Reihe neuer Delegatenmethoden verwenden, ExternalContext#getResponseOutputStream()
ohne die HttpServletResponse
unter den JSF-Hauben hervorheben zu müssen.
In der Antwort sollten Sie den Content-Type
Header so festlegen , dass der Client weiß, welche Anwendung mit der bereitgestellten Datei verknüpft werden soll. Außerdem sollten Sie den Content-Length
Header so einstellen , dass der Client den Download-Fortschritt berechnen kann, da er sonst nicht bekannt ist. Außerdem sollten Sie den Content-Disposition
Header auf festlegen , attachment
wenn Sie ein Dialogfeld " Speichern unter" möchten. Andernfalls versucht der Client, ihn inline anzuzeigen. Zum Schluss schreiben Sie einfach den Dateiinhalt in den Antwortausgabestream.
Der wichtigste Teil ist ein Aufruf FacesContext#responseComplete()
, um JSF darüber zu informieren, dass es keine Navigation und kein Rendern durchführen soll, nachdem Sie die Datei in die Antwort geschrieben haben. Andernfalls wird das Ende der Antwort mit dem HTML-Inhalt der Seite oder in älteren JSF-Versionen verschmutzt erhalten Sie IllegalStateException
eine Meldung, beispielsweise getoutputstream() has already been called for this response
wenn die JSF-Implementierung getWriter()
zum Rendern von HTML aufruft .
Ajax ausschalten / Remote-Befehl nicht verwenden!
Sie müssen nur sicherstellen, dass die Aktionsmethode nicht von einer Ajax-Anforderung aufgerufen wird, sondern von einer normalen Anforderung, wenn Sie mit <h:commandLink>
und feuern <h:commandButton>
. Ajax-Anforderungen und Remote-Befehle werden von JavaScript verarbeitet, das aus Sicherheitsgründen keine Möglichkeit bietet, einen Dialog zum Speichern unter mit dem Inhalt der Ajax-Antwort zu erzwingen .
Wenn Sie zB PrimeFaces verwenden <p:commandXxx>
, müssen Sie sicherstellen, dass Sie Ajax über ajax="false"
Attribut explizit deaktivieren . Wenn Sie ICEfaces verwenden, müssen Sie ein <f:ajax disabled="true" />
in der Befehlskomponente verschachteln .
Generisches JSF 2.x-Beispiel
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.
}
Generisches JSF 1.x-Beispiel
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.
}
Beispiel für eine allgemeine statische Datei
Wenn Sie eine statische Datei vom lokalen Datenträger-Dateisystem streamen müssen, ersetzen Sie den folgenden Code:
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);
Beispiel für eine allgemeine dynamische Datei
Wenn Sie eine dynamisch generierte Datei wie PDF oder XLS streamen müssen, geben Sie einfach output
dort an, wo die verwendete API eine erwartet OutputStream
.
ZB 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();
ZB 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();
Beachten Sie, dass Sie die Inhaltslänge hier nicht festlegen können. Sie müssen also die Zeile entfernen, um die Länge des Antwortinhalts festzulegen. Dies ist technisch kein Problem, der einzige Nachteil ist, dass dem Endbenutzer ein unbekannter Download-Fortschritt angezeigt wird. Wenn dies wichtig ist, müssen Sie zuerst in eine lokale (temporäre) Datei schreiben und diese dann wie im vorherigen Kapitel gezeigt bereitstellen.
Dienstprogrammmethode
Wenn Sie die JSF-Dienstprogrammbibliothek OmniFaces verwenden , können Sie eine der drei praktischen Faces#sendFile()
Methoden verwenden, indem Sie entweder a File
, a oder InputStream
oder a verwenden byte[]
und angeben, ob die Datei als Anhang ( true
) oder inline ( false
) heruntergeladen werden soll .
public void download() throws IOException {
Faces.sendFile(file, true);
}
Ja, dieser Code ist unverändert vollständig. Sie müssen sich nicht responseComplete()
selbst aufrufen und so weiter. Diese Methode behandelt auch IE-spezifische Header und UTF-8-Dateinamen. Den Quellcode finden Sie hier .
InputStream
Infrastrukturp:fileDownload
, und ich habe es nicht geschafft , wie zu konvertierenOutputStream
zuInputStream
. Jetzt ist klar, dass sogar ein Aktionslistener den Antwortinhaltstyp ändern kann, und die Antwort wird dann ohnehin als Dateidownload auf der Seite des Benutzeragenten angesehen. Danke dir!