lolly/internal/lua/api_balancer.go
xfy ad177e9640 docs(lua): 为 Lua API 模块添加标准化 godoc 注释
为所有 Lua API 文件添加完整的包级和函数级文档注释:
- api_balancer: 负载均衡 API(set_current_peer, set_more_tries 等)
- api_ctx: 请求上下文存储 API(ngx.ctx)
- api_location: 子请求捕获 API(ngx.location.capture)
- api_log: 日志输出 API(ngx.log)
- api_req: 请求对象 API
- api_resp: 响应对象 API
- api_shared_dict: 共享字典 API
- api_socket_tcp: TCP socket API
- api_timer: 定时器 API
- api_var: 变量 API
- engine: Lua 引擎核心
- context: 请求上下文管理
- coroutine: 协程调度器
- middleware: 中间件集成
- filter_writer: 响应过滤器
- cache: Lua 脚本缓存
- shared_dict: 共享字典实现
- socket_manager: socket 连接管理

注释格式遵循 Go 官方风格,包含功能说明、参数说明和注意事项。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 10:59:17 +08:00

192 lines
5.1 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package lua 提供 ngx.balancer API 实现。
//
// 该文件实现负载均衡相关的 Lua API用于在 Lua 脚本中选择后端目标服务器。
// 兼容 OpenResty/ngx_lua 的 ngx.balancer 语义。
//
// 主要功能:
// - set_current_peer选择后端目标支持 URL 或 host+port 格式)
// - set_more_tries设置重试次数
// - get_last_failure获取上次失败类型
// - get_targets获取所有可用目标列表
// - get_client_ip获取客户端 IP
//
// 注意事项:
// - BalancerContext 非并发安全,应在单请求上下文中使用
// - 回调函数不能捕获 upvalue闭包变量需使用共享字典
//
// 作者xfy
package lua
import (
"net/url"
"strings"
glua "github.com/yuin/gopher-lua"
"rua.plus/lolly/internal/loadbalance"
)
// BalancerContext Lua 负载均衡上下文。
//
// 存储当前请求的负载均衡状态,包括可选目标列表、已选目标、客户端 IP 和重试次数。
// 该上下文在请求级别使用,非并发安全。
type BalancerContext struct {
// LastError 上次失败错误
LastError error
// Selected 已选中的目标
Selected *loadbalance.Target
// ClientIP 客户端 IP 地址
ClientIP string
// Targets 所有可用后端目标
Targets []*loadbalance.Target
// Retries 剩余重试次数
Retries int
// selected 是否已调用 set_current_peer
selected bool
}
// RegisterBalancerAPI 注册 ngx.balancer API 到 Lua 状态机。
//
// 在 ngx 表下创建 balancer 子表,注册以下方法:
// - set_current_peer(host, port) 或 set_current_peer(url):选择后端目标
// - set_more_tries(count):设置重试次数
// - get_last_failure():获取上次失败类型
// - get_targets():获取所有可用目标
// - get_client_ip():获取客户端 IP
//
// 参数:
// - L: Lua 状态
// - bctx: 负载均衡上下文
// - ngx: ngx 全局表
func RegisterBalancerAPI(L *glua.LState, bctx *BalancerContext, ngx *glua.LTable) {
balancer := L.NewTable()
// set_current_peer(host, port) 或 set_current_peer(url)
L.SetField(balancer, "set_current_peer", L.NewFunction(func(L *glua.LState) int {
nargs := L.GetTop()
var host, port string
if nargs >= 2 {
// set_current_peer(host, port) 形式
host = L.CheckString(1)
port = L.CheckString(2)
if !strings.HasPrefix(port, ":") {
port = ":" + port
}
} else if nargs == 1 {
// set_current_peer(url) 形式
targetURL := L.CheckString(1)
u, err := url.Parse(targetURL)
if err != nil {
L.Push(glua.LBool(false))
L.Push(glua.LString("invalid url: " + err.Error()))
return 2
}
host = u.Hostname()
port = ":" + u.Port()
if u.Port() == "" {
if u.Scheme == "https" {
port = ":443"
} else {
port = ":80"
}
}
} else {
L.RaiseError("set_current_peer requires 1 or 2 arguments")
return 0
}
// 在 Targets 中查找匹配的目标
targetURL := "http://" + host + port
for _, t := range bctx.Targets {
if t.URL == targetURL || strings.HasPrefix(t.URL, targetURL) {
bctx.Selected = t
bctx.selected = true
L.Push(glua.LBool(true))
return 1
}
}
L.Push(glua.LBool(false))
L.Push(glua.LString("target not found: " + host + port))
return 2
}))
// set_more_tries(count)
L.SetField(balancer, "set_more_tries", L.NewFunction(func(L *glua.LState) int {
count := L.CheckInt(1)
bctx.Retries = count
L.Push(glua.LBool(true))
return 1
}))
// get_last_failure()
L.SetField(balancer, "get_last_failure", L.NewFunction(func(L *glua.LState) int {
if bctx.LastError == nil {
L.Push(glua.LNil)
return 1
}
// 返回失败类型: "failed", "timeout", "next"
failType := classifyError(bctx.LastError)
L.Push(glua.LString(failType))
return 1
}))
// get_targets() - 返回所有可用目标
L.SetField(balancer, "get_targets", L.NewFunction(func(L *glua.LState) int {
targetsTable := L.NewTable()
for i, t := range bctx.Targets {
targetTable := L.NewTable()
L.SetField(targetTable, "url", glua.LString(t.URL))
L.SetField(targetTable, "weight", glua.LNumber(t.Weight))
L.SetField(targetTable, "healthy", glua.LBool(t.Healthy.Load()))
targetsTable.RawSetInt(i+1, targetTable)
}
L.Push(targetsTable)
return 1
}))
// get_client_ip()
L.SetField(balancer, "get_client_ip", L.NewFunction(func(L *glua.LState) int {
L.Push(glua.LString(bctx.ClientIP))
return 1
}))
L.SetField(ngx, "balancer", balancer)
}
// IsSelected 检查是否已调用 set_current_peer 选择后端目标。
//
// 返回值:
// - bool: true 表示已选择目标
func (bctx *BalancerContext) IsSelected() bool {
return bctx.selected
}
// classifyError 分类错误类型为 OpenResty 兼容的失败字符串。
//
// 根据错误消息内容判断失败类型:
// - "timeout":超时失败
// - 其他:连接失败("failed"
//
// 返回值:
// - string: 失败类型字符串
func classifyError(err error) string {
if err == nil {
return ""
}
// 根据错误类型返回字符串
errStr := err.Error()
if strings.Contains(errStr, "timeout") {
return "timeout"
}
if strings.Contains(errStr, "connection") {
return "failed"
}
return "failed"
}