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

csh*_*bie 7 java spring server-sent-events angular

我正在尝试为我的应用程序实现 SSE。我的客户端是 Angular 4 应用程序,我使用 EventSourcePolyfill (以使其也适用于 IE)。我的服务器端是 spring,我使用 spring SseEmitter 来实现相同的目的。

我根据客户端的特定用户事件打开一个新的 SSE 连接。我可以看到请求到达服务器,记录了 SSE 事件,并且还可以看到响应正在创建。我想以 json 形式发送响应。我基本上是在尝试发送数据更新,并使用更新的数据创建一个 json 。但 SSE 永远不会到达浏览器。它仅转到 Eventsource.onerror 方法。所以浏览器会不断重试。*

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

  • 我把代码片段和错误信息放在这里。

角度代码:

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

弹簧(服务器端)

控制器:

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

服务:

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

注释的代码行是我尝试过但不起作用的几行。

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

错误

从 console.log onError 方法:

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

浏览器错误:

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

在此寻求朋友们的帮助,解决我的问题。

ESa*_*ala 4

您在返回之前正在处理SseEmitter它,它不是这样工作的。由于直到最后才将发射器引用返回给客户端,因此客户端不会收到任何内容。

正确的步骤:您必须创建一个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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在此示例中,我们执行GET /listen订阅事件流,然后我们可以发布消息以POST /notify将消息推送到所有侦听客户端,但您可以从其他源发送消息,例如像示例中那样的循环线程。

关键概念是顺序:创建发射器、存储发射器、返回发射器,然后发送到发射器。