GoNote第六章 GoFrame 接入SSE
什么是SSE(Server Sent Events)
引用维基百科:
Server-Sent Events (SSE) is a server push technology enabling a client to receive automatic updates from a server via HTTP connection. The Server-Sent Events EventSource API is standardized as part of HTML5[1] by the W3C.
在Web开发时,由于HTTP是无状态的协议,所以客户端浏览器必须首先向服务器发送请求才能接收新数据。所以如果要实现服务端向客户端发起通知,通常可以使用WebSocket或者客户端长轮询(Long-Poling)的方式。但是其实如果只是服务端向客户端推送单方向的数据流时,可以使用H5标准中的SSE,SSE使用户可以订阅服务器端的实时数据流。
SSE与WebSocket
Server Sent Events(SSE)和WebSocket都是Web技术中用于实现实时通信的协议。它们之间的一些差异如下:
- SSE与WebSocket之间的主要区别在于,SSE是基于HTTP协议进行的,而WebSocket是独立的协议。因此,SSE可以轻松地使用现有的HTTP基础设施进行部署,而WebSocket则需要单独的网络端口并在服务器上进行特殊的配置。
- 对于服务器推送数据到客户端,SSE使用常规的HTTP响应,其中包含了许多按照事件流格式格式化的消息。而WebSocket则使用专用的双向数据通道,更适合双向通信的场景。
- 在使用SSE时,服务器推送消息的速度相对较慢,并且不能自由控制消息的发送时间间隔。而WebSocket具有更高的性能和更灵活的控制机制,可以处理更复杂的应用程序需求。
- 由于SSE是基于HTTP协议实现的,因此可以借助浏览器的缓存机制,从而减少网络流量和服务器负担。而WebSocket则需要通过自己的协议处理缓存问题,在这方面较为繁琐。
综上所述,SSE和WebSocket都有自己的优势和劣势,适用于不同的应用场景。对于需要快速构建实时通信的简单场景,SSE是一种快速且易于部署的解决方案。而对于更复杂的应用程序需求,WebSocket则提供了更高效的数据传输和更灵活的控制机制。
goFrame 引入SSE
Golang有开源库eventsource直接支持了SSE,在这里我们直接使用这个库构建服务器:
导入插件
gopkg.in/antage/eventsource.v1
直接运行,直接访问接口,获取响应值就行,
可以循环读取redis中的信息,推送给前端
package mainimport ("context""fmt""net/http""time""github.com/gogf/gf/v2/os/gtime""github.com/gogf/gf/v2/frame/g""github.com/gogf/gf/v2/net/ghttp""github.com/gogf/gf/v2/os/gctx""gopkg.in/antage/eventsource.v1"
)func main() {ctx := gctx.New()StartSSE(ctx)
}func StartSSE(ctx context.Context) {s := g.Server()s.BindHandler("/sse", func(r *ghttp.Request) {es := eventsource.New(&eventsource.Settings{Timeout: 5 * time.Second,IdleTimeout: 1 * time.Minute,CloseOnTimeout: true,},func(request *http.Request) [][]byte {return [][]byte{[]byte("Content-Type: text/event-stream; charset=utf-8"),[]byte("Connection: keep-alive"),[]byte("X-Accel-Buffering: no"), // 防止 nginx 中间启用缓存,导致不能刷新到客户端[]byte("Cache-Control: no-cache,no-store"),[]byte("Access-Control-Allow-Origin: *"),[]byte("Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept"),}},)defer es.Close()es.ServeHTTP(r.Response.Writer, r.Request)// 请求参数校验eventId := r.Get("eventId").String()event := r.Get("event").String()eventTime := r.Get("eventTime").Int64()if eventId == "" || event == "" || eventTime < 1 {es.SendEventMessage("请求事件异常", event, eventId)return}now := gtime.Now()t := gtime.NewFromTimeStamp(eventTime)if now.Timestamp() < eventTime || now.Sub(t) > 3*time.Minute {es.SendEventMessage("请求事件超时", event, eventId)return}// 循环执行时间 3 分钟to := time.After(3 * time.Minute)for {select {case <-to:fmt.Println("Timeout!")returndefault:es.SendEventMessage("data数据", event, eventId)// 每秒轮询一次time.Sleep(1 * time.Second)}}})s.port(8080)s.Run()
}
跳转Html页面
type HelloReq struct {g.Meta `path:"/hello" tags:"Hello" method:"get" summary:"You first hello api"`
}
type HelloRes struct {g.Meta `mime:"text/html" type:"string" example:"<html/>" `
}var (Hello = cHello{}
)type cHello struct{}func (c *cHello) Hello(ctx context.Context, req *common.HelloReq) (res *common.HelloRes, err error) {err = g.RequestFromCtx(ctx).Response.WriteTpl("/home/index.html")liberr.ErrIsNil(ctx, err)return
}
客户端HTML页面
客户端HTML页面在resource/template目录下,通过new EventSource("/events")
创建了前端的SSE接收实例对象evsrc,并设置了onmessage方法:每次接收到请求就在页面列表中加入一条数据;
<!DOCTYPE html>
<html>
<head><title>SSE test</title><script type="text/javascript">window.addEventListener("DOMContentLoaded", function () {var evsrc = new EventSource("/sse");evsrc.onmessage = function (ev) {document.getElementById("log").insertAdjacentHTML("beforeend", "<li>" + ev.data + "</li>");}evsrc.onerror = function (ev) {console.log("readyState = " + ev.currentTarget.readyState);}})</script>
</head>
<body>
<h1>SSE test</h1>
<div><ul id="log"></ul>
</div>
</body>
</html>