【问题标题】:IllegalStateException when HTTP Streaming using ResponseBodyEmitter in Spring-MVC在 Spring-MVC 中使用 ResponseBodyEmitter 进行 HTTP 流式处理时出现 IllegalStateException
【发布时间】:2015-09-20 18:06:12
【问题描述】:

我在 Spring 4.2.0.BUILD-SNAPSHOT 中使用新添加的 HTTP 流功能和 ResponseBodyEmitter。

我想在(可能是 java)客户端和服务器之间的无休止的数据流上实现一个长时间运行的持久 TCP 连接,直到客户端断开连接。我想避免使用 websocket 协议。

  1. 如果客户端在流式传输时断开连接,则会引发运行时 IllegalStateException。我想优雅地处理这个并清理发射器。没有捕获运行时异常,有什么方法可以优雅地处理这个问题?
  2. 我必须在发射器上为“持久”连接指定一个人为的高超时值。我可以不设置超时吗?

webapp 部署在 apache-tomcat-7.0.62 上。

相关代码如下:

@RequestMapping(value = "stream", method = RequestMethod.GET)
public ResponseBodyEmitter handleStreaming() {
    ResponseBodyEmitter emitter = new ResponseBodyEmitter(timeout);
    emitters.add(emitter);
    emitter.onCompletion(new Runnable() {
        @Override
        public void run() {
            emitters.remove(emitter);
        }
    });
    emitter.onTimeout(new Runnable() {
        @Override
        public void run() {
            emitters.remove(emitter);
        }
    });
    return emitter;
}

.

    while (true) {
        for (Iterator<ResponseBodyEmitter> iterator = emitters.iterator(); iterator.hasNext();) {
            ResponseBodyEmitter emitter = iterator.next();
            try {
                emitter.send("data...", MediaType.TEXT_PLAIN);
            } catch (IOException | IllegalStateException e) {
                LOGGER.error(e);
                iterator.remove();
            }
        }

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            LOGGER.error(e);
        }
    }

日志:

INFO: An error occurred in processing while on a non-container thread. The connection will be closed immediately
java.net.SocketException: Broken pipe
    at java.net.SocketOutputStream.socketWrite0(Native Method)
    at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:109)
    at java.net.SocketOutputStream.write(SocketOutputStream.java:153)
    at org.apache.coyote.http11.InternalOutputBuffer.realWriteBytes(InternalOutputBuffer.java:215)
    at org.apache.tomcat.util.buf.ByteChunk.flushBuffer(ByteChunk.java:480)
    at org.apache.coyote.http11.InternalOutputBuffer.flush(InternalOutputBuffer.java:119)
    at org.apache.coyote.http11.AbstractHttp11Processor.action(AbstractHttp11Processor.java:801)
    at org.apache.coyote.Response.action(Response.java:172)
    at org.apache.catalina.connector.OutputBuffer.doFlush(OutputBuffer.java:363)
    at org.apache.catalina.connector.OutputBuffer.flush(OutputBuffer.java:331)
    at org.apache.catalina.connector.CoyoteOutputStream.flush(CoyoteOutputStream.java:101)
    at sun.nio.cs.StreamEncoder.implFlush(StreamEncoder.java:297)
    at sun.nio.cs.StreamEncoder.flush(StreamEncoder.java:141)
    at java.io.OutputStreamWriter.flush(OutputStreamWriter.java:229)
    at org.springframework.util.StreamUtils.copy(StreamUtils.java:106)
    at org.springframework.http.converter.StringHttpMessageConverter.writeInternal(StringHttpMessageConverter.java:109)
    at org.springframework.http.converter.StringHttpMessageConverter.writeInternal(StringHttpMessageConverter.java:40)
    at org.springframework.http.converter.AbstractHttpMessageConverter.write(AbstractHttpMessageConverter.java:193)
    at org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitterReturnValueHandler$HttpMessageConvertingHandler.sendInternal(ResponseBodyEmitterReturnValueHandler.java:157)
    at org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitterReturnValueHandler$HttpMessageConvertingHandler.send(ResponseBodyEmitterReturnValueHandler.java:150)
    at org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter.sendInternal(ResponseBodyEmitter.java:180)
    at org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter.send(ResponseBodyEmitter.java:164)
    ....

[ERROR] [02/07/15 18:11 PM] [Controller$TestResponseBodyEmitter:74] - java.lang.IllegalStateException: The request associated with the AsyncContext has already completed processing.

命令:

curl http://localhost:8080/myapp/stream -v -N
data...data...
Ctrl-C

【问题讨论】:

  • 我遇到了同样的问题。我想知道为什么需要超时。它似乎扼杀了SSE的目的,但也许我误解了这个概念......

标签: java spring spring-mvc exception exception-handling


【解决方案1】:

根据 ResponseBodyEmitter 的构造函数的 Javadoc(找到 here)。

使用自定义超时值创建一个 ResponseBodyEmitter。默认 未设置在这种情况下,在 MVC Java Config 中配置的默认值或 使用 MVC 命名空间,或者如果未设置,则超时 取决于底层服务器的默认值。

因此,在创建 c 实例时要设置一个超时时间

PS:在我的环境中 ResponseBodyEmitter#getTimeout() 返回 null;这并不意味着存在无限超时。相反,在 5-10 秒后连接超时。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-05-27
    • 2011-01-05
    • 1970-01-01
    • 2012-03-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多