Server-Sent Events (SSE)简单实践的一些问题和思考
SSE是什么
WIKI的介绍如下(百度没有词条,看看维基的)
Server-Sent Events (SSE) is a server push technology enabling a client to receive automatic updates from a server via an HTTP connection, and describes how servers can initiate data transmission towards clients once an initial client connection has been established. They are commonly used to send message updates or continuous data streams to a browser client and designed to enhance native, cross-browser streaming through a JavaScript API called EventSource, through which a client requests a particular URL in order to receive an event stream. The EventSource API is standardized as part of HTML5 by the WHATWG. The media type for SSE is text/event-stream.
网上很多人的blog都提到,SSE是HTML5的一个标准套件、是基于HTTP的一个websocket的轻量化替代协议、可以服务端流式传输到客户端、media类型是text/event-steam。
为什么会用到这个
但对于最近爆火的chatgpt来说,SSE的“单次请求、多段返回”正是一个极好的应用。
SSE的使用
package mainimport ("context""fmt""log""net/http""time")// MessageEvent 事件type MessageEvent struct {Id string // 事件IDEvent string // 事件类型Data string // 发送数据}// 事件转字符串func (e MessageEvent) String() string {return fmt.Sprintf("id:%s\nevent:%s\ndata:%s\n\n", e.Id, e.Event, e.Data)}func main() {// 事件消息通道messageChan := make(chan MessageEvent)// HTTP 请求处理函数http.HandleFunc("/events", func(w http.ResponseWriter, r *http.Request) {// 设置 response header// 媒体类型设为事件流w.Header().Set("Content-Type", "text/event-stream")// 因为是不断往缓冲区写入并flush出去,因此要禁用缓存w.Header().Set("Cache-Control", "no-cache")// 保持tcp连接,SSE是基于HTTP的,因此可以减少连接开销w.Header().Set("Connection", "keep-alive")// 设置响应码w.WriteHeader(http.StatusOK)// 结束上下文t, cancel := context.WithCancel(context.Background())// 写入事件信息go func() {for i := 0; i < 3; i++ {messageChan <- MessageEvent{Id: fmt.Sprintf("%d", i),Event: "test",Data: fmt.Sprintf("Message %d from server", i),}time.Sleep(time.Second)}// 结束上下文cancel()}()L:// 监听事件消息for {select {case <-t.Done():// 结束连接log.Println("send finish")break Lcase message := <-messageChan:// 发送数据fmt.Fprintf(w, "%s", &message)// 从缓冲区刷新数据出去w.(http.Flusher).Flush()}}})// 启动监听err := http.ListenAndServe(":8080", nil)if err != nil {panic(err)}}
在技术中心tcf框架的实践
package controllerimport ("context""fmt"v1 "ig-gptword-service/api/v1""log""net/http""time""github.com/gogf/gf/v2/frame/g")var (TestSse = cTestSse{})// MessageEvent 事件type MessageEvent struct {Id string // 事件IDEvent string // 事件类型Data string // 发送数据}// 事件转字符串func (e MessageEvent) String() string {return fmt.Sprintf("id:%s\nevent:%s\ndata:%s\n\n", e.Id, e.Event, e.Data)}type cTestSse struct{}func (c *cTestSse) Translate(ctx context.Context, req *v1.SseClientReq) (res *v1.SseClientRes, err error) {r := g.RequestFromCtx(ctx)w := r.Response.ResponseWriter.RawWriter()// 事件消息通道messageChan := make(chan MessageEvent)// 设置 response header// 媒体类型设为事件流w.Header().Set("Content-Type", "text/event-stream")// 因为是不断往缓冲区写入并flush出去,因此要禁用缓存w.Header().Set("Cache-Control", "no-cache")// 保持tcp连接,SSE是基于HTTP的,因此可以减少连接开销w.Header().Set("Connection", "keep-alive")// 设置响应码w.WriteHeader(http.StatusOK)// 结束上下文t, cancel := context.WithCancel(context.Background())// 向事件通道中写入数据go func() {for i := 0; i < 3; i++ {messageChan <- MessageEvent{Id: fmt.Sprintf("%d", i),Event: "test",Data: fmt.Sprintf("Message %d from server", i),}time.Sleep(time.Second)}// 结束上下文cancel()}()L:// 循环监听 SSE 事件通道for {select {case <-t.Done():// 结束连接log.Println("send finish")break Lcase message := <-messageChan:// 向客户端发送 SSE 事件fmt.Fprintf(w, "%s", &message)// 刷新 response bufferw.(http.Flusher).Flush()}}return}
// 劫持原连接,接管后续生命周期conn, _, err := r.Response.ResponseWriter.Hijack()if err != nil {log.Println("close sse connection failed: ", err.Error())return}// 关闭连接err = conn.Close()if err != nil {log.Println("close sse connection failed: ", err.Error())}return
// 劫持原连接,接管后续生命周期conn, _, err := r.Response.ResponseWriter.Hijack()if err != nil {log.Println("close sse connection failed: ", err.Error())return}// 关闭连接conn.Write([]byte("0\r\n\r\n"))err = conn.Close()if err != nil {log.Println("close sse connection failed: ", err.Error())}return
一点应用场景的思考
报表应用
每个报表查询背后是无数个SQL,这些SQL执行效率各有差异,以往短连接开发模式有水桶效应,报表呈现效率取决于最慢的SQL。
如果能在报表应用上使用SSE,可以打破水桶效应,实时反馈每个SQL、指标的查询进度和结果。
而使用SSE相比于websocket或TCP/UDP,对现有应用、开发人员来说压力最小,改造成本最低。订阅服务 取代轮询