wiola
LUA WAMP 路由器
$ opm get KSDaemon/wiola
wiola
基于 Lua 的 WAMP (WebSocket 应用程序消息协议) 实现,利用了 Lua Nginx 模块、Lua WebSocket 扩展和 Redis 作为缓存存储的强大功能。
描述
Wiola 在 OpenResty web 服务器之上实现了 [WAMP 规范][] v2 路由器规范,OpenResty 实际上是 nginx 加上一堆第三方模块,比如 lua-nginx-module、lua-resty-websocket、lua-resty-redis 等等。
Wiola 支持以下 WAMP 角色和功能
代理:具有以下功能的增强配置文件
基于模式的订阅
发布者排除
发布者识别
发布信任级别
会话元数据 API
订阅者黑白名单
订阅元数据 API(部分)
交易员:具有以下功能的增强配置文件
取消调用
调用超时
调用者识别
调用信任级别
基于模式的注册
渐进式调用结果
注册元数据 API(部分)
会话元数据 API
挑战响应认证 ("WAMP-CRA")
Cookie 认证
原始套接字传输
会话元数据 API
Wiola 支持 JSON 和 msgpack 序列化器。
从 v0.3.1 开始,Wiola 还支持轻量级 POST 事件发布。有关详细信息,请参见 processPostData 方法和 post-handler.lua 文件。
使用示例
有关示例用法,请参阅 ws-handler.lua 文件。
安装
要使用 wiola,您需要
Nginx 或 OpenResty
[luajit][]
[lua-nginx-module][]
[lua-resty-websocket][]
[lua-resty-redis][]
[Redis 服务器][]
[lua-rapidjson][]
[lua-resty-hmac][](可选,WAMP-CRA 所需)
[lua-MessagePack][](可选)
[redis-lua][](可选)
[stream-lua-nginx-module][](可选)
您可以直接使用 [OpenResty][] 服务器,而不是将 lua-* 模块编译到 nginx 中。
无论哪种情况,为了方便起见,您可以通过 luarocks 使用 luarocks install wiola
或通过 [OpenResty 包管理器] 使用 opm install KSDaemon/wiola
安装 Wiola。不幸的是,并非所有依赖项都可以在 opm 中找到,因此您需要手动安装缺失的依赖项。
接下来是配置 nginx 主机。请参阅以下示例。
http {
# set search paths for pure Lua external libraries (';;' is the default path):
# add paths for wiola and msgpack libs
lua_package_path '/usr/local/lualib/wiola/?.lua;/usr/local/lualib/lua-MessagePack/?.lua;;';
init_worker_by_lua_block {
-- Initializing math.randomseed for every worker/luaVM
local f = io.open('/dev/random', 'rb')
local seed
if f then
local b1, b2, b3, b4 = string.byte(f:read(4), 1, 4)
seed = b1 * 0x1000000 + b2 * 0x10000 + b3 * 0x100 + b4
f:close()
else
seed = ngx.time() + ngx.worker.pid()
end
math.randomseed(seed)
math.randomseed = function()end
}
init_by_lua_block {
-- Wiola configuration. You can read more in description of .configure() method below.
local cfg = require "wiola.config"
cfg.config({
socketTimeout = 1000, -- one second
maxPayloadLen = 65536,
pingInterval = 1000, -- interval in ms for sending ping frames. set to 0 for disabling
realms = { "app", "admin" },
store = "redis",
storeConfig = {
host = "unix:///tmp/redis.sock", -- Optional parameter. Can be hostname/ip or socket path
--port = 6379 -- Optional parameter. Should be set when using hostname/ip
-- Omit for socket connection
--db = 5 -- Optional parameter. Redis db to use
},
callerIdentification = "auto", -- Optional parameter. auto | never | always
cookieAuth = { -- Optional parameter.
authType = "none", -- none | static | dynamic
cookieName = "wampauth",
staticCredentials = nil, --{
-- "user1", "user2:password2", "secretkey3"
--},
authCallback = nil
},
wampCRA = { -- Optional parameter.
authType = "none", -- none | static | dynamic
staticCredentials = nil, --{
-- user1 = { authrole = "userRole1", secret="secret1" },
-- user2 = { authrole = "userRole2", secret="secret2" }
--},
challengeCallback = nil,
authCallback = nil
},
trustLevels = { -- Optional parameter.
authType = "none", -- none | static | dynamic
defaultTrustLevel = nil,
staticCredentials = {
byAuthid = {
--{ authid = "user1", trustlevel = 1 },
--{ authid = "admin1", trustlevel = 5 }
},
byAuthRole = {
--{ authrole = "user-role", trustlevel = 2 },
--{ authrole = "admin-role", trustlevel = 4 }
},
byClientIp = {
--{ clientip = "127.0.0.1", trustlevel = 10 }
}
},
authCallback = nil -- function that accepts (client ip address, realm,
-- authid, authrole) and returns trust level
},
metaAPI = { -- Expose META API ? Optional parameter.
session = true,
subscription = true,
registration = true
}
})
-- If you want automatically clean up redis db during nginx restart uncomment next two lines
-- for this to work, you need redis-lua library
-- Use it only with lua_code_cache on; !!!
--local wflush = require "wiola.flushdb"
--wflush.flushAll()
}
# Configure a vhost
server {
# example location for websocket WAMP connection
location /ws/ {
set $wiola_max_payload_len 65535; # Optional parameter. Set the value to suit your needs
lua_socket_log_errors off;
lua_check_client_abort on;
# This is needed to set additional websocket protocol headers
header_filter_by_lua_file $document_root/lua/wiola/headers.lua;
# Set a handler for connection
content_by_lua_file $document_root/lua/wiola/ws-handler.lua;
}
# example location for a lightweight POST event publishing
location /wslight/ {
lua_socket_log_errors off;
lua_check_client_abort on;
content_by_lua_file $document_root/lua/wiola/post-handler.lua;
}
}
}
如果您想使用原始套接字传输而不是(或除)websocket,您还需要配置 nginx 流
stream {
# set search paths for pure Lua external libraries (';;' is the default path):
# add paths for wiola and msgpack libs
lua_package_path '/usr/local/lualib/wiola/?.lua;/usr/local/lualib/lua-MessagePack/?.lua;;';
init_worker_by_lua_block {
# Actually same one as in http example above...
}
init_by_lua_block {
# Actually same one as in http example above...
}
server {
listen 1234;
lua_check_client_abort on;
content_by_lua_file $document_root/lua/wiola/raw-handler.lua;
}
}
此外,从 v0.12.0 开始,Wiola 使用 Redis 发布/订阅系统,而不是轮询来检索客户端数据。因此,您需要配置 Redis 服务器并启用 keyspace-events。顺便说一下,您不需要启用所有事件。Wiola 只需要列表的 keyspace 事件。
编辑 redis.conf 并设置 notify-keyspace-events 选项。
notify-keyspace-events "Kl"
实际上,您不需要做其他任何事情。只需使用任何 WAMP 客户端并建立连接即可。
认证
从 v0.6.0 开始,Wiola 支持多种类型的认证
Cookie 认证
静态配置
动态回调
挑战响应认证
静态配置
动态回调
也可以同时使用两种类型的认证 :) 要设置认证,您需要在 nginx/openresty 中对 Wiola 进行 配置,以便在请求处理之前进行。在简单的情况下,您只需在 nginx http 配置部分进行配置即可。
local cfg = require "wiola.config"
cfg.config({
cookieAuth = {
authType = "dynamic", -- none | static | dynamic
cookieName = "wampauth",
staticCredentials = { "user1:pass1", "user2:pass2"},
authCallback = function (creds)
-- Validate credentials somehow
-- return true, if valid
if isValid(creds) then
return true
end
return false
end
},
wampCRA = {
authType = "dynamic", -- none | static | dynamic
staticCredentials = {
user1 = { authrole = "userRole1", secret="secret1" },
user2 = { authrole = "userRole2", secret="secret2" }
},
challengeCallback = function (sessionid, authid)
-- Generate a challenge string somehow and return it
-- Do not forget to save it somewhere for response validation!
return "{ \"nonce\": \"LHRTC9zeOIrt_9U3\"," ..
"\"authprovider\": \"usersProvider\", \"authid\": \"" .. authid .. "\"," ..
"\"timestamp\": \"" .. os.date("!%FT%TZ") .. "\"," ..
"\"authrole\": \"userRole1\", \"authmethod\": \"wampcra\"," ..
"\"session\": " .. sessionid .. "}"
end,
authCallback = function (sessionid, signature)
-- Validate responsed signature against challenge
-- return auth info object (like bellow) or nil if failed
return { authid="user1", authrole="userRole1", authmethod="wampcra", authprovider="usersProvider" }
end
}
})
调用和发布信任级别
从 v0.9.0 开始,Wiola 支持调用和发布信任级别标记。要设置信任级别,您需要在 nginx/openresty 中对 Wiola 进行 配置,以便在请求处理之前进行。在简单的情况下,您只需在 nginx http 配置部分进行配置即可。对于静态配置,authid 选项优先于 authrole,authrole 优先于客户端 IP。例如,如果客户端匹配所有三个选项(authid、authrole、客户端 IP),那么将设置 authid 的信任级别。
local cfg = require "wiola.config"
-- Static trustlevel configuration
cfg.config({
trustLevels = {
authType = "static",
defaultTrustLevel = 5,
staticCredentials = {
byAuthid = {
{ authid = "user1", trustlevel = 1 },
{ authid = "admin1", trustlevel = 5 }
},
byAuthRole = {
{ authrole = "user-role", trustlevel = 2 },
{ authrole = "admin-role", trustlevel = 4 }
},
byClientIp = {
{ clientip = "127.0.0.1", trustlevel = 10 }
}
}
}
})
-- Dynamic trustlevel configuration
cfg.config({
trustLevels = {
authType = "dynamic",
authCallback = function (clientIp, realm, authid, authrole)
-- write your own logic for setting trust level
-- just a simple example
if clientIp == "127.0.0.1" then
return 15
end
if realm == "test" then
return nil
end
return 5
end
}
})
方法
config(config)
配置 Wiola 实例或检索当前配置。所有选项都是可选的。某些选项具有默认值,或者如果没有指定,则为 nil。
参数
config - 包含可能选项的配置表
socketTimeout - 底层套接字连接操作的超时时间。默认值:100 毫秒
maxPayloadLen - 使用底层套接字发送和接收时允许的最大有效负载长度。默认值:65536 字节 (2^16)。对于原始套接字传输,请使用 9 到 24 之间 2 的幂次方对齐的值。2^9, 2^10 .. 2^24。
pingInterval - 发送 ping 帧的间隔时间(以毫秒为单位)。设置为 0 可禁用服务器发起的 ping。默认值:1000 毫秒
realms - 允许的 WAMP 域数组。默认值:{} - 因此没有客户端会连接到路由器。还可以设置特殊的域 { "*" } - 允许在客户端请求时创建任何域(如果不存在),非常适合开发使用。
redis - Redis 连接配置表
host - Redis 服务器主机或 Redis unix 套接字路径。默认值:"unix:/tmp/redis.sock"
port - Redis 服务器端口(如果使用网络连接)。对于套接字连接,请省略
db - 要选择的 Redis 数据库索引
callerIdentification - 公开调用者识别信息?可能的值:auto | never | always。默认值:"auto"
cookieAuth - 基于 Cookie 的认证配置表
authType - 认证类型。可能的值:none | static | dynamic。默认值:"none",表示不使用
cookieName - 包含认证信息的 Cookie 的名称。默认值:"wampauth"
staticCredentials - 包含字符串项目的类似数组的表,允许连接。用于 authType="static"
authCallback - 认证回调函数。用于 authType="dynamic"。cookieName 的值作为第一个参数传递。应返回一个布尔标志,true - 允许连接,false - 阻止连接
wampCRA - WAMP 挑战响应 ("WAMP-CRA") 认证配置表
authType - 认证类型。可能的值:none | static | dynamic。默认值:"none",表示不使用
staticCredentials - 包含键的表,键的名称为 authid,值为 { authrole = "userRole1", secret="secret1" },允许连接。用于 authType="static"
challengeCallback - 用于生成挑战信息的回调函数。用于 authType="dynamic"。在收到 HELLO 消息时调用,将会话 ID 作为第一个参数传递,将 authid 作为第二个参数传递。应返回客户端需要用来创建签名的挑战字符串。有关更多信息,请参阅 [WAMP 规范中的挑战响应认证部分][]。
authCallback - 用于检查认证签名的回调函数。用于 authType="dynamic"。在收到 AUTHENTICATE 消息时调用,将会话 ID 作为第一个参数传递,将签名作为第二个参数传递。应返回认证信息对象 { authid="user1", authrole="userRole", authmethod="wampcra", authprovider="usersProvider" } 或 nil | false(如果失败)。
trustLevels - 信任级别配置表
authType - 认证类型。可能的值:none | static | dynamic。默认值:"none",表示不使用
defaultTrustLevel - 对于不匹配任何静态凭据的客户端的默认信任级别。应为任何正整数或 nil(表示省略)
staticCredentials - 用于 authType="static"。包含 3 个子表
byAuthid。这个类似数组的表包含类似
{ authid = "user1", trustlevel = 1 }
的项目byAuthRole。这个类似数组的表包含类似
{ authrole = "user-role", trustlevel = 2 }
的项目byClientIp。这个类似数组的表包含类似
{ clientip = "127.0.0.1", trustlevel = 10 }
的项目
authCallback - 用于获取客户端信任级别的回调函数。它接受 (客户端 IP 地址、域、authid、authrole) 并返回信任级别(正整数或 nil)
metaAPI - 元数据 API 配置表
session - 公开会话元数据 API?可能的值:true | false。默认值:false。
subscription - 公开订阅元数据 API?可能的值:true | false。默认值:false。
registration - 公开注册元数据 API?可能的值:true | false。默认值:false。
在不带参数的情况下调用时,返回当前配置。在设置配置时,不返回任何内容。
配置示例(多个选项,仅供展示)
init_by_lua_block {
local cfg = require "wiola.config"
cfg.config({
socketTimeout = 1000, -- one second
maxPayloadLen = 65536,
realms = { "test", "app" },
callerIdentification = "always",
redis = {
host = "unix:/tmp/redis.sock" -- Optional parameter. Can be hostname/ip or socket path
--port = 6379 -- Optional parameter. Should be set when using hostname/ip
-- Omit for socket connection
--db = 5 -- Optional parameter. Redis db to use
},
cookieAuth = {
authType = "none", -- none | static | dynamic
cookieName = "wampauth",
staticCredentials = { "user1:pass1", "user2:pass2"},
authCallback = function (creds)
if creds ~= "" then
return true
end
return false
end
},
wampCRA = {
authType = "dynamic", -- none | static | dynamic
staticCredentials = {
user1 = { authrole = "userRole1", secret="secret1" },
user2 = { authrole = "userRole2", secret="secret2" }
},
challengeCallback = function (sessionid, authid)
return "{ \"nonce\": \"LHRTC9zeOIrt_9U3\"," ..
"\"authprovider\": \"usersProvider\", \"authid\": \"" .. authid .. "\"," ..
"\"timestamp\": \"" .. os.date("!%FT%TZ") .. "\"," ..
"\"authrole\": \"userRole1\", \"authmethod\": \"wampcra\"," ..
"\"session\": " .. sessionid .. "}"
end,
authCallback = function (sessionid, signature)
return { authid="user1", authrole="userRole1", authmethod="wampcra", authprovider="usersProvider" }
end
},
metaAPI = {
session = true,
subscription = false,
registration = false
}
})
}
addConnection(sid, wampProto)
将新的连接实例添加到 wiola 控制。
参数
sid - nginx 会话 ID
wampProto - 选定的 WAMP 子协议。它在标头过滤器中设置。因此,只需在此处传递 ngx.header["Sec-WebSocket-Protocol"] 即可。这样做是为了避免使用共享变量。
返回值
WAMP 会话 ID(整数)
连接数据类型(字符串:“text” 或 “binary”)
receiveData(regId, data)
当从 websocket 接收新数据时,应调用此方法。此方法分析所有传入的消息、设置状态并为客户端准备响应数据。
参数
regId - WAMP 会话 ID
data - 接收到的数据
返回值:无
getPendingData(regId)
检查存储以获取客户端的新数据。
参数
regId - WAMP 会话 ID
返回值
客户端数据(类型取决于会话数据类型)或 null
错误描述(如果发生错误)
此方法实际上是 redis:lpop() 方法的代理。
processPostData(sid, realm, data)
处理来自客户端的轻量级 POST 数据,其中包含发布消息。此方法旨在快速发布事件,例如,当 WAMP 客户端是浏览器应用程序时,它对后端服务器进行了一些更改,因此后端是通知其他 WAMP 订阅者的正确位置,但建立完整的 WAMP 连接并不理想。
参数
sid - nginx 会话连接 ID
realm - 要在其中操作的 WAMP 域
data - 通过 POST 接收到的数据(JSON 编码的 WAMP 发布事件)
返回值
响应数据(如果发生错误,则为 JSON 编码的 WAMP 响应消息,否则为 { result = true })
httpCode HTTP 状态代码(如果成功,则为 HTTP_OK/200,如果发生错误,则为 HTTP_FORBIDDEN/403)
版权和许可
Wiola 库是在 BSD 2-Clause 许可证下发布的。
版权所有 (c) 2014-2017,Konstantin Burkalev 保留所有权利。
在满足以下条件的情况下,允许以源代码和二进制形式重新分发和使用本软件,无论是否修改:
源代码的重新分发必须保留上述版权声明、此条件列表和以下免责声明。
二进制形式的重新分发必须在随分发提供的文档和/或其他材料中复制上述版权声明、此条件列表和以下免责声明。
本软件由版权持有人和贡献者“按原样”提供,不提供任何形式的明示或暗示的担保,包括但不限于适销性和特定用途适用性的暗示担保。在任何情况下,版权持有人或贡献者均不对因使用本软件而造成的任何直接、间接、偶然、特殊、示例性或后果性损害(包括但不限于替代商品或服务的采购;使用、数据或利润的损失;或业务中断)负责,无论损害原因是基于合同、严格责任或侵权(包括疏忽或其他原因),即使已告知可能发生此类损害。
另请参阅
[WAMP 规范][]
[WAMP 规范中的挑战响应认证部分][]
[Wampy.js][]. WAMP Javascript 客户端实现
[OpenResty][]
[lua-nginx-module][]
[lua-resty-websocket][]
[lua-rapidjson][]
[lua-resty-redis][]
[Redis 服务器][]
[lua-MessagePack][]
感谢 JetBrains 提供最佳的 IDE 和对开源的支持!
[![jetbrains logo]][jetbrains url]
[WAMP 规范]: http://wamp-proto.org/ [WAMP 规范中的挑战响应认证部分]: https://tools.ietf.org/html/draft-oberstet-hybi-tavendo-wamp-02#section-13.7.2.3 [Wampy.js]: https://github.com/KSDaemon/wampy.js [OpenResty]: https://openresty.org.cn [OpenResty 包管理器]: https://opm.openresty.org.cn/ [luajit]: http://luajit.org/ [lua-nginx-module]: https://github.com/chaoslawful/lua-nginx-module [lua-resty-websocket]: https://github.com/agentzh/lua-resty-websocket [lua-rapidjson]: https://github.com/xpol/lua-rapidjson [lua-resty-redis]: https://github.com/agentzh/lua-resty-redis [Redis 服务器]: https://redis.ac.cn [lua-MessagePack]: http://fperrad.github.io/lua-MessagePack/ [lua-resty-hmac]: https://github.com/jamesmarlowe/lua-resty-hmac [redis-lua]: https://github.com/nrk/redis-lua [stream-lua-nginx-module]: https://github.com/openresty/stream-lua-nginx-module
[jetbrains logo]: jetbrains.svg [jetbrains url]: (https://www.jetbrains.com)
作者
Konstantin Burkalev <KSDaemon@ya.ru>
许可证
2bsd
依赖项
版本
-
KSDaemon/wiola 0.13.3LUA WAMP 路由器 2021-09-08 19:44:19
-
KSDaemon/wiola 0.13.2LUA WAMP 路由器 2021-08-19 22:19:29
-
KSDaemon/wiola 0.13.1LUA WAMP 路由器 2021-08-19 21:45:38
-
KSDaemon/wiola 0.13.0LUA WAMP 路由器 2021-08-17 21:17:43
-
KSDaemon/wiola 0.12.1LUA WAMP 路由器 2019-10-22 14:44:42
-
KSDaemon/wiola 0.12.0LUA WAMP 路由器 2019-10-22 13:40:43
-
KSDaemon/wiola 0.6.1LUA WAMP 路由器 2017-04-07 12:57:17
-
KSDaemon/wiola 0.6.0LUA WAMP 路由器 2017-04-03 09:58:53