You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Nginx Ingress Controller中通过Lua读取SignalR消息

可行性说明

WSS请求完全可以参照HTTP payload的读取逻辑实现SignalR双向消息捕获。Nginx Ingress Controller默认内置lua-nginx-module与lua-resty-websocket模块,无需额外编译安装组件,即可在不改动后端.NET SignalR服务的前提下,透明拦截WebSocket双向帧,和现有HTTP请求记录逻辑复用同一套存储/分析链路。

SignalR基于WebSocket传输的消息分为两类:文本帧承载JSON格式协议消息(含普通调用、心跳、协商消息),二进制帧承载MessagePack编码的协议消息,两类帧均可在Lua层读取原始内容,按需解码或直接记录。

前置配置

首先需要给对应Ingress开启WebSocket支持,适配SignalR长连接特性,在Ingress资源中添加以下注解:

annotations:
  nginx.ingress.kubernetes.io/websocket-services: "你的signalr后端服务名"
  nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
  nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
  nginx.ingress.kubernetes.io/proxy-http-version: "1.1"
核心Lua实现代码

通过Nginx Ingress的配置片段注解注入Lua逻辑,实现透明代理+双向帧捕获,逻辑不会中断原有WebSocket连接,仅复制帧内容做记录:

# 以下内容通过 nginx.ingress.kubernetes.io/location-snippet 注解注入到对应SignalR路由的location块中
location = /chatHub { # 替换为实际的SignalR Hub路由路径
    # WebSocket代理基础配置
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_pass http://$proxy_upstream_name;

    # WebSocket帧拦截Lua逻辑
    content_by_lua_block {
        local server = require "resty.websocket.server"
        local client = require "resty.websocket.client"
        local cjson = require "cjson.safe"

        -- 建立和前端客户端的WebSocket连接
        local wb_server, err = server:new{
            timeout = 60000, -- 帧读取超时,适配SignalR默认心跳间隔
            max_payload_len = 1024 * 1024 -- 单帧最大体积,根据业务消息大小调整
        }
        if not wb_server then
            ngx.log(ngx.ERR, "WebSocket握手失败: ", err)
            return ngx.exit(444)
        end

        -- 建立和后端.NET SignalR服务的WebSocket连接
        local wb_client, err = client:new()
        local backend_uri = "ws://" .. ngx.var.upstream_addr .. ngx.var.request_uri
        local ok, err = wb_client:connect(backend_uri, {
            timeout = 60000,
            max_payload_len = 1024 * 1024
        })
        if not ok then
            ngx.log(ngx.ERR, "连接后端SignalR服务失败: ", err)
            wb_server:send_close(1011, "后端连接失败")
            return
        end

        -- 消息记录公共方法,可直接对接原有HTTP payload的记录逻辑
        local function log_msg(direction, data, data_type)
            local log_entry = {
                trace_id = ngx.var.request_id,
                direction = direction, -- client_to_server / server_to_client
                data_type = data_type, -- text / binary / ping / pong / close
                payload = data_type == "binary" and ngx.encode_base64(data) or data,
                client_ip = ngx.var.remote_addr,
                timestamp = ngx.time()
            }
            -- 异步写日志,禁止阻塞转发流程
            ngx.log(ngx.INFO, "SignalR消息: ", cjson.encode(log_entry))
        end

        -- 协程1:读取客户端发往服务端的消息,记录后转发给后端
        local function client_to_server()
            while true do
                local data, typ, err = wb_server:recv_frame()
                if not data then
                    wb_client:send_close(1000, "客户端断开")
                    return
                end

                if typ == "close" then
                    log_msg("client_to_server", data, typ)
                    wb_client:send_close(1000, data)
                    return
                end

                if typ == "ping" then
                    wb_client:send_ping(data)
                    log_msg("client_to_server", data, typ)
                elseif typ == "pong" then
                    wb_client:send_pong(data)
                    log_msg("client_to_server", data, typ)
                elseif typ == "text" then
                    wb_client:send_text(data)
                    log_msg("client_to_server", data, typ)
                elseif typ == "binary" then
                    wb_client:send_binary(data)
                    log_msg("client_to_server", data, typ)
                end
            end
        end

        -- 协程2:读取服务端返回给客户端的消息,记录后转发给前端
        local function server_to_client()
            while true do
                local data, typ, err = wb_client:recv_frame()
                if not data then
                    wb_server:send_close(1011, "后端断开")
                    return
                end

                if typ == "close" then
                    log_msg("server_to_client", data, typ)
                    wb_server:send_close(1000, data)
                    return
                end

                if typ == "ping" then
                    wb_server:send_ping(data)
                    log_msg("server_to_client", data, typ)
                elseif typ == "pong" then
                    wb_server:send_pong(data)
                    log_msg("server_to_client", data, typ)
                elseif typ == "text" then
                    wb_server:send_text(data)
                    log_msg("server_to_client", data, typ)
                elseif typ == "binary" then
                    wb_server:send_binary(data)
                    log_msg("server_to_client", data, typ)
                end
            end
        end

        -- 启动双向转发协程
        local co1 = ngx.thread.spawn(client_to_server)
        local co2 = ngx.thread.spawn(server_to_client)

        -- 任意一端连接断开即清理资源
        ngx.thread.wait(co1, co2)
        wb_client:close()
        wb_server:close()
    }
}
注意事项
  • 上述逻辑为透明代理,不会修改原始WebSocket帧内容,不影响SignalR的正常协商、心跳、消息收发流程
  • 若使用MessagePack二进制协议,示例中默认将二进制内容转Base64记录,如需可读格式可引入Lua端MessagePack解码库解析后再记录
  • 消息记录逻辑建议使用异步写入方式,不要直接加磁盘IO、外部接口同步调用逻辑,避免阻塞WebSocket长连接
  • 单帧最大长度配置需要匹配业务实际的SignalR消息大小,避免大消息被截断

内容的提问来源于stack exchange,提问作者Manish Ballav

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 15:21:22