当前位置:   article > 正文

springboot实现SSE服务端主动向客户端推送数据,java服务端向客户端推送数据,kotlin模拟客户端向服务端推送数据_postman sse

postman sse

SSE服务端推送

服务器向浏览器推送信息,除了 WebSocket,还有一种方法:Server-Sent Events(以下简称 SSE)。本文介绍它的用法。

在很多业务场景中,会涉及到服务端向客户端发起推送通知,但HTTP 协议无法做到服务器主动推送信息。

如何实现呢? 很多人知道WebSocket,使用长连接,实现客户端与服务端的全双工通信。但在一些场景,如支付的回调功能,这时候我们的业务只有一个功能点需要用到服务器的推送,为了一个支付功能去建立长连接,从而实现服务器推送又有些过度设计,并不合理。

但是,有一种变通方法,就是服务器向客户端声明,接下来要发送的是流信息(streaming)。

也就是说,发送的不是一次性的数据包,而是一个数据流,会连续不断地发送过来。这时,客户端不会关闭连接,会一直等着服务器发过来的新的数据流,视频播放就是这样的例子。本质上,这种通信就是以流信息的方式,完成一次用时很长的下载。

SSE 就是利用这种机制,使用流信息向浏览器推送信息。它基于 HTTP 协议,目前除了 IE/Edge,其他浏览器都支持。

接下来模拟一种网络支付场景,使用SSE,该如何实现这个过程呢?

image-20220314114431233

  1. 用户扫码向支付系统(微信、支付宝、苹果)进行支付。
  2. 支付完成之后,告知服务端我已经发起支付了(建立SSE连接)。
  3. 支付系统告诉服务端(支付宝、微信的做法),或者客户端将支付凭证传给服务器做校验(IAP),这个用户确实支付成功了。
  4. 服务端向用户发送消息:你已经支付成功,跳转到支付成功页面。(通过SSE连接,由服务器端告知用户客户端浏览器)。

这里,我们先建立SSE连接,然后在支付后将结果使用SSE推送给客户端(浏览器)。两步。

客户端需要注意的是两个概念: 事件源、事件

var es = new EventSource('事件源名称') ;  //与事件源建立连接
//标准事件处理方法,还有onopen、onerror
es.onmessage = function(e) {
};
//可以监听自定义的事件名称
es.addEventListener('自定义事件名称', function(e) {
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

通过对事件进行自定义,我们可以进行不同的操作处理,比如订单完成,订单失效等等。

一、模拟客户端

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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

二、模拟服务端

  • java版本:
@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事件
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • kotlin版本
@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>()
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

三、测试

在浏览器打开页面,页面会自动与服务器建立连接,这里自动完成了第一步,发起支付。

第二步:postman或者浏览器调用

localhost:8844/payback?payid=1

模拟支付成功,服务器回调客户端。

结果如下:

image-20220314142053416

demo见:https://gitee.com/ck_567/springboot-sse.git

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号