Вступ
Ви можете все пройти 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
. Тепер зрозуміло, що навіть прослуховувач дій може змінити тип вмісту відповіді, і тоді відповідь все одно буде поважатись як завантаження файлу на стороні агента користувача. Дякую!