SSE全称server-sent events,翻译过来是服务端发送事件,通常的http请求,客户端请求,服务端返回响应,一次只能返回一个值;SSE使用的http协议,客户端请求后,服务端可以多次返回响应,返回的http Content-Type:text/event-stream,客户端请求后,仍然保持http链接不关闭,服务端可多次发送响应,客户端监听事件,获取响应,这适用新闻实时更新、股票实时更新等服务端推送到客户端的场景。在chatgpt中提问问题后,答案不是一下子出来的,而是断断续续出来的,就是用的SSE技术。
SSE | websocket |
---|---|
基于http协议的 | 基于websocket协议 |
单向的,只能服务端---->客户端 | 双向的,服务端<------>客户端 |
消息有id、event、data、retry等字段 | 不限格式 |
SSE通信分为客户端(通常是浏览器端)和服务端,客户端用来发送请求,监听事件,关闭链接等。服务端,就是普通的http服务,区别就是Content-Type:text/event-stream
客户端使用EventSource API来实现通信,
const evtSource = new EventSource("ssedemo.php");
如果是跨域,则
const evtSource = new EventSource("//api.example.com/ssedemo.php", {
withCredentials: true,
});
evtSource.onmessage = (event) => {
const newElement = document.createElement("li");
const eventList = document.getElementById("list");
newElement.textContent = `message: ${event.data}`;
eventList.appendChild(newElement);
};
evtSource.addEventListener("ping", (event) => {
const newElement = document.createElement("li");
const eventList = document.getElementById("list");
const time = JSON.parse(event.data).time;
newElement.textContent = `ping at ${time}`;
eventList.appendChild(newElement);
});
evtSource.onerror = (err) => {
console.error("EventSource failed:", err);
};
evtSource.close();
服务端跟正常的http 服务差不多,除了响应类型是text/event-stream
Event Stream格式
event: 事件名称,如果不写事件名,则被客户端的onMessage
监听;有事件名,则被对应的addEventListener
监听。
data: 消息的数据字段
id: 事件的id
retry: 断线重连的等待时间,单位毫秒
只发数据
//冒号开头的是注释,可用于心跳
: this is a test stream
data: some text
data: another message
data: with two lines
event: userconnect
data: {"username": "bobby", "time": "02:33:48"}
event: usermessage
data: {"username": "bobby", "time": "02:34:11", "text": "Hi everyone."}
event: userdisconnect
data: {"username": "bobby", "time": "02:34:23"}
event: usermessage
data: {"username": "sean", "time": "02:34:36", "text": "Bye, bobby."}
也可以一会发数据消息、一会发命名事件消息
在spring framework文档中,springmvc中ResponseBodyEmitter 、SseEmitter (ResponseBodyEmitter的子类)归属为异步请求(适用异步返回请求值),异步请求细分为Http Streaming(适用返回多个值的情况)
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Slf4j
@RestController
@RequestMapping("question")
public class AskQuestionController {
@GetMapping(value = "/askQuestion", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter askQuestion() {
final SseEmitter sseEmitter = new SseEmitter();
final ExecutorService executorService = Executors.newCachedThreadPool();
executorService.submit(() -> {
for (int i = 0; i < 100; i++) {
String data = getCurDateStr();
try {
sseEmitter.send(SseEmitter.event().id(String.valueOf(i+1)).data(data).name("custom_event_name"));
} catch (IOException e) {
e.printStackTrace();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
return sseEmitter;
}
private static String getCurDateStr() {
final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
return simpleDateFormat.format(new Date());
}
}
内容是:
var source = new SSE("get_message.php", {payload: 'Hello World'});
SSE是一种规范而不是一种新协议,它使用http通信,返回的Content-Type:text/event-stream
,客户端通过监听事件不断获取数据,链接断掉后会自动重连,接收完数据后由客户端发起关闭连接。
Using server-sent events
Can Server Sent Events (SSE) with EventSource pass parameter by POST
How to pass POST parameters with HTML SSE?