Spring服务器发送事件 - ResponseBodyEmitter已经设置完成。

5
我正在使用spring 4.3.7.RELEASE来设置服务器发送事件。并使用@scheduled注释每2秒发送一次消息。 以下是控制器代码。
@Controller
public class MySSEController {
    private final SseEmitter sseEmitter = new SseEmitter();
    private int counter = 0;

    @RequestMapping("/ssestream")
    public SseEmitter getRealTimeMessageAction() throws IOException {

        sseEmitter.send("MessageCounter : " + counter);
        return sseEmitter;
    }

    @Scheduled(fixedDelay = 2*1000)
    public void scheduledMsgEmitter() throws IOException
    {
        if(null != sseEmitter) {
            sseEmitter.send("MessageCounter : " + ++counter);
        }
    }

}

我正在使用Eclipse直接运行Tomcat 9。应用程序每2秒向浏览器发送消息。但是一段时间后,它停止发送消息,并在Eclipse控制台中看到下面的异常。

Mar 16, 2017 6:57:34 PM org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver handleAsyncRequestTimeoutException
SEVERE: Async timeout for GET [/streaming-web/stream/ssestream]
Mar 16, 2017 6:57:35 PM org.springframework.scheduling.support.TaskUtils$LoggingErrorHandler handleError
SEVERE: Unexpected error occurred in scheduled task.
java.lang.IllegalStateException: ResponseBodyEmitter is already set complete
    at org.springframework.util.Assert.state(Assert.java:70)
    at org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter.send(ResponseBodyEmitter.java:158)
    at org.springframework.web.servlet.mvc.method.annotation.SseEmitter.send(SseEmitter.java:126)
    at org.springframework.web.servlet.mvc.method.annotation.SseEmitter.send(SseEmitter.java:107)
    at org.springframework.web.servlet.mvc.method.annotation.SseEmitter.send(SseEmitter.java:89)
    at com.mycomp.test.controllers.MySSEController.scheduledMsgEmitter(MySSEController.java:25)
    at sun.reflect.GeneratedMethodAccessor31.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:65)
    at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

我有些不理解,请帮助我解决这个问题。


你尝试使用自定义超时选项来调用SseEmitter构造函数了吗? - gvmani
@ gvmani 是的,我试过使用 new SseEmitter(60000l),并在1分钟后遇到了相同的异常。浏览器接收到的最后一条消息是 _"MessageCounter : 31"_。 - Master Po
嗨@Pete,你找到解决方案了吗?请发布解决方案,我也遇到了同样的问题。谢谢。 - Mohammad nabil
@Mohammadnabil 请看下面的答案。我对事件发射器的实现进行了一些修改。 - Master Po
4个回答

6

我不确定这是否是正确的解决方案。

我稍微修改了实现方式,以使其适用于所有连接的客户端。之前我无法将相同的消息发送到连接到此流的不同客户端 此实现不会抛出IllegalStateException异常。

import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

@Controller
public class MySSEController_Working {

    private Set<SseEmitter> sseEmitters = new HashSet<SseEmitter>();
    private int messageCount = 0;

    @RequestMapping("/ssestream")
    public SseEmitter getRealTimeMessageAction(HttpServletRequest request, HttpServletResponse response) throws IOException {

        final SseEmitter sseEmitter = new SseEmitter();

        sseEmitter.onCompletion(() -> {
            synchronized (this.sseEmitters) {
                this.sseEmitters.remove(sseEmitter);
            }
        });

        sseEmitter.onTimeout(()-> {
            sseEmitter.complete();
        });

        // Put context in a map
        sseEmitters.add(sseEmitter);

        return sseEmitter;
    }

    @Scheduled(fixedDelay = 2*1000)
    public void scheduledMsgEmitter() throws IOException
    {
        if(!sseEmitters.isEmpty())
            ++messageCount;
        else 
            System.out.println("No active Emitters ");

        System.out.println("Sent Messages : " + messageCount);

        sseEmitters.forEach(emitter -> {
            if (null != emitter)
                try {
                    System.out.println("Timeout : "+ emitter.getTimeout());
                    emitter.send("MessageCounter : " + messageCount);
                    emitter.complete();
                } catch (IOException e) {
                    e.printStackTrace();
                }
        });
    }
}

3
SseEmitter timeout 是以毫秒为单位的:60001 表示 60 秒。它是会话超时而不受会话活动影响。
超时时间需要设置为会话预期持续时间的毫秒数,因此,86400000(或更多)是完全合适的。

0

我在没有完成事件的情况下使用了new org.springframework.web.servlet.mvc.method.annotation.SseEmitter(0L)


0

记录我关于SSE的信息。

要点:

  • 1.默认超时时间为30秒,当超时时,前端将触发onerror监听器,后端将命中sseEmitter.onComplete()
  • 2.我认为计划任务不是发送消息的好工具;我的建议是ScheduledExecutorService

答案:

  • 1.正如@NeoJhou所提到的,new SseEmitter(0L)对我有用
  • 2.在前端代码中,当出现错误时,'你'可以关闭()事件源连接
  • 3.在后端代码中,也需要手动完成()

参考资料:


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接