Spring 应用的 SSE 响应未到达客户端
SSE response from Spring app does not reach client side
我正在尝试为我的应用程序实施 SSE。我的客户是 angular 4 应用程序,我使用 EventSourcePolyfill(使其也适用于 IE)。我的服务器端是 spring,我也使用 spring SseEmitter。
我根据客户端的特定用户事件打开一个新的 SSE 连接。我可以看到请求到达服务器,记录了 SSE 事件,我也可以看到正在创建响应。我想将响应发送为 json。我基本上是在尝试发送数据更新,并使用更新后的数据创建 json。但是 SSE 永远不会到达浏览器。它只转到 Eventsource.onerror 方法。所以浏览器不断重试。 *
在 Chrome -> Dev tools -> Network 选项卡中,我可以看到 SSE 请求显示为 pending 几秒钟,然后变为 cancelled。
我把代码片段和错误信息放在这里了。
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,并以文本形式发送,但没有帮助。
错误:
来自 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}}
浏览器错误:
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)
在这里寻求朋友的帮助,以解决我的问题。
您在 return 之前使用 SseEmitter
,它不是那样工作的。由于您直到最后才 return 对客户端的发射器引用,客户端不会收到任何东西。
正确步骤:您必须创建一个 SseEmitter
,将其存储在内存中,让控制器方法 return 它,然后开始发射物品。
下面的快速但可能不完整的示例:
@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
发布消息来将消息推送给所有监听的客户端,但是你可以发送消息来自其他来源,例如您示例中的循环线程。
关键概念是顺序:创建发射器、存储发射器、return发射器、然后发送到发射器。
我正在尝试为我的应用程序实施 SSE。我的客户是 angular 4 应用程序,我使用 EventSourcePolyfill(使其也适用于 IE)。我的服务器端是 spring,我也使用 spring SseEmitter。
我根据客户端的特定用户事件打开一个新的 SSE 连接。我可以看到请求到达服务器,记录了 SSE 事件,我也可以看到正在创建响应。我想将响应发送为 json。我基本上是在尝试发送数据更新,并使用更新后的数据创建 json。但是 SSE 永远不会到达浏览器。它只转到 Eventsource.onerror 方法。所以浏览器不断重试。 *
在 Chrome -> Dev tools -> Network 选项卡中,我可以看到 SSE 请求显示为 pending 几秒钟,然后变为 cancelled。
我把代码片段和错误信息放在这里了。
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,并以文本形式发送,但没有帮助。
错误:
来自 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}}
浏览器错误:
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)
在这里寻求朋友的帮助,以解决我的问题。
您在 return 之前使用 SseEmitter
,它不是那样工作的。由于您直到最后才 return 对客户端的发射器引用,客户端不会收到任何东西。
正确步骤:您必须创建一个 SseEmitter
,将其存储在内存中,让控制器方法 return 它,然后开始发射物品。
下面的快速但可能不完整的示例:
@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
发布消息来将消息推送给所有监听的客户端,但是你可以发送消息来自其他来源,例如您示例中的循环线程。
关键概念是顺序:创建发射器、存储发射器、return发射器、然后发送到发射器。