Wprowadzenie
Możesz przejść wszystko ExternalContext
. W JSF 1.x możesz pobrać surowy HttpServletResponse
obiekt przez ExternalContext#getResponse()
. W JSF 2.x możesz użyć wielu nowych metod delegatów, ExternalContext#getResponseOutputStream()
bez potrzeby chwytania ich HttpServletResponse
spod masek JSF.
W odpowiedzi należy ustawić Content-Type
nagłówek, aby klient wiedział, którą aplikację skojarzyć z podanym plikiem. I powinieneś ustawić Content-Length
nagłówek tak, aby klient mógł obliczyć postęp pobierania, w przeciwnym razie będzie on nieznany. I powinieneś ustawić Content-Disposition
nagłówek na, attachment
jeśli chcesz okno dialogowe Zapisz jako , w przeciwnym razie klient spróbuje wyświetlić je w tekście. Na koniec po prostu zapisz zawartość pliku w strumieniu wyjściowym odpowiedzi.
Najważniejszą częścią jest wezwanie FacesContext#responseComplete()
do poinformowania JSF, że nie powinno wykonywać nawigacji i renderowania po zapisaniu pliku do odpowiedzi, w przeciwnym razie koniec odpowiedzi zostanie zanieczyszczony treścią HTML strony lub w starszych wersjach JSF , otrzymasz IllegalStateException
komunikat podobny do tego, getoutputstream() has already been called for this response
gdy implementacja JSF wywołuje getWriter()
renderowanie HTML.
Wyłącz Ajax / nie używaj zdalnego polecenia!
Musisz tylko upewnić się, że metoda akcji nie jest wywoływana przez żądanie AJAX, ale jest wywoływana przez normalne żądanie podczas uruchamiania z <h:commandLink>
i <h:commandButton>
. Żądania Ajax i polecenia zdalne są obsługiwane przez JavaScript, który z kolei, ze względów bezpieczeństwa, nie ma możliwości wymuszenia dialogu Zapisz jako z treścią odpowiedzi Ajax .
W przypadku, gdy używasz np. PrimeFaces <p:commandXxx>
, musisz upewnić się, że jawnie wyłączyłeś Ajax poprzez ajax="false"
atrybut. Jeśli używasz ICEfaces, musisz zagnieździć <f:ajax disabled="true" />
w komponencie poleceń.
Ogólny przykład JSF 2.x.
public void download() throws IOException {
FacesContext fc = FacesContext.getCurrentInstance();
ExternalContext ec = fc.getExternalContext();
ec.responseReset();
ec.setResponseContentType(contentType);
ec.setResponseContentLength(contentLength);
ec.setResponseHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
OutputStream output = ec.getResponseOutputStream();
fc.responseComplete();
}
Ogólny przykład JSF 1.x.
public void download() throws IOException {
FacesContext fc = FacesContext.getCurrentInstance();
HttpServletResponse response = (HttpServletResponse) fc.getExternalContext().getResponse();
response.reset();
response.setContentType(contentType);
response.setContentLength(contentLength);
response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
OutputStream output = response.getOutputStream();
fc.responseComplete();
}
Przykład typowego pliku statycznego
Jeśli chcesz przesłać strumieniowo plik statyczny z lokalnego systemu plików dysku, zastąp kod jak poniżej:
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);
Przykład typowego pliku dynamicznego
Jeśli chcesz przesłać strumieniowo dynamicznie generowany plik, taki jak PDF lub XLS, po prostu podaj output
tam, gdzie używane API oczekuje pliku OutputStream
.
Np. IText PDF:
String fileName = "dynamic.pdf";
String contentType = "application/pdf";
Document document = new Document();
PdfWriter writer = PdfWriter.getInstance(document, output);
document.open();
document.close();
Np. Apache POI HSSF:
String fileName = "dynamic.xls";
String contentType = "application/vnd.ms-excel";
HSSFWorkbook workbook = new HSSFWorkbook();
workbook.write(output);
workbook.close();
Zwróć uwagę, że nie możesz tutaj ustawić długości treści. Musisz więc usunąć wiersz, aby ustawić długość treści odpowiedzi. Z technicznego punktu widzenia nie stanowi to problemu, jedyną wadą jest to, że użytkownikowi końcowemu zostanie przedstawiony nieznany postęp pobierania. W przypadku, gdy jest to ważne, naprawdę musisz najpierw zapisać do lokalnego (tymczasowego) pliku, a następnie dostarczyć go, jak pokazano w poprzednim rozdziale.
Metoda użytkowa
Jeśli korzystasz z biblioteki narzędziowej JSF OmniFaces , możesz użyć jednej z trzech wygodnych Faces#sendFile()
metod, przyjmując a File
, lub an InputStream
, lub a byte[]
i określając, czy plik powinien zostać pobrany jako załącznik ( true
) czy inline ( false
).
public void download() throws IOException {
Faces.sendFile(file, true);
}
Tak, ten kod jest kompletny bez zmian. Nie musisz wzywać siebie responseComplete()
i tak dalej. Ta metoda również poprawnie radzi sobie z nagłówkami specyficznymi dla IE i nazwami plików UTF-8. Możesz znaleźć kod źródłowy tutaj .
InputStream
infrastrukturyp:fileDownload
, a nie udało mi się przekonwertowaćOutputStream
naInputStream
. Teraz jest jasne, że nawet detektor akcji może zmienić typ zawartości odpowiedzi, a wtedy odpowiedź i tak zostanie uwzględniona jako plik do pobrania po stronie klienta użytkownika. Dziękuję Ci!