SpringBoot集成SSE

SSE 是啥?

服务器发送事件SSE(Server-Sent Events)是一种基于HTTP的单向通信机制,用于实现服务器主动向客户端推送数据的技术,也被称为“事件流”(Event Stream)。它基于HTTP协议,利用其长链接的特性,在客户端与服务器之间建立一条持久化连接,并通过这条连接实现服务器向客户端实时数据推送。

工作原理

  • 建立连接:客户端通过发送HTTP请求与服务器建立连接。在请求中,客户端指定了接收事件的终点(Endpoint)。
  • 保持连接:服务器接收到连接请求后,保持连接打开,并定期发送事件数据给客户端。
  • 事件流:服务器使用 “Content-Type: text/event-stream” 头部响应标识SSE连接,并使用特定格式的数据(事件流)发送给客户端。
  • 客户端处理事件:客户端通过JavaScript的 EventSource 接口监听SSE连接,一旦接收到事件,就可以处理数据并更新页面。

适用场景

  • 单向通信:SSE是从服务器到客户端的单向通信模型,只能由服务器推送数据给客户端。
  • 实时更新:SSE适用于需要实时更新数据的应用场景,如股票行情、新闻推送等。
  • 简单易用:使用SSE相对简单,无需额外的库或框架支持,可以直接使用浏览器的原生API进行开发。
    当下火热的ChatGPT实现对话消息的流式返回就是基于服务器发送事件SSE技术来实现的。

协议

SSE 本质是浏览器发起 http 请求,服务器在收到请求后,返回状态与数据,并附带以下 response headers:

Content-Type 必须设置为 text/event-stream ,以表明这是一个 SSE 流。
Cache-Control 通常设置为 no-cache ,防止浏览器缓存响应。
Connection 通常设置为 keep-alive ,以保持连接的持久性。

在这里插入图片描述

消息格式

  • EventStream(事件流)为 UTF-8 格式编码的文本或使用 Base64 编码和 gzip 压缩的二进制消息。

  • 每条消息由一行或多行字段(event、id、retry、data)组成,每个字段组成形式为:字段名:字段值。字段以行为单位,每行一个(即以 \n 结尾)。以冒号开头的行为注释行,会被浏览器忽略。

  • 每次推送,可由多个消息组成,每个消息之间以空行分隔(即最后一个字段以\n\n结尾)。

注意:

  • 除上述四个字段外,其他所有字段都会被忽略。
  • 如果一行字段中不包含冒号,则整行文本将被视为字段名,字段值为空。
  • 注释行可以用来防止链接超时,服务端可以定期向浏览器发送一条消息注释行,以保持连接不断。
    在这里插入图片描述
event

事件类型。如果指定了该字段,则在浏览器收到该条消息时,会在当前 EventSource 对象上触发一个事件,事件类型就是该字段的字段值。可以使用 addEventListener 方法在当前 EventSource 对象上监听任意类型的命名事件。

id

事件ID。事件的唯一标识符,浏览器会跟踪事件ID,如果发生断连,浏览器会把收到的最后一个事件ID放到 HTTP Header Last-Event-Id 中进行重连,作为一种简单的同步机制。

例如可以在服务端将每次发送的事件ID值自动加 1,当浏览器接收到该事件ID后,下次与服务端建立连接后再请求的 Header 中将同时提交该事件ID,服务端检查该事件ID是否为上次发送的事件ID,如果与上次发送的事件ID不一致则说明浏览器存在与服务器连接失败的情况,本次需要同时发送前几次浏览器未接收到的数据。

retry

重连时间。整数值,单位 ms,如果与服务器的连接丢失,浏览器将等待指定时间,然后尝试重新连接。如果该字段不是整数值,会被忽略。

当服务端没有指定浏览器的重连时间时,由浏览器自行决定每隔多久与服务端建立一次连接(一般为 30s)。

data

消息数据。数据内容只能以一个字符串的文本形式进行发送,如果需要发送一个对象时,需要将该对象以一个 JSON 格式的字符串的形式进行发送。在浏览器接收到该字符串后,再把它还原为一个 JSON 对象。

浏览器API

在浏览器端,可以使用 JavaScript 的 EventSource API 创建 EventSource 对象监听服务器发送的事件。一旦建立连接,服务器就可以使用 HTTP 响应的 ‘text/event-stream’ 内容类型发送事件消息,浏览器则可以通过监听 EventSource 对象的 onmessageonopenonerror 事件来处理这些消息。

建立连接

EventSource 接受两个参数:URLoptions
URL 为 http 事件来源,一旦 EventSource 对象被创建后,浏览器立即开始对该 URL 地址发送过来的事件进行监听。

options 是一个可选的对象,包含 withCredentials 属性,表示是否发送凭证(cookie、HTTP认证信息等)到服务端,默认为 false。

const eventSource = new EventSource('http_api_url',{withCredentials: true})

与 XMLHttpRequest 对象类型,EventSource 对象有一个 readyState 属性值,具体含义如下表:

readyState含义
0浏览器与服务端尚未建立连接或连接已被关闭
1浏览器与服务端已成功连接,浏览器正在处理接收到的事件及数据
2浏览器与服务端建立连接失败,客户端不再继续建立与服务端之间的连接

可以使用 EventSource 对象的 close 方法关闭与服务端之间的连接,使浏览器不再建立与服务端之间的连接。

//初始化eventSource等省略
//关闭连接
eventSource.close()
监听事件

EventSource 对象本身继承自 EventTarget 接口,因此可以使用 addEventListener() 方法来监听事件。EventSource 对象触发的事件主要包括以下三种:

  • open 事件:当成功连接到服务端时触发。
  • message 事件:当接收到服务器发送的消息时触发。该事件对象的 data 属性包含了服务器发送的消息内容。
  • error 事件:当发生错误时触发。该事件对象的 event 属性包含了错误信息。
// 建立连接事件
eventSource.addEventListener('open', function(event) {
  console.log('Connection opened')
})
eventSource.onopen = function(event) {
  console.log('Connection opened')
}
// 收到消息事件
eventSource.addEventListener('message', function(event) {
  console.log('Received message: ' + event.data);
})
eventSource.onmessage = function(event) {
  console.log('Received message: ' + event.data);
}
// 错误事件
eventSource.addEventListener('error', function(event) {
  console.log('Error occurred: ' + event.event);
})
eventSource.onerror = function(event) {
  console.log('Error occurred: '+ event.event);
}
// 监听自定义事件
eventSource.addEventListener('xxx', function(event) {
  console.log('Received message: ' + event.data);
})

注意:
EventSource 对象的属性监听只能监听预定义的事件类型(openmessageerror)。不能用于监听自定义事件类型。如果要实现自定义事件类型的监听,可以使用 addEventListener() 方法。

浏览器兼容性

发展至今,SSE 已具有广泛的的浏览器兼容性,几乎除 IE 之外的浏览器均已支持。
对于不支持EventSource 的浏览器,可以使用 polyfill实现。判断浏览器是否支持 EventSource

if(typeof(EventSource) !== "undefined") {
 // 支持
} else {
 //不支持,使用 polyfill
}

SpringBoot集成SSE

添加依赖

原则上是不需要引入额外的依赖,因为springboot底层已经整合了SSE

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
添加接口
// 与客户端建立连接,注意返回值的类型
@RequestMapping("/sse")
public SseEmitter stream(@RequestParam("uid")Long uid)throws Exception{
	SseEmitter emitter = new SseEmitter(-1L);
	emitter.onTimeout(()->{
		removeClient(uid);
	});
	emitter.onCompletion(()->{
		removeClient(uid);
	});
	emitter.onError((error)->{
		removeClient(uid);
		log.error(error.getMessage());
	});
	log.info("user:{} connected", uid);
	clientMap.put(uid, emitter);
	return emitter;
}
// 推送给所有的客户端
@GetMapping("sendToAll")
public void sendToAll() throws Exception{
	if(clientMap.size() <= 0){
		return;
	}
	for(Map.Entry<Long, SseEmitter> entry : clientMap.entrySet()){
		Long userId = entry.getKey();
		SseEmitter sseEmitter = entry.getValue();
		sseEmitter.send("userId:" + userId + ", group message");
	}
}
// 推送给单个客户端
@GetMapping("sendToUser")
public void sendToOne(@RequestParam("userId") Long userId) throws Exception{
	SseEmitter sseEmitter = clientMap.get(userId);
	if(sseEmitter == null){
		log.error("user:"+ userId + "不在线");
		return;
	}
	sseEmitter.send("userId:" + userId + ", p2p message");
}

源码下载:https://github.com/xjs1919/learning-demo/tree/master/sse-demo

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值