【SpringBoot WEB系列】SSE 服务器发送事件详解“yobo体育全站app手机版”

来源:yobo体育全站app手机版作者:yobo体育全站app手机版 日期:2022-08-20 浏览:
本文摘要:【SpringBoot WEB系列】SSE 服务器发送事件详解SSE 全称Server Sent Event,直译一下就是服务器发送事件,一般的项目开发中,用到的时机不多,可能许多小同伴不太清楚这个工具,到底是干啥的,有啥用本文主要知识点如下:SSE 扫盲,应用场景分析借助异步请求实现 sse 功效,加深观点明白使用SseEmitter实现一个简朴的推送示例I. SSE 扫盲对于 sse 基础观点比力清楚的可以跳过本节1. 观点先容sse(Server Sent Event

yobo体育全站app下载yobo体育APP

【SpringBoot WEB系列】SSE 服务器发送事件详解SSE 全称Server Sent Event,直译一下就是服务器发送事件,一般的项目开发中,用到的时机不多,可能许多小同伴不太清楚这个工具,到底是干啥的,有啥用本文主要知识点如下:SSE 扫盲,应用场景分析借助异步请求实现 sse 功效,加深观点明白使用SseEmitter实现一个简朴的推送示例I. SSE 扫盲对于 sse 基础观点比力清楚的可以跳过本节1. 观点先容sse(Server Sent Event),直译为服务器发送事件,顾名思义,也就是客户端可以获取到服务器发送的事件我们常见的 http 交互方式是客户端提倡请求,服务端响应,然后一次请求完毕;可是在 sse 的场景下,客户端提倡请求,毗连一直保持,服务端有数据就可以返回数据给客户端,这个返回可以是多次距离的方式2. 特点分析SSE 最大的特点,可以简朴计划为两个长毗连服务端可以向客户端推送信息相识 websocket 的小同伴,可能也知道它也是长毗连,可以推送信息,可是它们有一个显着的区别sse 是单通道,只能服务端向客户端发消息;而 webscoket 是双通道那么为什么有了 webscoket 还要搞出一个 sse 呢?既然存在,一定有着它的优越之处sse websocket http 协议 独立的 websocket 协议 轻量,使用简朴 相对庞大 默认支持断线重连 需要自己实现断线重连 文本传输 二进制传输 支持自界说发送的消息类型 -3. 应用场景从 sse 的特点出发,我们可以大致的判断出它的应用场景,需要轮询获取服务端最新数据的 case 下,多数是可以用它的好比显示当前网站在线的实时人数,法币汇率显示当前实时汇率,电商大促的实时成交额等等...II. 手动实现 sse 功效sse 自己是有自己的一套玩法的,后面会举行说明,这一小节,则主要针对 sse 的两个特点长毗连 + 后端推送数据,如果让我们自己来实现这样的一个接口,可以怎么做?1. 项目建立借助 SpringBoot 2.2.1.RELEASE来建立一个用于演示的工程项目,焦点的 xml 依赖如下<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --></parent><properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version></properties><dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency></dependencies><build> <pluginManagement> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </pluginManagement></build><repositories> <repository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/libs-snapshot-local</url> <snapshots> <enabled>true</enabled> </snapshots> </repository> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/libs-milestone-local</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> <repository> <id>spring-releases</id> <name>Spring Releases</name> <url>https://repo.spring.io/libs-release-local</url> <snapshots> <enabled>false</enabled> </snapshots> </repository></repositories>2. 功效实现在 Http1.1 支持了长毗连,请求头添加一个Connection: keep-alive即可在这里我们借助异步请求来实现 sse 功效,至于什么是异步请求,推荐检察博文: 【WEB 系列】异步请求知识点与使用姿势小结因为后端可以不定时返回数据,所以我们需要注意的就是需要保持毗连,不要返回一次数据之后就断开了;其次就是需要设置请求头Content-Type: text/event-stream;charset=UTF-8 (如果不是流的话会怎样?)// 新建一个容器,生存毗连,用于输出返回private Map<String, PrintWriter> responseMap = new ConcurrentHashMap<>();// 发送数据给客户端private void writeData(String id, String msg, boolean over) throws IOException { PrintWriter writer = responseMap.get(id); if (writer == null) { return; } writer.println(msg); writer.flush(); if (over) { responseMap.remove(id); }}// 推送@ResponseBody@GetMapping(path = "subscribe")public WebAsyncTask<Void> subscribe(String id, HttpServletResponse response) { Callable<Void> callable = () -> { response.setHeader("Content-Type", "text/event-stream;charset=UTF-8"); responseMap.put(id, response.getWriter()); writeData(id, "订阅乐成", false); while (true) { Thread.sleep(1000); if (!responseMap.containsKey(id)) { break; } } return null; }; // 接纳WebAsyncTask 返回 这样可以处置惩罚超时和错误 同时也可以指定使用的Excutor名称 WebAsyncTask<Void> webAsyncTask = new WebAsyncTask<>(30000, callable); // 注意:onCompletion表现完成,不管你是否超时、是否抛出异常,这个函数都市执行的 webAsyncTask.onCompletion(() -> System.out.println("法式[正常执行]完成的回调")); // 这两个返回的内容,最终都市放进response内里去=========== webAsyncTask.onTimeout(() -> { responseMap.remove(id); System.out.println("超时了!!!"); return null; })

yobo体育全站app下载yobo体育APP

; // 备注:这个是Spring5新增的 webAsyncTask.onError(() -> { System.out.println("泛起异常!!!"); return null; }); return webAsyncTask;}看一下上面的实现,基本上还是异步请求的那一套逻辑,请仔细看一下callable中的逻辑,有一个 while 循环,来保证长毗连不中断接下来我们新增两个接口,用来模拟后端给客户端发送消息,关闭毗连的场景@ResponseBody@GetMapping(path = "push")public String pushData(String id, String content) throws IOException { writeData(id, content, false); return "over!";}@ResponseBody@GetMapping(path = "over")public String over(String id) throws IOException { writeData(id, "over", true); return "over!";}我们简朴的来演示下操作历程III. SseEmitter上面只是简朴实现了 sse 的长毗连 + 后端推送消息,可是与尺度的 SSE 还是有区此外,sse 有自己的规范,而我们上面的实现,实际上并没有管这个,导致的问题是前端根据 sse 的玩法来请求数据,可能并不能正常事情1. sse 规范在 html5 的界说中,服务端 sse,一般需要遵循以下要求请求头开启长毗连 + 流方式通报Content-Type: text/event-stream;charset=UTF-8Cache-Control: no-cacheConnection: keep-alive数据花样服务端发送的消息,由 message 组成,其花样如下:field:valuenn其中 field 有五种可能空: 即以:开头,表现注释,可以明白为服务端向客户端发送的心跳,确保毗连不中断data:数据event: 事件,默认值id: 数据标识符用 id 字段表现,相当于每一条数据的编号retry: 重连时间2. 实现SpringBoot 使用 SseEmitter 来支持 sse,可以说很是简朴了,直接返回SseEmitter工具即可;重写一下上面的逻辑@RestController@RequestMapping(path = "sse")public class SseRest { private static Map<String, SseEmitter> sseCache = new ConcurrentHashMap<>(); @GetMapping(path = "subscribe") public SseEmitter push(String id) { // 超时时间设置为1小时 SseEmitter sseEmitter = new SseEmitter(3600_000L); sseCache.put(id, sseEmitter); sseEmitter.onTimeout(() -> sseCache.remove(id)); sseEmitter.onCompletion(() -> System.out.println("完成!!!")); return sseEmitter; } @GetMapping(path = "push") public String push(String id, String content) throws IOException { SseEmitter sseEmitter = sseCache.get(id); if (sseEmitter != null) { sseEmitter.send(content); } return "over"; } @GetMapping(path = "over") public String over(String id) { SseEmitter sseEmitter = sseCache.get(id); if (sseEmitter != null) { sseEmitter.complete(); sseCache.remove(id); } return "over"; }}上面的实现,用到了 SseEmitter 的几个方法,解释如下send(): 发送数据,如果传入的是一个非SseEventBuilder工具,那么通报参数会被封装到 data 中complete(): 表现执行完毕,会断开毗连onTimeout(): 超时回调触发onCompletion(): 竣事之后的回调触发同样演示一下会见请求上图总的效果和前面的效果差不多,而且输出还待上了前缀,接下来我们写一个简朴的 html 消费端,用来演示一下完整的 sse 的更多特性<!doctype html><html lang="en"><head> <title>Sse测试文档</title></head><body><div>sse测试</div><div id="result"></div></body></html><script> var source = new EventSource('http://localhost:8080/sse/subscribe?id=yihuihui'); source.onmessage = function (event) { text = document.getElementById('result').innerText; text += 'n' + event.data; document.getElementById('result').innerText = text; }; <!-- 添加一个开启回调 --> source.onopen = function (event) { text = document.getElementById('result').innerText; text += 'n 开启: '; console.log(event); document.getElementById('result').innerText = text; };</script>将上面的 html 文件放在项目的resources/static目录下;然后修改一下前面的SseRest@Controller@RequestMapping(path = "sse")public class SseRest { @GetMapping(path = "") public String index() { return "index.html"; } @ResponseBody @GetMapping(path = "subscribe", produces = {MediaType.TEXT_EVENT_STREAM_VALUE}) public SseEmitter push(String id) { // 超时时间设置为3s,用于演示客户端自动重连 SseEmitter sseEmitter = new SseEmitter(1_000L); // 设置前端的重试时间为1s sseEmitter.send(SseEmitter.event().reconnectTime(1000).data("毗连乐成")); sseCache.put(id, sseEmitter); System.out.println("add " + id); sseEmitter.onTimeout(() -> { System.out.println(id + "超时"); sseCache.remove(id); }); sseEmitter.onCompletion(() -> System.out.println("完成!!!")); return sseEmitter; }}我们上面超时时间设置的比力短,用来测试下客户端的自动重连,如下,开启的日志不停增加其次将 SseEmitter 的超时时间设长一点,再试一下数据推送功效请注意上面的演示,当后端竣事了长毗连之后,客户端会自动重新再次毗连,不用写分外的重试逻辑了,就这么神奇3. 小结本篇文章先容了 SSE 的相关知识点,并对比 websocket 给出了 sse 的优点(至于啥优点请往上翻)请注意,本文虽然先容了两种 sse 的方式,第一种借助异步请求来实现,如果需要完成 sse 的规范要求,需要自己做一些适配,如果需要相识 sse 底层实现原理的话,可以参考一下;在实际的业务开发中,推荐使用SseEmitter源码工程:https://github.com/liuyueyi/spring-boot-demo[1]项目源码: https://github.com/liuyueyi/spring-boot-demo/blob/master/spring-boot/220-web-sse[2]
本文关键词:【,SpringBoot,WEB,系列,yobo体育全站app下载yobo体育APP,】,SSE,服务器,发送,事件

本文来源:yobo体育全站app下载yobo体育APP-www.jjhmy.com

0
无法在这个位置找到: foot.htm