【SpringBoot WEB系列】SSE 服务器发送事件详解
SSE 全称 Server Sent Event ,直译一下就是服务器发送事件,一般的项目开发中,用到的机会不多,可能很多小伙伴不太清楚这个东西,到底是干啥的,有啥用
Server Sent Event
本文主要知识点如下:
SseEmitter
对于 sse 基础概念比较清楚的可以跳过本节
sse( Server Sent Event ),直译为服务器发送事件,顾名思义,也就是客户端可以获取到服务器发送的事件
我们常见的 http 交互方式是客户端发起请求,服务端响应,然后一次请求完毕;但是在 sse 的场景下,客户端发起请求,连接一直保持,服务端有数据就可以返回数据给客户端,这个返回可以是多次间隔的方式
SSE 最大的特点,可以简单规划为两个
了解 websocket 的小伙伴,可能也知道它也是长连接,可以推送信息,但是它们有一个明显的区别
sse 是单通道,只能服务端向客户端发消息;而 webscoket 是双通道
那么为什么有了 webscoket 还要搞出一个 sse 呢?既然存在,必然有着它的优越之处
从 sse 的特点出发,我们可以大致的判断出它的应用场景,需要轮询获取服务端最新数据的 case 下,多半是可以用它的
比如显示当前网站在线的实时人数,法币汇率显示当前实时汇率,电商大促的实时成交额等等…
sse 本身是有自己的一套玩法的,后面会进行说明,这一小节,则主要针对 sse 的两个特点 长连接 + 后端推送数据 ,如果让我们自己来实现这样的一个接口,可以怎么做?
长连接 + 后端推送数据
借助 SpringBoot 2.2.1.RELEASE 来创建一个用于演示的工程项目,核心的 xml 依赖如下
2.2.1.RELEASE
<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; }); // 备注:这个是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-8 Cache-Control: no-cache Connection: keep-alive 服务端发送的消息,由 message 组成,其格式如下: field:value\n\n 其中 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(): 结束之后的回调触发 同样演示一下访问请求 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W3mifby7-1585829791863)(https://spring.hhui.top/spring-blog/imgs/200401/01.gif)] 上图总的效果和前面的效果差不多,而且输出还待上了前缀,接下来我们写一个简单的 html 消费端,用来演示一下完整的 sse 的更多特性 <!doctype html> <html lang="en"> <title>Sse测试文档</title> </head> <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 的超时时间设长一点,再试一下数据推送功能 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bl5EdI8i-1585829791875)(https://spring.hhui.top/spring-blog/imgs/200401/03.gif)] 请注意上面的演示,当后端结束了长连接之后,客户端会自动重新再次连接,不用写额外的重试逻辑了,就这么神奇 3. 小结 本篇文章介绍了 SSE 的相关知识点,并对比 websocket 给出了 sse 的优点(至于啥优点请往上翻) 请注意,本文虽然介绍了两种 sse 的方式,第一种借助异步请求来实现,如果需要完成 sse 的规范要求,需要自己做一些适配,如果需要了解 sse 底层实现原理的话,可以参考一下;在实际的业务开发中,推荐使用SseEmitter IV. 其他 0. 项目 200329-SpringBoot 系列教程 web 篇之异步请求知识点与使用姿势小结200105-SpringBoot 系列教程 web 篇之自定义返回 Http-Code 的 n 种姿势191222-SpringBoot 系列教程 web 篇之自定义请求匹配条件 RequestCondition191206-SpringBoot 系列教程 web 篇 Listener 四种注册姿势191122-SpringBoot 系列教程 web 篇 Servlet 注册的四种姿势191120-SpringBoot 系列教程 Web 篇之开启 GZIP 数据压缩191018-SpringBoot 系列教程 web 篇之过滤器 Filter 使用指南扩展篇191016-SpringBoot 系列教程 web 篇之过滤器 Filter 使用指南191012-SpringBoot 系列教程 web 篇之自定义异常处理 HandlerExceptionResolver191010-SpringBoot 系列教程 web 篇之全局异常处理190930-SpringBoot 系列教程 web 篇之 404、500 异常页面配置190929-SpringBoot 系列教程 web 篇之重定向190913-SpringBoot 系列教程 web 篇之返回文本、网页、图片的操作姿势190905-SpringBoot 系列教程 web 篇之中文乱码问题解决190831-SpringBoot 系列教程 web 篇之如何自定义参数解析器190828-SpringBoot 系列教程 web 篇之 Post 请求参数解析姿势汇总190824-SpringBoot 系列教程 web 篇之 Get 请求参数解析姿势汇总190822-SpringBoot 系列教程 web 篇之 Beetl 环境搭建190820-SpringBoot 系列教程 web 篇之 Thymeleaf 环境搭建190816-SpringBoot 系列教程 web 篇之 Freemaker 环境搭建190421-SpringBoot 高级篇 WEB 之 websocket 的使用说明190327-Spring-RestTemplate 之 urlencode 参数解析异常全程分析190317-Spring MVC 之基于 java config 无 xml 配置的 web 应用构建190316-Spring MVC 之基于 xml 配置的 web 应用构建190213-SpringBoot 文件上传异常之提示 The temporary upload location xxx is not valid 工程:https://github.com/liuyueyi/spring-boot-demo项目源码: https://github.com/liuyueyi/spring-boot-demo/blob/master/spring-boot/220-web-sse 1. 一灰灰 Blog 尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现 bug 或者有更好的建议,欢迎批评指正,不吝感激 下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛 一灰灰 Blog 个人博客 https://blog.hhui.top一灰灰 Blog-Spring 专题博客 http://spring.hhui.top golang 服务器发送事件 (sse) todo 示例 这个例子的灵感来自和 。 它使用作为UI,使用作为存储。 它与其他 TodoMVC 示例略有不同,因为它添加了 SSE 功能来同步待办事项列表。 启动并运行 安装 git 和 bazaar sudo apt-get install git bzr 安装,用于golang MongoDB的驱动程序 mkdir -p ~/Go/vendor export GOPATH=~/Go/vendor go get gopkg.in/mgo.v2 将此存储库克隆到本地文件系统 git clone https://github.com/rwynn/golang-sse-todo.git 启动服务器 如果 mongo 服务器尚未运行,则启动它 sudo mongod -f /etc/mongodb.conf 服务端常用推送技术有: 1、客户端轮询:ajax定时拉取 2、服务端主动推送:WebSocket。全双工的,本质上是一个额外的tcp连接,建立和关闭时握手使用http协议,其他数据传输不使用http协议,更加复杂一些,适用于需要进行复杂双向数据通讯的场景。 3、服务端主动推送:SSE (Server Send Event)。html5新标准,用来从服务端实时推送数据到浏览器端, 直接建立在当前http连接上,本质上是保持一个http长连接,轻量协议简单的服务器数据推送的场景,使用服务器推送事件, SSE技. 在servlet配置完之后继续在XML里面的加上true注:在XML里所有的的标签中加上true在XML中servlet配置 true注:只需要在springmvc 控制器的中配置。 推送的场景比较多,比如有人关注我的公众号,这时我就会收到一条推送消息,以此来吸引我点击打开应用。消息推送通常是指网站的运营工作等人员,通过某种工具对用户当前网页或移动设备 APP 进行的主动消息推送。消息推送一般又分为 Web 端消息推送和移动端消息推送。移动端消息推送示例 :Web 端消息推送示例:在具体实现之前,咱们再来分析一下前边的需求,其实功能很简单,只要触发某个事件(主动分享了资源或者后台主动推送消息),Web 页面的通知小红点就会实时的+1就可以了。 2.利用长链接,后端向前端主动推送下单消息 果断选择第二种。从以往的经验,首先想到websocket,但是websocket属于双向通道,且服务端比较琐碎,就在网上找了下其他类似技术,看到了SseMmitter,查看了向网上相关文章,及用例。正好符合我们的需求,话不多说,上代码 一、新增see对象 public class SeeResult {
<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; }); // 备注:这个是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-8 Cache-Control: no-cache Connection: keep-alive 服务端发送的消息,由 message 组成,其格式如下: field:value\n\n 其中 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(): 结束之后的回调触发 同样演示一下访问请求 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W3mifby7-1585829791863)(https://spring.hhui.top/spring-blog/imgs/200401/01.gif)] 上图总的效果和前面的效果差不多,而且输出还待上了前缀,接下来我们写一个简单的 html 消费端,用来演示一下完整的 sse 的更多特性 <!doctype html> <html lang="en"> <title>Sse测试文档</title> </head> <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 的超时时间设长一点,再试一下数据推送功能 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bl5EdI8i-1585829791875)(https://spring.hhui.top/spring-blog/imgs/200401/03.gif)] 请注意上面的演示,当后端结束了长连接之后,客户端会自动重新再次连接,不用写额外的重试逻辑了,就这么神奇 3. 小结 本篇文章介绍了 SSE 的相关知识点,并对比 websocket 给出了 sse 的优点(至于啥优点请往上翻) 请注意,本文虽然介绍了两种 sse 的方式,第一种借助异步请求来实现,如果需要完成 sse 的规范要求,需要自己做一些适配,如果需要了解 sse 底层实现原理的话,可以参考一下;在实际的业务开发中,推荐使用SseEmitter IV. 其他 0. 项目 200329-SpringBoot 系列教程 web 篇之异步请求知识点与使用姿势小结200105-SpringBoot 系列教程 web 篇之自定义返回 Http-Code 的 n 种姿势191222-SpringBoot 系列教程 web 篇之自定义请求匹配条件 RequestCondition191206-SpringBoot 系列教程 web 篇 Listener 四种注册姿势191122-SpringBoot 系列教程 web 篇 Servlet 注册的四种姿势191120-SpringBoot 系列教程 Web 篇之开启 GZIP 数据压缩191018-SpringBoot 系列教程 web 篇之过滤器 Filter 使用指南扩展篇191016-SpringBoot 系列教程 web 篇之过滤器 Filter 使用指南191012-SpringBoot 系列教程 web 篇之自定义异常处理 HandlerExceptionResolver191010-SpringBoot 系列教程 web 篇之全局异常处理190930-SpringBoot 系列教程 web 篇之 404、500 异常页面配置190929-SpringBoot 系列教程 web 篇之重定向190913-SpringBoot 系列教程 web 篇之返回文本、网页、图片的操作姿势190905-SpringBoot 系列教程 web 篇之中文乱码问题解决190831-SpringBoot 系列教程 web 篇之如何自定义参数解析器190828-SpringBoot 系列教程 web 篇之 Post 请求参数解析姿势汇总190824-SpringBoot 系列教程 web 篇之 Get 请求参数解析姿势汇总190822-SpringBoot 系列教程 web 篇之 Beetl 环境搭建190820-SpringBoot 系列教程 web 篇之 Thymeleaf 环境搭建190816-SpringBoot 系列教程 web 篇之 Freemaker 环境搭建190421-SpringBoot 高级篇 WEB 之 websocket 的使用说明190327-Spring-RestTemplate 之 urlencode 参数解析异常全程分析190317-Spring MVC 之基于 java config 无 xml 配置的 web 应用构建190316-Spring MVC 之基于 xml 配置的 web 应用构建190213-SpringBoot 文件上传异常之提示 The temporary upload location xxx is not valid 工程:https://github.com/liuyueyi/spring-boot-demo项目源码: https://github.com/liuyueyi/spring-boot-demo/blob/master/spring-boot/220-web-sse 1. 一灰灰 Blog 尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现 bug 或者有更好的建议,欢迎批评指正,不吝感激 下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛 一灰灰 Blog 个人博客 https://blog.hhui.top一灰灰 Blog-Spring 专题博客 http://spring.hhui.top golang 服务器发送事件 (sse) todo 示例 这个例子的灵感来自和 。 它使用作为UI,使用作为存储。 它与其他 TodoMVC 示例略有不同,因为它添加了 SSE 功能来同步待办事项列表。 启动并运行 安装 git 和 bazaar sudo apt-get install git bzr 安装,用于golang MongoDB的驱动程序 mkdir -p ~/Go/vendor export GOPATH=~/Go/vendor go get gopkg.in/mgo.v2 将此存储库克隆到本地文件系统 git clone https://github.com/rwynn/golang-sse-todo.git 启动服务器 如果 mongo 服务器尚未运行,则启动它 sudo mongod -f /etc/mongodb.conf 服务端常用推送技术有: 1、客户端轮询:ajax定时拉取 2、服务端主动推送:WebSocket。全双工的,本质上是一个额外的tcp连接,建立和关闭时握手使用http协议,其他数据传输不使用http协议,更加复杂一些,适用于需要进行复杂双向数据通讯的场景。 3、服务端主动推送:SSE (Server Send Event)。html5新标准,用来从服务端实时推送数据到浏览器端, 直接建立在当前http连接上,本质上是保持一个http长连接,轻量协议简单的服务器数据推送的场景,使用服务器推送事件, SSE技. 在servlet配置完之后继续在XML里面的加上true注:在XML里所有的的标签中加上true在XML中servlet配置 true注:只需要在springmvc 控制器的中配置。 推送的场景比较多,比如有人关注我的公众号,这时我就会收到一条推送消息,以此来吸引我点击打开应用。消息推送通常是指网站的运营工作等人员,通过某种工具对用户当前网页或移动设备 APP 进行的主动消息推送。消息推送一般又分为 Web 端消息推送和移动端消息推送。移动端消息推送示例 :Web 端消息推送示例:在具体实现之前,咱们再来分析一下前边的需求,其实功能很简单,只要触发某个事件(主动分享了资源或者后台主动推送消息),Web 页面的通知小红点就会实时的+1就可以了。 2.利用长链接,后端向前端主动推送下单消息 果断选择第二种。从以往的经验,首先想到websocket,但是websocket属于双向通道,且服务端比较琐碎,就在网上找了下其他类似技术,看到了SseMmitter,查看了向网上相关文章,及用例。正好符合我们的需求,话不多说,上代码 一、新增see对象 public class SeeResult {
在 Http1.1 支持了长连接,请求头添加一个Connection: keep-alive即可
Connection: keep-alive
在这里我们借助异步请求来实现 sse 功能,至于什么是异步请求,推荐查看博文: 【WEB 系列】异步请求知识点与使用姿势小结
因为后端可以不定时返回数据,所以我们需要注意的就是需要保持连接,不要返回一次数据之后就断开了;其次就是需要设置请求头Content-Type: text/event-stream;charset=UTF-8 (如果不是流的话会怎样?)
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; }); // 备注:这个是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-8 Cache-Control: no-cache Connection: keep-alive 服务端发送的消息,由 message 组成,其格式如下: field:value\n\n 其中 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(): 结束之后的回调触发 同样演示一下访问请求 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W3mifby7-1585829791863)(https://spring.hhui.top/spring-blog/imgs/200401/01.gif)] 上图总的效果和前面的效果差不多,而且输出还待上了前缀,接下来我们写一个简单的 html 消费端,用来演示一下完整的 sse 的更多特性 <!doctype html> <html lang="en"> <title>Sse测试文档</title> </head> <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 的超时时间设长一点,再试一下数据推送功能 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bl5EdI8i-1585829791875)(https://spring.hhui.top/spring-blog/imgs/200401/03.gif)] 请注意上面的演示,当后端结束了长连接之后,客户端会自动重新再次连接,不用写额外的重试逻辑了,就这么神奇 3. 小结 本篇文章介绍了 SSE 的相关知识点,并对比 websocket 给出了 sse 的优点(至于啥优点请往上翻) 请注意,本文虽然介绍了两种 sse 的方式,第一种借助异步请求来实现,如果需要完成 sse 的规范要求,需要自己做一些适配,如果需要了解 sse 底层实现原理的话,可以参考一下;在实际的业务开发中,推荐使用SseEmitter IV. 其他 0. 项目 200329-SpringBoot 系列教程 web 篇之异步请求知识点与使用姿势小结200105-SpringBoot 系列教程 web 篇之自定义返回 Http-Code 的 n 种姿势191222-SpringBoot 系列教程 web 篇之自定义请求匹配条件 RequestCondition191206-SpringBoot 系列教程 web 篇 Listener 四种注册姿势191122-SpringBoot 系列教程 web 篇 Servlet 注册的四种姿势191120-SpringBoot 系列教程 Web 篇之开启 GZIP 数据压缩191018-SpringBoot 系列教程 web 篇之过滤器 Filter 使用指南扩展篇191016-SpringBoot 系列教程 web 篇之过滤器 Filter 使用指南191012-SpringBoot 系列教程 web 篇之自定义异常处理 HandlerExceptionResolver191010-SpringBoot 系列教程 web 篇之全局异常处理190930-SpringBoot 系列教程 web 篇之 404、500 异常页面配置190929-SpringBoot 系列教程 web 篇之重定向190913-SpringBoot 系列教程 web 篇之返回文本、网页、图片的操作姿势190905-SpringBoot 系列教程 web 篇之中文乱码问题解决190831-SpringBoot 系列教程 web 篇之如何自定义参数解析器190828-SpringBoot 系列教程 web 篇之 Post 请求参数解析姿势汇总190824-SpringBoot 系列教程 web 篇之 Get 请求参数解析姿势汇总190822-SpringBoot 系列教程 web 篇之 Beetl 环境搭建190820-SpringBoot 系列教程 web 篇之 Thymeleaf 环境搭建190816-SpringBoot 系列教程 web 篇之 Freemaker 环境搭建190421-SpringBoot 高级篇 WEB 之 websocket 的使用说明190327-Spring-RestTemplate 之 urlencode 参数解析异常全程分析190317-Spring MVC 之基于 java config 无 xml 配置的 web 应用构建190316-Spring MVC 之基于 xml 配置的 web 应用构建190213-SpringBoot 文件上传异常之提示 The temporary upload location xxx is not valid 工程:https://github.com/liuyueyi/spring-boot-demo项目源码: https://github.com/liuyueyi/spring-boot-demo/blob/master/spring-boot/220-web-sse 1. 一灰灰 Blog 尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现 bug 或者有更好的建议,欢迎批评指正,不吝感激 下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛 一灰灰 Blog 个人博客 https://blog.hhui.top一灰灰 Blog-Spring 专题博客 http://spring.hhui.top golang 服务器发送事件 (sse) todo 示例 这个例子的灵感来自和 。 它使用作为UI,使用作为存储。 它与其他 TodoMVC 示例略有不同,因为它添加了 SSE 功能来同步待办事项列表。 启动并运行 安装 git 和 bazaar sudo apt-get install git bzr 安装,用于golang MongoDB的驱动程序 mkdir -p ~/Go/vendor export GOPATH=~/Go/vendor go get gopkg.in/mgo.v2 将此存储库克隆到本地文件系统 git clone https://github.com/rwynn/golang-sse-todo.git 启动服务器 如果 mongo 服务器尚未运行,则启动它 sudo mongod -f /etc/mongodb.conf 服务端常用推送技术有: 1、客户端轮询:ajax定时拉取 2、服务端主动推送:WebSocket。全双工的,本质上是一个额外的tcp连接,建立和关闭时握手使用http协议,其他数据传输不使用http协议,更加复杂一些,适用于需要进行复杂双向数据通讯的场景。 3、服务端主动推送:SSE (Server Send Event)。html5新标准,用来从服务端实时推送数据到浏览器端, 直接建立在当前http连接上,本质上是保持一个http长连接,轻量协议简单的服务器数据推送的场景,使用服务器推送事件, SSE技. 在servlet配置完之后继续在XML里面的加上true注:在XML里所有的的标签中加上true在XML中servlet配置 true注:只需要在springmvc 控制器的中配置。 推送的场景比较多,比如有人关注我的公众号,这时我就会收到一条推送消息,以此来吸引我点击打开应用。消息推送通常是指网站的运营工作等人员,通过某种工具对用户当前网页或移动设备 APP 进行的主动消息推送。消息推送一般又分为 Web 端消息推送和移动端消息推送。移动端消息推送示例 :Web 端消息推送示例:在具体实现之前,咱们再来分析一下前边的需求,其实功能很简单,只要触发某个事件(主动分享了资源或者后台主动推送消息),Web 页面的通知小红点就会实时的+1就可以了。 2.利用长链接,后端向前端主动推送下单消息 果断选择第二种。从以往的经验,首先想到websocket,但是websocket属于双向通道,且服务端比较琐碎,就在网上找了下其他类似技术,看到了SseMmitter,查看了向网上相关文章,及用例。正好符合我们的需求,话不多说,上代码 一、新增see对象 public class SeeResult {
看一下上面的实现,基本上还是异步请求的那一套逻辑,请仔细看一下callable中的逻辑,有一个 while 循环,来保证长连接不中断
callable
接下来我们新增两个接口,用来模拟后端给客户端发送消息,关闭连接的场景
@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-8 Cache-Control: no-cache Connection: keep-alive 服务端发送的消息,由 message 组成,其格式如下: field:value\n\n 其中 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(): 结束之后的回调触发 同样演示一下访问请求 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W3mifby7-1585829791863)(https://spring.hhui.top/spring-blog/imgs/200401/01.gif)] 上图总的效果和前面的效果差不多,而且输出还待上了前缀,接下来我们写一个简单的 html 消费端,用来演示一下完整的 sse 的更多特性 <!doctype html> <html lang="en"> <title>Sse测试文档</title> </head> <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 的超时时间设长一点,再试一下数据推送功能 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bl5EdI8i-1585829791875)(https://spring.hhui.top/spring-blog/imgs/200401/03.gif)] 请注意上面的演示,当后端结束了长连接之后,客户端会自动重新再次连接,不用写额外的重试逻辑了,就这么神奇 3. 小结 本篇文章介绍了 SSE 的相关知识点,并对比 websocket 给出了 sse 的优点(至于啥优点请往上翻) 请注意,本文虽然介绍了两种 sse 的方式,第一种借助异步请求来实现,如果需要完成 sse 的规范要求,需要自己做一些适配,如果需要了解 sse 底层实现原理的话,可以参考一下;在实际的业务开发中,推荐使用SseEmitter IV. 其他 0. 项目 200329-SpringBoot 系列教程 web 篇之异步请求知识点与使用姿势小结200105-SpringBoot 系列教程 web 篇之自定义返回 Http-Code 的 n 种姿势191222-SpringBoot 系列教程 web 篇之自定义请求匹配条件 RequestCondition191206-SpringBoot 系列教程 web 篇 Listener 四种注册姿势191122-SpringBoot 系列教程 web 篇 Servlet 注册的四种姿势191120-SpringBoot 系列教程 Web 篇之开启 GZIP 数据压缩191018-SpringBoot 系列教程 web 篇之过滤器 Filter 使用指南扩展篇191016-SpringBoot 系列教程 web 篇之过滤器 Filter 使用指南191012-SpringBoot 系列教程 web 篇之自定义异常处理 HandlerExceptionResolver191010-SpringBoot 系列教程 web 篇之全局异常处理190930-SpringBoot 系列教程 web 篇之 404、500 异常页面配置190929-SpringBoot 系列教程 web 篇之重定向190913-SpringBoot 系列教程 web 篇之返回文本、网页、图片的操作姿势190905-SpringBoot 系列教程 web 篇之中文乱码问题解决190831-SpringBoot 系列教程 web 篇之如何自定义参数解析器190828-SpringBoot 系列教程 web 篇之 Post 请求参数解析姿势汇总190824-SpringBoot 系列教程 web 篇之 Get 请求参数解析姿势汇总190822-SpringBoot 系列教程 web 篇之 Beetl 环境搭建190820-SpringBoot 系列教程 web 篇之 Thymeleaf 环境搭建190816-SpringBoot 系列教程 web 篇之 Freemaker 环境搭建190421-SpringBoot 高级篇 WEB 之 websocket 的使用说明190327-Spring-RestTemplate 之 urlencode 参数解析异常全程分析190317-Spring MVC 之基于 java config 无 xml 配置的 web 应用构建190316-Spring MVC 之基于 xml 配置的 web 应用构建190213-SpringBoot 文件上传异常之提示 The temporary upload location xxx is not valid 工程:https://github.com/liuyueyi/spring-boot-demo项目源码: https://github.com/liuyueyi/spring-boot-demo/blob/master/spring-boot/220-web-sse 1. 一灰灰 Blog 尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现 bug 或者有更好的建议,欢迎批评指正,不吝感激 下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛 一灰灰 Blog 个人博客 https://blog.hhui.top一灰灰 Blog-Spring 专题博客 http://spring.hhui.top golang 服务器发送事件 (sse) todo 示例 这个例子的灵感来自和 。 它使用作为UI,使用作为存储。 它与其他 TodoMVC 示例略有不同,因为它添加了 SSE 功能来同步待办事项列表。 启动并运行 安装 git 和 bazaar sudo apt-get install git bzr 安装,用于golang MongoDB的驱动程序 mkdir -p ~/Go/vendor export GOPATH=~/Go/vendor go get gopkg.in/mgo.v2 将此存储库克隆到本地文件系统 git clone https://github.com/rwynn/golang-sse-todo.git 启动服务器 如果 mongo 服务器尚未运行,则启动它 sudo mongod -f /etc/mongodb.conf 服务端常用推送技术有: 1、客户端轮询:ajax定时拉取 2、服务端主动推送:WebSocket。全双工的,本质上是一个额外的tcp连接,建立和关闭时握手使用http协议,其他数据传输不使用http协议,更加复杂一些,适用于需要进行复杂双向数据通讯的场景。 3、服务端主动推送:SSE (Server Send Event)。html5新标准,用来从服务端实时推送数据到浏览器端, 直接建立在当前http连接上,本质上是保持一个http长连接,轻量协议简单的服务器数据推送的场景,使用服务器推送事件, SSE技. 在servlet配置完之后继续在XML里面的加上true注:在XML里所有的的标签中加上true在XML中servlet配置 true注:只需要在springmvc 控制器的中配置。 推送的场景比较多,比如有人关注我的公众号,这时我就会收到一条推送消息,以此来吸引我点击打开应用。消息推送通常是指网站的运营工作等人员,通过某种工具对用户当前网页或移动设备 APP 进行的主动消息推送。消息推送一般又分为 Web 端消息推送和移动端消息推送。移动端消息推送示例 :Web 端消息推送示例:在具体实现之前,咱们再来分析一下前边的需求,其实功能很简单,只要触发某个事件(主动分享了资源或者后台主动推送消息),Web 页面的通知小红点就会实时的+1就可以了。 2.利用长链接,后端向前端主动推送下单消息 果断选择第二种。从以往的经验,首先想到websocket,但是websocket属于双向通道,且服务端比较琐碎,就在网上找了下其他类似技术,看到了SseMmitter,查看了向网上相关文章,及用例。正好符合我们的需求,话不多说,上代码 一、新增see对象 public class SeeResult {
我们简单的来演示下操作过程
上面只是简单实现了 sse 的长连接 + 后端推送消息,但是与标准的 SSE 还是有区别的,sse 有自己的规范,而我们上面的实现,实际上并没有管这个,导致的问题是前端按照 sse 的玩法来请求数据,可能并不能正常工作
在 html5 的定义中,服务端 sse,一般需要遵循以下要求
开启长连接 + 流方式传递
Content-Type: text/event-stream;charset=UTF-8 Cache-Control: no-cache Connection: keep-alive 服务端发送的消息,由 message 组成,其格式如下: field:value\n\n 其中 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(): 结束之后的回调触发 同样演示一下访问请求 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W3mifby7-1585829791863)(https://spring.hhui.top/spring-blog/imgs/200401/01.gif)] 上图总的效果和前面的效果差不多,而且输出还待上了前缀,接下来我们写一个简单的 html 消费端,用来演示一下完整的 sse 的更多特性 <!doctype html> <html lang="en"> <title>Sse测试文档</title> </head> <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 的超时时间设长一点,再试一下数据推送功能 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bl5EdI8i-1585829791875)(https://spring.hhui.top/spring-blog/imgs/200401/03.gif)] 请注意上面的演示,当后端结束了长连接之后,客户端会自动重新再次连接,不用写额外的重试逻辑了,就这么神奇 3. 小结 本篇文章介绍了 SSE 的相关知识点,并对比 websocket 给出了 sse 的优点(至于啥优点请往上翻) 请注意,本文虽然介绍了两种 sse 的方式,第一种借助异步请求来实现,如果需要完成 sse 的规范要求,需要自己做一些适配,如果需要了解 sse 底层实现原理的话,可以参考一下;在实际的业务开发中,推荐使用SseEmitter IV. 其他 0. 项目 200329-SpringBoot 系列教程 web 篇之异步请求知识点与使用姿势小结200105-SpringBoot 系列教程 web 篇之自定义返回 Http-Code 的 n 种姿势191222-SpringBoot 系列教程 web 篇之自定义请求匹配条件 RequestCondition191206-SpringBoot 系列教程 web 篇 Listener 四种注册姿势191122-SpringBoot 系列教程 web 篇 Servlet 注册的四种姿势191120-SpringBoot 系列教程 Web 篇之开启 GZIP 数据压缩191018-SpringBoot 系列教程 web 篇之过滤器 Filter 使用指南扩展篇191016-SpringBoot 系列教程 web 篇之过滤器 Filter 使用指南191012-SpringBoot 系列教程 web 篇之自定义异常处理 HandlerExceptionResolver191010-SpringBoot 系列教程 web 篇之全局异常处理190930-SpringBoot 系列教程 web 篇之 404、500 异常页面配置190929-SpringBoot 系列教程 web 篇之重定向190913-SpringBoot 系列教程 web 篇之返回文本、网页、图片的操作姿势190905-SpringBoot 系列教程 web 篇之中文乱码问题解决190831-SpringBoot 系列教程 web 篇之如何自定义参数解析器190828-SpringBoot 系列教程 web 篇之 Post 请求参数解析姿势汇总190824-SpringBoot 系列教程 web 篇之 Get 请求参数解析姿势汇总190822-SpringBoot 系列教程 web 篇之 Beetl 环境搭建190820-SpringBoot 系列教程 web 篇之 Thymeleaf 环境搭建190816-SpringBoot 系列教程 web 篇之 Freemaker 环境搭建190421-SpringBoot 高级篇 WEB 之 websocket 的使用说明190327-Spring-RestTemplate 之 urlencode 参数解析异常全程分析190317-Spring MVC 之基于 java config 无 xml 配置的 web 应用构建190316-Spring MVC 之基于 xml 配置的 web 应用构建190213-SpringBoot 文件上传异常之提示 The temporary upload location xxx is not valid 工程:https://github.com/liuyueyi/spring-boot-demo项目源码: https://github.com/liuyueyi/spring-boot-demo/blob/master/spring-boot/220-web-sse 1. 一灰灰 Blog 尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现 bug 或者有更好的建议,欢迎批评指正,不吝感激 下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛 一灰灰 Blog 个人博客 https://blog.hhui.top一灰灰 Blog-Spring 专题博客 http://spring.hhui.top golang 服务器发送事件 (sse) todo 示例 这个例子的灵感来自和 。 它使用作为UI,使用作为存储。 它与其他 TodoMVC 示例略有不同,因为它添加了 SSE 功能来同步待办事项列表。 启动并运行 安装 git 和 bazaar sudo apt-get install git bzr 安装,用于golang MongoDB的驱动程序 mkdir -p ~/Go/vendor export GOPATH=~/Go/vendor go get gopkg.in/mgo.v2 将此存储库克隆到本地文件系统 git clone https://github.com/rwynn/golang-sse-todo.git 启动服务器 如果 mongo 服务器尚未运行,则启动它 sudo mongod -f /etc/mongodb.conf 服务端常用推送技术有: 1、客户端轮询:ajax定时拉取 2、服务端主动推送:WebSocket。全双工的,本质上是一个额外的tcp连接,建立和关闭时握手使用http协议,其他数据传输不使用http协议,更加复杂一些,适用于需要进行复杂双向数据通讯的场景。 3、服务端主动推送:SSE (Server Send Event)。html5新标准,用来从服务端实时推送数据到浏览器端, 直接建立在当前http连接上,本质上是保持一个http长连接,轻量协议简单的服务器数据推送的场景,使用服务器推送事件, SSE技. 在servlet配置完之后继续在XML里面的加上true注:在XML里所有的的标签中加上true在XML中servlet配置 true注:只需要在springmvc 控制器的中配置。 推送的场景比较多,比如有人关注我的公众号,这时我就会收到一条推送消息,以此来吸引我点击打开应用。消息推送通常是指网站的运营工作等人员,通过某种工具对用户当前网页或移动设备 APP 进行的主动消息推送。消息推送一般又分为 Web 端消息推送和移动端消息推送。移动端消息推送示例 :Web 端消息推送示例:在具体实现之前,咱们再来分析一下前边的需求,其实功能很简单,只要触发某个事件(主动分享了资源或者后台主动推送消息),Web 页面的通知小红点就会实时的+1就可以了。 2.利用长链接,后端向前端主动推送下单消息 果断选择第二种。从以往的经验,首先想到websocket,但是websocket属于双向通道,且服务端比较琐碎,就在网上找了下其他类似技术,看到了SseMmitter,查看了向网上相关文章,及用例。正好符合我们的需求,话不多说,上代码 一、新增see对象 public class SeeResult {
Content-Type: text/event-stream;charset=UTF-8 Cache-Control: no-cache Connection: keep-alive 服务端发送的消息,由 message 组成,其格式如下:
服务端发送的消息,由 message 组成,其格式如下:
field:value\n\n 其中 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(): 结束之后的回调触发 同样演示一下访问请求 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W3mifby7-1585829791863)(https://spring.hhui.top/spring-blog/imgs/200401/01.gif)] 上图总的效果和前面的效果差不多,而且输出还待上了前缀,接下来我们写一个简单的 html 消费端,用来演示一下完整的 sse 的更多特性 <!doctype html> <html lang="en"> <title>Sse测试文档</title> </head> <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 的超时时间设长一点,再试一下数据推送功能 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bl5EdI8i-1585829791875)(https://spring.hhui.top/spring-blog/imgs/200401/03.gif)] 请注意上面的演示,当后端结束了长连接之后,客户端会自动重新再次连接,不用写额外的重试逻辑了,就这么神奇 3. 小结 本篇文章介绍了 SSE 的相关知识点,并对比 websocket 给出了 sse 的优点(至于啥优点请往上翻) 请注意,本文虽然介绍了两种 sse 的方式,第一种借助异步请求来实现,如果需要完成 sse 的规范要求,需要自己做一些适配,如果需要了解 sse 底层实现原理的话,可以参考一下;在实际的业务开发中,推荐使用SseEmitter IV. 其他 0. 项目 200329-SpringBoot 系列教程 web 篇之异步请求知识点与使用姿势小结200105-SpringBoot 系列教程 web 篇之自定义返回 Http-Code 的 n 种姿势191222-SpringBoot 系列教程 web 篇之自定义请求匹配条件 RequestCondition191206-SpringBoot 系列教程 web 篇 Listener 四种注册姿势191122-SpringBoot 系列教程 web 篇 Servlet 注册的四种姿势191120-SpringBoot 系列教程 Web 篇之开启 GZIP 数据压缩191018-SpringBoot 系列教程 web 篇之过滤器 Filter 使用指南扩展篇191016-SpringBoot 系列教程 web 篇之过滤器 Filter 使用指南191012-SpringBoot 系列教程 web 篇之自定义异常处理 HandlerExceptionResolver191010-SpringBoot 系列教程 web 篇之全局异常处理190930-SpringBoot 系列教程 web 篇之 404、500 异常页面配置190929-SpringBoot 系列教程 web 篇之重定向190913-SpringBoot 系列教程 web 篇之返回文本、网页、图片的操作姿势190905-SpringBoot 系列教程 web 篇之中文乱码问题解决190831-SpringBoot 系列教程 web 篇之如何自定义参数解析器190828-SpringBoot 系列教程 web 篇之 Post 请求参数解析姿势汇总190824-SpringBoot 系列教程 web 篇之 Get 请求参数解析姿势汇总190822-SpringBoot 系列教程 web 篇之 Beetl 环境搭建190820-SpringBoot 系列教程 web 篇之 Thymeleaf 环境搭建190816-SpringBoot 系列教程 web 篇之 Freemaker 环境搭建190421-SpringBoot 高级篇 WEB 之 websocket 的使用说明190327-Spring-RestTemplate 之 urlencode 参数解析异常全程分析190317-Spring MVC 之基于 java config 无 xml 配置的 web 应用构建190316-Spring MVC 之基于 xml 配置的 web 应用构建190213-SpringBoot 文件上传异常之提示 The temporary upload location xxx is not valid 工程:https://github.com/liuyueyi/spring-boot-demo项目源码: https://github.com/liuyueyi/spring-boot-demo/blob/master/spring-boot/220-web-sse 1. 一灰灰 Blog 尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现 bug 或者有更好的建议,欢迎批评指正,不吝感激 下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛 一灰灰 Blog 个人博客 https://blog.hhui.top一灰灰 Blog-Spring 专题博客 http://spring.hhui.top golang 服务器发送事件 (sse) todo 示例 这个例子的灵感来自和 。 它使用作为UI,使用作为存储。 它与其他 TodoMVC 示例略有不同,因为它添加了 SSE 功能来同步待办事项列表。 启动并运行 安装 git 和 bazaar sudo apt-get install git bzr 安装,用于golang MongoDB的驱动程序 mkdir -p ~/Go/vendor export GOPATH=~/Go/vendor go get gopkg.in/mgo.v2 将此存储库克隆到本地文件系统 git clone https://github.com/rwynn/golang-sse-todo.git 启动服务器 如果 mongo 服务器尚未运行,则启动它 sudo mongod -f /etc/mongodb.conf 服务端常用推送技术有: 1、客户端轮询:ajax定时拉取 2、服务端主动推送:WebSocket。全双工的,本质上是一个额外的tcp连接,建立和关闭时握手使用http协议,其他数据传输不使用http协议,更加复杂一些,适用于需要进行复杂双向数据通讯的场景。 3、服务端主动推送:SSE (Server Send Event)。html5新标准,用来从服务端实时推送数据到浏览器端, 直接建立在当前http连接上,本质上是保持一个http长连接,轻量协议简单的服务器数据推送的场景,使用服务器推送事件, SSE技. 在servlet配置完之后继续在XML里面的加上true注:在XML里所有的的标签中加上true在XML中servlet配置 true注:只需要在springmvc 控制器的中配置。 推送的场景比较多,比如有人关注我的公众号,这时我就会收到一条推送消息,以此来吸引我点击打开应用。消息推送通常是指网站的运营工作等人员,通过某种工具对用户当前网页或移动设备 APP 进行的主动消息推送。消息推送一般又分为 Web 端消息推送和移动端消息推送。移动端消息推送示例 :Web 端消息推送示例:在具体实现之前,咱们再来分析一下前边的需求,其实功能很简单,只要触发某个事件(主动分享了资源或者后台主动推送消息),Web 页面的通知小红点就会实时的+1就可以了。 2.利用长链接,后端向前端主动推送下单消息 果断选择第二种。从以往的经验,首先想到websocket,但是websocket属于双向通道,且服务端比较琐碎,就在网上找了下其他类似技术,看到了SseMmitter,查看了向网上相关文章,及用例。正好符合我们的需求,话不多说,上代码 一、新增see对象 public class SeeResult {
field:value\n\n 其中 field 有五种可能 空: 即以:开头,表示注释,可以理解为服务端向客户端发送的心跳,确保连接不中断data:数据event: 事件,默认值id: 数据标识符用 id 字段表示,相当于每一条数据的编号retry: 重连时间
其中 field 有五种可能
:
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(): 结束之后的回调触发 同样演示一下访问请求 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W3mifby7-1585829791863)(https://spring.hhui.top/spring-blog/imgs/200401/01.gif)] 上图总的效果和前面的效果差不多,而且输出还待上了前缀,接下来我们写一个简单的 html 消费端,用来演示一下完整的 sse 的更多特性 <!doctype html> <html lang="en"> <title>Sse测试文档</title> </head> <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 的超时时间设长一点,再试一下数据推送功能 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bl5EdI8i-1585829791875)(https://spring.hhui.top/spring-blog/imgs/200401/03.gif)] 请注意上面的演示,当后端结束了长连接之后,客户端会自动重新再次连接,不用写额外的重试逻辑了,就这么神奇 3. 小结 本篇文章介绍了 SSE 的相关知识点,并对比 websocket 给出了 sse 的优点(至于啥优点请往上翻) 请注意,本文虽然介绍了两种 sse 的方式,第一种借助异步请求来实现,如果需要完成 sse 的规范要求,需要自己做一些适配,如果需要了解 sse 底层实现原理的话,可以参考一下;在实际的业务开发中,推荐使用SseEmitter IV. 其他 0. 项目 200329-SpringBoot 系列教程 web 篇之异步请求知识点与使用姿势小结200105-SpringBoot 系列教程 web 篇之自定义返回 Http-Code 的 n 种姿势191222-SpringBoot 系列教程 web 篇之自定义请求匹配条件 RequestCondition191206-SpringBoot 系列教程 web 篇 Listener 四种注册姿势191122-SpringBoot 系列教程 web 篇 Servlet 注册的四种姿势191120-SpringBoot 系列教程 Web 篇之开启 GZIP 数据压缩191018-SpringBoot 系列教程 web 篇之过滤器 Filter 使用指南扩展篇191016-SpringBoot 系列教程 web 篇之过滤器 Filter 使用指南191012-SpringBoot 系列教程 web 篇之自定义异常处理 HandlerExceptionResolver191010-SpringBoot 系列教程 web 篇之全局异常处理190930-SpringBoot 系列教程 web 篇之 404、500 异常页面配置190929-SpringBoot 系列教程 web 篇之重定向190913-SpringBoot 系列教程 web 篇之返回文本、网页、图片的操作姿势190905-SpringBoot 系列教程 web 篇之中文乱码问题解决190831-SpringBoot 系列教程 web 篇之如何自定义参数解析器190828-SpringBoot 系列教程 web 篇之 Post 请求参数解析姿势汇总190824-SpringBoot 系列教程 web 篇之 Get 请求参数解析姿势汇总190822-SpringBoot 系列教程 web 篇之 Beetl 环境搭建190820-SpringBoot 系列教程 web 篇之 Thymeleaf 环境搭建190816-SpringBoot 系列教程 web 篇之 Freemaker 环境搭建190421-SpringBoot 高级篇 WEB 之 websocket 的使用说明190327-Spring-RestTemplate 之 urlencode 参数解析异常全程分析190317-Spring MVC 之基于 java config 无 xml 配置的 web 应用构建190316-Spring MVC 之基于 xml 配置的 web 应用构建190213-SpringBoot 文件上传异常之提示 The temporary upload location xxx is not valid 工程:https://github.com/liuyueyi/spring-boot-demo项目源码: https://github.com/liuyueyi/spring-boot-demo/blob/master/spring-boot/220-web-sse 1. 一灰灰 Blog 尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现 bug 或者有更好的建议,欢迎批评指正,不吝感激 下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛 一灰灰 Blog 个人博客 https://blog.hhui.top一灰灰 Blog-Spring 专题博客 http://spring.hhui.top golang 服务器发送事件 (sse) todo 示例 这个例子的灵感来自和 。 它使用作为UI,使用作为存储。 它与其他 TodoMVC 示例略有不同,因为它添加了 SSE 功能来同步待办事项列表。 启动并运行 安装 git 和 bazaar sudo apt-get install git bzr 安装,用于golang MongoDB的驱动程序 mkdir -p ~/Go/vendor export GOPATH=~/Go/vendor go get gopkg.in/mgo.v2 将此存储库克隆到本地文件系统 git clone https://github.com/rwynn/golang-sse-todo.git 启动服务器 如果 mongo 服务器尚未运行,则启动它 sudo mongod -f /etc/mongodb.conf 服务端常用推送技术有: 1、客户端轮询:ajax定时拉取 2、服务端主动推送:WebSocket。全双工的,本质上是一个额外的tcp连接,建立和关闭时握手使用http协议,其他数据传输不使用http协议,更加复杂一些,适用于需要进行复杂双向数据通讯的场景。 3、服务端主动推送:SSE (Server Send Event)。html5新标准,用来从服务端实时推送数据到浏览器端, 直接建立在当前http连接上,本质上是保持一个http长连接,轻量协议简单的服务器数据推送的场景,使用服务器推送事件, SSE技. 在servlet配置完之后继续在XML里面的加上true注:在XML里所有的的标签中加上true在XML中servlet配置 true注:只需要在springmvc 控制器的中配置。 推送的场景比较多,比如有人关注我的公众号,这时我就会收到一条推送消息,以此来吸引我点击打开应用。消息推送通常是指网站的运营工作等人员,通过某种工具对用户当前网页或移动设备 APP 进行的主动消息推送。消息推送一般又分为 Web 端消息推送和移动端消息推送。移动端消息推送示例 :Web 端消息推送示例:在具体实现之前,咱们再来分析一下前边的需求,其实功能很简单,只要触发某个事件(主动分享了资源或者后台主动推送消息),Web 页面的通知小红点就会实时的+1就可以了。 2.利用长链接,后端向前端主动推送下单消息 果断选择第二种。从以往的经验,首先想到websocket,但是websocket属于双向通道,且服务端比较琐碎,就在网上找了下其他类似技术,看到了SseMmitter,查看了向网上相关文章,及用例。正好符合我们的需求,话不多说,上代码 一、新增see对象 public class SeeResult {
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(): 结束之后的回调触发 同样演示一下访问请求 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W3mifby7-1585829791863)(https://spring.hhui.top/spring-blog/imgs/200401/01.gif)] 上图总的效果和前面的效果差不多,而且输出还待上了前缀,接下来我们写一个简单的 html 消费端,用来演示一下完整的 sse 的更多特性 <!doctype html> <html lang="en"> <title>Sse测试文档</title> </head> <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 的超时时间设长一点,再试一下数据推送功能 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bl5EdI8i-1585829791875)(https://spring.hhui.top/spring-blog/imgs/200401/03.gif)] 请注意上面的演示,当后端结束了长连接之后,客户端会自动重新再次连接,不用写额外的重试逻辑了,就这么神奇 3. 小结 本篇文章介绍了 SSE 的相关知识点,并对比 websocket 给出了 sse 的优点(至于啥优点请往上翻) 请注意,本文虽然介绍了两种 sse 的方式,第一种借助异步请求来实现,如果需要完成 sse 的规范要求,需要自己做一些适配,如果需要了解 sse 底层实现原理的话,可以参考一下;在实际的业务开发中,推荐使用SseEmitter IV. 其他 0. 项目 200329-SpringBoot 系列教程 web 篇之异步请求知识点与使用姿势小结200105-SpringBoot 系列教程 web 篇之自定义返回 Http-Code 的 n 种姿势191222-SpringBoot 系列教程 web 篇之自定义请求匹配条件 RequestCondition191206-SpringBoot 系列教程 web 篇 Listener 四种注册姿势191122-SpringBoot 系列教程 web 篇 Servlet 注册的四种姿势191120-SpringBoot 系列教程 Web 篇之开启 GZIP 数据压缩191018-SpringBoot 系列教程 web 篇之过滤器 Filter 使用指南扩展篇191016-SpringBoot 系列教程 web 篇之过滤器 Filter 使用指南191012-SpringBoot 系列教程 web 篇之自定义异常处理 HandlerExceptionResolver191010-SpringBoot 系列教程 web 篇之全局异常处理190930-SpringBoot 系列教程 web 篇之 404、500 异常页面配置190929-SpringBoot 系列教程 web 篇之重定向190913-SpringBoot 系列教程 web 篇之返回文本、网页、图片的操作姿势190905-SpringBoot 系列教程 web 篇之中文乱码问题解决190831-SpringBoot 系列教程 web 篇之如何自定义参数解析器190828-SpringBoot 系列教程 web 篇之 Post 请求参数解析姿势汇总190824-SpringBoot 系列教程 web 篇之 Get 请求参数解析姿势汇总190822-SpringBoot 系列教程 web 篇之 Beetl 环境搭建190820-SpringBoot 系列教程 web 篇之 Thymeleaf 环境搭建190816-SpringBoot 系列教程 web 篇之 Freemaker 环境搭建190421-SpringBoot 高级篇 WEB 之 websocket 的使用说明190327-Spring-RestTemplate 之 urlencode 参数解析异常全程分析190317-Spring MVC 之基于 java config 无 xml 配置的 web 应用构建190316-Spring MVC 之基于 xml 配置的 web 应用构建190213-SpringBoot 文件上传异常之提示 The temporary upload location xxx is not valid 工程:https://github.com/liuyueyi/spring-boot-demo项目源码: https://github.com/liuyueyi/spring-boot-demo/blob/master/spring-boot/220-web-sse 1. 一灰灰 Blog 尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现 bug 或者有更好的建议,欢迎批评指正,不吝感激 下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛 一灰灰 Blog 个人博客 https://blog.hhui.top一灰灰 Blog-Spring 专题博客 http://spring.hhui.top golang 服务器发送事件 (sse) todo 示例 这个例子的灵感来自和 。 它使用作为UI,使用作为存储。 它与其他 TodoMVC 示例略有不同,因为它添加了 SSE 功能来同步待办事项列表。 启动并运行 安装 git 和 bazaar sudo apt-get install git bzr 安装,用于golang MongoDB的驱动程序 mkdir -p ~/Go/vendor export GOPATH=~/Go/vendor go get gopkg.in/mgo.v2 将此存储库克隆到本地文件系统 git clone https://github.com/rwynn/golang-sse-todo.git 启动服务器 如果 mongo 服务器尚未运行,则启动它 sudo mongod -f /etc/mongodb.conf 服务端常用推送技术有: 1、客户端轮询:ajax定时拉取 2、服务端主动推送:WebSocket。全双工的,本质上是一个额外的tcp连接,建立和关闭时握手使用http协议,其他数据传输不使用http协议,更加复杂一些,适用于需要进行复杂双向数据通讯的场景。 3、服务端主动推送:SSE (Server Send Event)。html5新标准,用来从服务端实时推送数据到浏览器端, 直接建立在当前http连接上,本质上是保持一个http长连接,轻量协议简单的服务器数据推送的场景,使用服务器推送事件, SSE技. 在servlet配置完之后继续在XML里面的加上true注:在XML里所有的的标签中加上true在XML中servlet配置 true注:只需要在springmvc 控制器的中配置。 推送的场景比较多,比如有人关注我的公众号,这时我就会收到一条推送消息,以此来吸引我点击打开应用。消息推送通常是指网站的运营工作等人员,通过某种工具对用户当前网页或移动设备 APP 进行的主动消息推送。消息推送一般又分为 Web 端消息推送和移动端消息推送。移动端消息推送示例 :Web 端消息推送示例:在具体实现之前,咱们再来分析一下前边的需求,其实功能很简单,只要触发某个事件(主动分享了资源或者后台主动推送消息),Web 页面的通知小红点就会实时的+1就可以了。 2.利用长链接,后端向前端主动推送下单消息 果断选择第二种。从以往的经验,首先想到websocket,但是websocket属于双向通道,且服务端比较琐碎,就在网上找了下其他类似技术,看到了SseMmitter,查看了向网上相关文章,及用例。正好符合我们的需求,话不多说,上代码 一、新增see对象 public class SeeResult {
@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(): 结束之后的回调触发 同样演示一下访问请求 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W3mifby7-1585829791863)(https://spring.hhui.top/spring-blog/imgs/200401/01.gif)] 上图总的效果和前面的效果差不多,而且输出还待上了前缀,接下来我们写一个简单的 html 消费端,用来演示一下完整的 sse 的更多特性
上面的实现,用到了 SseEmitter 的几个方法,解释如下
send()
SseEventBuilder
complete()
onTimeout()
onCompletion()
同样演示一下访问请求
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W3mifby7-1585829791863)(https://spring.hhui.top/spring-blog/imgs/200401/01.gif)]
上图总的效果和前面的效果差不多,而且输出还待上了前缀,接下来我们写一个简单的 html 消费端,用来演示一下完整的 sse 的更多特性
<!doctype html> <html lang="en"> <title>Sse测试文档</title> </head> <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 的超时时间设长一点,再试一下数据推送功能 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bl5EdI8i-1585829791875)(https://spring.hhui.top/spring-blog/imgs/200401/03.gif)] 请注意上面的演示,当后端结束了长连接之后,客户端会自动重新再次连接,不用写额外的重试逻辑了,就这么神奇 3. 小结 本篇文章介绍了 SSE 的相关知识点,并对比 websocket 给出了 sse 的优点(至于啥优点请往上翻) 请注意,本文虽然介绍了两种 sse 的方式,第一种借助异步请求来实现,如果需要完成 sse 的规范要求,需要自己做一些适配,如果需要了解 sse 底层实现原理的话,可以参考一下;在实际的业务开发中,推荐使用SseEmitter IV. 其他 0. 项目 200329-SpringBoot 系列教程 web 篇之异步请求知识点与使用姿势小结200105-SpringBoot 系列教程 web 篇之自定义返回 Http-Code 的 n 种姿势191222-SpringBoot 系列教程 web 篇之自定义请求匹配条件 RequestCondition191206-SpringBoot 系列教程 web 篇 Listener 四种注册姿势191122-SpringBoot 系列教程 web 篇 Servlet 注册的四种姿势191120-SpringBoot 系列教程 Web 篇之开启 GZIP 数据压缩191018-SpringBoot 系列教程 web 篇之过滤器 Filter 使用指南扩展篇191016-SpringBoot 系列教程 web 篇之过滤器 Filter 使用指南191012-SpringBoot 系列教程 web 篇之自定义异常处理 HandlerExceptionResolver191010-SpringBoot 系列教程 web 篇之全局异常处理190930-SpringBoot 系列教程 web 篇之 404、500 异常页面配置190929-SpringBoot 系列教程 web 篇之重定向190913-SpringBoot 系列教程 web 篇之返回文本、网页、图片的操作姿势190905-SpringBoot 系列教程 web 篇之中文乱码问题解决190831-SpringBoot 系列教程 web 篇之如何自定义参数解析器190828-SpringBoot 系列教程 web 篇之 Post 请求参数解析姿势汇总190824-SpringBoot 系列教程 web 篇之 Get 请求参数解析姿势汇总190822-SpringBoot 系列教程 web 篇之 Beetl 环境搭建190820-SpringBoot 系列教程 web 篇之 Thymeleaf 环境搭建190816-SpringBoot 系列教程 web 篇之 Freemaker 环境搭建190421-SpringBoot 高级篇 WEB 之 websocket 的使用说明190327-Spring-RestTemplate 之 urlencode 参数解析异常全程分析190317-Spring MVC 之基于 java config 无 xml 配置的 web 应用构建190316-Spring MVC 之基于 xml 配置的 web 应用构建190213-SpringBoot 文件上传异常之提示 The temporary upload location xxx is not valid 工程:https://github.com/liuyueyi/spring-boot-demo项目源码: https://github.com/liuyueyi/spring-boot-demo/blob/master/spring-boot/220-web-sse 1. 一灰灰 Blog 尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现 bug 或者有更好的建议,欢迎批评指正,不吝感激 下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛 一灰灰 Blog 个人博客 https://blog.hhui.top一灰灰 Blog-Spring 专题博客 http://spring.hhui.top golang 服务器发送事件 (sse) todo 示例 这个例子的灵感来自和 。 它使用作为UI,使用作为存储。 它与其他 TodoMVC 示例略有不同,因为它添加了 SSE 功能来同步待办事项列表。 启动并运行 安装 git 和 bazaar sudo apt-get install git bzr 安装,用于golang MongoDB的驱动程序 mkdir -p ~/Go/vendor export GOPATH=~/Go/vendor go get gopkg.in/mgo.v2 将此存储库克隆到本地文件系统 git clone https://github.com/rwynn/golang-sse-todo.git 启动服务器 如果 mongo 服务器尚未运行,则启动它 sudo mongod -f /etc/mongodb.conf 服务端常用推送技术有: 1、客户端轮询:ajax定时拉取 2、服务端主动推送:WebSocket。全双工的,本质上是一个额外的tcp连接,建立和关闭时握手使用http协议,其他数据传输不使用http协议,更加复杂一些,适用于需要进行复杂双向数据通讯的场景。 3、服务端主动推送:SSE (Server Send Event)。html5新标准,用来从服务端实时推送数据到浏览器端, 直接建立在当前http连接上,本质上是保持一个http长连接,轻量协议简单的服务器数据推送的场景,使用服务器推送事件, SSE技. 在servlet配置完之后继续在XML里面的加上true注:在XML里所有的的标签中加上true在XML中servlet配置 true注:只需要在springmvc 控制器的中配置。 推送的场景比较多,比如有人关注我的公众号,这时我就会收到一条推送消息,以此来吸引我点击打开应用。消息推送通常是指网站的运营工作等人员,通过某种工具对用户当前网页或移动设备 APP 进行的主动消息推送。消息推送一般又分为 Web 端消息推送和移动端消息推送。移动端消息推送示例 :Web 端消息推送示例:在具体实现之前,咱们再来分析一下前边的需求,其实功能很简单,只要触发某个事件(主动分享了资源或者后台主动推送消息),Web 页面的通知小红点就会实时的+1就可以了。 2.利用长链接,后端向前端主动推送下单消息 果断选择第二种。从以往的经验,首先想到websocket,但是websocket属于双向通道,且服务端比较琐碎,就在网上找了下其他类似技术,看到了SseMmitter,查看了向网上相关文章,及用例。正好符合我们的需求,话不多说,上代码 一、新增see对象 public class SeeResult {
<!doctype html> <html lang="en"> <title>Sse测试文档</title> </head> <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
将上面的 html 文件放在项目的resources/static目录下;然后修改一下前面的SseRest
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 的超时时间设长一点,再试一下数据推送功能 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bl5EdI8i-1585829791875)(https://spring.hhui.top/spring-blog/imgs/200401/03.gif)] 请注意上面的演示,当后端结束了长连接之后,客户端会自动重新再次连接,不用写额外的重试逻辑了,就这么神奇 3. 小结 本篇文章介绍了 SSE 的相关知识点,并对比 websocket 给出了 sse 的优点(至于啥优点请往上翻) 请注意,本文虽然介绍了两种 sse 的方式,第一种借助异步请求来实现,如果需要完成 sse 的规范要求,需要自己做一些适配,如果需要了解 sse 底层实现原理的话,可以参考一下;在实际的业务开发中,推荐使用SseEmitter IV. 其他 0. 项目 200329-SpringBoot 系列教程 web 篇之异步请求知识点与使用姿势小结200105-SpringBoot 系列教程 web 篇之自定义返回 Http-Code 的 n 种姿势191222-SpringBoot 系列教程 web 篇之自定义请求匹配条件 RequestCondition191206-SpringBoot 系列教程 web 篇 Listener 四种注册姿势191122-SpringBoot 系列教程 web 篇 Servlet 注册的四种姿势191120-SpringBoot 系列教程 Web 篇之开启 GZIP 数据压缩191018-SpringBoot 系列教程 web 篇之过滤器 Filter 使用指南扩展篇191016-SpringBoot 系列教程 web 篇之过滤器 Filter 使用指南191012-SpringBoot 系列教程 web 篇之自定义异常处理 HandlerExceptionResolver191010-SpringBoot 系列教程 web 篇之全局异常处理190930-SpringBoot 系列教程 web 篇之 404、500 异常页面配置190929-SpringBoot 系列教程 web 篇之重定向190913-SpringBoot 系列教程 web 篇之返回文本、网页、图片的操作姿势190905-SpringBoot 系列教程 web 篇之中文乱码问题解决190831-SpringBoot 系列教程 web 篇之如何自定义参数解析器190828-SpringBoot 系列教程 web 篇之 Post 请求参数解析姿势汇总190824-SpringBoot 系列教程 web 篇之 Get 请求参数解析姿势汇总190822-SpringBoot 系列教程 web 篇之 Beetl 环境搭建190820-SpringBoot 系列教程 web 篇之 Thymeleaf 环境搭建190816-SpringBoot 系列教程 web 篇之 Freemaker 环境搭建190421-SpringBoot 高级篇 WEB 之 websocket 的使用说明190327-Spring-RestTemplate 之 urlencode 参数解析异常全程分析190317-Spring MVC 之基于 java config 无 xml 配置的 web 应用构建190316-Spring MVC 之基于 xml 配置的 web 应用构建190213-SpringBoot 文件上传异常之提示 The temporary upload location xxx is not valid 工程:https://github.com/liuyueyi/spring-boot-demo项目源码: https://github.com/liuyueyi/spring-boot-demo/blob/master/spring-boot/220-web-sse 1. 一灰灰 Blog 尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现 bug 或者有更好的建议,欢迎批评指正,不吝感激 下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛 一灰灰 Blog 个人博客 https://blog.hhui.top一灰灰 Blog-Spring 专题博客 http://spring.hhui.top golang 服务器发送事件 (sse) todo 示例 这个例子的灵感来自和 。 它使用作为UI,使用作为存储。 它与其他 TodoMVC 示例略有不同,因为它添加了 SSE 功能来同步待办事项列表。 启动并运行 安装 git 和 bazaar sudo apt-get install git bzr 安装,用于golang MongoDB的驱动程序 mkdir -p ~/Go/vendor export GOPATH=~/Go/vendor go get gopkg.in/mgo.v2 将此存储库克隆到本地文件系统 git clone https://github.com/rwynn/golang-sse-todo.git 启动服务器 如果 mongo 服务器尚未运行,则启动它 sudo mongod -f /etc/mongodb.conf 服务端常用推送技术有: 1、客户端轮询:ajax定时拉取 2、服务端主动推送:WebSocket。全双工的,本质上是一个额外的tcp连接,建立和关闭时握手使用http协议,其他数据传输不使用http协议,更加复杂一些,适用于需要进行复杂双向数据通讯的场景。 3、服务端主动推送:SSE (Server Send Event)。html5新标准,用来从服务端实时推送数据到浏览器端, 直接建立在当前http连接上,本质上是保持一个http长连接,轻量协议简单的服务器数据推送的场景,使用服务器推送事件, SSE技. 在servlet配置完之后继续在XML里面的加上true注:在XML里所有的的标签中加上true在XML中servlet配置 true注:只需要在springmvc 控制器的中配置。 推送的场景比较多,比如有人关注我的公众号,这时我就会收到一条推送消息,以此来吸引我点击打开应用。消息推送通常是指网站的运营工作等人员,通过某种工具对用户当前网页或移动设备 APP 进行的主动消息推送。消息推送一般又分为 Web 端消息推送和移动端消息推送。移动端消息推送示例 :Web 端消息推送示例:在具体实现之前,咱们再来分析一下前边的需求,其实功能很简单,只要触发某个事件(主动分享了资源或者后台主动推送消息),Web 页面的通知小红点就会实时的+1就可以了。 2.利用长链接,后端向前端主动推送下单消息 果断选择第二种。从以往的经验,首先想到websocket,但是websocket属于双向通道,且服务端比较琐碎,就在网上找了下其他类似技术,看到了SseMmitter,查看了向网上相关文章,及用例。正好符合我们的需求,话不多说,上代码 一、新增see对象 public class SeeResult {
@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 的超时时间设长一点,再试一下数据推送功能 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bl5EdI8i-1585829791875)(https://spring.hhui.top/spring-blog/imgs/200401/03.gif)] 请注意上面的演示,当后端结束了长连接之后,客户端会自动重新再次连接,不用写额外的重试逻辑了,就这么神奇
我们上面超时时间设置的比较短,用来测试下客户端的自动重连,如下,开启的日志不断增加
其次将 SseEmitter 的超时时间设长一点,再试一下数据推送功能
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bl5EdI8i-1585829791875)(https://spring.hhui.top/spring-blog/imgs/200401/03.gif)]
请注意上面的演示,当后端结束了长连接之后,客户端会自动重新再次连接,不用写额外的重试逻辑了,就这么神奇
3. 小结 本篇文章介绍了 SSE 的相关知识点,并对比 websocket 给出了 sse 的优点(至于啥优点请往上翻) 请注意,本文虽然介绍了两种 sse 的方式,第一种借助异步请求来实现,如果需要完成 sse 的规范要求,需要自己做一些适配,如果需要了解 sse 底层实现原理的话,可以参考一下;在实际的业务开发中,推荐使用SseEmitter IV. 其他 0. 项目 200329-SpringBoot 系列教程 web 篇之异步请求知识点与使用姿势小结200105-SpringBoot 系列教程 web 篇之自定义返回 Http-Code 的 n 种姿势191222-SpringBoot 系列教程 web 篇之自定义请求匹配条件 RequestCondition191206-SpringBoot 系列教程 web 篇 Listener 四种注册姿势191122-SpringBoot 系列教程 web 篇 Servlet 注册的四种姿势191120-SpringBoot 系列教程 Web 篇之开启 GZIP 数据压缩191018-SpringBoot 系列教程 web 篇之过滤器 Filter 使用指南扩展篇191016-SpringBoot 系列教程 web 篇之过滤器 Filter 使用指南191012-SpringBoot 系列教程 web 篇之自定义异常处理 HandlerExceptionResolver191010-SpringBoot 系列教程 web 篇之全局异常处理190930-SpringBoot 系列教程 web 篇之 404、500 异常页面配置190929-SpringBoot 系列教程 web 篇之重定向190913-SpringBoot 系列教程 web 篇之返回文本、网页、图片的操作姿势190905-SpringBoot 系列教程 web 篇之中文乱码问题解决190831-SpringBoot 系列教程 web 篇之如何自定义参数解析器190828-SpringBoot 系列教程 web 篇之 Post 请求参数解析姿势汇总190824-SpringBoot 系列教程 web 篇之 Get 请求参数解析姿势汇总190822-SpringBoot 系列教程 web 篇之 Beetl 环境搭建190820-SpringBoot 系列教程 web 篇之 Thymeleaf 环境搭建190816-SpringBoot 系列教程 web 篇之 Freemaker 环境搭建190421-SpringBoot 高级篇 WEB 之 websocket 的使用说明190327-Spring-RestTemplate 之 urlencode 参数解析异常全程分析190317-Spring MVC 之基于 java config 无 xml 配置的 web 应用构建190316-Spring MVC 之基于 xml 配置的 web 应用构建190213-SpringBoot 文件上传异常之提示 The temporary upload location xxx is not valid 工程:https://github.com/liuyueyi/spring-boot-demo项目源码: https://github.com/liuyueyi/spring-boot-demo/blob/master/spring-boot/220-web-sse 1. 一灰灰 Blog 尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现 bug 或者有更好的建议,欢迎批评指正,不吝感激 下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛 一灰灰 Blog 个人博客 https://blog.hhui.top一灰灰 Blog-Spring 专题博客 http://spring.hhui.top golang 服务器发送事件 (sse) todo 示例 这个例子的灵感来自和 。 它使用作为UI,使用作为存储。 它与其他 TodoMVC 示例略有不同,因为它添加了 SSE 功能来同步待办事项列表。 启动并运行 安装 git 和 bazaar sudo apt-get install git bzr 安装,用于golang MongoDB的驱动程序 mkdir -p ~/Go/vendor export GOPATH=~/Go/vendor go get gopkg.in/mgo.v2 将此存储库克隆到本地文件系统 git clone https://github.com/rwynn/golang-sse-todo.git 启动服务器 如果 mongo 服务器尚未运行,则启动它 sudo mongod -f /etc/mongodb.conf 服务端常用推送技术有: 1、客户端轮询:ajax定时拉取 2、服务端主动推送:WebSocket。全双工的,本质上是一个额外的tcp连接,建立和关闭时握手使用http协议,其他数据传输不使用http协议,更加复杂一些,适用于需要进行复杂双向数据通讯的场景。 3、服务端主动推送:SSE (Server Send Event)。html5新标准,用来从服务端实时推送数据到浏览器端, 直接建立在当前http连接上,本质上是保持一个http长连接,轻量协议简单的服务器数据推送的场景,使用服务器推送事件, SSE技. 在servlet配置完之后继续在XML里面的加上true注:在XML里所有的的标签中加上true在XML中servlet配置 true注:只需要在springmvc 控制器的中配置。 推送的场景比较多,比如有人关注我的公众号,这时我就会收到一条推送消息,以此来吸引我点击打开应用。消息推送通常是指网站的运营工作等人员,通过某种工具对用户当前网页或移动设备 APP 进行的主动消息推送。消息推送一般又分为 Web 端消息推送和移动端消息推送。移动端消息推送示例 :Web 端消息推送示例:在具体实现之前,咱们再来分析一下前边的需求,其实功能很简单,只要触发某个事件(主动分享了资源或者后台主动推送消息),Web 页面的通知小红点就会实时的+1就可以了。 2.利用长链接,后端向前端主动推送下单消息 果断选择第二种。从以往的经验,首先想到websocket,但是websocket属于双向通道,且服务端比较琐碎,就在网上找了下其他类似技术,看到了SseMmitter,查看了向网上相关文章,及用例。正好符合我们的需求,话不多说,上代码 一、新增see对象 public class SeeResult {
本篇文章介绍了 SSE 的相关知识点,并对比 websocket 给出了 sse 的优点(至于啥优点请往上翻)
请注意,本文虽然介绍了两种 sse 的方式,第一种借助异步请求来实现,如果需要完成 sse 的规范要求,需要自己做一些适配,如果需要了解 sse 底层实现原理的话,可以参考一下;在实际的业务开发中,推荐使用SseEmitter
尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现 bug 或者有更好的建议,欢迎批评指正,不吝感激
下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛