Вступ
Ви можете все пройти ExternalContext. У JSF 1.x ви можете отримати необроблений HttpServletResponseоб'єкт за допомогою ExternalContext#getResponse(). У JSF 2.x ви можете використовувати купу нових методів делегування, наприклад, ExternalContext#getResponseOutputStream()без необхідності захоплювати HttpServletResponseз-під капотів JSF.
У відповіді слід встановити Content-Typeзаголовок, щоб клієнт знав, яку програму пов’язати із наданим файлом. І, ви повинні встановити Content-Lengthзаголовок, щоб клієнт міг розрахувати хід завантаження, інакше він буде невідомий. І, ви повинні встановити Content-Dispositionзаголовок, attachmentякщо ви хочете діалогове вікно Зберегти як , інакше клієнт спробує відобразити його в рядку. Нарешті, просто запишіть вміст файлу у вихідний потік відповіді.
Найважливіша частина - це зателефонувати, FacesContext#responseComplete()щоб повідомити JSF, що він не повинен виконувати навігацію та рендеринг після того, як ви записали файл у відповідь, інакше кінець відповіді буде забруднений вмістом HTML сторінки або у старих версіях JSF , ви отримаєте повідомлення IllegalStateExceptionз повідомленням, наприклад, getoutputstream() has already been called for this responseколи реалізація JSF вимагає getWriter()відтворення HTML.
Вимкніть ajax / не використовуйте віддалену команду!
Вам потрібно лише переконатися, що метод дії не викликається запитом ajax, а що він викликається звичайним запитом під час запуску за допомогою <h:commandLink>та <h:commandButton>. Запити Ajax та віддалені команди обробляються JavaScript, який, в свою чергу, з міркувань безпеки не має засобів, щоб змусити діалог " Зберегти як" із вмістом відповіді ajax.
Якщо ви використовуєте, наприклад, PrimeFaces <p:commandXxx>, вам потрібно переконатися, що ви явно вимкнули ajax через ajax="false"атрибут. Якщо ви використовуєте ICEfaces, то вам потрібно вкласти a <f:ajax disabled="true" />в командний компонент.
Загальний приклад 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();
}
Загальний приклад 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();
}
Поширений приклад статичного файлу
Якщо вам потрібно передати потоковий файл із локальної дискової файлової системи, підставте код, як показано нижче:
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);
Поширений приклад динамічного файлу
Якщо вам потрібно передати потоково динамічно створений файл, такий як PDF або XLS, просто вкажіть outputтам, де використовуваний API очікує OutputStream.
Наприклад, iText PDF:
String fileName = "dynamic.pdf";
String contentType = "application/pdf";
Document document = new Document();
PdfWriter writer = PdfWriter.getInstance(document, output);
document.open();
document.close();
Наприклад, Apache POI HSSF:
String fileName = "dynamic.xls";
String contentType = "application/vnd.ms-excel";
HSSFWorkbook workbook = new HSSFWorkbook();
workbook.write(output);
workbook.close();
Зверніть увагу, що тут ви не можете встановити довжину вмісту. Тому вам потрібно видалити рядок, щоб встановити довжину вмісту відповіді. Це технічно не проблема, єдиним недоліком є те, що кінцевому користувачеві буде представлений невідомий хід завантаження. Якщо це важливо, то вам дійсно потрібно спочатку написати в локальний (тимчасовий) файл, а потім надати його, як показано в попередньому розділі.
Корисний метод
Якщо ви використовуєте службову бібліотеку JSF OmniFaces , ви можете скористатися одним із трьох зручних Faces#sendFile()методів, використовуючи або a File, або an InputStream, або a byte[], і вказавши, чи слід завантажувати файл як вкладення ( true) або вбудований ( false).
public void download() throws IOException {
Faces.sendFile(file, true);
}
Так, цей код є повним як є. Не потрібно посилатися responseComplete()і так далі на себе. Цей метод також належним чином обробляє заголовки, специфічні для IE, та імена файлів UTF-8. Ви можете знайти вихідний код тут .
InputStreamінфраструктураp:fileDownload, і я не зумів перетворитиOutputStreamнаInputStream. Тепер зрозуміло, що навіть прослуховувач дій може змінити тип вмісту відповіді, і тоді відповідь все одно буде поважатись як завантаження файлу на стороні агента користувача. Дякую!