【问题标题】:Unable to show PDF in p:media generated from streamed content in Primefaces无法在 p:media 中显示由 Primefaces 中的流式内容生成的 PDF
【发布时间】:2013-01-07 05:13:58
【问题描述】:

我正在尝试显示在新浏览器窗口中打开的内联 PDF。我有以下情况:

  1. 在一些由 ajax 调用的 ActionListen 中,我生成 PDF 内容,将数据放入会话中,并发送要执行的 Javascript(window.open 打开新页面以显示 PDF)
  2. 在打开的页面上,我在h:body 内只有p:media 标记,其值指向StreamedContent

现在,在该页面上没有生成我的 PDF。在日志中我可以看到这两行:

org.primefaces.application.PrimeResourceHandler handleResourceRequest
SEVERE: Error in streaming dynamic resource. Expression cannot be null

我开始调试并发现了一些东西。

首先,我在RequestScoped bean 的@PostConstruct 方法中添加了断点。有趣的是断点到达了两次,并且在完美显示 PDF 之后让我大吃一惊?!

通过PrimeResourceHandler进行一些调试后,我发现在某些情况下ValueExpression没有计算出来,实际上它抛出了NullPointerException,再次在调试时我看到发送了两个请求,第二个请求失败了,因为@ 987654333@ 在第一个请求中被删除,第二次调用 handleResourceRequest 没有意义。

通过 Firebug 我可以看到两个请求,第一个是 PDF 数据,第二个也是内容类型 application/pdf 但为空,大小为 0。

xhtml 页面:

<html>
  <h:head></h:head>
  <h:body>
    <p:media value="#{reportBean.streamedContent}" player="pdf" width="500" height="500"/>
  </h:body>
</html>

支持 bean:

@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 标记的页面上发送两个请求,并弄清楚如何使其工作。支持 bean 是请求范围的,它在 @PostConstruct 方法中创建 StreamedContent,并具有该字段的 getter 和 setter。 Primefaces 版本是 3.4.2,Mojarra 是 2.1.14。

添加:

很容易重现我的问题。如果init 方法中的代码替换为以下代码:

FileInputStream fis = new FileInputStream(new File("C:\\samplexxx.pdf"));
streamedContent = new DefaultStreamedContent(fis, "application/pdf");

问题可以重现。

【问题讨论】:

  • 关于观察到的 bean 行为,这是从 &lt;p:graphicImage&gt; 知道的,并在此处详细回答:stackoverflow.com/questions/8207325/… 至于观察到的异常,抱歉,不知道。一些具体的代码会有所帮助。
  • @BalusC 我添加了xhtml和java代码,很简单。我还添加了 Firebug 的检查。
  • 如果你使用像&lt;p:media value="some.pdf"&gt;这样的静态路径呢?只是为了排除资源 servlet 的原因。 PrimeFaces 自己的展示示例怎么样?它也适合你吗?只是为了排除浏览器的原因(它对我有用)primefaces.org/showcase-labs/ui/media.jsf
  • @BalusC 它运行良好。 Primefaces 示例也可以正常工作。但正如我所见,他们在所有示例中也使用静态路径。我还尝试了不同范围的 bean,但没有帮助。

标签: pdf jsf-2 primefaces stream media


【解决方案1】:

我可以重现您的问题。它确实在 Firefox 中不起作用(在 IE9 中也不起作用,但它在 Chrome 中起作用)。 PrimeFaces 负责人 Cagatay 也提到了 several times

我不确定这是 PrimeFaces 资源处理程序还是浏览器中的错误。我会把它放在中间。

与此同时,您最好的选择是简单的web servlet 来完成这项工作。只需创建此类:

@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 配置。它适用于所有浏览器。根据功能要求,您可能需要进一步微调与浏览器缓存相关的响应标头。

【讨论】:

  • :) 谢谢BalusC。现在可以了。这教会我不要总是依赖框架小部件来完成每件简单的事情。
  • 我认为真正的问题在于PrimeResourceHandler.handleResourceRequest,最后部分是这一行session.remove(dynamicContentId)。这可能是这两个请求具有竞争条件的地方。
  • 使用 servlet 解决方法,Firefox 在 PDF 上只发送 1 个请求而不是 2 个。所以问题实际上在于第一个请求的响应;浏览器不知何故无法处理它。如果我有更多时间我会调查它,但不是现在:)
  • 谢谢 BalusC。 -虽然,令人遗憾的是,在 2015 年,我们不得不为 IE11 求助于“解决方法”。 (IE11 仍然出现“必须以 '%PDF-' 开头的错误,所以我正在使用你的 servlet 技术)
  • 感谢 baluc 的帖子,您的帖子适用于带有 jsf 2.1 和 2.1.2 的 IE8,但不适用于带有 javax.faces-2.2.7 的 IE8。请帮忙。
【解决方案2】:

这不是浏览器或 primefaces 问题,只是一个有趣的 getter 问题。

p:media 调用 getter 两次(或者如果您刷新页面多次),但只有第一次调用获得正确的数据。 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;
            }

【讨论】:

    【解决方案3】:

    我希望我的一点点贡献可以帮助那些无法在 Firefox 中显示 pdf 预览的人。我使用的是 Primefaces 6 + Spring,我遇到了同样的问题,但可能不是出于同样的原因。事实上,我尝试了 Balus C 提出的解决方案。它帮助我在 Chrome 和 IE11 中显示 pdf,但在 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>

    【讨论】:

    • 非常有用。 Thx,就我而言,我使用了 java config:http.headers() .frameOptions().sameOrigin() .httpStrictTransportSecurity().disable();
    猜你喜欢
    • 2023-03-05
    • 1970-01-01
    • 2018-06-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-02-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多