Glassfish 4.1中的Jersey过滤器中的ChunkedOutput / OutputBuffer中的SSE nullpointer

时间:2015-07-24 12:30:50

标签: java glassfish jersey server-sent-events

我有一个问题(similar to this one?),当我将jersey(2.19)从无描述符部署通过@ApplicationPath切换到现有JAX-RS Web应用程序中的servlet 2.x过滤器时开始。 我完成SSE后就不再工作了。当我使用SSEBroadcaster时,我构建了一个更简单的测试方法,因为我没有与广播公司有任何例外。

最后弹出了异常:

Severe:   java.lang.NullPointerException
at org.apache.catalina.connector.OutputBuffer.writeBytes(OutputBuffer.java:350)
at org.apache.catalina.connector.OutputBuffer.write(OutputBuffer.java:342)
at org.apache.catalina.connector.CoyoteOutputStream.write(CoyoteOutputStream.java:161)
at org.apache.catalina.connector.CoyoteOutputStream.write(CoyoteOutputStream.java:150)
at org.glassfish.jersey.servlet.internal.ResponseWriter$NonCloseableOutputStreamWrapper.write(ResponseWriter.java:293)
at org.glassfish.jersey.message.internal.CommittingOutputStream.write(CommittingOutputStream.java:214)
at org.glassfish.jersey.media.sse.OutboundEventWriter.writeTo(OutboundEventWriter.java:100)
at org.glassfish.jersey.media.sse.OutboundEventWriter.writeTo(OutboundEventWriter.java:63)
at org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.invokeWriteTo(WriterInterceptorExecutor.java:263)
at org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.aroundWriteTo(WriterInterceptorExecutor.java:250)
at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:162)
at org.glassfish.jersey.message.internal.MessageBodyFactory.writeTo(MessageBodyFactory.java:1154)
at org.glassfish.jersey.server.ChunkedOutput$1.call(ChunkedOutput.java:219)
at org.glassfish.jersey.server.ChunkedOutput$1.call(ChunkedOutput.java:190)
at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
at org.glassfish.jersey.internal.Errors.process(Errors.java:242)
at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:347)
at org.glassfish.jersey.server.ChunkedOutput.flushQueue(ChunkedOutput.java:190)
at org.glassfish.jersey.server.ChunkedOutput.write(ChunkedOutput.java:180)
at ch.company.app.controller.MyController$1.run(MyController.java:62)
at java.lang.Thread.run(Thread.java:745)

我使用的代码是发现here的官方Jersey文档的变体:

@RequestScoped
@Path("api")
public class MyController {

  private final static Logger LOGGER = Logger.getLogger(MyController.class.getName());

  @GET
  @Path("test")
  @Produces(SseFeature.SERVER_SENT_EVENTS)
  public EventOutput getServerSentEvents() {
    final EventOutput eventOutput = new EventOutput();
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                int counter = 0;
                while (!eventOutput.isClosed()) {
                    LOGGER.log(Level.INFO, "Eventoutput closed?: " + eventOutput.isClosed());
                    final OutboundEvent.Builder eventBuilder = new OutboundEvent.Builder();
                    eventBuilder.name("message-to-client");
                    eventBuilder.id("c" + counter);
                    eventBuilder.comment("this is a test comment");
                    eventBuilder.data(String.class, "Hello world " + counter + "!");
                    counter++;
                    final OutboundEvent event = eventBuilder.build();
                    eventOutput.write(event);
                    //Thread.sleep(Integer.toUnsignedLong(5000));
                }
            } catch (IOException e) {
                throw new RuntimeException("Error when writing the event.", e);
            //} catch (InterruptedException ex) {
                //  Logger.getLogger(MyController.class.getName()).log(Level.SEVERE, null, ex);
            } finally {
                try {
                    eventOutput.close();
                } catch (IOException ioClose) {
                    throw new RuntimeException("Error when closing the event output.", ioClose);
                }
            }
        }
    }).start();
    return eventOutput;
  }
}

如果我只使用eventBuilder.data(...)而不使用name(), id(), comment(),则异常(如建议的here)会稍微改变一下:

Severe:   java.lang.NullPointerException
at org.apache.catalina.connector.OutputBuffer.writeBytes(OutputBuffer.java:350)
at org.apache.catalina.connector.OutputBuffer.write(OutputBuffer.java:342)
at org.apache.catalina.connector.CoyoteOutputStream.write(CoyoteOutputStream.java:161)
at org.apache.catalina.connector.CoyoteOutputStream.write(CoyoteOutputStream.java:150)
at org.glassfish.jersey.servlet.internal.ResponseWriter$NonCloseableOutputStreamWrapper.write(ResponseWriter.java:293)
at org.glassfish.jersey.message.internal.CommittingOutputStream.write(CommittingOutputStream.java:214)
at org.glassfish.jersey.media.sse.OutboundEventWriter$1.write(OutboundEventWriter.java:141)
at java.io.OutputStream.write(OutputStream.java:116)
at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:221)
at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:291)
at sun.nio.cs.StreamEncoder.implFlush(StreamEncoder.java:295)
at sun.nio.cs.StreamEncoder.flush(StreamEncoder.java:141)
at java.io.OutputStreamWriter.flush(OutputStreamWriter.java:229)
at java.io.BufferedWriter.flush(BufferedWriter.java:254)
at org.glassfish.jersey.message.internal.ReaderWriter.writeToAsString(ReaderWriter.java:192)
at org.glassfish.jersey.message.internal.AbstractMessageReaderWriterProvider.writeToAsString(AbstractMessageReaderWriterProvider.java:129)
at org.glassfish.jersey.message.internal.StringMessageProvider.writeTo(StringMessageProvider.java:99)
at org.glassfish.jersey.message.internal.StringMessageProvider.writeTo(StringMessageProvider.java:59)
at org.glassfish.jersey.media.sse.OutboundEventWriter.writeTo(OutboundEventWriter.java:127)
at org.glassfish.jersey.media.sse.OutboundEventWriter.writeTo(OutboundEventWriter.java:63)
at org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.invokeWriteTo(WriterInterceptorExecutor.java:263)
at org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.aroundWriteTo(WriterInterceptorExecutor.java:250)
at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:162)
at org.glassfish.jersey.message.internal.MessageBodyFactory.writeTo(MessageBodyFactory.java:1154)
at org.glassfish.jersey.server.ChunkedOutput$1.call(ChunkedOutput.java:219)
at org.glassfish.jersey.server.ChunkedOutput$1.call(ChunkedOutput.java:190)
at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
at org.glassfish.jersey.internal.Errors.process(Errors.java:242)
at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:347)
at org.glassfish.jersey.server.ChunkedOutput.flushQueue(ChunkedOutput.java:190)
at org.glassfish.jersey.server.ChunkedOutput.write(ChunkedOutput.java:180)
at ch.company.app.controller.MyController$1.run(MyController.java:62)
at java.lang.Thread.run(Thread.java:745)

奇怪的是,有时候,在重新启动Glassfish(4.1)和新部署之后,在调用方法时,我实际上得到了所需的输出1到90次(不使用Thread.sleep() - >为什么它在这里的评论中)只能再次被NullPointerException所遵循。就我所知,它只做了一次。

NullPointerException之前的输出示例:

: this is a test comment
event: message-to-client
id: c0
data: Hello world 0!

关于jersey servlet 2.x容器过滤器的我的web.xml:

<filter>
    <filter-name>jersey</filter-name>
    <filter-class>org.glassfish.jersey.servlet.ServletContainer</filter-class>
    <init-param>
        <param-name>jersey.config.server.provider.packages</param-name>
        <param-value>ch.company.app</param-value>
    </init-param>
    <init-param>
        <param-name>jersey.config.servlet.filter.staticContentRegex</param-name>
        <param-value>^.+?\.(?:bmp|gif|png|jpg|jpeg|ico|css|js|pdf|txt|svg|eot|otf|ttf|woff|map)$</param-value>
    </init-param>
</filter>
<!--
<filter-mapping>
    <filter-name>jersey</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

由于@ApplicationPath生成了一个Servlet 3.x容器,我决定使用以下web.xml而不是Servlet 2.x过滤器来尝试Servlet 3.x,但它也引发了我同样的异常:

<servlet>
    <servlet-name>javax.ws.rs.core.Application</servlet-name>
    <init-param>
        <param-name>jersey.config.server.provider.packages</param-name>
        <param-value>ch.company.app</param-value>
    </init-param>
    <init-param>
        <param-name>jersey.config.server.provider.classnames</param-name>
        <param-value>org.glassfish.jersey.media.sse.SseFeature</param-value>
    </init-param>
    <init-param>
        <param-name>jersey.config.disableMoxyJson</param-name>
        <param-value>true</param-value>
    </init-param>
    <init-param>
        <param-name>jersey.config.servlet.filter.staticContentRegex</param-name>
        <param-value>^.+?\.(?:bmp|gif|png|jpg|jpeg|ico|css|js|pdf|txt|svg|eot|otf|ttf|woff|map)$</param-value>
    </init-param>
</servlet>
<servlet-mapping>
    <servlet-name>javax.ws.rs.core.Application</servlet-name>
    <url-pattern>/*</url-pattern>
</servlet-mapping>

我甚至尝试手动添加SseFeature(如此处所示:https://java.net/jira/browse/JERSEY-2150),这对任何一方都没有帮助,根据SSE文档,只有在球衣版本2.8之前才有必要。

<init-param>
        <param-name>jersey.config.server.provider.classnames</param-name>
        <param-value>org.glassfish.jersey.media.sse.SseFeature</param-value>
</init-param>

所以现在我花了两天的时间来讨论这个问题,但我仍然无法弄明白。 有什么想法吗?

更新

我发现只有当Jersey被配置为作为过滤器运行时才会发生。当设置为Servlet时,我设法使用Servlet 3.x和web描述符省略了init-params,这不适用,如下所述:https://jersey.java.net/apidocs/latest/jersey/org/glassfish/jersey/servlet/ServletProperties.html

<servlet>
    <servlet-name>ch.company.app.ApplicationConfig</servlet-name>
    <init-param>
        <param-name>javax.ws.rs.Application</param-name>
        <param-value>ch.company.app.ApplicationConfig</param-value>
    </init-param>
</servlet>
<servlet-mapping>
    <servlet-name>ch.company.app.ApplicationConfig</servlet-name>
    <url-pattern>/*</url-pattern>
</servlet-mapping>

和相应的班级

public class ApplicationConfig extends Application {
  @Override
  public Set<Class<?>> getClasses() {
    Set<Class<?>> resources = new java.util.HashSet<>();
    addRestResourceClasses(resources);
    return resources;
  }

  private void addRestResourceClasses(Set<Class<?>> resources) {
    resources.add(ch.company.app.controller.AppController.class);
    resources.add(ch.company.app.controller.IndexController.class);
  }
}

不幸的是,我仍然无法通过这种方法弄清楚如何访问我的静态资源(css,js,images),因为Servlet配置中没有FILTER_STATIC_CONTENT_REGEX。 (我会另外调查一下。)

所以问题是:当Jersey被配置为过滤器时,为什么它不起作用?

0 个答案:

没有答案