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

依赖项

版本