Введение
Вы можете пройти через все 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
) или inline ( false
).
public void download() throws IOException {
Faces.sendFile(file, true);
}
Да, этот код является полным как есть. Не нужно призывать себя responseComplete()
и тд. Этот метод также правильно работает с заголовками IE и именами файлов UTF-8. Вы можете найти исходный код здесь .
InputStream
инфраструктураp:fileDownload
, и я не сумел преобразоватьOutputStream
вInputStream
. Теперь ясно, что даже прослушиватель действий может изменить тип содержимого ответа, и тогда ответ в любом случае будет учитываться как загрузка файла на стороне пользовательского агента. Спасибо!