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

如何将Lua变量同步至Nginx变量并实现协程套接字请求路由

解决Nginx中access_by_lua阶段变量无法用于路由的问题

这个问题的核心在于Nginx配置指令的执行顺序和阶段特性,我来帮你拆解清楚并给出可行的解决方案:

为什么你的原有方案不生效?

你遇到的问题本质是:

  • access_by_lua*是请求运行时阶段,设置的$blocked变量是动态生成的,只在当前请求的生命周期内有效。
  • 而Nginx的if指令属于配置解析阶段的静态指令,它的判断逻辑在Nginx启动加载配置时就已经确定了,根本不会感知到运行时才设置的变量值。所以哪怕你在access_by_lua里把$blocked设为1,if ( $blocked )的判断也不会触发。
  • 至于set_by_lua*,它确实能同步设置Nginx变量,但这个阶段被设计为同步阻塞且不支持异步操作,co-sockets这类需要协程异步的API自然无法使用。

可行的解决方案

方案1:在access_by_lua阶段直接做内部重定向

这是最简洁高效的方式,跳过Nginx变量传递,直接在Lua逻辑里完成路由决策:

  1. 编写你的Lua脚本(用resty.http封装co-sockets,更易用):
local http = require "resty.http"
local httpc = http.new()

-- 调用目标服务获取路由决策结果
local res, err = httpc:request_uri("http://your-decision-service", {
    method = "GET",
    headers = {
        ["Host"] = "your-decision-service.com"
    }
})

if not res then
    ngx.log(ngx.ERR, "调用决策服务失败: ", err)
    -- 错误场景下的默认处理,比如返回500或走正常路由
    return ngx.exit(500)
end

-- 根据服务返回结果判断路由
if res.body == "blocked" then -- 假设返回内容标识需要拦截路由
    -- 内部重定向到预定义的blocked后端
    return ngx.exec("@blocked_backend")
else
    -- 继续执行默认请求流程
    return
end
  1. 配置Nginx:
http {
    # 定义被拦截后的后端
    upstream haproxy_blocked {
        server haproxy-9001;
    }

    # 正常业务后端
    upstream normal_backend {
        server your-normal-server;
    }

    server {
        listen 80;

        location / {
            access_by_lua_file /path/to/your/route-script.lua;
            # 默认路由逻辑,只有当Lua脚本未触发重定向时才会执行
            proxy_pass http://normal_backend;
        }

        # 内部重定向专用location,禁止外部直接访问
        location @blocked_backend {
            internal;
            proxy_pass http://haproxy_blocked;
        }
    }
}

方案2:在content_by_lua阶段基于变量处理路由

如果你坚持要通过变量传递决策结果,可以在access_by_lua设置变量后,在content_by_lua阶段(运行时阶段)处理路由:

location / {
    access_by_lua_block {
        local http = require "resty.http"
        local httpc = http.new()
        local res, err = httpc:request_uri("http://your-decision-service", {method = "GET"})
        
        if res and res.body == "blocked" then
            ngx.var.blocked = "1"
        else
            ngx.var.blocked = "0"
        end
    }

    content_by_lua_block {
        if ngx.var.blocked == "1" then
            -- 用子请求调用blocked后端
            local blocked_res = ngx.location.capture("@blocked_backend")
            ngx.status = blocked_res.status
            ngx.print(blocked_res.body)
        else
            -- 用子请求调用正常后端
            local normal_res = ngx.location.capture("@normal_backend")
            ngx.status = normal_res.status
            ngx.print(normal_res.body)
        end
    }
}

location @blocked_backend {
    internal;
    proxy_pass http://haproxy-9001;
}

location @normal_backend {
    internal;
    proxy_pass http://normal_backend;
}

关键注意点

  • 永远不要用Nginx的if指令处理运行时动态变量,它的设计初衷不是用来做动态路由判断的,很容易引发意外行为。
  • resty.http是openresty官方推荐的co-sockets封装库,比直接用底层co-sockets API更简洁可靠。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 04:30:25