如何将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逻辑里完成路由决策:
- 编写你的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
- 配置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
相关产品推荐
相关产品推荐

