服务端推送、 server sent event、sse、springboot+sse

SSE(server-sent events)

SSE全称server-sent events,翻译过来是服务端发送事件,通常的http请求,客户端请求,服务端返回响应,一次只能返回一个值;SSE使用的http协议,客户端请求后,服务端可以多次返回响应,返回的http Content-Type:text/event-stream,客户端请求后,仍然保持http链接不关闭,服务端可多次发送响应,客户端监听事件,获取响应,这适用新闻实时更新、股票实时更新等服务端推送到客户端的场景。在chatgpt中提问问题后,答案不是一下子出来的,而是断断续续出来的,就是用的SSE技术。
服务端推送、 server sent event、sse、springboot+sse_第1张图片

SSE与websocket的异同

SSE websocket
基于http协议的 基于websocket协议
单向的,只能服务端---->客户端 双向的,服务端<------>客户端
消息有id、event、data、retry等字段 不限格式
  • 普通http请求
    服务端推送、 server sent event、sse、springboot+sse_第2张图片
  • SSE
    服务端推送、 server sent event、sse、springboot+sse_第3张图片

实践

SSE通信分为客户端(通常是浏览器端)和服务端,客户端用来发送请求,监听事件,关闭链接等。服务端,就是普通的http服务,区别就是Content-Type:text/event-stream

客户端

客户端使用EventSource API来实现通信,

  • 创建EventSource实例
const evtSource = new EventSource("ssedemo.php");

如果是跨域,则

const evtSource = new EventSource("//api.example.com/ssedemo.php", {
  withCredentials: true,
});
  • 监听服务端推送的消息
evtSource.onmessage = (event) => {
  const newElement = document.createElement("li");
  const eventList = document.getElementById("list");

  newElement.textContent = `message: ${event.data}`;
  eventList.appendChild(newElement);
};
  • 监听自定义的事件,使用addEventListener方法,监听事件为ping的
evtSource.addEventListener("ping", (event) => {
  const newElement = document.createElement("li");
  const eventList = document.getElementById("list");
  const time = JSON.parse(event.data).time;
  newElement.textContent = `ping at ${time}`;
  eventList.appendChild(newElement);
});
  • 监听错误
evtSource.onerror = (err) => {
  console.error("EventSource failed:", err);
};
  • 关闭,注意,关闭只能从客户端发起。
evtSource.close();

服务端

服务端跟正常的http 服务差不多,除了响应类型是text/event-stream

  • Event Stream格式
    event: 事件名称,如果不写事件名,则被客户端的onMessage监听;有事件名,则被对应的addEventListener监听。
    data: 消息的数据字段
    id: 事件的id
    retry: 断线重连的等待时间,单位毫秒

  • 只发数据

//冒号开头的是注释,可用于心跳
: this is a test stream

data: some text

data: another message
data: with two lines
  • 命名事件
event: userconnect
data: {"username": "bobby", "time": "02:33:48"}

event: usermessage
data: {"username": "bobby", "time": "02:34:11", "text": "Hi everyone."}

event: userdisconnect
data: {"username": "bobby", "time": "02:34:23"}

event: usermessage
data: {"username": "sean", "time": "02:34:36", "text": "Bye, bobby."}

也可以一会发数据消息、一会发命名事件消息

springboot支持SSE

在spring framework文档中,springmvc中ResponseBodyEmitter 、SseEmitter (ResponseBodyEmitter的子类)归属为异步请求(适用异步返回请求值),异步请求细分为Http Streaming(适用返回多个值的情况)

  • java端代码

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Slf4j
@RestController
@RequestMapping("question")
public class AskQuestionController {



    @GetMapping(value = "/askQuestion", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter askQuestion() {
        final SseEmitter sseEmitter = new SseEmitter();
        final ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.submit(() -> {
            for (int i = 0; i < 100; i++) {
                String data = getCurDateStr();
                try {
                    sseEmitter.send(SseEmitter.event().id(String.valueOf(i+1)).data(data).name("custom_event_name"));
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        return sseEmitter;

    }

    private static String getCurDateStr() {
        final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        return simpleDateFormat.format(new Date());
    }
}

  • 客户端代码


内容是:
  • 效果,用了原生EventSource在dev tools的EventStream中显示事件数据
    服务端推送、 server sent event、sse、springboot+sse_第4张图片

  • 客户端自动重连
    服务端推送、 server sent event、sse、springboot+sse_第5张图片

问题

  • 在chrome devtools中的EventStream中看不到数据
    • 这是因为devtools只支持原生的EventSource,不支持EventSource Polyfill
    • Event does not show up in the Chrome EventStream subtab inside the Network tab when viewing the eventstream connection
    • why did not found the chatgpt event stream data in google chrome devtools
    • 服务端推送、 server sent event、sse、springboot+sse_第6张图片
  • EventSource API不支持POST方法,只能GET,但可以不使用EventSource API,用sse.js来实现SSE POST
var source = new SSE("get_message.php", {payload: 'Hello World'});

总结

SSE是一种规范而不是一种新协议,它使用http通信,返回的Content-Type:text/event-stream,客户端通过监听事件不断获取数据,链接断掉后会自动重连,接收完数据后由客户端发起关闭连接。

参考

  • Using server-sent events

  • Can Server Sent Events (SSE) with EventSource pass parameter by POST

  • How to pass POST parameters with HTML SSE?

你可能感兴趣的:(spring,boot,后端,java)