Невозможно отобразить PDF в p: мультимедиа, созданный из потокового контента в Primefaces
Я пытаюсь показать встроенный PDF, который открывается в новом окне браузера. У меня есть следующий сценарий:
- В некотором ActionListen, который вызывается ajax, я генерирую содержимое PDF, помещаю данные в сессию и отправляю Javascript для выполнения (
window.open
открыть новую страницу, чтобы показать PDF) На открывшейся странице у меня просто есть
p:media
тег внутриh:body
со значением, указывающим наStreamedContent
:
Теперь на этой странице мой PDF не создается. В журнале я вижу эти две строки:
org.primefaces.application.PrimeResourceHandler handleResourceRequest
SEVERE: Error in streaming dynamic resource. Expression cannot be null
Я начал отлаживать и узнавать несколько вещей.
Во-первых, я добавил точку останова @PostConstruct
метод моего RequestScoped
боб. Интересно то, что точка останова достигается дважды, и, к моему большому удивлению, после этого PDF отлично отображается?!
После некоторой отладки PrimeResourceHandler
Я понимаю, что в некоторых случаях ValueExpression
не рассчитывается, на самом деле он бросает NullPointerException
и снова во время отладки я увидел, что два запроса отправляются, а второй запрос не выполняется, потому что dynamicContentId
удаляется в первом запросе и втором вызове handleResourceRequest
не имеет смысла
Через Firebug я вижу два запроса: первый, который хорош с данными PDF, и второй, который также с типом содержимого application/pdf, но пустой, с размером 0.
HTML-страница:
<html>
<h:head></h:head>
<h:body>
<p:media value="#{reportBean.streamedContent}" player="pdf" width="500" height="500"/>
</h:body>
</html>
поддерживающий боб:
@RequestScoped
public class StampaListeBackingBean implements Serializable {
private static final long serialVersionUID = 1L;
private StreamedContent streamedContent;
@PostConstruct
public void init() {
Map<String, Object> session = FacesContext.getCurrentInstance().getExternalContext().getSessionMap();
byte[] b = (byte[]) session.get("reportBytes");
if (b != null) {
streamedContent = new DefaultStreamedContent(new ByteArrayInputStream(b), "application/pdf");
}
}
public StreamedContent getStreamedContent() {
if (FacesContext.getCurrentInstance().getRenderResponse()) {
return new DefaultStreamedContent();
} else {
return streamedContent;
}
}
public void setStreamedContent(StreamedContent streamedContent) {
this.streamedContent = streamedContent;
}
}
Мне нужно понять, почему два запроса отправляются на страницу с p:media
тег, и выяснить, как заставить это работать. Бэк-бин является областью запроса, он создает StreamedContent
в @PostConstruct
метод, и имеет геттер и сеттер для этого поля. Версия Primefaces - 3.4.2, с Мохаррой 2.1.14.
ДОБАВЛЕНО:
Это легко воспроизвести мою проблему. Если код в init
метод заменяется следующим:
FileInputStream fis = new FileInputStream(new File("C:\\samplexxx.pdf"));
streamedContent = new DefaultStreamedContent(fis, "application/pdf");
проблема может быть воспроизведена.
3 ответа
Я могу воспроизвести вашу проблему. Это действительно не работает в Firefox (ни в IE9, но в Chrome). Лидер PrimeFaces Кагатай также упоминал об этом несколько раз.
Я не уверен, что это ошибка в обработчике ресурса PrimeFaces или в браузере. Я оставлю это в середине.
Между тем, ваш лучший выбор - это простой веб-сервлет для работы. Просто создайте этот класс:
@WebServlet("/report.pdf")
public class PdfReportServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
byte[] content = (byte[]) request.getSession().getAttribute("reportBytes");
response.setContentType("application/pdf");
response.setContentLength(content.length);
response.getOutputStream().write(content);
}
}
И вызвать его следующим образом:
<p:media value="/report.pdf" ... />
Вот и все. Конфигурация XML не требуется. Это работает для меня во всех браузерах. В зависимости от функциональных требований может потребоваться дополнительная настройка заголовков ответов, связанных с кэшированием в браузере.
Это не проблема браузера или простых лиц, просто забавная проблема получателя.
Получатель вызывается дважды с помощью p:media (или если вы обновляете страницу больше, чем раз), но только 1-й вызов получает правильные данные. StreamedContent инкапсулирует InputStream, у которого есть свойство, которое он не будет давать байтов, если поток находится в конце файла. Первый раз он читается до конца (данные в порядке), но каждый следующий вызов не будет получать данные.:)
javadoc of inputStream.read (): если байт недоступен, поскольку поток находится в конце файла, возвращается значение -1; в противном случае, по крайней мере один байт считывается и сохраняется в b.
Решение:
private StreamedContent streamedContent;
private InputStream stream;
public void somewhere(){
byte[] b = ...
stream = new ByteArrayInputStream( b );
stream.mark(0); //remember to this position!
streamedContent = new DefaultStreamedContent(stream, "application/pdf");
}
public StreamedContent getStreamedContent() {
if (streamedContent != null)
streamedContent.getStream().reset(); //reset stream to the start position!
return streamedContent;
}
Я надеюсь, что мой небольшой вклад может помочь любому, кто не может отобразить предварительный просмотр PDF в Firefox. Я использовал Primefaces 6 + Spring, и у меня была та же проблема, но, возможно, не по той же причине. Действительно, я попробовал предлагаемое решение от Balus C. Это помогло мне отобразить PDF в Chrome и IE11, но оно все еще не работало в Firefox 52.
Я заметил ошибку в консоли Firefox: загрузка запрещена X-Frame-Options: http://localhost:8080/myapp/ не разрешает кадрирование
В моем случае это было потому, что конфигурация Spring-Security и решение были отредактированы Spring-context.xml следующим образом:
<sec:http ...>
...
<sec:headers>
<sec:frame-options policy="SAMEORIGIN" />
</sec:headers>
...
</sec:http>