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

rem*_*gli 5 java glassfish jersey server-sent-events

我有一个问题(类似于这个?),当我将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)
Run Code Online (Sandbox Code Playgroud)

我使用的代码是这里发现的官方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;
  }
}
Run Code Online (Sandbox Code Playgroud)

如果我只使用eventBuilder.data(...)而不使用name(), id(), comment()异常(如此处所示)会略微改为:

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)
Run Code Online (Sandbox Code Playgroud)

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

NullPointerException之前的输出示例:

: this is a test comment
event: message-to-client
id: c0
data: Hello world 0!
Run Code Online (Sandbox Code Playgroud)

关于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>
Run Code Online (Sandbox Code Playgroud)

由于@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>
Run Code Online (Sandbox Code Playgroud)

我甚至试图手动添加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>
Run Code Online (Sandbox Code Playgroud)

所以现在我花了两天的时间来讨论这个问题,但我仍然无法理解.有任何想法吗?

更新

我发现只有当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>
Run Code Online (Sandbox Code Playgroud)

和相应的班级

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);
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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