Spring应用程序的SSE响应未能到达客户端

6

我正在尝试为我的应用程序实现SSE。我的客户端是Angular 4应用程序,我使用EventSourcePolyfill(也可以在IE中使用)。我的服务器端是Spring,我也使用Spring SseEmitter。

我基于客户端特定用户事件打开一个新的SSE连接。我可以看到请求到达服务器,SSE事件被记录下来,我也能看到响应正在创建。我想要以json格式发送响应。我基本上是尝试发送数据更新,并创建带有更新数据的json。但是SSE从未到达浏览器,只进入了Eventsource.onerror方法。因此,浏览器一直在重试。

在Chrome的开发工具-网络选项卡中,我可以看到SSE请求显示为等待几秒钟,然后更改为取消。

  • 这里是我放置代码片段和错误信息的地方。

Angular 代码:

  let eventSource = new EventSourcePolyfill('v1/sse/getInfiniteMessages', {
  // headers: {
  //   'Accept': 'text/event-stream'
  // }, 
  heartbeatTimeout:180
});

eventSource.onmessage = (eventResponse => {
  console.log("Message from event source is :: " + eventResponse);
  console.log("JSON from event source is :: " + eventResponse.data);

});
eventSource.onopen = (a) => {
  // Do stuff here
  console.log("Eventsource.onopen.. " + JSON.stringify(a));
};
eventSource.onerror = (e) => {
  // Do stuff here
  console.log("Eventsource.onerror.. Exception is:: " + JSON.stringify(e));
  if (e.readyState == eventSource.CLOSED) {
    console.log('event source is closed');
    eventSource.close();
   }
   else {
     console.log("Not a event source closed error");
   }
}

Spring(服务器端)

控制器:

@RequestMapping(method = RequestMethod.GET, value = "/getInfiniteMessages")
public SseEmitter getInfiniteMessages() { 
    return iSSEService.getInfiniteMessages();
}

服务:

public SseEmitter getInfiniteMessages(String chatRefId) {
    logger.info("In SSEService.. getInfiniteMessages method.." );

    boolean stopSSE = false;
    while (!stopSSE) {
        try {
            ResponseVo responseVO = new ResponseVo();
            responseVO = getData();
//              Gson gson = new Gson();
//              String sseMessage = gson.toJson(responseVO);
//              logger.info("sseMessage to send: "  + sseMessage);
//                emitter.send(sseMessage , MediaType.APPLICATION_JSON);
            emitter.send(responseVO);

            Thread.sleep(30000);
            //stopSSE = true;
        } catch (Exception e) {
            e.printStackTrace();
            emitter.completeWithError(e);
            //return;
        }
    }

    /*for (int i = 0; i < 100; i++) {
        try {
            emitter.send(i + " - Message", MediaType.TEXT_PLAIN);

            Thread.sleep(10);
        } catch (Exception e) {
            e.printStackTrace();
            emitter.completeWithError(e);
            //return;
        }
    }*/

    return emitter;
}    

有几行注释掉的代码,是我尝试过但没有成功的。

注意在服务端代码中,如果我取消注释for循环(运行100次),sse文本会到达浏览器,但我想发送包含数据更新的json。我尝试将对象创建为json并作为文本发送,但这并没有帮助。

错误

从onError方法的console.log中看到:

Not a event source closed error
    Eventsource.onerror.. Exception is:: {"type":"error","target":{"listeners":{"data":{}},"url":"/v1/sse/getInfiniteMessages","readyState":0,"withCredentials":false}}

浏览器错误:

core.es5.js:1020 ERROR Error: No activity within 45000 milliseconds. Reconnecting.
    at eventsource.js:363
    at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:425)
    at Object.onInvokeTask (core.es5.js:3881)
    at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:424)
    at Zone.webpackJsonp.../../../../zone.js/dist/zone.js.Zone.runTask (zone.js:192)
    at webpackJsonp.../../../../zone.js/dist/zone.js.ZoneTask.invokeTask (zone.js:499)
    at ZoneTask.invoke (zone.js:488)
    at timer (zone.js:2040)

希望在这里得到朋友们的帮助,来解决我的问题。

1个回答

4

在返回SseEmitter之前你正在处理它,这样是不起作用的。因为只有在最后客户端才能接收到emitter引用。

正确的步骤是:你需要创建一个SseEmitter,将其存储在内存中,在控制器方法中返回它,然后开始发送信息。

下面是一个快速但可能不完整的示例:

@Controller
public SseController {
    private final List<SseEmitter> emitters = new ArrayList<>();

    @GetMapping("/listen")
    public SseEmitter getEvents() {
        SseEmitter emitter = new SseEmitter();
        emitters.add(emitter);
        emitter.onCompletion(() -> emitters.remove(emitter));
        return emitter;
    }

    @PostMapping("/notify")
    public void postMessage(String message) {
        for (SseEmitter emitter : emitters) {
            emitter.send(message);
        }
    }
}

在这个例子中,我们通过执行 GET /listen 来订阅事件流,然后我们可以使用 POST /notify 推送消息到所有正在监听的客户端,但你也可以像你的示例中一样从其他来源发送消息。
关键概念是顺序:创建发射器,存储发射器,返回发射器,然后发送到发射器。

谢谢。正如我所提到的,在服务代码中,如果我取消注释for循环(运行100次),sse文本将传达到浏览器。您能帮助我理解吗?在您的示例中,客户端将调用GET /listen来注册事件流。谁会调用POST /notify?或者您的意思是创建一种线程,该线程将向所有已订阅的客户端发送消息?此外,是否有特定的方法可以发送json响应?还是我只需使用emitter.send(object),它就会自动转换为json? - csharpnewbie
@csharpnewbie 抱歉,由于您的代码有误,我不会浪费时间进行调试。至于我的示例,您可以以任何方式向发射器列表发送消息,在本例中我使用了 POST /notify,但您也可以使用定期方法、线程或您需要的任何方法。您还可以选择性地向哪些发射器推送(您不必一次性向所有发射器推送)。关于发送JSON响应,它应该会自动转换为JSON。请参阅此完整教程以获得更详细的解释:https://golb.hplar.ch/p/Server-Sent-Events-with-Spring - ESala
@ESala 我有一个类似的问题,响应只有在返回SSE发射器后才到达客户端。SseEmiter->send不会将响应发送回客户端。所需条件是->EventSource('startListen'),并且每次服务器执行SseEmiter.send时都应该返回响应。 - know_a_guy_hu_knows_anothr_guy

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