lua-resty-websocket
用于 ngx_lua 模块的 Lua WebSocket 实现
$ opm get openresty/lua-resty-websocket
名称
lua-resty-websocket - 用于 ngx_lua 模块的 Lua WebSocket 实现
状态
该库被认为是生产就绪的。
描述
该 Lua 库基于 ngx_lua 模块 实现了一个 WebSocket 服务器和客户端库。
该 Lua 库利用了 ngx_lua 的 cosocket API,确保 100% 的非阻塞行为。
请注意,只支持 RFC 6455。早期的协议版本,如“hybi-10”、“hybi-07”和“hybi-00”不受支持,也不会考虑。
概要
local server = require "resty.websocket.server"
local wb, err = server:new{
timeout = 5000, -- in milliseconds
max_payload_len = 65535,
}
if not wb then
ngx.log(ngx.ERR, "failed to new websocket: ", err)
return ngx.exit(444)
end
local data, typ, err = wb:recv_frame()
if not data then
if not string.find(err, "timeout", 1, true) then
ngx.log(ngx.ERR, "failed to receive a frame: ", err)
return ngx.exit(444)
end
end
if typ == "close" then
-- for typ "close", err contains the status code
local code = err
-- send a close frame back:
local bytes, err = wb:send_close(1000, "enough, enough!")
if not bytes then
ngx.log(ngx.ERR, "failed to send the close frame: ", err)
return
end
ngx.log(ngx.INFO, "closing with status code ", code, " and message ", data)
return
end
if typ == "ping" then
-- send a pong frame back:
local bytes, err = wb:send_pong(data)
if not bytes then
ngx.log(ngx.ERR, "failed to send frame: ", err)
return
end
elseif typ == "pong" then
-- just discard the incoming pong frame
else
ngx.log(ngx.INFO, "received a frame of type ", typ, " and payload ", data)
end
wb:set_timeout(1000) -- change the network timeout to 1 second
bytes, err = wb:send_text("Hello world")
if not bytes then
ngx.log(ngx.ERR, "failed to send a text frame: ", err)
return ngx.exit(444)
end
bytes, err = wb:send_binary("blah blah blah...")
if not bytes then
ngx.log(ngx.ERR, "failed to send a binary frame: ", err)
return ngx.exit(444)
end
local bytes, err = wb:send_close(1000, "enough, enough!")
if not bytes then
ngx.log(ngx.ERR, "failed to send the close frame: ", err)
return
end
模块
resty.websocket.server
要加载此模块,只需执行以下操作
local server = require "resty.websocket.server"
方法
new
语法:wb, err = server:new()
语法:wb, err = server:new(opts)
在服务器端执行 websocket 握手过程,并返回一个 WebSocket 服务器对象。
如果发生错误,则返回 nil
和一个描述错误的字符串。
可以指定一个可选的选项表。以下选项如下所示
max_payload_len
指定发送和接收 WebSocket 帧时允许的最大有效载荷长度。
send_masked
指定是否发送屏蔽的 WebSocket 帧。当它为
true
时,始终发送屏蔽的帧。默认为false
。timeout
指定网络超时阈值,以毫秒为单位。您可以通过
set_timeout
方法调用更改此设置。请注意,此超时设置不会影响 websocket 握手的 HTTP 响应头发送过程;您需要同时配置 send_timeout 指令。
set_timeout
语法:wb:set_timeout(ms)
设置网络相关操作的超时延迟(以毫秒为单位)。
send_text
语法:bytes, err = wb:send_text(text)
将 text
参数发送出去,作为 text
类型的未分段数据帧。返回已在 TCP 级别实际发送的字节数。
如果发生错误,则返回 nil
和一个描述错误的字符串。
send_binary
语法:bytes, err = wb:send_binary(data)
将 data
参数发送出去,作为 binary
类型的未分段数据帧。返回已在 TCP 级别实际发送的字节数。
如果发生错误,则返回 nil
和一个描述错误的字符串。
send_ping
语法:bytes, err = wb:send_ping()
语法:bytes, err = wb:send_ping(msg)
发送一个 ping
帧,该帧带有 msg
参数指定的可选消息。返回已在 TCP 级别实际发送的字节数。
如果发生错误,则返回 nil
和一个描述错误的字符串。
请注意,此方法不会等待远程端的 pong 帧。
send_pong
语法:bytes, err = wb:send_pong()
语法:bytes, err = wb:send_pong(msg)
发送一个 pong
帧,该帧带有 msg
参数指定的可选消息。返回已在 TCP 级别实际发送的字节数。
如果发生错误,则返回 nil
和一个描述错误的字符串。
send_close
语法:bytes, err = wb:send_close()
语法:bytes, err = wb:send_close(code, msg)
发送一个带有可选状态码和消息的 close
帧。
如果发生错误,则返回 nil
和一个描述错误的字符串。
有关有效状态码的列表,请参阅以下文档
http://tools.ietf.org/html/rfc6455#section-7.4.1
请注意,此方法不会等待远程端的 close
帧。
send_frame
语法:bytes, err = wb:send_frame(fin, opcode, payload)
通过指定 fin
字段(布尔值)、操作码和有效载荷来发送一个原始 websocket 帧。
有关有效操作码的列表,请参阅
http://tools.ietf.org/html/rfc6455#section-5.2
如果发生错误,则返回 nil
和一个描述错误的字符串。
要控制允许的最大有效载荷长度,您可以将 max_payload_len
选项传递给 new
构造函数。
要控制是否发送屏蔽帧,您可以在 new
构造函数方法中将 true
传递给 send_masked
选项。默认情况下,会发送未屏蔽的帧。
recv_frame
语法:data, typ, err = wb:recv_frame()
从网络接收 WebSocket 帧。
如果发生错误,则返回两个 nil
值和一个描述错误的字符串。
第二个返回值始终是帧类型,它可以是 continuation
、text
、binary
、close
、ping
、pong
或 nil
之一(对于未知类型)。
对于 close
帧,返回 3 个值:额外的状态消息(可以是空字符串)、字符串“close”和状态码的 Lua 数字(如果有)。有关可能的关闭状态码,请参阅
http://tools.ietf.org/html/rfc6455#section-7.4.1
对于其他类型的帧,只需返回有效载荷和类型。
对于分段帧,err
返回值为 Lua 字符串“again”。
resty.websocket.client
要加载此模块,只需执行以下操作
local client = require "resty.websocket.client"
一个演示用法的简单示例
local client = require "resty.websocket.client"
local wb, err = client:new()
local uri = "ws://127.0.0.1:" .. ngx.var.server_port .. "/s"
local ok, err = wb:connect(uri)
if not ok then
ngx.say("failed to connect: " .. err)
return
end
local data, typ, err = wb:recv_frame()
if not data then
ngx.say("failed to receive the frame: ", err)
return
end
ngx.say("received: ", data, " (", typ, "): ", err)
local bytes, err = wb:send_text("copy: " .. data)
if not bytes then
ngx.say("failed to send frame: ", err)
return
end
local bytes, err = wb:send_close()
if not bytes then
ngx.say("failed to send frame: ", err)
return
end
方法
client:new
语法:wb, err = client:new()
语法:wb, err = client:new(opts)
实例化一个 WebSocket 客户端对象。
如果发生错误,则返回 nil
和一个描述错误的字符串。
可以指定一个可选的选项表。以下选项如下所示
max_payload_len
指定发送和接收 WebSocket 帧时允许的最大有效载荷长度。
send_unmasked
指定是否发送未屏蔽的 WebSocket 帧。当它为
true
时,始终发送未屏蔽的帧。默认为false
。但是,RFC 6455 要求客户端必须向服务器发送屏蔽帧,因此除非您知道自己在做什么,否则永远不要将此选项设置为true
。timeout
指定默认的网络超时阈值,以毫秒为单位。您可以通过
set_timeout
方法调用更改此设置。
client:connect
语法:ok, err = wb:connect("ws://<host>:<port>/<path>")
语法:ok, err = wb:connect("wss://<host>:<port>/<path>")
语法:ok, err = wb:connect("ws://<host>:<port>/<path>", options)
语法:ok, err = wb:connect("wss://<host>:<port>/<path>", options)
连接到远程 WebSocket 服务端口,并在客户端执行 websocket 握手过程。
在实际解析主机名并连接到远程后端之前,此方法将始终查找连接池以匹配先前此方法调用创建的空闲连接。
可以指定一个可选的 Lua 表作为此方法的最后一个参数,以指定各种连接选项
protocols
指定当前 WebSocket 会话使用的所有子协议。它可以是包含所有子协议名称的 Lua 表,也可以只是一个 Lua 字符串。
origin
指定
Origin
请求头的值。pool
指定正在使用的连接池的自定义名称。如果省略,则连接池名称将从字符串模板
<host>:<port>
生成。ssl_verify
指定如果使用
wss://
方案,是否在 SSL 握手期间执行 SSL 证书验证。
SSL 连接模式 (wss://
) 至少需要 ngx_lua
0.9.11 或 OpenResty 1.7.4.1。
client:close
语法:ok, err = wb:close()
关闭当前 WebSocket 连接。如果尚未发送 close
帧,则会自动发送 close
帧。
client:set_keepalive
语法:ok, err = wb:set_keepalive(max_idle_timeout, pool_size)
将当前 WebSocket 连接立即放入 ngx_lua
cosocket 连接池中。
您可以指定连接在池中的最大空闲超时时间(以毫秒为单位)以及每个 nginx 工作进程池的最大大小。
如果成功,则返回 1
。如果发生错误,则返回 nil
和一个描述错误的字符串。
仅在您原本会调用 close
方法的地方调用此方法。调用此方法将立即将当前 WebSocket 对象置于 closed
状态。除 connect()
之外的任何后续操作都将返回 closed
错误。
client:set_timeout
语法:wb:set_timeout(ms)
与 resty.websocket.server
对象的 set_timeout
方法相同。
client:send_text
语法:bytes, err = wb:send_text(text)
与 resty.websocket.server
对象的 send_text 方法相同。
client:send_binary
语法:bytes, err = wb:send_binary(data)
与 resty.websocket.server
对象的 send_binary 方法相同。
client:send_ping
语法:bytes, err = wb:send_ping()
语法:bytes, err = wb:send_ping(msg)
与 resty.websocket.server
对象的 send_ping 方法相同。
client:send_pong
语法:bytes, err = wb:send_pong()
语法:bytes, err = wb:send_pong(msg)
与 resty.websocket.server
对象的 send_pong 方法相同。
client:send_close
语法:bytes, err = wb:send_close()
语法:bytes, err = wb:send_close(code, msg)
与 resty.websocket.server
对象的 send_close 方法相同。
client:send_frame
语法:bytes, err = wb:send_frame(fin, opcode, payload)
与 resty.websocket.server
对象的 send_frame 方法相同。
要控制是否发送未屏蔽的帧,您可以在 new
构造函数方法中将 true
传递给 send_unmasked
选项。默认情况下,会发送屏蔽的帧。
client:recv_frame
语法:data, typ, err = wb:recv_frame()
与 resty.websocket.server
对象的 recv_frame 方法相同。
resty.websocket.protocol
要加载此模块,只需执行以下操作
local protocol = require "resty.websocket.protocol"
方法
recv_frame
语法:data, typ, err = protocol.recv_frame(socket, max_payload_len, force_masking)
从网络接收 WebSocket 帧。
build_frame
语法:frame = protocol.build_frame(fin, opcode, payload_len, payload, masking)
构建一个原始 WebSocket 帧。
send_frame
语法:bytes, err = protocol.send_frame(socket, fin, opcode, payload, max_payload_len, masking)
发送一个原始 WebSocket 帧。
自动错误记录
默认情况下,底层的 ngx_lua 模块会在发生套接字错误时记录错误。如果您已经在自己的 Lua 代码中进行了适当的错误处理,那么建议您通过关闭 ngx_lua 的 lua_socket_log_errors 指令来禁用此自动错误记录,即
lua_socket_log_errors off;
局限性
此库不能在 init_by_lua、set_by_lua、log_by_lua* 和 header_filter_by_lua* 等代码上下文中使用,因为这些上下文中 ngx_lua cosocket API 不可用。
resty.websocket
对象实例不能存储在 Lua 模块级 Lua 变量中,因为这样会导致同一个 nginx 工作进程处理的所有并发请求共享该实例(参见 http://wiki.nginx.org/HttpLuaModule#Data_Sharing_within_an_Nginx_Worker),并导致在并发请求尝试使用同一个resty.websocket
实例时出现严重竞争条件。您应该始终在函数局部变量或ngx.ctx
表中初始化resty.websocket
对象。这些位置都为每个请求保存了它们自己的数据副本。
安装
建议直接使用最新的 OpenResty bundle,其中包含此库并默认启用。至少需要 OpenResty 1.4.2.9。在构建 OpenResty bundle 时,您需要通过将 --with-luajit
选项传递给它的 ./configure
脚本来启用 LuaJIT。不需要额外的 Nginx 配置。
如果您想将此库与您自己的 Nginx 版本(使用 ngx_lua)一起使用,那么您需要确保您至少使用的是 ngx_lua 0.9.0(如果您没有使用 LuaJIT,还需要使用 lua-bitop 库)。此外,您需要配置 lua_package_path 指令,将 lua-resty-websocket 源代码树的路径添加到 ngx_lua 的 Lua 模块搜索路径中,例如
# nginx.conf
http {
lua_package_path "/path/to/lua-resty-websocket/lib/?.lua;;";
...
}
然后在 Lua 中加载库
local server = require "resty.websocket.server"
待办事项
社区
英文邮件列表
英文邮件列表 openresty-en 适用于英语使用者。
中文邮件列表
中文邮件列表 openresty 适用于中文使用者。
错误报告和补丁提交
请通过以下方式报告错误或提交补丁:
在 GitHub Issue Tracker 上创建工单,
或发布到 OpenResty 社区。
作者
章亦春 (agentzh) <[email protected]>,OpenResty Inc.
版权和许可
此模块采用 BSD 许可证。
版权所有 (C) 2013-2017,章亦春 (agentzh) <[email protected]>,OpenResty Inc.
保留所有权利。
只要满足以下条件,则允许以源代码和二进制形式重新分发和使用此软件,无论是否修改。
重新分发源代码必须保留上述版权声明、此条件列表以及以下免责声明。
以二进制形式重新分发必须在随发行版提供的文档和/或其他材料中复制上述版权声明、此条件列表以及以下免责声明。
此软件由版权所有者和贡献者“按原样”提供,并且任何明示或暗示的保证(包括但不限于适销性和特定用途适用性的暗示保证)均被免除。在任何情况下,版权所有者或贡献者均不对任何直接、间接、偶然、特殊、示例性或后果性损害(包括但不限于替代商品或服务的采购;使用、数据或利润损失;或业务中断)负责,无论这些损害是因本软件的使用引起,还是基于任何责任理论,无论是在合同、严格责任或侵权行为(包括疏忽或其他)中,即使已告知有发生此类损害的可能性。
另请参阅
博文 使用 OpenResty 的 WebSockets,作者:Aapo Talvensaari。
ngx_lua 模块:http://wiki.nginx.org/HttpLuaModule
WebSocket 协议:http://tools.ietf.org/html/rfc6455
作者
章亦春 (agentzh)
许可证
2bsd
版本
-
ngx_lua 模块的 Lua WebSocket 实现 2020-04-03 08:44:38
-
ngx_lua 模块的 Lua WebSocket 实现 2016-09-29 03:26:34