赞
踩
SSE服务端推送
服务器向浏览器推送信息,除了 WebSocket,还有一种方法:Server-Sent Events(以下简称 SSE)。本文介绍它的用法。
在很多业务场景中,会涉及到服务端向客户端发起推送通知,但HTTP 协议无法做到服务器主动推送信息。
如何实现呢? 很多人知道WebSocket,使用长连接,实现客户端与服务端的全双工通信。但在一些场景,如支付的回调功能,这时候我们的业务只有一个功能点需要用到服务器的推送,为了一个支付功能去建立长连接,从而实现服务器推送又有些过度设计,并不合理。
但是,有一种变通方法,就是服务器向客户端声明,接下来要发送的是流信息(streaming)。
也就是说,发送的不是一次性的数据包,而是一个数据流,会连续不断地发送过来。这时,客户端不会关闭连接,会一直等着服务器发过来的新的数据流,视频播放就是这样的例子。本质上,这种通信就是以流信息的方式,完成一次用时很长的下载。
SSE 就是利用这种机制,使用流信息向浏览器推送信息。它基于 HTTP 协议,目前除了 IE/Edge,其他浏览器都支持。
接下来模拟一种网络支付场景,使用SSE,该如何实现这个过程呢?
这里,我们先建立SSE连接,然后在支付后将结果使用SSE推送给客户端(浏览器)。两步。
客户端需要注意的是两个概念: 事件源、事件
var es = new EventSource('事件源名称') ; //与事件源建立连接
//标准事件处理方法,还有onopen、onerror
es.onmessage = function(e) {
};
//可以监听自定义的事件名称
es.addEventListener('自定义事件名称', function(e) {
});
通过对事件进行自定义,我们可以进行不同的操作处理,比如订单完成,订单失效等等。
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>SSE</title> </head> <body> <div id = "message"> //这里展示支付状态信息 </div> <script> if (window.EventSource) { //判断浏览器是否支持SSE //第2步,主动进行建立长连接,表明用户已经发起支付 let source = new EventSource( 'http://127.0.0.1:8844/orderpay?payid=1'); let innerHTML = ''; //监听服务器端发来的事件:open source.onopen = function(e) { innerHTML += "onopen:准备就绪,可以开始接收服务器数据" + "<br/>"; //支付结果 document.getElementById("message").innerHTML = innerHTML; }; //监听服务器端发来的事件:message source.onmessage = function(e) { innerHTML += "onmessage:" + e.data + "<br/>"; //支付结果 document.getElementById("message").innerHTML = innerHTML; }; //自定义finish事件,主动关闭EventSource source.addEventListener('finish', function(e) { source.close(); innerHTML += "支付结果接收完毕,通知服务端关闭EventSource" + "<br/>"; document.getElementById("message").innerHTML = innerHTML; }, false); //监听服务器端发来的事件:error source.onerror = function(e) { if (e.readyState === EventSource.CLOSED) { innerHTML += "sse连接已关闭" + "<br/>"; } else { console.log(e); } }; } else { console.log("你的浏览器不支持SSE"); } </script> </body> </html>
@RestController @CrossOrigin @RequestMapping public class SSEControler { //建立之后根据订单id,将SseEmitter存到ConcurrentHashMap //正常应该存到数据库里面,生成数据库订单,这里我们只是模拟一下 public static final ConcurrentHashMap<Long, SseEmitter> sseEmitters = new ConcurrentHashMap<>(); //第2步:接受用户建立长连接,表示该用户已支付,已支付就可以生成订单(未确认状态) @GetMapping("/orderpay") public SseEmitter orderpay(Long payid) { //设置默认的超时时间60秒,超时之后服务端主动关闭连接。 SseEmitter emitter = new SseEmitter(60 * 1000L); sseEmitters.put(payid,emitter); emitter.onTimeout(() -> sseEmitters.remove(payid)); return emitter; } //第3步:接受支付系统的支付结果告知,表明用户支付成功 @GetMapping("/payback") public void payback (Long payid){ //把SSE连接取出来 SseEmitter emitter = sseEmitters.get(payid); try { //第4步:由服务端告知浏览器端:该用户支付成功了 emitter.send("用户支付成功"); //触发前端message事件。 //触发前端自定义的finish事件 emitter.send(SseEmitter.event().name("finish").id("6666").data("哈哈")); } catch (IOException e) { emitter.completeWithError(e); //出发前端onerror事件 } } }
@RestController @CrossOrigin @RequestMapping class Hello { //第2步:接受用户建立长连接,表示该用户已支付,已支付就可以生成订单(未确认状态) @GetMapping("/orderpay") fun orderpay( @RequestParam("payid")payid: Long): SseEmitter { println("接受连接建立") //设置默认的超时时间60秒,超时之后服务端主动关闭连接。 val emitter = SseEmitter(60 * 1000L) sseEmitters.put(payid, emitter) emitter.onTimeout { sseEmitters.remove(payid) } return emitter } //第3步:接受支付系统的支付结果告知,表明用户支付成功 @GetMapping("/payback") fun payback( @RequestParam("payid") payid: Long) { //把SSE连接取出来 val emitter = sseEmitters[payid] try { println("支付成功") //第4步:由服务端告知浏览器端:该用户支付成功了 emitter!!.send("用户支付成功") //触发前端message事件。 // emitter.send(SseEmitter.event().name("nothing").id("1").data("哈哈")) //触发前端自定义的finish事件 emitter.send(SseEmitter.event().name("finish").id("1").data("哈哈")) } catch (e: IOException) { emitter!!.completeWithError(e) //出发前端onerror事件 } } companion object { //建立之后根据订单id,将SseEmitter存到ConcurrentHashMap //正常应该存到数据库里面,生成数据库订单,这里我们只是模拟一下 val sseEmitters = ConcurrentHashMap<Long, SseEmitter>() } }
在浏览器打开页面,页面会自动与服务器建立连接,这里自动完成了第一步,发起支付。
第二步:postman或者浏览器调用
localhost:8844/payback?payid=1
模拟支付成功,服务器回调客户端。
结果如下:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。