介绍¶
通常我们在做后端推送方案选型的时候,如果不使用 WebSocket,我会选用什么方案呢?
先说最基础的 轮询 Polling,client 每隔一段时间请求 server 查询是否有更新数据,但是这样没办法保证数据的实时,而且也需要耗费大量的流量。
那我们进一步,如果没有数据更新,server 就不要响应 client,而是 hold connection,直到数据更新了,再将最新的数据返回给 client,随后 client 再建立新的连接。这就是长轮询 Long Polling 机制。相比简单的轮询,更加实时,网络开销更小,但是也不是非常优雅,需要不断重连。
那么是不是可以让服务端响应数据的时候不要断开 HTTP 连接?那我们只需要将所有响应作为 stream 下发,client 不断处理下发的事件即可。这便形成了 Server-Side Events(下面简称 SSE) 的基本原理。
相比 WebSocket,SSE 完全基于 HTTP,对于服务端来说实现更为简单轻量。
服务端实现¶
以下我使用 Golang+echo 实现了一个简单的 SSE 服务端:
e:=echo.New()
e.GET("/sub", func(c echo.Context) error {
c.Response().Header().Set(echo.HeaderContentType, "text/event-stream")
c.Response().WriteHeader(http.StatusOK)
for {
select {
case <-c.Request().Context().Done():
return nil
default:
}
fmt.Fprint(c.Response(), "data: hi\n\n")
c.Response().Flush()
time.Sleep(1 * time.Second)
}
return nil
})
前端实现¶
EventSource 是 W3C 标准的正式成员,浏览器也为其提供了完整的实现,只需要直接创建 EventSource 就够直接订阅一个后端数据。
const evtSrc = new EventSource("http://example.com/eventsource");
evtSrc.onmessage = function (event) {
console.log(event.data)
}
很多情况下后端服务或者网关会给一个 HTTP 请求设定最长连接时长,一旦超时,会自动断开 client 的连接。这就需要 client 实现自动重连机制,以确保数据的实时。而 EventSource 已经原生实现自动重连机制,一旦连接断开,浏览器不不断重试直到连接恢复。
适用场景¶
经过上面的介绍,可以看到 SSE 无论对于前端还是后端都及其友好,非常容易实现,可以非常好地满足事件下发的需求。对于前端展示的消息订阅,非常适合使用 SSE 实现。
这是 Twitter 开发者文档,可以看到 Twitter 的事件推送也是基于 HTTP Stream 实现。