Невозможно отобразить PDF в p: мультимедиа, созданный из потокового контента в Primefaces

Я пытаюсь показать встроенный PDF, который открывается в новом окне браузера. У меня есть следующий сценарий:

  1. В некотором ActionListen, который вызывается ajax, я генерирую содержимое PDF, помещаю данные в сессию и отправляю Javascript для выполнения (window.open открыть новую страницу, чтобы показать PDF)
  2. На открывшейся странице у меня просто есть 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>

Другие вопросы по тегам