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 main
import (
"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!")
return
default:
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>