- config: 反向代理、缓存、负载均衡、安全、SSL 等配置模板 - lua: API 网关、认证、动态路由、限流、WebSocket 等脚本示例 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
174 lines
4.8 KiB
Lua
174 lines
4.8 KiB
Lua
--- router.lua - 动态路由模块
|
|
--
|
|
-- 基于路径和请求头的动态路由分发
|
|
--
|
|
-- 路由优先级:
|
|
-- 1. X-Service 请求头 -> 直接指定 upstream
|
|
-- 2. X-Canary 请求头 -> 灰度 upstream
|
|
-- 3. X-Version 请求头 -> 版本化 upstream
|
|
-- 4. 路径前缀匹配 -> 规则匹配 upstream
|
|
-- 5. 默认 upstream -> 兜底
|
|
|
|
local cjson = require "cjson.safe"
|
|
|
|
local _M = {}
|
|
|
|
--- 获取路由规则
|
|
-- @return table 路由规则
|
|
function _M.get_rules()
|
|
local router_config = ngx.shared.router_config
|
|
if not router_config then
|
|
return { error = "router_config shared dict not found" }
|
|
end
|
|
|
|
local raw = router_config:get("rules")
|
|
return cjson.decode(raw) or {}
|
|
end
|
|
|
|
--- 更新路由规则
|
|
-- @param body string JSON 格式的规则数据
|
|
-- @return boolean, string|nil 成功标志, 错误信息
|
|
function _M.update_rules(body)
|
|
local router_config = ngx.shared.router_config
|
|
if not router_config then
|
|
return false, "router_config shared dict not found"
|
|
end
|
|
|
|
local data, err = cjson.decode(body)
|
|
if not data then
|
|
return false, "invalid JSON: " .. (err or "unknown error")
|
|
end
|
|
|
|
-- 验证规则格式
|
|
if not data.path or type(data.path) ~= "table" then
|
|
return false, "missing or invalid 'path' field"
|
|
end
|
|
|
|
if not data.default or type(data.default) ~= "string" then
|
|
return false, "missing or invalid 'default' field"
|
|
end
|
|
|
|
local ok = router_config:set("rules", cjson.encode(data))
|
|
if not ok then
|
|
return false, "failed to store rules"
|
|
end
|
|
|
|
ngx.log(ngx.INFO, "Routing rules updated")
|
|
return true
|
|
end
|
|
|
|
--- 根据 X-Service 头选择 upstream
|
|
-- @param ngx nginx 对象
|
|
-- @return string|nil upstream 名称
|
|
local function select_by_service_header(ngx)
|
|
local service = ngx.req.get_headers()["X-Service"]
|
|
if service and service ~= "" then
|
|
ngx.log(ngx.DEBUG, "Route by X-Service header: ", service)
|
|
return service
|
|
end
|
|
return nil
|
|
end
|
|
|
|
--- 根据灰度头选择 upstream
|
|
-- @param ngx nginx 对象
|
|
-- @param base_name string 基础 upstream 名称
|
|
-- @return string|nil 灰度 upstream 名称
|
|
local function select_canary(ngx, base_name)
|
|
local canary = ngx.req.get_headers()["X-Canary"]
|
|
if canary and (canary == "true" or canary == "1") then
|
|
local canary_name = base_name .. "_canary"
|
|
ngx.log(ngx.DEBUG, "Route to canary: ", canary_name)
|
|
return canary_name
|
|
end
|
|
return nil
|
|
end
|
|
|
|
--- 根据版本号选择 upstream
|
|
-- @param ngx nginx 对象
|
|
-- @param base_name string 基础 upstream 名称
|
|
-- @return string|nil 版本化 upstream 名称
|
|
local function select_by_version(ngx, base_name)
|
|
local version = ngx.req.get_headers()["X-Version"]
|
|
if version and version ~= "" then
|
|
local ver_name = base_name .. "_" .. version
|
|
ngx.log(ngx.DEBUG, "Route by X-Version header: ", ver_name)
|
|
return ver_name
|
|
end
|
|
return nil
|
|
end
|
|
|
|
--- 根据路径前缀匹配 upstream
|
|
-- @param uri string 请求 URI
|
|
-- @param rules table 路由规则
|
|
-- @return string|nil 匹配的 upstream 名称
|
|
local function match_path(uri, rules)
|
|
if not rules or not rules.path then
|
|
return nil
|
|
end
|
|
|
|
for _, rule in ipairs(rules.path) do
|
|
if rule.prefix and string.sub(uri, 1, #rule.prefix) == rule.prefix then
|
|
ngx.log(ngx.DEBUG, "Route by path prefix '", rule.prefix, "': ", rule.upstream)
|
|
return rule.upstream
|
|
end
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
--- 选择目标 upstream
|
|
--
|
|
-- 按优先级依次尝试:
|
|
-- 1. X-Service 头
|
|
-- 2. 路径匹配 + X-Canary 灰度
|
|
-- 3. 路径匹配 + X-Version 版本化
|
|
-- 4. 路径匹配
|
|
-- 5. 默认 upstream
|
|
--
|
|
-- @param ngx nginx 对象
|
|
-- @return string|nil 选中的 upstream 名称
|
|
function _M.select(ngx)
|
|
local rules = _M.get_rules()
|
|
if rules.error then
|
|
ngx.log(ngx.ERR, "Failed to load routing rules: ", rules.error)
|
|
return nil
|
|
end
|
|
|
|
local uri = ngx.var.uri
|
|
|
|
-- 优先级 1: X-Service 头直接指定
|
|
local service = select_by_service_header(ngx)
|
|
if service then
|
|
return select_canary(ngx, service) or service
|
|
end
|
|
|
|
-- 优先级 2: 路径匹配
|
|
local base_upstream = match_path(uri, rules)
|
|
if base_upstream then
|
|
-- 检查灰度
|
|
local canary = select_canary(ngx, base_upstream)
|
|
if canary then
|
|
return canary
|
|
end
|
|
|
|
-- 检查版本号
|
|
local versioned = select_by_version(ngx, base_upstream)
|
|
if versioned then
|
|
return versioned
|
|
end
|
|
|
|
return base_upstream
|
|
end
|
|
|
|
-- 优先级 3: 默认回退
|
|
if rules.default then
|
|
ngx.log(ngx.DEBUG, "Route to default upstream: ", rules.default)
|
|
return rules.default
|
|
end
|
|
|
|
ngx.log(ngx.WARN, "No upstream matched for URI: ", uri)
|
|
return nil
|
|
end
|
|
|
|
return _M
|