服务器向浏览器推送信息,除了 WebSocket,还有一种方法:Server-Sent Events(以下简称 SSE)。
严格地说,HTTP 协议无法做到服务器主动推送信息。但是,有一种变通方法,就是服务器向客户端声明,接下来要发送的是流信息(streaming)。
也就是说,发送的不是一次性的数据包,而是一个数据流,会连续不断地发送过来。这时,客户端不会关闭连接,会一直等着服务器发过来的新的数据流,视频播放就是这样的例子。本质上,这种通信就是以流信息的方式,完成一次用时很长的下载。
SSE 就是利用这种机制,使用流信息向浏览器推送信息。它基于 HTTP 协议,目前除了 IE/Edge,其他浏览器都支持。
SSE 与 WebSocket 作用相似,都是建立浏览器与服务器之间的通信渠道,然后服务器向浏览器推送信息。
总体来说,WebSocket 更强大和灵活。因为它是全双工通道,可以双向通信;SSE 是单向通道,只能服务器向浏览器发送,因为流信息本质上就是下载。如果浏览器向服务器发送信息,就变成了另一次 HTTP 请求。
SSE 使用 HTTP 协议,现有的服务器软件都支持。WebSocket 是一个独立协议。
SSE 属于轻量级,使用简单;WebSocket 协议相对复杂。
SSE 默认支持断线重连,WebSocket 需要自己实现。
SSE 一般只用来传送文本,二进制数据需要编码后传送,WebSocket 默认支持传送二进制数据。
SSE 支持自定义发送的消息类型。
服务器向浏览器发送的 SSE 数据,必须是 UTF-8 编码的文本,具有如下的 HTTP 头信息:
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
每一次发送的信息,由若干个message组成,每个message之间用\n\n分隔。每个message内部由若干行组成,每一行都是如下格式。
[field]: value\n
field可以取四个值:
data
event
id
retry
此外,还可以有冒号开头的行,表示注释。通常,服务器每隔一段时间就会向浏览器发送一个注释,保持连接不中断。
: This is a comment
总之,服务端发送的是字符串文本,一条数据有多个\n分行,\n\n表示一条完整数据的结束。
SSE更详细信息参考,介绍的简单易懂很详细
引入sse.lua库 ,需要依赖 ledgetech/lua-resty-http(opm get ledgetech/lua-resty-http
安装引用即可)。
如何使用:
location /sse_proxy {
lua_check_client_abort on;
content_by_lua_block {
local sse = require("sse")
local function return_client_res(res)
ngx.status = res.status
for k, v in pairs(res.header) do
ngx.header[k] = v
end
ngx.say(res.body)
return ngx.exit(ngx.status)
end
local function my_cleanup()
-- need config lua_check_client_abort on;
ngx.log(ngx.NOTICE,"onabort exit 499")
ngx.exit(499)
end
local ok, err0 = ngx.on_abort(my_cleanup)
if not ok then
ngx.log(ngx.ERR, "failed to register the on_abort callback: ", err0)
end
local conn, err = sse.new()
if not conn then
ngx.log(ngx.ERR,"failed to get connection: ", err)
return
end
--conn:set_timeout(50000)
local path = 'http://127.0.0.1:8080/events/123'
local res, err2, is_sse_resp = conn:request_uri(path,{
headers = req_headers,
ssl_verify = false
})
if not res then
ngx.log(ngx.ERR,"failed to request: ", err2)
return
end
-- response header同步
conn:transfer_encoding_is_chunked() --有Transfer-Encoding:chunked,需要去掉
for k, v in pairs(res.headers) do
ngx.header[k] = v
end
while is_sse_resp
do
local event, err3 = conn:receive()
if err3 then
ngx.log(ngx.ERR,"sse request over"..err3)
return ngx.exit(ngx.HTTP_OK)
end
if event then
ngx.log(ngx.NOTICE,"sse received success, event="..event)
ngx.print(event)
ngx.flush() -- 一定要flush
end
end
-- not content-type:text/event-steam
ngx.status=res.status
ngx.say(res.body)
return ngx.exit(ngx.status)
}
}
https://github.com/ldongxu/openresty-starter 这里有更详细的openresty脚手架项目,包括http、websocket、sse,也有springboot开发的SSE服务demo供调试使用。