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>
This commit is contained in:
xfy 2026-04-20 10:59:17 +08:00
parent 5f5717d6a4
commit ad177e9640
22 changed files with 2330 additions and 518 deletions

View File

@ -1,5 +1,20 @@
// Package lua 提供 ngx.balancer API 实现
// 本文件实现负载均衡相关的 Lua API用于在 Lua 脚本中选择后端目标
// 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 (
@ -10,17 +25,43 @@ import (
"rua.plus/lolly/internal/loadbalance"
)
// BalancerContext Lua Balancer 上下文
// BalancerContext Lua 负载均衡上下文。
//
// 存储当前请求的负载均衡状态,包括可选目标列表、已选目标、客户端 IP 和重试次数。
// 该上下文在请求级别使用,非并发安全。
type BalancerContext struct {
// LastError 上次失败错误
LastError error
Selected *loadbalance.Target
ClientIP string
Targets []*loadbalance.Target
Retries int
selected bool
// 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
// 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()
@ -118,12 +159,22 @@ func RegisterBalancerAPI(L *glua.LState, bctx *BalancerContext, ngx *glua.LTable
L.SetField(ngx, "balancer", balancer)
}
// IsSelected 检查是否调用了 set_current_peer
// IsSelected 检查是否已调用 set_current_peer 选择后端目标。
//
// 返回值:
// - bool: true 表示已选择目标
func (bctx *BalancerContext) IsSelected() bool {
return bctx.selected
}
// classifyError 分类错误类型
// classifyError 分类错误类型为 OpenResty 兼容的失败字符串。
//
// 根据错误消息内容判断失败类型:
// - "timeout":超时失败
// - 其他:连接失败("failed"
//
// 返回值:
// - string: 失败类型字符串
func classifyError(err error) string {
if err == nil {
return ""

View File

@ -1,12 +1,27 @@
// Package lua 提供 ngx.ctx API 实现
// Package lua 提供 ngx.ctx API 实现。
//
// 该文件实现 ngx.ctx 子模块,提供每请求独立的 Lua table 存储。
// ngx.ctx 是 OpenResty/ngx_lua 中的标准 API允许在请求生命周期内
// 跨不同阶段rewrite、access、content、log共享数据。
//
// 注意事项:
// - ngx.ctx 在 timer callback 上下文中不可用(通过 RegisterSchedulerUnsafeCtxAPI 拦截)
//
// 作者xfy
package lua
import (
glua "github.com/yuin/gopher-lua"
)
// RegisterNgxCtxAPI 在 Lua 状态机中注册 ngx.ctx API
// ngx.ctx 是一个普通的 Lua table每请求独立支持任意 Lua 值类型
// RegisterNgxCtxAPI 在 Lua 状态机中注册 ngx.ctx API。
//
// ngx.ctx 是一个每请求独立的 Lua table可通过 ngx.ctx.key 或 ngx.ctx[key]
// 读写任意 Lua 值类型字符串、数字、table 等)。
//
// 参数:
// - L: Lua 状态
// - ngxTable: ngx 全局表
func RegisterNgxCtxAPI(L *glua.LState, ngxTable *glua.LTable) {
// 创建请求级的 ctx table
ctxTable := L.NewTable()
@ -15,7 +30,14 @@ func RegisterNgxCtxAPI(L *glua.LState, ngxTable *glua.LTable) {
ngxTable.RawSetString("ctx", ctxTable)
}
// RegisterSchedulerUnsafeCtxAPI 为 Scheduler LState 注册不安全的 ngx.ctx API
// RegisterSchedulerUnsafeCtxAPI 为 Scheduler LState 注册不可用的 ngx.ctx API。
//
// 在 timer callback 等受限上下文中ngx.ctx 不可用(无请求上下文)。
// 此函数将 ngx.ctx 的所有读写操作替换为返回错误的桩函数。
//
// 参数:
// - L: Lua 状态
// - ngx: ngx 全局表
func RegisterSchedulerUnsafeCtxAPI(L *glua.LState, ngx *glua.LTable) {
ctxTable := L.NewTable()
mt := L.NewTable()
@ -30,6 +52,7 @@ func RegisterSchedulerUnsafeCtxAPI(L *glua.LState, ngx *glua.LTable) {
ngx.RawSetString("ctx", ctxTable)
}
// luaSchedulerUnsafeCtx 返回 scheduler 模式下 ngx.ctx 不可用的错误。
func luaSchedulerUnsafeCtx(L *glua.LState) int {
L.RaiseError("API ngx.ctx not available in timer callback context")
return 0

View File

@ -1,4 +1,16 @@
// Package lua 提供 Lua 脚本嵌入能力
// Package lua 提供 ngx.location API 实现。
//
// 该文件实现 ngx.location.capture 子请求功能,兼容 OpenResty/ngx_lua 语义。
// 支持:
// - 注册 location handler 映射
// - 执行子请求并捕获响应(状态码、响应头、响应体)
// - 选项控制:方法、请求体、自定义头、查询参数
//
// 注意事项:
// - 子请求复用父请求的数据(深拷贝请求体)
// - Scheduler 模式下 ngx.location.capture 不可用
//
// 作者xfy
package lua
import (
@ -9,34 +21,66 @@ import (
glua "github.com/yuin/gopher-lua"
)
// LocationCaptureResult 子请求结果
// LocationCaptureResult 子请求捕获结果。
//
// 包含子请求的响应状态码、响应头和响应体数据。
type LocationCaptureResult struct {
// Headers 响应头映射
Headers map[string]string
Body []byte
Status int
// Body 响应体数据
Body []byte
// Status HTTP 状态码
Status int
}
// LocationManager location 管理(用于子请求)
// LocationManager location 管理器,用于注册和管理子请求 handler。
//
// 维护 location 路径到 fasthttp.RequestHandler 的映射,
// 支持并发安全的注册和捕获操作。
type LocationManager struct {
// handlers 路径到 handler 的映射
handlers map[string]fasthttp.RequestHandler
mu sync.Mutex
// mu 读写锁
mu sync.Mutex
}
// NewLocationManager 创建 location 管理器
// NewLocationManager 创建 location 管理器实例。
//
// 返回值:
// - *LocationManager: 初始化的管理器实例
func NewLocationManager() *LocationManager {
return &LocationManager{
handlers: make(map[string]fasthttp.RequestHandler),
}
}
// Register 注册 location handler
// Register 注册 location handler。
//
// 参数:
// - location: 路径标识(如 "/api/internal"
// - handler: fasthttp 请求处理器
func (m *LocationManager) Register(location string, handler fasthttp.RequestHandler) {
m.mu.Lock()
defer m.mu.Unlock()
m.handlers[location] = handler
}
// Capture 执行子请求
// Capture 执行子请求。
//
// 查找指定 location 的 handler创建子请求上下文复制父请求数据
// 应用选项(方法、请求体、头、查询参数),执行 handler 并收集响应。
//
// 参数:
// - parentCtx: 父请求上下文,用于数据复制
// - location: 目标 location 路径
// - opts: 可选参数映射method、body、headers、args
//
// 返回值:
// - *LocationCaptureResult: 子请求响应结果
// - error: 当前实现始终返回 nil
func (m *LocationManager) Capture(parentCtx *fasthttp.RequestCtx, location string, opts map[string]interface{}) (*LocationCaptureResult, error) {
m.mu.Lock()
handler, ok := m.handlers[location]
@ -114,7 +158,14 @@ func getRequestCtx(L *glua.LState) *fasthttp.RequestCtx {
return nil
}
// RegisterLocationAPI 注册 ngx.location API
// RegisterLocationAPI 注册 ngx.location API 到 Lua 状态机。
//
// 在 ngx 表下创建 location 子表,注册 capture 方法用于执行子请求。
//
// 参数:
// - L: Lua 状态
// - manager: location 管理器实例
// - ngx: ngx 全局表
func RegisterLocationAPI(L *glua.LState, manager *LocationManager, ngx *glua.LTable) {
// 创建 ngx.location 表
location := L.NewTable()

View File

@ -1,4 +1,18 @@
// Package lua 提供 ngx.log 和输出控制 API 实现
// Package lua 提供 ngx.log 和输出控制 API 实现。
//
// 该文件实现与 OpenResty/ngx_lua 兼容的日志和输出控制 API包括
// - ngx.log日志输出兼容 OpenResty 日志级别常量)
// - ngx.say/print内容输出追加到响应缓冲区
// - ngx.flush刷新输出缓冲区
// - ngx.exit终止请求处理
// - ngx.redirectHTTP 重定向
// - HTTP 状态码常量(如 ngx.HTTP_OK、ngx.HTTP_NOT_FOUND 等)
//
// 注意事项:
// - ngx.exit/ngx.redirect 通过 RaiseError 终止 Lua 执行
// - Scheduler 模式下 ngx.log 不依赖 RequestCtx仅输出到标准日志
//
// 作者xfy
package lua
import (
@ -60,19 +74,32 @@ const (
HTTPHTTPVersionNotSupported = 505
)
// ngxLogAPI ngx.log 和输出控制 API 实现
// ngxLogAPI 封装 ngx.log 和输出控制相关的 API。
//
// 包含请求上下文、Lua 上下文和日志记录器,用于:
// - 将 Lua 日志消息转发到 zerolog 记录器
// - 通过 ngx.say/print 写入响应缓冲区
// - 通过 ngx.exit/redirect 终止请求处理
type ngxLogAPI struct {
// 请求上下文
// ctx 关联的 fasthttp 请求上下文,用于直接写入响应
ctx *fasthttp.RequestCtx
// Lua 上下文(用于访问输出缓冲等)
// luaCtx Lua 上下文,用于访问输出缓冲区
luaCtx *LuaContext
// 日志记录器
// logger zerolog 日志记录器,用于结构化日志输出
logger *zerolog.Logger
}
// newNgxLogAPI 创建 ngx.log API 实例
// newNgxLogAPI 创建 ngx.log API 实例。
//
// 参数:
// - ctx: fasthttp 请求上下文,用于直接写入响应
// - luaCtx: Lua 上下文,用于访问输出缓冲区
// - logger: zerolog 日志记录器,为 nil 时禁用结构化日志
//
// 返回值:
// - *ngxLogAPI: 初始化的 API 实例
func newNgxLogAPI(ctx *fasthttp.RequestCtx, luaCtx *LuaContext, logger *zerolog.Logger) *ngxLogAPI {
return &ngxLogAPI{
ctx: ctx,

View File

@ -1,5 +1,22 @@
// Package lua 提供 ngx.req API 实现
// 本文件实现双层 API 边界验证原型,用于测量直接映射层 vs 兼容层的性能差异
// Package lua 提供 ngx.req API 实现。
//
// 该文件实现请求操作相关的 Lua API兼容 OpenResty/ngx_lua 语义。
// 采用分层 API 设计:
// - 直接映射层APILayerDirectfasthttp 直接映射,零拷贝,最小开销
// - 兼容层APILayerCompatible模拟 nginx 语义,增加解析开销
// - 伪非阻塞层APILayerPseudoNonBlockingyield/resume 支持
//
// 主要功能:
// - get_method/get_uri直接映射层
// - get_uri_args/set_uri_args兼容层解析 query string
// - get_headers/set_header/clear_header请求头操作
// - get_body_data/read_body请求体操作
//
// 指标收集:
// - 每层 API 独立记录调用次数、累积延迟和最大延迟
// - 支持性能比率计算(兼容层/直接映射层)
//
// 作者xfy
package lua
import (

View File

@ -1,5 +1,21 @@
// Package lua 提供 ngx.resp API 实现
// 本文件实现 nginx 风格的响应 API用于操作 HTTP 响应
// Package lua 提供 ngx.resp API 实现。
//
// 该文件实现 nginx 风格的响应操作 Lua API用于操作 HTTP 响应。
// 兼容 OpenResty/ngx_lua 的 ngx.resp 语义。
//
// 主要功能:
// - ngx.resp.get_status():获取响应状态码
// - ngx.resp.set_status(code):设置响应状态码
// - ngx.resp.get_headers():获取响应头表
// - ngx.resp.set_header(key, value):设置响应头
// - ngx.resp.clear_header(key):清除响应头
//
// 注意事项:
// - 响应头缓存使用 sync.Once 延迟初始化
// - 修改响应头后会自动清除缓存
// - Scheduler 模式下 ngx.resp 不可用
//
// 作者xfy
package lua
import (

View File

@ -1,4 +1,17 @@
// Package lua 提供 Lua 脚本嵌入能力
// Package lua 提供 ngx.shared.DICT API 实现。
//
// 该文件实现共享内存字典的 Lua API兼容 OpenResty/ngx_lua 语义。
// 包括:
// - SharedDictManager管理多个命名的 SharedDict 实例
// - Lua 元表注册:支持 dict:get/set/add/replace/incr/delete 等方法
// - dict:flush_all/flush_expired/get_keys/size/free_space 管理方法
//
// 安全说明:
// - 共享字典仅支持字符串键值对
// - incr 操作使用手动整数解析(不使用 strconv 依赖)
// - Scheduler 模式下共享字典仍然可用
//
// 作者xfy
package lua
import (
@ -8,21 +21,37 @@ import (
glua "github.com/yuin/gopher-lua"
)
// SharedDictManager 共享字典管理器
// 管理多个命名的 SharedDict 实例
// SharedDictManager 共享字典管理器。
//
// 管理多个命名的 SharedDict 实例,支持并发安全的创建、查询和清理操作。
type SharedDictManager struct {
// dicts 字典名称到实例的映射
dicts map[string]*SharedDict
mu sync.RWMutex
// mu 读写锁
mu sync.RWMutex
}
// NewSharedDictManager 创建字典管理器
// NewSharedDictManager 创建共享字典管理器实例。
//
// 返回值:
// - *SharedDictManager: 初始化的管理器实例
func NewSharedDictManager() *SharedDictManager {
return &SharedDictManager{
dicts: make(map[string]*SharedDict),
}
}
// CreateDict 创建或获取字典
// CreateDict 创建或获取指定名称的共享字典。
//
// 如果字典已存在则直接返回,否则创建新字典。
//
// 参数:
// - name: 字典名称
// - maxItems: 最大条目数LRU 淘汰阈值)
//
// 返回值:
// - *SharedDict: 共享字典实例
func (m *SharedDictManager) CreateDict(name string, maxItems int) *SharedDict {
m.mu.Lock()
defer m.mu.Unlock()
@ -36,27 +65,46 @@ func (m *SharedDictManager) CreateDict(name string, maxItems int) *SharedDict {
return dict
}
// GetDict 获取字典
// GetDict 获取指定名称的共享字典。
//
// 参数:
// - name: 字典名称
//
// 返回值:
// - *SharedDict: 字典实例,不存在时返回 nil
func (m *SharedDictManager) GetDict(name string) *SharedDict {
m.mu.RLock()
defer m.mu.RUnlock()
return m.dicts[name]
}
// Close 清理所有字典
// Close 清理所有字典引用。
//
// 将 dicts 映射置为 nil释放所有字典引用。
func (m *SharedDictManager) Close() {
m.mu.Lock()
defer m.mu.Unlock()
m.dicts = nil
}
// DictConfig 字典配置
// DictConfig 共享字典配置
type DictConfig struct {
Name string
// Name 字典名称
Name string
// MaxItems 最大条目数
MaxItems int
}
// RegisterSharedDictAPI 注册 ngx.shared.DICT API
// RegisterSharedDictAPI 注册 ngx.shared.DICT API 到 Lua 状态机。
//
// 在 ngx 表下创建 shared 子表和字典元表,
// 注册 get/set/add/replace/incr/delete/flush_all/flush_expired/get_keys/size/free_space 方法。
//
// 参数:
// - L: Lua 状态
// - manager: 共享字典管理器
// - ngx: ngx 全局表
func RegisterSharedDictAPI(L *glua.LState, manager *SharedDictManager, ngx *glua.LTable) {
// 创建 ngx.shared 表
shared := L.NewTable()
@ -104,7 +152,13 @@ func RegisterSharedDictAPI(L *glua.LState, manager *SharedDictManager, ngx *glua
L.SetField(mt, "methods", methods)
}
// checkSharedDict 检查并获取 SharedDict
// checkSharedDict 从 Lua 调用参数中验证并获取 SharedDict 实例。
//
// 参数:
// - L: Lua 状态
//
// 返回值:
// - *SharedDict: 字典实例,类型不正确时引发 Lua 错误
func checkSharedDict(L *glua.LState) *SharedDict {
ud := L.CheckUserData(1)
dict, ok := ud.Value.(*SharedDict)
@ -114,7 +168,10 @@ func checkSharedDict(L *glua.LState) *SharedDict {
return dict
}
// dictIndex 字典索引方法
// dictIndex 字典 __index 元方法。
//
// 先检查是否为方法名(如 get、set 等),若是则返回方法;
// 否则作为 key 获取字典中的值。
func dictIndex(L *glua.LState) int {
dict := checkSharedDict(L)
@ -152,7 +209,9 @@ func dictIndex(L *glua.LState) int {
return 1
}
// dictNewIndex 字典设置方法
// dictNewIndex 字典 __newindex 元方法。
//
// 处理 dict[key] = value 形式的赋值,设置永不过期的键值对。
func dictNewIndex(L *glua.LState) int {
dict := checkSharedDict(L)
@ -173,15 +232,21 @@ func dictNewIndex(L *glua.LState) int {
return 1
}
// dictToString 字典字符串表示
// dictToString 字典 __tostring 元方法,返回 "ngx.shared.dict:{name}" 格式字符串。
func dictToString(L *glua.LState) int {
dict := checkSharedDict(L)
L.Push(glua.LString("ngx.shared.dict:" + dict.name))
return 1
}
// dictGet 获取值
// dict:get(key) -> value, flags | nil, err
// dictGet 实现 dict:get(key) 方法。
//
// 获取指定键的值,支持过期检测。
//
// 返回值(推入 Lua 栈):
// - value: 键对应的值,不存在或过期时返回 nil
// - flags: 标志位(当前始终返回 0
// - err: 错误信息(不存在时不返回)
func dictGet(L *glua.LState) int {
dict := checkSharedDict(L)
@ -207,8 +272,13 @@ func dictGet(L *glua.LState) int {
return 2
}
// dictSet 设置值
// dict:set(key, value, exptime?, flags?) -> ok, err
// dictSet 实现 dict:set(key, value, exptime?, flags?) 方法。
//
// 设置键值对,支持可选过期时间(秒)。
//
// 返回值(推入 Lua 栈):
// - ok: true 表示设置成功
// - err: 失败时的错误信息
func dictSet(L *glua.LState) int {
dict := checkSharedDict(L)
@ -217,7 +287,7 @@ func dictSet(L *glua.LState) int {
ttl := time.Duration(0)
if L.GetTop() >= 4 {
ttl = time.Duration(L.CheckNumber(4)) * time.Second
ttl = time.Duration(float64(L.CheckNumber(4)) * float64(time.Second))
}
// flags 参数暂不使用
@ -237,8 +307,9 @@ func dictSet(L *glua.LState) int {
return 1
}
// dictAdd 添加值(不存在时)
// dict:add(key, value, exptime?, flags?) -> ok, err
// dictAdd 实现 dict:add(key, value, exptime?, flags?) 方法。
//
// 仅在键不存在时添加,存在时返回错误。
func dictAdd(L *glua.LState) int {
dict := checkSharedDict(L)
@ -247,7 +318,7 @@ func dictAdd(L *glua.LState) int {
ttl := time.Duration(0)
if L.GetTop() >= 4 {
ttl = time.Duration(L.CheckNumber(4)) * time.Second
ttl = time.Duration(float64(L.CheckNumber(4)) * float64(time.Second))
}
ok, err := dict.Add(key, value, ttl)
@ -265,8 +336,9 @@ func dictAdd(L *glua.LState) int {
return 1
}
// dictReplace 替换值(存在时)
// dict:replace(key, value, exptime?, flags?) -> ok, err
// dictReplace 实现 dict:replace(key, value, exptime?, flags?) 方法。
//
// 仅在键存在且未过期时替换值,不存在时返回 "not found" 错误。
func dictReplace(L *glua.LState) int {
dict := checkSharedDict(L)
@ -275,7 +347,7 @@ func dictReplace(L *glua.LState) int {
ttl := time.Duration(0)
if L.GetTop() >= 4 {
ttl = time.Duration(L.CheckNumber(4)) * time.Second
ttl = time.Duration(float64(L.CheckNumber(4)) * float64(time.Second))
}
// 检查是否存在Get 返回: value, expired, error
@ -312,8 +384,11 @@ func dictReplace(L *glua.LState) int {
return 1
}
// dictIncr 自增数值
// dict:incr(key, value) -> new_value, err
// dictIncr 实现 dict:incr(key, value) 方法。
//
// 将指定键的值作为整数自增,返回新值。
// 如果键不存在则创建初始值为 0 再自增。
// 如果值不是纯数字,返回 nil 和错误。
func dictIncr(L *glua.LState) int {
dict := checkSharedDict(L)
@ -326,12 +401,20 @@ func dictIncr(L *glua.LState) int {
L.Push(glua.LString(err.Error()))
return 2
}
if newValue == 0 {
// Incr 返回 0 表示值不是纯数字字符串(非错误)
// 在 Lua 中返回 nil 表示操作失败
L.Push(glua.LNil)
L.Push(glua.LString("not a number"))
return 2
}
L.Push(glua.LNumber(newValue))
return 1
}
// dictDelete 删除条目
// dict:delete(key) -> ok
// dictDelete 实现 dict:delete(key) 方法。
//
// 删除指定键。
func dictDelete(L *glua.LState) int {
dict := checkSharedDict(L)
@ -341,8 +424,9 @@ func dictDelete(L *glua.LState) int {
return 1
}
// dictFlushAll 清空字典
// dict:flush_all()
// dictFlushAll 实现 dict:flush_all() 方法。
//
// 清空字典中的所有条目。
func dictFlushAll(L *glua.LState) int {
dict := checkSharedDict(L)
@ -350,8 +434,9 @@ func dictFlushAll(L *glua.LState) int {
return 0
}
// dictFlushExpired 清除过期条目
// dict:flush_expired(max_count?) -> flushed_count
// dictFlushExpired 实现 dict:flush_expired(max_count?) 方法。
//
// 清除所有过期条目,返回被清除的数量。
func dictFlushExpired(L *glua.LState) int {
dict := checkSharedDict(L)
@ -360,8 +445,9 @@ func dictFlushExpired(L *glua.LState) int {
return 1
}
// dictGetKeys 获取所有键
// dict:get_keys(max_count?) -> keys
// dictGetKeys 实现 dict:get_keys(max_count?) 方法。
//
// 获取字典中的所有键。当前实现返回空表(待完善)。
func dictGetKeys(L *glua.LState) int {
checkSharedDict(L) // 验证参数但不使用返回值

View File

@ -1,4 +1,23 @@
// Package lua 提供 Cosocket TCP API 实现
// Package lua 提供 Cosocket TCP API 实现。
//
// 该文件实现 ngx.socket.tcp 相关的 Lua API兼容 OpenResty cosocket 语义。
// 包括:
// - TCPSocketTCP 连接封装,支持 connect/send/receive/close
// - CosocketManager异步操作生命周期管理已在 socket_manager.go 中)
// - 异步 yield/resume 机制(通过 handleCosocket* 系列函数)
// - ReceiveUntil 模式匹配读取
//
// 特性:
// - 操作状态机Idle -> Connecting -> Connected -> Sending/Receiving -> Closed
// - 超时检测:连接、发送、接收各自独立超时
// - 原子操作标记完成,避免竞态条件
//
// 注意事项:
// - TCPSocket 非并发安全,每个 Lua 协程独占一个 socket
// - ReceiveUntil 有 1MB 缓冲区限制,防止内存耗尽
// - Cosocket 的 yield 当前为同步模拟,待实现真正的非阻塞 yield
//
// 作者xfy
package lua
import (
@ -12,22 +31,55 @@ import (
glua "github.com/yuin/gopher-lua"
)
// TCPSocket TCP socket 对象
// TCPSocket 封装 TCP 连接,提供同步和异步操作。
//
// 支持 connect、send、receive、receiveuntil、close 等操作,
// 兼容 OpenResty cosocket API 语义。
//
// 每个 socket 关联一个 CosocketManager 用于异步操作跟踪和超时检测。
// 状态转换通过 sync.RWMutex 保护。
type TCPSocket struct {
createdAt time.Time
conn net.Conn
currentOp *SocketOperation
manager *CosocketManager
addr *net.TCPAddr
readTimeout time.Duration
sendTimeout time.Duration
// createdAt 创建时间
createdAt time.Time
// conn 底层 TCP 连接
conn net.Conn
// currentOp 当前进行中的异步操作
currentOp *SocketOperation
// manager 关联的 Cosocket 管理器
manager *CosocketManager
// addr 目标地址
addr *net.TCPAddr
// readTimeout 读取超时
readTimeout time.Duration
// sendTimeout 发送超时
sendTimeout time.Duration
// connectTimeout 连接超时
connectTimeout time.Duration
state SocketState
mu sync.RWMutex
closed int32
// state 当前 socket 状态
state SocketState
// mu 状态读写锁
mu sync.RWMutex
// closed 关闭标记(原子操作)
closed int32
}
// NewTCPSocket 创建新的 TCP socket
// NewTCPSocket 创建新的 TCP socket 实例。
//
// 参数:
// - manager: Cosocket 管理器,为 nil 时使用默认全局管理器
//
// 返回值:
// - *TCPSocket: 初始化的 socket 实例
func NewTCPSocket(manager *CosocketManager) *TCPSocket {
if manager == nil {
manager = DefaultCosocketManager
@ -46,7 +98,17 @@ func NewTCPSocket(manager *CosocketManager) *TCPSocket {
return s
}
// Connect 连接到指定地址(支持 yield
// Connect 连接到指定地址(同步版本)。
//
// 发起 TCP 连接,并在后台 goroutine 中执行实际的 dial 操作。
// 连接结果通过 manager 的 SocketOperation 通知。
//
// 参数:
// - host: 目标主机地址
// - port: 目标端口号
//
// 返回值:
// - error: 状态不正确或地址解析失败时返回错误
func (s *TCPSocket) Connect(host string, port int) error {
s.mu.Lock()
if s.state != SocketStateIdle {
@ -96,7 +158,13 @@ func (s *TCPSocket) Connect(host string, port int) error {
return nil
}
// ConnectAsync 异步连接(用于 Lua yield
// ConnectAsync 异步连接(用于 Lua yield/resume
//
// 调用 Connect 并返回关联的 SocketOperation供 Lua 协程 yield 等待。
//
// 返回值:
// - *SocketOperation: 连接操作实例
// - error: 连接失败时返回错误
func (s *TCPSocket) ConnectAsync(_ *glua.LState, host string, port int) (*SocketOperation, error) {
err := s.Connect(host, port)
if err != nil {

View File

@ -1,4 +1,22 @@
// Package lua 提供 Lua 脚本嵌入能力
// Package lua 提供 ngx.timer API 实现。
//
// 该文件实现定时器相关的 Lua API兼容 OpenResty/ngx_lua 语义。
// 包括:
// - TimerManager定时器管理器支持延迟执行和取消
// - TimerEntry单个定时器条目包含回调和参数
// - TimerHandleLua 可见的定时器句柄userdata
// - 调度器 goroutine在专用 LState 中安全执行 Lua 回调
//
// 设计说明:
// - 回调通过 FunctionProto 编译后在调度器 goroutine 中执行
// - 不允许回调捕获 upvalue闭包变量防止内存泄漏
// - 优雅关闭:等待回调队列排空,超时后放弃剩余回调
//
// 注意事项:
// - 定时器回调中不可用 ngx.req/ngx.resp/ngx.var/ngx.ctx 等请求级 API
// - 队列满时回调被丢弃(不重试)
//
// 作者xfy
package lua
import (
@ -11,46 +29,115 @@ import (
glua "github.com/yuin/gopher-lua"
)
// CallbackEntry 回调队列条目
// CallbackEntry 回调队列条目,封装定时器触发的 Lua 回调。
//
// 包含编译后的 FunctionProto 和调用参数,
// 由 TimerManager 推入 engine 的 callbackQueue 供调度器执行。
type CallbackEntry struct {
// proto 编译后的 Lua 函数原型
proto *glua.FunctionProto
args []glua.LValue
// args 调用参数
args []glua.LValue
}
// TimerManager 定时器管理器
// TimerManager 定时器管理器。
//
// 负责创建、取消和执行 Lua 定时器。
// 回调在专用调度器 goroutine 的 LState 中执行,实现线程隔离。
//
// 并发安全说明:
// - mu 保护 timers 映射的读写
// - queueMu 保护队列关闭状态
// - active/stopping 使用 atomic 操作
type TimerManager struct {
timers map[uint64]*TimerEntry
engine *LuaEngine
// timers 活跃定时器映射ID -> 条目)
timers map[uint64]*TimerEntry
// engine 关联的 Lua 引擎
engine *LuaEngine
// callbackQueue 回调队列,推入调度器执行
callbackQueue chan *CallbackEntry
// schedulerDone 调度器 goroutine 退出信号
schedulerDone chan struct{}
schedulerL *glua.LState
nextID uint64
mu sync.Mutex
queueMu sync.Mutex
active int32
stopping int32
queueClosed bool
// schedulerL 调度器专用 LState
schedulerL *glua.LState
// nextID 下一个定时器 ID原子操作
nextID uint64
// mu 定时器映射读写锁
mu sync.Mutex
// queueMu 队列状态锁
queueMu sync.Mutex
// active 活跃定时器计数(原子操作)
active int32
// stopping 停止标记(原子操作)
stopping int32
// queueClosed 队列是否已关闭
queueClosed bool
// closed 是否已完全关闭(防止重复关闭)
closed bool
}
// TimerEntry 定时器条目
// TimerEntry 定时器条目。
//
// 封装单个定时器的回调、参数、生命周期和取消信号。
type TimerEntry struct {
callback *glua.LFunction
// callback 原始回调函数
callback *glua.LFunction
// callbackProto 编译后的回调原型(无 upvalue 限制)
callbackProto *glua.FunctionProto
timer *time.Timer
cancel chan struct{}
done chan struct{}
args []glua.LValue
id uint64
delay time.Duration
// timer 底层 time.Timer
timer *time.Timer
// cancel 取消信号通道
cancel chan struct{}
// done 完成信号通道
done chan struct{}
// args 回调参数
args []glua.LValue
// ID 定时器唯一标识
id uint64
// delay 延迟时间
delay time.Duration
}
// TimerHandle 定时器句柄Lua userdata
// TimerHandle 定时器句柄,暴露给 Lua 的 userdata。
//
// 通过该句柄可在 Lua 中取消定时器。
type TimerHandle struct {
// manager 关联的定时器管理器
manager *TimerManager
id uint64
// id 定时器 ID
id uint64
}
// NewTimerManager 创建定时器管理器
// NewTimerManager 创建定时器管理器实例。
//
// 初始化定时器映射、回调队列、调度器 LState
// 并启动调度器 goroutine。
//
// 参数:
// - engine: Lua 引擎实例
//
// 返回值:
// - *TimerManager: 初始化的管理器实例
func NewTimerManager(engine *LuaEngine) *TimerManager {
m := &TimerManager{
timers: make(map[uint64]*TimerEntry),
@ -80,8 +167,22 @@ func NewTimerManager(engine *LuaEngine) *TimerManager {
return m
}
// At 创建定时器
// 返回定时器句柄和错误
// At 创建延迟定时器。
//
// 在指定延迟后执行回调函数。回调在调度器 goroutine 的 LState 中执行。
//
// 参数:
// - delay: 延迟时间
// - callback: Lua 回调函数(不能捕获 upvalue
// - args: 回调参数
//
// 返回值:
// - *TimerHandle: 定时器句柄
// - error: 创建失败或服务器正在关闭时返回错误
//
// 安全说明:
// - 回调不能捕获 upvalue闭包变量因为它们会在不同 goroutine 中执行
// - 跨协程数据共享应使用 shared dict
func (m *TimerManager) At(delay time.Duration, callback *glua.LFunction, args []glua.LValue) (*TimerHandle, error) {
if atomic.LoadInt32(&m.stopping) != 0 {
return nil, nil // 服务器正在关闭,不接受新定时器
@ -123,8 +224,10 @@ func (m *TimerManager) At(delay time.Duration, callback *glua.LFunction, args []
return &TimerHandle{id: id, manager: m}, nil
}
// executeTimer 执行定时器回调
// 通过 channel 将回调调度到调度器 goroutine 执行
// executeTimer 执行定时器回调。
//
// 定时器到期时由 time.AfterFunc 调用,将回调推入调度器队列。
// 检查取消信号,清理条目,并在队列满时丢弃回调。
func (m *TimerManager) executeTimer(entry *TimerEntry) {
defer func() {
atomic.AddInt32(&m.active, -1)
@ -166,7 +269,10 @@ func (m *TimerManager) executeTimer(entry *TimerEntry) {
}
}
// schedulerLoop 调度器循环,在专用 goroutine 中执行 Lua 回调
// schedulerLoop 调度器循环,在专用 goroutine 中执行 Lua 回调。
//
// 从 callbackQueue 中读取回调条目,从 FunctionProto 重建 Lua 函数并执行。
// 队列关闭时退出循环。
func (m *TimerManager) schedulerLoop() {
defer close(m.schedulerDone)
@ -188,7 +294,15 @@ func (m *TimerManager) schedulerLoop() {
}
}
// Cancel 取消定时器
// Cancel 取消定时器。
//
// 停止底层 time.Timer发送取消信号清理定时器条目。
//
// 参数:
// - handle: 定时器句柄
//
// 返回值:
// - bool: true 表示成功取消false 表示定时器不存在或已执行
func (m *TimerManager) Cancel(handle *TimerHandle) bool {
m.mu.Lock()
defer m.mu.Unlock()
@ -213,7 +327,16 @@ func (m *TimerManager) Cancel(handle *TimerHandle) bool {
return true
}
// WaitAll 等待所有定时器完成
// WaitAll 等待所有定时器完成。
//
// 设置停止标志(拒绝新定时器),轮询等待活跃计数器归零。
// 超时后强制取消所有剩余定时器。
//
// 参数:
// - timeout: 最大等待时间
//
// 返回值:
// - bool: true 表示所有定时器已正常完成false 表示超时
func (m *TimerManager) WaitAll(timeout time.Duration) bool {
// 设置停止标志
atomic.StoreInt32(&m.stopping, 1)
@ -240,8 +363,19 @@ func (m *TimerManager) WaitAll(timeout time.Duration) bool {
return true
}
// Close 关闭定时器管理器
// Close 关闭定时器管理器。
//
// 执行顺序:
// 1. 设置停止标志,拒绝新定时器
// 2. 优雅关闭等待回调队列排空5 秒超时)
// 3. 关闭调度器 LState
//
// 注意:该方法是幂等的,可安全调用多次。
func (m *TimerManager) Close() {
if m == nil || atomic.LoadInt32(&m.stopping) != 0 {
return // 已关闭或 nil
}
// 1. 停止接受新定时器
atomic.StoreInt32(&m.stopping, 1)
@ -255,7 +389,13 @@ func (m *TimerManager) Close() {
}
}
// gracefulShutdown 优雅关闭:排空回调队列,超时后放弃
// gracefulShutdown 优雅关闭定时器管理器。
//
// 关闭回调队列,等待调度器 goroutine 退出。
// 超时后记录被丢弃的回调数量。
//
// 参数:
// - timeout: 最大等待时间
func (m *TimerManager) gracefulShutdown(timeout time.Duration) {
m.queueMu.Lock()
m.queueClosed = true
@ -273,12 +413,26 @@ func (m *TimerManager) gracefulShutdown(timeout time.Duration) {
}
}
// ActiveCount 返回活跃定时器数
// ActiveCount 返回当前活跃定时器数量。
//
// 返回值:
// - int32: 活跃定时器数量
func (m *TimerManager) ActiveCount() int32 {
return atomic.LoadInt32(&m.active)
}
// RegisterTimerAPI 注册 ngx.timer API
// RegisterTimerAPI 注册 ngx.timer API 到 Lua 状态机。
//
// 在 ngx 表下创建 timer 子表,注册以下方法:
// - at(delay, callback, ...):创建延迟定时器,返回 userdata 句柄
// - running_count():返回活跃定时器数量
//
// 同时创建定时器句柄元表,支持 cancel 方法。
//
// 参数:
// - L: Lua 状态
// - manager: 定时器管理器实例
// - ngx: ngx 全局表
func RegisterTimerAPI(L *glua.LState, manager *TimerManager, ngx *glua.LTable) {
// 创建 ngx.timer 表
timer := L.NewTable()

View File

@ -1,4 +1,20 @@
// Package lua 提供 ngx.var API 实现
// Package lua 提供 ngx.var API 实现。
//
// 该文件实现 nginx 变量访问的 Lua API兼容 OpenResty/ngx_lua 语义。
// 支持:
// - 标准 nginx 变量request_method、request_uri、uri、query_string 等
// - 请求头变量http_host、http_user_agent、http_content_type 等
// - 客户端信息remote_addr、server_addr 等
// - arg_ 前缀变量arg_name 用于访问查询参数
// - http_ 前缀变量http_xxx 用于访问任意请求头
// - 自定义变量存储:支持通过 store map 读写自定义变量
//
// 实现说明:
// - 通过元表__index/__newindex实现动态变量读写
// - 读取优先级:自定义变量 > fasthttp 内置变量
// - 写入始终存储到自定义变量 store 中
//
// 作者xfy
package lua
import (
@ -11,16 +27,25 @@ import (
// argPrefix 是 arg_ 变量的前缀,用于获取查询参数
const argPrefix = "arg_"
// ngxVarAPI ngx.var API 实现
// ngxVarAPI 封装 nginx 变量访问 API。
//
// 支持读取 fasthttp 请求中的标准 nginx 变量,
// 以及读写自定义变量(存储于 store map 中)。
type ngxVarAPI struct {
// 请求上下文
// ctx 关联的 fasthttp 请求上下文
ctx *fasthttp.RequestCtx
// 变量存储(用于自定义变量)
// store 自定义变量存储,支持 Lua 脚本读写自定义变量
store map[string]string
}
// newNgxVarAPI 创建 ngx.var API 实例
// newNgxVarAPI 创建 ngx.var API 实例。
//
// 参数:
// - ctx: fasthttp 请求上下文
//
// 返回值:
// - *ngxVarAPI: 初始化的 API 实例
func newNgxVarAPI(ctx *fasthttp.RequestCtx) *ngxVarAPI {
return &ngxVarAPI{
ctx: ctx,

View File

@ -1,4 +1,17 @@
// Package lua 提供 Lua 脚本嵌入能力
// Package lua 提供 Lua 脚本嵌入能力。
//
// 该文件实现 Lua 脚本字节码缓存CodeCache包括
// - 内联脚本缓存:基于 SHA256 哈希去重
// - 文件脚本缓存:基于路径哈希 + 文件变更检测
// - LRU 淘汰策略:容量满时淘汰最久未访问的缓存
// - TTL 过期机制:超过生存期的缓存自动失效
// - 文件监控:文件修改时间变化时自动重新编译
//
// 注意事项:
// - 缓存读写使用 sync.RWMutex 保证并发安全
// - 统计计数使用 atomic 操作
//
// 作者xfy
package lua
import (
@ -16,40 +29,85 @@ import (
"github.com/yuin/gopher-lua/parse"
)
// CacheKeyType 缓存键类型
// CacheKeyType 缓存键类型,区分内联脚本和文件脚本。
type CacheKeyType int
// 缓存键类型常量:内联脚本和文件脚本
const (
// CacheKeyInline 内联脚本缓存键
CacheKeyInline CacheKeyType = iota // 内联脚本
// CacheKeyFile 文件脚本缓存键
CacheKeyFile // 文件脚本
// CacheKeyInline 内联脚本缓存键(通过 SHA256 哈希标识)
CacheKeyInline CacheKeyType = iota
// CacheKeyFile 文件脚本缓存键(通过路径 SHA256 哈希标识)
CacheKeyFile
)
// CachedProto 缓存的字节码
// CachedProto 缓存的编译后字节码
type CachedProto struct {
ModTime time.Time
CachedAt time.Time
AccessAt atomic.Value
Proto *glua.FunctionProto
// ModTime 文件修改时间(仅文件脚本有效)
ModTime time.Time
// CachedAt 缓存存入时间(用于 TTL 过期检测)
CachedAt time.Time
// AccessAt 最后访问时间(用于 LRU 淘汰)
AccessAt atomic.Value
// Proto 编译后的 Lua 函数原型
Proto *glua.FunctionProto
// SourcePath 源文件路径(仅文件脚本有效)
SourcePath string
// SourceType 缓存键类型
SourceType CacheKeyType
}
// CodeCache 字节码缓存
// CodeCache Lua 脚本字节码缓存。
//
// 支持两种缓存源:
// - 内联脚本:基于内容 SHA256 哈希去重
// - 文件脚本:基于路径哈希 + 文件变更检测
//
// 特性:
// - LRU 淘汰:容量满时淘汰最久未访问的条目
// - TTL 过期:超过生存期的缓存自动失效
// - 文件监控:文件修改时间变化时自动重新编译
// - 并发安全:使用 sync.RWMutex 保护读写
type CodeCache struct {
protos map[string]*CachedProto
order []string
maxSize int
ttl time.Duration
hits uint64
misses uint64
mu sync.RWMutex
// protos 缓存映射:键 -> 编译后的字节码
protos map[string]*CachedProto
// order 访问顺序列表(用于 LRU 淘汰)
order []string
// 最大缓存条目数
maxSize int
// 缓存生存时间
ttl time.Duration
// 缓存命中次数
hits uint64
// 缓存未命中次数
misses uint64
// 读写锁
mu sync.RWMutex
// 是否启用文件变更检测
fileWatch bool
}
// NewCodeCache 创建字节码缓存
// NewCodeCache 创建字节码缓存实例。
//
// 参数:
// - maxSize: 最大缓存条目数
// - ttl: 缓存生存时间,零值表示永不过期
// - fileWatch: 是否启用文件变更检测
//
// 返回值:
// - *CodeCache: 初始化的缓存实例
func NewCodeCache(maxSize int, ttl time.Duration, fileWatch bool) *CodeCache {
return &CodeCache{
protos: make(map[string]*CachedProto),
@ -60,19 +118,36 @@ func NewCodeCache(maxSize int, ttl time.Duration, fileWatch bool) *CodeCache {
}
}
// generateInlineKey 生成内联脚本缓存键
// generateInlineKey 生成内联脚本的缓存键。
//
// 使用 SHA256 哈希算法对脚本内容进行摘要,前缀为 "nhli_"。
func (c *CodeCache) generateInlineKey(src string) string {
hash := sha256.Sum256([]byte(src))
return "nhli_" + hex.EncodeToString(hash[:])
}
// generateFileKey 生成文件脚本缓存键
// generateFileKey 生成文件脚本的缓存键。
//
// 使用 SHA256 哈希算法对文件路径进行摘要,前缀为 "nhlf_"。
// 注意:键基于路径而非内容,文件变更检测由 isFileChanged 负责。
func (c *CodeCache) generateFileKey(path string) string {
hash := sha256.Sum256([]byte(path))
return "nhlf_" + hex.EncodeToString(hash[:])
}
// GetOrCompileInline 获取或编译内联脚本
// GetOrCompileInline 获取或编译内联脚本。
//
// 查找流程:
// 1. 基于脚本内容生成缓存键
// 2. 检查缓存是否命中且未过期
// 3. 未命中则解析并编译脚本,存入缓存
//
// 参数:
// - src: Lua 源代码字符串
//
// 返回值:
// - *glua.FunctionProto: 编译后的函数原型
// - error: 解析或编译失败时返回错误
func (c *CodeCache) GetOrCompileInline(src string) (*glua.FunctionProto, error) {
key := c.generateInlineKey(src)
@ -113,7 +188,19 @@ func (c *CodeCache) GetOrCompileInline(src string) (*glua.FunctionProto, error)
return proto, nil
}
// GetOrCompileFile 获取或编译文件脚本
// GetOrCompileFile 获取或编译文件脚本。
//
// 查找流程:
// 1. 基于文件路径生成缓存键
// 2. 检查缓存是否命中、未过期且文件未变更
// 3. 未命中则读取文件、解析并编译,存入缓存
//
// 参数:
// - path: Lua 脚本文件路径
//
// 返回值:
// - *glua.FunctionProto: 编译后的函数原型
// - error: 读取、解析或编译失败时返回错误
func (c *CodeCache) GetOrCompileFile(path string) (*glua.FunctionProto, error) {
key := c.generateFileKey(path)
@ -170,7 +257,9 @@ func (c *CodeCache) GetOrCompileFile(path string) (*glua.FunctionProto, error) {
return proto, nil
}
// storeLocked 存入缓存(需持有锁)
// storeLocked 将缓存条目存入映射(需已持有写锁)。
//
// 如果键已存在则更新;否则先检查容量并可能触发 LRU 淘汰。
func (c *CodeCache) storeLocked(key string, cached *CachedProto) {
// 如果已存在,更新
if _, ok := c.protos[key]; ok {
@ -187,7 +276,9 @@ func (c *CodeCache) storeLocked(key string, cached *CachedProto) {
c.order = append(c.order, key)
}
// evictLocked 淘汰最久未使用的缓存(需持有锁)
// evictLocked 淘汰最久未访问的缓存条目(需已持有写锁)。
//
// 遍历 order 列表,找到 AccessAt 最早的条目并删除。
func (c *CodeCache) evictLocked() {
if len(c.order) == 0 {
return
@ -215,7 +306,9 @@ func (c *CodeCache) evictLocked() {
}
}
// isExpired 检查缓存是否过期
// isExpired 检查缓存条目是否超过 TTL。
//
// 如果 TTL 为零或负数,永不过期。
func (c *CodeCache) isExpired(cached *CachedProto) bool {
if c.ttl <= 0 {
return false
@ -223,7 +316,13 @@ func (c *CodeCache) isExpired(cached *CachedProto) bool {
return time.Since(cached.CachedAt) > c.ttl
}
// isFileChanged 检查文件是否变更
// isFileChanged 检查文件脚本是否已变更。
//
// 通过比较文件的修改时间与缓存中记录的 ModTime 判断。
// 如果文件不存在或无法 stat视为已变更触发重新编译
//
// 返回值:
// - bool: true 表示文件已变更false 表示未变更或文件监控未启用
func (c *CodeCache) isFileChanged(cached *CachedProto) bool {
if !c.fileWatch || cached.SourceType != CacheKeyFile {
return false
@ -237,7 +336,12 @@ func (c *CodeCache) isFileChanged(cached *CachedProto) bool {
return info.ModTime().After(cached.ModTime)
}
// Stats 返回缓存统计
// Stats 返回缓存统计信息。
//
// 返回值:
// - hits: 缓存命中次数
// - misses: 缓存未命中次数
// - size: 当前缓存条目数
func (c *CodeCache) Stats() (hits, misses uint64, size int) {
c.mu.RLock()
defer c.mu.RUnlock()

View File

@ -1,28 +1,73 @@
// Package lua 提供 Lua 脚本嵌入能力
// 采用 Server 级单 LState + 请求级临时协程架构
// Package lua 提供 Lua 脚本嵌入能力。
//
// 该文件定义 Lua 引擎的配置结构及其默认值,包括:
// - 并发控制:最大协程数、超时设置、协程栈大小
// - 缓存配置:字节码缓存大小与 TTL
// - 安全选项OS/IO/Load 库的启用控制
// - 内存优化:协程栈收缩、池预热
//
// 注意事项:
// - 安全相关的库OS、IO、LoadLib默认禁用防止不安全操作
// - 协程栈默认 64KB最大 256KB较小的栈可减少内存分配
// - 最大执行时间与协程超时默认均为 30 秒
//
// 作者xfy
package lua
import (
"time"
)
// Config Lua 引擎配置
// Config Lua 引擎配置。
//
// 控制引擎的并发、缓存、安全和内存行为。
type Config struct {
// MaxConcurrentCoroutines 最大并发协程数(默认 1000
MaxConcurrentCoroutines int
CoroutineTimeout time.Duration
CodeCacheSize int
CodeCacheTTL time.Duration
MaxExecutionTime time.Duration
CoroutineStackSize int // 协程栈大小默认64最大256
CoroutinePoolWarmup int // 协程池预热数量,启动时预创建
EnableFileWatch bool // 1
EnableOSLib bool // 1
EnableIOLib bool // 1
EnableLoadLib bool // 1
MinimizeStackMemory bool // 启用栈内存自动收缩以减少内存占用
// CoroutineTimeout 单个协程执行超时(默认 30 秒)
CoroutineTimeout time.Duration
// CodeCacheSize 字节码缓存最大条目数(默认 1000
CodeCacheSize int
// CodeCacheTTL 字节码缓存生存时间(默认 1 小时)
CodeCacheTTL time.Duration
// MaxExecutionTime 最大执行时间(默认 30 秒)
MaxExecutionTime time.Duration
// CoroutineStackSize 协程栈大小(默认 64KB最大 256KB
CoroutineStackSize int
// CoroutinePoolWarmup 协程池预热数量,启动时预创建的协程结构数
CoroutinePoolWarmup int
// EnableFileWatch 是否启用文件变更检测(文件修改后自动重新编译)
EnableFileWatch bool
// EnableOSLib 是否启用 Lua os 库(默认禁用,出于安全考虑)
EnableOSLib bool
// EnableIOLib 是否启用 Lua io 库(默认禁用,出于安全考虑)
EnableIOLib bool
// EnableLoadLib 是否启用 Lua loadfile/dofile默认禁用出于安全考虑
EnableLoadLib bool
// MinimizeStackMemory 启用栈内存自动收缩以减少内存占用
MinimizeStackMemory bool
}
// DefaultConfig 返回默认配置
// DefaultConfig 返回默认配置。
//
// 安全策略:
// - os、io、loadlib 库默认禁用,防止文件系统访问和动态代码加载
// - 文件变更检测默认启用,便于开发时热更新
// - 协程栈默认 64KB 以节省内存
//
// 返回值:
// - *Config: 预填充的默认配置实例
func DefaultConfig() *Config {
return &Config{
MaxConcurrentCoroutines: 1000,

View File

@ -1,4 +1,15 @@
// Package lua 提供 Lua 脚本嵌入能力
// Package lua 提供 Lua 脚本嵌入能力。
//
// 该文件包含请求级 Lua 上下文的实现,包括:
// - LuaContext请求级上下文管理协程生命周期和输出缓冲
// - 对象池sync.Pool 复用 LuaContext 实例,减少 GC 压力
// - 变量存储:每个请求独立的变量存储空间
//
// 注意事项:
// - LuaContext 从对象池获取,使用后必须调用 Release() 放回
// - Release() 会重置所有可变状态,防止请求间污染
//
// 作者xfy
package lua
import (
@ -7,23 +18,45 @@ import (
"github.com/valyala/fasthttp"
)
// LuaContext 请求级 Lua 上下文
// LuaContext 请求级 Lua 上下文。
//
// 每个 HTTP 请求对应一个 LuaContext负责
// - 管理请求级 Lua 协程LuaCoroutine的生命周期
// - 维护请求级变量存储Variables
// - 缓冲 Lua 脚本的输出内容OutputBuffer
// - 跟踪请求处理阶段Phase和退出状态Exited
//
// 类型命名说明:虽然 lua.LuaContext 存在 stuttering但保持此命名以
// 1) 与 LuaEngine/LuaCoroutine 保持一致的 API 命名风格
// 2) 明确区分 Lua 上下文与其他上下文类型(如 context.Context
// 3) 保持向后兼容性
type LuaContext struct {
Engine *LuaEngine
Coroutine *LuaCoroutine
RequestCtx *fasthttp.RequestCtx
Variables map[string]string
// Engine 所属 Lua 引擎
Engine *LuaEngine
// Coroutine 请求级 Lua 协程
Coroutine *LuaCoroutine
// RequestCtx fasthttp 请求上下文
RequestCtx *fasthttp.RequestCtx
// Variables 自定义变量存储
Variables map[string]string
// OutputBuffer 输出缓冲,存储 ngx.say/print 的内容
OutputBuffer []byte
Phase Phase
Exited bool
// Phase 当前处理阶段
Phase Phase
// Exited 是否已通过 ngx.exit 退出
Exited bool
}
// luaContextPool LuaContext 对象池
// luaContextPool LuaContext 对象池。
//
// 使用 sync.Pool 复用 LuaContext 实例,减少频繁创建/销毁带来的 GC 压力。
// 从池中获取的实例必须在 Release() 中完全重置状态。
var luaContextPool = sync.Pool{
New: func() any {
return &LuaContext{
@ -32,7 +65,16 @@ var luaContextPool = sync.Pool{
},
}
// AcquireContext 从池中获取 LuaContext
// AcquireContext 从对象池中获取并初始化 LuaContext。
//
// 参数:
// - engine: Lua 引擎实例
// - req: fasthttp 请求上下文
//
// 返回值:
// - *LuaContext: 已初始化的上下文实例
//
// 注意:使用后必须调用 Release() 放回池中。
func AcquireContext(engine *LuaEngine, req *fasthttp.RequestCtx) *LuaContext {
v := luaContextPool.Get()
lc, ok := v.(*LuaContext)
@ -48,12 +90,20 @@ func AcquireContext(engine *LuaEngine, req *fasthttp.RequestCtx) *LuaContext {
return lc
}
// NewContext 创建请求上下文(从池中获取)
// NewContext 创建请求上下文(从池中获取)。
//
// 该函数是 AcquireContext 的别名,保持向后兼容。
func NewContext(engine *LuaEngine, req *fasthttp.RequestCtx) *LuaContext {
return AcquireContext(engine, req)
}
// InitCoroutine 初始化协程
// InitCoroutine 初始化请求级 Lua 协程。
//
// 从引擎创建新协程并设置沙箱环境。
// 如果协程已存在则跳过创建。
//
// 返回值:
// - error: 协程创建或沙箱设置失败时返回错误
func (c *LuaContext) InitCoroutine() error {
coro, err := c.Engine.NewCoroutine(c.RequestCtx)
if err != nil {
@ -63,7 +113,15 @@ func (c *LuaContext) InitCoroutine() error {
return c.Coroutine.SetupSandbox()
}
// Execute 执行 Lua 脚本
// Execute 执行 Lua 脚本字符串。
//
// 如果协程未初始化,会先自动调用 InitCoroutine()。
//
// 参数:
// - script: Lua 源代码字符串
//
// 返回值:
// - error: 编译或执行失败时返回错误
func (c *LuaContext) Execute(script string) error {
if c.Coroutine == nil {
if err := c.InitCoroutine(); err != nil {
@ -73,7 +131,15 @@ func (c *LuaContext) Execute(script string) error {
return c.Coroutine.Execute(script)
}
// ExecuteFile 执行文件脚本
// ExecuteFile 执行 Lua 脚本文件。
//
// 如果协程未初始化,会先自动调用 InitCoroutine()。
//
// 参数:
// - path: Lua 脚本文件路径
//
// 返回值:
// - error: 编译或执行失败时返回错误
func (c *LuaContext) ExecuteFile(path string) error {
if c.Coroutine == nil {
if err := c.InitCoroutine(); err != nil {
@ -83,45 +149,70 @@ func (c *LuaContext) ExecuteFile(path string) error {
return c.Coroutine.ExecuteFile(path)
}
// SetPhase 设置当前阶段
// SetPhase 设置当前请求处理阶段
func (c *LuaContext) SetPhase(phase Phase) {
c.Phase = phase
}
// GetPhase 获取当前阶段
// GetPhase 获取当前请求处理阶段
func (c *LuaContext) GetPhase() Phase {
return c.Phase
}
// GetVariable 获取变量
// GetVariable 获取自定义变量的值。
//
// 返回值:
// - string: 变量值,不存在时返回空字符串
// - bool: 是否存在
func (c *LuaContext) GetVariable(name string) (string, bool) {
val, ok := c.Variables[name]
return val, ok
}
// SetVariable 设置变量
// SetVariable 设置自定义变量的值。
//
// 参数:
// - name: 变量名
// - value: 变量值
func (c *LuaContext) SetVariable(name, value string) {
c.Variables[name] = value
}
// Write 输出内容
// Write 将数据追加到输出缓冲区。
//
// 数据不会立即发送到客户端,需调用 FlushOutput() 才会刷新。
func (c *LuaContext) Write(data []byte) {
c.OutputBuffer = append(c.OutputBuffer, data...)
}
// Say 输出内容并换行
// Say 将数据追加到输出缓冲区并附加换行符。
//
// 等效于 Write(data) + Write("\n")。
func (c *LuaContext) Say(data string) {
c.OutputBuffer = append(c.OutputBuffer, data...)
c.OutputBuffer = append(c.OutputBuffer, '\n')
}
// Exit 退出请求处理
// Exit 标记请求处理已退出,并设置 HTTP 状态码。
//
// 参数:
// - code: HTTP 状态码
//
// 注意调用此函数后Existed 标记为 true后续不会继续执行中间件链。
func (c *LuaContext) Exit(code int) {
c.Exited = true
c.RequestCtx.SetStatusCode(code)
}
// Release 释放资源并放回池中
// Release 释放协程资源、重置所有可变状态,并将上下文放回对象池。
//
// 该方法必须在请求处理结束时调用。
// 重置操作包括:
// 1. 关闭并清空协程引用
// 2. 清空 Variables map
// 3. 截断 OutputBuffer
// 4. 重置 Phase、Exited 标记
// 5. 清空 Engine 和 RequestCtx 引用
func (c *LuaContext) Release() {
if c.Coroutine != nil {
c.Coroutine.Close()
@ -141,7 +232,10 @@ func (c *LuaContext) Release() {
luaContextPool.Put(c)
}
// FlushOutput 刷新输出到响应
// FlushOutput 将输出缓冲区内容写入 HTTP 响应并清空缓冲。
//
// 如果缓冲区为空或 RequestCtx 为 nil则不执行任何操作。
// 注意:写入错误被忽略,因为此阶段出错时无法向客户端报告。
func (c *LuaContext) FlushOutput() {
if len(c.OutputBuffer) > 0 && c.RequestCtx != nil {
// Write 返回写入的字节数和可能的错误

View File

@ -1,4 +1,17 @@
// Package lua 提供 Lua 脚本嵌入能力
// Package lua 提供 Lua 脚本嵌入能力。
//
// 该文件包含请求级 Lua 协程的实现,包括:
// - Phase请求处理阶段常量对应 nginx 生命周期)
// - LuaCoroutine请求级临时协程每个请求独立创建
// - 沙箱机制:隔离用户脚本,防止全局污染和危险操作
// - ngx API 注册:为每个协程注册完整的 ngx.* API
//
// 注意事项:
// - 协程在 ResumeOK 后变成 dead 状态,不能复用
// - 每个协程拥有独立的 _ENV 沙箱环境
// - 协程库被安全替换,阻止用户创建嵌套协程
//
// 作者xfy
package lua
import (
@ -11,7 +24,9 @@ import (
glua "github.com/yuin/gopher-lua"
)
// Phase 处理阶段
// Phase 处理阶段。
//
// 对应 nginx 请求处理生命周期Lua 脚本可在这些阶段中执行。
type Phase int
// 处理阶段常量,对应 nginx 请求处理生命周期
@ -53,7 +68,14 @@ func (p Phase) String() string {
}
}
// LuaCoroutine 请求级临时协程
// LuaCoroutine 请求级临时协程。
//
// 每个 HTTP 请求创建一个独立的 LuaCoroutine负责
// - 执行用户 Lua 脚本
// - 管理 ngx.* API 实例req、resp、var、log 等)
// - 处理 yield/resume 循环(支持 sleep、cosocket 等异步操作)
// - 维护沙箱环境(独立 _ENV受限 coroutine 库)
//
// 注意:协程在 ResumeOK 后变成 dead 状态,不能复用
//
// 类型命名说明:虽然 lua.LuaCoroutine 存在 stuttering但保持此命名以
@ -61,23 +83,56 @@ func (p Phase) String() string {
// 2) 明确区分 Lua 运行时协程与 Go 协程概念
// 3) 保持向后兼容性
type LuaCoroutine struct {
CreatedAt time.Time
// CreatedAt 协程创建时间
CreatedAt time.Time
// ExecutionContext 执行上下文(含超时控制)
ExecutionContext context.Context
ngxReqAPI *ngxReqAPI
RequestCtx *fasthttp.RequestCtx
Co *glua.LState
ngxVarAPI *ngxVarAPI
ngxRespAPI *ngxRespAPI
ngxLogAPI *ngxLogAPI
Cancel context.CancelFunc
executionCancel context.CancelFunc
Engine *LuaEngine
OutputBuffer []byte
Exited bool
// ngx.req API 实例
ngxReqAPI *ngxReqAPI
// 请求上下文
RequestCtx *fasthttp.RequestCtx
// 底层 Lua 协程gopher-lua LState
Co *glua.LState
// ngx.var API 实例
ngxVarAPI *ngxVarAPI
// ngx.resp API 实例
ngxRespAPI *ngxRespAPI
// ngx.log API 实例
ngxLogAPI *ngxLogAPI
// Cancel 协程取消函数
Cancel context.CancelFunc
// executionCancel 执行超时取消函数
executionCancel context.CancelFunc
// 所属引擎
Engine *LuaEngine
// 输出缓冲
OutputBuffer []byte
// 退出标记ngx.exit 触发)
Exited bool
}
// SetupSandbox 创建 per-request _ENV 沙箱
// 每个请求创建独立的 _ENV 表,通过元表继承全局环境
// SetupSandbox 创建 per-request _ENV 沙箱。
//
// 每个请求创建独立的 _ENV 表,通过元表继承全局环境。
// 安全层:
// - Layer 1 & 2: 替换 coroutine 库,阻止 create/wrap/resume/running
// - Layer 3: 注册 ngx.* APIreq、resp、var、ctx、log、socket、shared、timer、location
//
// 注意事项:
// - 阻止写入全局环境__newindex 返回错误)
// - 不修改引擎级全局表,避免并发竞态条件
func (c *LuaCoroutine) SetupSandbox() error {
// 创建独立的 _ENV 表
env := c.Co.NewTable()
@ -112,8 +167,11 @@ func (c *LuaCoroutine) SetupSandbox() error {
return nil
}
// setupSecureCoroutineLib 创建安全的协程库替换
// 移除 coroutine.create/wrap/resume仅保留 yield/status
// setupSecureCoroutineLib 创建安全的协程库替换。
//
// 移除原始 coroutine 库中的危险函数create、wrap、resume、running
// 仅保留安全的 yield 和 status 函数。
// 被拦截的函数返回友好错误消息,而非直接崩溃。
func (c *LuaCoroutine) setupSecureCoroutineLib() {
// 获取原始 coroutine 表
originalCoroutine := c.Engine.L.GetGlobal("coroutine")
@ -155,8 +213,18 @@ func (c *LuaCoroutine) setupSecureCoroutineLib() {
// 因为协程继承的是引擎全局环境,而我们在协程级别设置了独立的 coroutine 表
}
// setupNgxAPI 创建 ngx API
// 注册 ngx.req、ngx.resp、ngx.var、ngx.ctx、ngx.log、ngx.socket 和 ngx.shared API
// setupNgxAPI 创建并注册 ngx API 到协程环境。
//
// 注册以下 API 子模块:
// - ngx.req请求头/URI/方法/请求体操作
// - ngx.resp响应状态码/头操作
// - ngx.varnginx 变量访问和自定义变量
// - ngx.ctx请求级上下文 table
// - ngx.log日志输出
// - ngx.socketTCP cosocket
// - ngx.shared共享内存字典
// - ngx.timer定时器
// - ngx.location子请求
func (c *LuaCoroutine) setupNgxAPI() {
// 创建 ngx 表
ngx := c.Co.NewTable()
@ -245,8 +313,13 @@ func setSchedulerMode(L *glua.LState, enabled bool) {
L.SetGlobal(schedulerModeKey, glua.LBool(enabled))
}
// IsSchedulerMode 检查 LState 是否处于 scheduler 模式
// 用于在 API 函数中判断是否在 timer callback 上下文中
// IsSchedulerMode 检查 LState 是否处于 scheduler 模式。
//
// 用于在 API 函数中判断是否在 timer callback 上下文中。
// timer callback 环境下某些 API如 ngx.req、ngx.ctx不可用。
//
// 返回值:
// - bool: true 表示处于 scheduler/timer 模式
func IsSchedulerMode(L *glua.LState) bool {
value := L.GetGlobal(schedulerModeKey)
if value == glua.LNil {
@ -258,7 +331,15 @@ func IsSchedulerMode(L *glua.LState) bool {
return false
}
// Execute 在协程中执行 Lua 脚本(支持 Yield/Resume
// Execute 在协程中执行 Lua 脚本(支持 Yield/Resume
//
// 该函数从代码缓存中获取或编译内联脚本,然后执行。
//
// 参数:
// - script: Lua 源代码字符串
//
// 返回值:
// - error: 编译或执行失败时返回错误
func (c *LuaCoroutine) Execute(script string) error {
proto, err := c.Engine.codeCache.GetOrCompileInline(script)
if err != nil {
@ -267,7 +348,13 @@ func (c *LuaCoroutine) Execute(script string) error {
return c.executeProto(proto)
}
// ExecuteFile 执行文件脚本
// ExecuteFile 执行文件中的 Lua 脚本。
//
// 参数:
// - path: Lua 脚本文件路径
//
// 返回值:
// - error: 编译或执行失败时返回错误
func (c *LuaCoroutine) ExecuteFile(path string) error {
proto, err := c.Engine.codeCache.GetOrCompileFile(path)
if err != nil {
@ -276,7 +363,14 @@ func (c *LuaCoroutine) ExecuteFile(path string) error {
return c.executeProto(proto)
}
// executeProto 执行编译后的字节码,处理 yield/resume 循环
// executeProto 执行编译后的字节码,处理 yield/resume 循环。
//
// 该函数是协程执行的核心循环:
// 1. 从 FunctionProto 创建 Lua 函数
// 2. Resume 执行协程
// 3. 如果 yield调用 handleYield 处理并继续 Resume
// 4. 如果 error记录统计并返回错误
// 5. 如果正常结束,更新执行计数
func (c *LuaCoroutine) executeProto(proto *glua.FunctionProto) error {
fn := c.Engine.L.NewFunctionFromProto(proto)
st, execErr, values := c.Engine.L.Resume(c.Co, fn)

View File

@ -1,4 +1,26 @@
// Package lua 提供 Lua 脚本嵌入能力
// Package lua 提供 Lua 脚本嵌入能力。
//
// 该文件包含 Lua 引擎的核心实现,包括:
// - LuaEngine全局引擎每个 HTTP Server 实例持有一个
// - LuaCoroutine请求级临时协程生命周期与请求绑定
// - CodeCache字节码缓存支持 LRU 淘汰和文件变更检测
// - 调度器:专用的 LState 用于定时器回调执行,实现线程隔离
//
// 架构设计:
// 采用 Server 级单 LState + 请求级临时协程架构。
// 所有请求共享一个主 LState 的全局环境,但各自拥有独立的协程状态,
// 确保请求间的数据隔离性和并发安全性。
//
// 主要用途:
// 用于在 fasthttp 服务中嵌入 Lua 脚本,实现动态请求处理、
// 负载均衡、响应过滤等可编程功能,兼容 OpenResty/ngx_lua API 语义。
//
// 注意事项:
// - LuaEngine 非并发安全NewEngine/Close 应在初始化/关闭阶段调用
// - LuaCoroutine 为请求级独占,不可跨请求复用
// - 协程在 ResumeOK 后变成 dead 状态,不能复用
//
// 作者xfy
package lua
import (
@ -12,45 +34,109 @@ import (
glua "github.com/yuin/gopher-lua"
)
// LuaEngine 全局 Lua 引擎
// 每个 HTTP Server 实例持有一个 LuaEngine
// LuaEngine 全局 Lua 引擎。
//
// 每个 HTTP Server 实例持有一个 LuaEngine负责
// - 管理主 LState全局 Lua 状态机)
// - 创建和回收请求级协程LuaCoroutine
// - 管理字节码缓存CodeCache
// - 管理共享字典、定时器、location 等子系统
// - 提供调度器 LState 用于定时器回调的线程隔离执行
//
// 类型命名说明:虽然 lua.LuaEngine 存在 stuttering但保持此命名以
// 1) 与 LuaContext/LuaCoroutine 保持一致的 API 命名风格
// 2) 明确区分 Lua 引擎与其他引擎类型
// 3) 保持向后兼容性
type LuaEngine struct {
coroutinePool sync.Pool
ctx context.Context
codeCache *CodeCache
L *glua.LState
config *Config
schedulerLState *glua.LState
cancel context.CancelFunc
// 主 LState所有协程通过 NewThread 继承其全局环境
L *glua.LState
// 引擎配置
config *Config
// 字节码缓存
codeCache *CodeCache
// 协程池,复用 LuaCoroutine 结构体内存(不复用协程状态)
coroutinePool sync.Pool
// 共享字典管理器
sharedDictManager *SharedDictManager
timerManager *TimerManager
locationManager *LocationManager
callbackQueue chan *CallbackEntry
stats EngineStats
maxCoroutines int
activeCount int32
// 定时器管理器
timerManager *TimerManager
// location 管理器(子请求)
locationManager *LocationManager
// 调度器 LState用于执行定时器回调
schedulerLState *glua.LState
// 回调队列,定时器触发后将回调入队
callbackQueue chan *CallbackEntry
// 上下文及取消函数
ctx context.Context
cancel context.CancelFunc
// 并发控制
maxCoroutines int
activeCount int32
// 引擎统计
stats EngineStats
}
// EngineStats 引擎统计信息
// EngineStats 引擎统计信息。
//
// 记录引擎运行期间的关键指标,用于监控和诊断。
// 所有字段均为原子操作,并发安全。
type EngineStats struct {
// CoroutinesCreated 已创建的协程总数
CoroutinesCreated uint64
CoroutinesClosed uint64
ScriptsExecuted uint64
ScriptsErrors uint64
// CoroutinesClosed 已关闭的协程总数
CoroutinesClosed uint64
// ScriptsExecuted 成功执行的脚本总数
ScriptsExecuted uint64
// ScriptsErrors 执行出错的脚本总数
ScriptsErrors uint64
}
// NewEngine 创建 Lua 引擎
// NewEngine 创建并初始化 Lua 引擎。
//
// 该函数执行以下初始化步骤:
// 1. 创建主 LState配置栈大小和内存优化选项
// 2. 加载安全的标准库base、table、string、math、coroutine
// 3. 按需加载危险库os、io默认禁止 package 库
// 4. 初始化字节码缓存、共享字典、定时器、location 管理器
// 5. 执行协程池预热
//
// 参数:
// - config: 引擎配置,为 nil 时使用 DefaultConfig()
//
// 返回值:
// - *LuaEngine: 初始化完成的引擎实例
// - error: 初始化失败时返回错误
//
// 使用示例:
// engine, err := lua.NewEngine(nil) // 使用默认配置
// if err != nil {
// // 处理初始化错误
// }
// defer engine.Close()
//
// 注意事项:
// - 该方法应在服务启动阶段调用,不应在请求处理路径中调用
// - 返回的引擎需要在使用完毕后调用 Close() 释放资源
func NewEngine(config *Config) (*LuaEngine, error) {
if config == nil {
config = DefaultConfig()
}
// 创建主 LState使用优化后的栈配置
// 步骤1: 创建主 LState使用优化后的栈配置
// 协程通过 NewThread 继承这些配置
L := glua.NewState(glua.Options{
SkipOpenLibs: true, // 禁用默认库,手动加载安全库
@ -58,14 +144,14 @@ func NewEngine(config *Config) (*LuaEngine, error) {
MinimizeStackMemory: config.MinimizeStackMemory,
})
// 加载安全的标准库
// 步骤2: 加载安全的标准库
glua.OpenBase(L)
glua.OpenTable(L)
glua.OpenString(L)
glua.OpenMath(L)
glua.OpenCoroutine(L) // 加载 coroutine 库支持 yield
// 可选加载危险库
// 步骤3: 可选加载危险库
if config.EnableOSLib {
glua.OpenOs(L)
}
@ -93,13 +179,13 @@ func NewEngine(config *Config) (*LuaEngine, error) {
},
}
// 创建定时器管理器(需要在 engine 创建后初始化)
// 步骤4: 创建定时器管理器(需要在 engine 创建后初始化)
engine.timerManager = NewTimerManager(engine)
// 创建 location 管理器
// 步骤5: 创建 location 管理器
engine.locationManager = NewLocationManager()
// 协程池预热:预创建 LuaCoroutine 结构体对象
// 步骤6: 协程池预热:预创建 LuaCoroutine 结构体对象
if config.CoroutinePoolWarmup > 0 {
for i := 0; i < config.CoroutinePoolWarmup; i++ {
engine.coroutinePool.Put(&LuaCoroutine{})
@ -109,8 +195,20 @@ func NewEngine(config *Config) (*LuaEngine, error) {
return engine, nil
}
// Close 关闭引擎
// Close 关闭 Lua 引擎,释放所有资源。
//
// 关闭顺序:
// 1. 取消引擎上下文,通知所有子 goroutine 退出
// 2. 关闭定时器管理器(等待定时器回调排空)
// 3. 关闭共享字典管理器
// 4. 关闭主 LState
//
// 注意:该方法是幂等的,可安全调用多次。
func (e *LuaEngine) Close() {
if e == nil || e.L == nil {
return // 已关闭或 nil
}
e.cancel()
if e.timerManager != nil {
e.timerManager.Close()
@ -121,19 +219,37 @@ func (e *LuaEngine) Close() {
if e.L != nil {
e.L.Close()
}
// 标记为已关闭,防止重复关闭
e.L = nil
}
// NewCoroutine 创建临时协程
// 注意:协程在 ResumeOK 后变成 dead 状态,不能复用
// NewCoroutine 创建请求级临时协程。
//
// 该方法执行以下操作:
// 1. 检查并发限制,超过最大协程数时返回错误
// 2. 通过主 LState.NewThread() 创建底层 Lua 协程
// 3. 从对象池中获取 LuaCoroutine 结构体(复用内存)
// 4. 设置执行上下文(含超时控制)和请求上下文
//
// 参数:
// - req: fasthttp 请求上下文,用于 API 访问ngx.req、ngx.resp 等)
//
// 返回值:
// - *LuaCoroutine: 新创建的协程实例
// - error: 创建失败时返回错误(如超出并发限制)
//
// 注意事项:
// - 协程在 ResumeOK 后变成 dead 状态,不能复用
// - 使用完毕后必须调用 Close() 或 releaseCoroutine() 释放资源
func (e *LuaEngine) NewCoroutine(req *fasthttp.RequestCtx) (*LuaCoroutine, error) {
// 检查并发限制
// 步骤1: 检查并发限制
current := atomic.AddInt32(&e.activeCount, 1)
if current > int32(e.maxCoroutines) {
atomic.AddInt32(&e.activeCount, -1)
return nil, fmt.Errorf("max concurrent coroutines exceeded: %d/%d", current, e.maxCoroutines)
}
// 通过 NewThread 创建协程
// 步骤2: 通过 NewThread 创建协程
// 协程继承主 LState 的全局环境
co, cancel := e.L.NewThread()
if co == nil {
@ -141,7 +257,7 @@ func (e *LuaEngine) NewCoroutine(req *fasthttp.RequestCtx) (*LuaCoroutine, error
return nil, fmt.Errorf("failed to create coroutine")
}
// 从池中获取协程对象结构(复用内存,不复用协程状态)
// 步骤3: 从池中获取协程对象结构(复用内存,不复用协程状态)
coro, ok := e.coroutinePool.Get().(*LuaCoroutine)
if !ok {
coro = &LuaCoroutine{}
@ -153,7 +269,7 @@ func (e *LuaEngine) NewCoroutine(req *fasthttp.RequestCtx) (*LuaCoroutine, error
coro.CreatedAt = time.Now()
coro.ExecutionContext, coro.executionCancel = context.WithTimeout(e.ctx, e.config.MaxExecutionTime)
// 设置 LState 的上下文为执行上下文(用于超时控制)
// 步骤4: 设置 LState 的上下文为执行上下文(用于超时控制)
// 注意:不直接使用 RequestCtx因为 RequestCtx.Done() 依赖服务器连接
// RequestCtx 通过 coro.RequestCtx 字段访问,而不是 L.Context()
co.SetContext(coro.ExecutionContext)
@ -163,43 +279,59 @@ func (e *LuaEngine) NewCoroutine(req *fasthttp.RequestCtx) (*LuaCoroutine, error
return coro, nil
}
// releaseCoroutine 释放协程(内部方法)
// releaseCoroutine 释放协程资源并放回对象池。
//
// 该方法执行以下清理操作:
// 1. 取消执行上下文和协程
// 2. 清空所有引用字段,防止内存泄漏
// 3. 更新活跃协程计数和关闭计数
// 4. 将 LuaCoroutine 结构体放回对象池(仅复用内存)
//
// 注意:这是内部方法,外部应通过 LuaCoroutine.Close() 间接调用。
func (e *LuaEngine) releaseCoroutine(coro *LuaCoroutine) {
if coro == nil {
return
}
// 取消执行上下文
// 步骤1: 取消执行上下文
if coro.executionCancel != nil {
coro.executionCancel()
}
// 取消协程
// 步骤2: 取消协程
if coro.Cancel != nil {
coro.Cancel()
}
// 清理状态
// 步骤3: 清理状态,防止内存泄漏
coro.Co = nil
coro.Cancel = nil
coro.RequestCtx = nil
coro.ExecutionContext = nil
coro.executionCancel = nil
// 更新计数
// 步骤4: 更新计数
atomic.AddInt32(&e.activeCount, -1)
atomic.AddUint64(&e.stats.CoroutinesClosed, 1)
// 放回池中(仅复用 LuaCoroutine 结构体内存)
// 步骤5: 放回池中(仅复用 LuaCoroutine 结构体内存)
e.coroutinePool.Put(coro)
}
// CodeCache 返回字节码缓存
// CodeCache 返回字节码缓存实例。
//
// 返回值:
// - *CodeCache: 字节码缓存,用于脚本编译缓存
func (e *LuaEngine) CodeCache() *CodeCache {
return e.codeCache
}
// Stats 返回引擎统计
// Stats 返回引擎运行统计信息。
//
// 返回值:
// - EngineStats: 包含创建/关闭协程数、执行/出错脚本数的统计快照
//
// 注意:返回值为快照副本,非实时引用。
func (e *LuaEngine) Stats() EngineStats {
return EngineStats{
CoroutinesCreated: atomic.LoadUint64(&e.stats.CoroutinesCreated),
@ -209,46 +341,81 @@ func (e *LuaEngine) Stats() EngineStats {
}
}
// ActiveCoroutines 返回活跃协程数
// ActiveCoroutines 返回当前活跃的协程数量。
//
// 返回值:
// - int32: 当前正在执行的协程数
func (e *LuaEngine) ActiveCoroutines() int32 {
return atomic.LoadInt32(&e.activeCount)
}
// SharedDictManager 返回共享字典管理器
// SharedDictManager 返回共享字典管理器实例。
//
// 返回值:
// - *SharedDictManager: 用于管理多个命名的 SharedDict 实例
func (e *LuaEngine) SharedDictManager() *SharedDictManager {
return e.sharedDictManager
}
// CreateSharedDict 创建共享字典
// CreateSharedDict 创建或获取指定名称的共享字典。
//
// 参数:
// - name: 字典名称
// - maxItems: 字典最大条目数LRU 淘汰阈值)
//
// 返回值:
// - *SharedDict: 共享字典实例
func (e *LuaEngine) CreateSharedDict(name string, maxItems int) *SharedDict {
return e.sharedDictManager.CreateDict(name, maxItems)
}
// TimerManager 返回定时器管理器
// TimerManager 返回定时器管理器实例。
//
// 返回值:
// - *TimerManager: 用于管理 ngx.timer.* API 的定时器
func (e *LuaEngine) TimerManager() *TimerManager {
return e.timerManager
}
// LocationManager 返回 location 管理器
// LocationManager 返回 location 管理器实例。
//
// 返回值:
// - *LocationManager: 用于管理 ngx.location.capture 子请求
func (e *LuaEngine) LocationManager() *LocationManager {
return e.locationManager
}
// InitSchedulerLState 初始化调度器 LState
// 创建专用的 LState 用于定时器回调执行,线程隔离
// InitSchedulerLState 初始化调度器 LState。
//
// 创建专用的 LState 用于定时器回调执行,实现与请求处理线程的隔离。
// 该调度器 LState 仅加载安全子集库,禁止危险操作。
//
// 初始化步骤:
// 1. 创建 LState跳过默认库
// 2. 加载安全库base、table、string、math
// 3. 注册安全的 APIngx.shared、ngx.log、ngx.timer
// 4. 创建回调队列(容量 1024
// 5. 启动调度器 goroutine
//
// 返回值:
// - error: 初始化失败时返回错误
//
// 注意事项:
// - 该方法应在引擎启动后、定时器使用前调用
// - 调度器 LState 与主 LState 共享同一个共享字典管理器
func (e *LuaEngine) InitSchedulerLState() error {
// 创建调度器 LState
// 步骤1: 创建调度器 LState
e.schedulerLState = glua.NewState(glua.Options{
SkipOpenLibs: true, // 禁用默认库,手动加载安全库
})
// 加载安全的标准库
// 步骤2: 加载安全的标准库
glua.OpenBase(e.schedulerLState)
glua.OpenTable(e.schedulerLState)
glua.OpenString(e.schedulerLState)
glua.OpenMath(e.schedulerLState)
// 创建 ngx 表
// 步骤3: 创建 ngx 表并注册安全 API
ngx := e.schedulerLState.NewTable()
e.schedulerLState.SetGlobal("ngx", ngx)
@ -261,17 +428,22 @@ func (e *LuaEngine) InitSchedulerLState() error {
// 注册定时器 API仅安全函数
RegisterTimerAPI(e.schedulerLState, e.timerManager, ngx)
// 创建回调队列
// 步骤4: 创建回调队列
e.callbackQueue = make(chan *CallbackEntry, 1024)
// 启动调度器 goroutine
// 步骤5: 启动调度器 goroutine
go e.SchedulerLoop()
return nil
}
// SchedulerLoop 调度器循环
// 在独立的 goroutine 中运行,处理定时器回调
// SchedulerLoop 调度器循环。
//
// 在独立的 goroutine 中运行,持续监听回调队列和引擎上下文:
// - 从 callbackQueue 接收定时器回调并执行
// - 监听 ctx.Done() 信号,引擎关闭时退出循环
//
// 注意:该方法由 InitSchedulerLState 自动启动,不应手动调用。
func (e *LuaEngine) SchedulerLoop() {
for {
select {
@ -289,7 +461,17 @@ func (e *LuaEngine) SchedulerLoop() {
}
}
// executeCallback 执行定时器回调
// executeCallback 执行单个定时器回调。
//
// 该函数从 FunctionProto 重建 Lua 函数并在调度器 LState 中调用。
// 使用 Protect 模式捕获执行错误,防止回调 panic 导致调度器崩溃。
//
// 参数:
// - entry: 回调队列条目,包含编译后的 FunctionProto 和参数
//
// 注意事项:
// - 使用 defer+recover 捕获 panic保护调度器稳定性
// - 错误在 Protect 模式下被 gopher-lua 内部捕获,不向外传播
func (e *LuaEngine) executeCallback(entry *CallbackEntry) {
defer func() {
if r := recover(); r != nil {
@ -314,8 +496,19 @@ func (e *LuaEngine) executeCallback(entry *CallbackEntry) {
// 错误已在 Protect 模式下被捕获
}
// EnqueueCallback 将回调加入调度队列
// 由 TimerManager 在定时器触发时调用
// EnqueueCallback 将回调加入调度队列。
//
// 由 TimerManager 在定时器触发时调用,将回调推入 callbackQueue。
//
// 参数:
// - entry: 回调条目
//
// 返回值:
// - bool: true 表示入队成功false 表示队列已满(回调被丢弃)
//
// 注意事项:
// - 使用非阻塞发送,队列满时直接返回 false
// - 丢弃的回调不会被重试
func (e *LuaEngine) EnqueueCallback(entry *CallbackEntry) bool {
select {
case e.callbackQueue <- entry:
@ -326,10 +519,17 @@ func (e *LuaEngine) EnqueueCallback(entry *CallbackEntry) bool {
}
}
// CloseScheduler 关闭调度器
// CloseScheduler 关闭调度器。
//
// 执行以下操作:
// 1. 关闭回调队列(阻止新回调入队)
// 2. 关闭调度器 LState
//
// 注意:该方法是幂等的,可安全调用多次。
func (e *LuaEngine) CloseScheduler() {
if e.callbackQueue != nil {
close(e.callbackQueue)
e.callbackQueue = nil
}
if e.schedulerLState != nil {
e.schedulerLState.Close()

View File

@ -1,4 +1,23 @@
// Package lua 提供 Lua 脚本嵌入能力
// Package lua 提供 Lua 脚本嵌入能力。
//
// 该文件实现响应拦截器和延迟写入机制,用于 Lua header_filter/body_filter 阶段。
// 包括:
// - ResponseInterceptor延迟 header 写入,允许在发送前修改响应
// - DelayedResponseWriter包装 fasthttp.RequestCtx 提供延迟写入能力
// - BufferedWriter带缓冲区的写入器支持自动刷新
// - 对象池ResponseInterceptorPool、bufferPool 减少 GC 压力
//
// 执行流程:
// 1. 启用拦截模式后header 和 body 写入被延迟
// 2. HeaderFilter 阶段可执行 Lua 脚本修改响应头
// 3. BodyFilter 阶段可执行 Lua 脚本修改响应体
// 4. Flush 时应用所有修改并发送响应
//
// 注意事项:
// - 流式 bodySetBodyStream无法缓冲header filter 在设置前应用
// - 拦截器使用后必须调用 ReleaseResponseInterceptor 放回池中
//
// 作者xfy
package lua
import (
@ -9,22 +28,52 @@ import (
"github.com/valyala/fasthttp"
)
// ResponseInterceptor 响应拦截器
// 用于延迟 header 写入,允许在发送前修改响应
// ResponseInterceptor 响应拦截器。
//
// 用于延迟 header 写入,允许在 header/body_filter 阶段执行 Lua 脚本
// 修改响应内容后再发送。所有 header 修改、删除和 body 缓冲均在
// Flush 时统一应用。
//
// 线程安全SetHeader 等方法使用 sync.RWMutex 保护。
type ResponseInterceptor struct {
ctx *fasthttp.RequestCtx
// ctx 关联的 fasthttp 请求上下文
ctx *fasthttp.RequestCtx
// headerFilterFunc header 过滤器回调(在 Flush 时执行)
headerFilterFunc func() error
bodyFilterFunc func([]byte) ([]byte, error)
customHeaders map[string]string
bodyBuffer []byte
headersToDelete []string
statusCode int
mu sync.RWMutex
headersWritten bool
intercepted bool
// bodyFilterFunc body 过滤器回调(在 Flush 时执行)
bodyFilterFunc func([]byte) ([]byte, error)
// customHeaders 自定义 header 映射(延迟发送)
customHeaders map[string]string
// headersToDelete 需要删除的 header 列表
headersToDelete []string
// bodyBuffer 缓冲的 body 数据
bodyBuffer []byte
// statusCode 响应状态码
statusCode int
// mu 读写锁
mu sync.RWMutex
// headersWritten 标记 header 是否已发送
headersWritten bool
// intercepted 是否启用拦截模式
intercepted bool
}
// NewResponseInterceptor 创建响应拦截器
// NewResponseInterceptor 创建响应拦截器。
//
// 参数:
// - ctx: fasthttp 请求上下文
//
// 返回值:
// - *ResponseInterceptor: 初始化的拦截器实例
func NewResponseInterceptor(ctx *fasthttp.RequestCtx) *ResponseInterceptor {
return &ResponseInterceptor{
ctx: ctx,
@ -34,44 +83,78 @@ func NewResponseInterceptor(ctx *fasthttp.RequestCtx) *ResponseInterceptor {
}
}
// SetHeaderFilter 设置 header 过滤器回调
// SetHeaderFilter 设置 header 过滤器回调。
//
// 参数:
// - fn: 回调函数,在 Flush 时执行,返回非 nil error 将中断响应
func (ri *ResponseInterceptor) SetHeaderFilter(fn func() error) {
ri.headerFilterFunc = fn
}
// SetBodyFilter 设置 body 过滤器回调
// SetBodyFilter 设置 body 过滤器回调。
//
// 参数:
// - fn: 回调函数,接收原始 body返回修改后的 body
func (ri *ResponseInterceptor) SetBodyFilter(fn func([]byte) ([]byte, error)) {
ri.bodyFilterFunc = fn
}
// SetStatusCode 设置状态码(延迟生效)
// SetStatusCode 设置响应状态码(延迟到 Flush 时生效)。
//
// 参数:
// - code: HTTP 状态码
func (ri *ResponseInterceptor) SetStatusCode(code int) {
ri.statusCode = code
}
// GetStatusCode 获取当前状态码
// GetStatusCode 获取当前状态码。
//
// 返回值:
// - int: 当前设置的状态码
func (ri *ResponseInterceptor) GetStatusCode() int {
return ri.statusCode
}
// SetHeader 设置 header延迟生效
// SetHeader 设置 header延迟到 Flush 时生效)。
//
// 参数:
// - key: header 名称
// - value: header 值
func (ri *ResponseInterceptor) SetHeader(key, value string) {
ri.mu.Lock()
defer ri.mu.Unlock()
ri.customHeaders[key] = value
}
// GetHeader 获取原始 header 值
// GetHeader 获取原始 header 值(直接从响应读取)。
//
// 参数:
// - key: header 名称
//
// 返回值:
// - []byte: header 值
func (ri *ResponseInterceptor) GetHeader(key string) []byte {
return ri.ctx.Response.Header.Peek(key)
}
// DelHeader 删除 header延迟生效
// DelHeader 标记删除 header延迟到 Flush 时生效)。
//
// 参数:
// - key: 要删除的 header 名称
func (ri *ResponseInterceptor) DelHeader(key string) {
ri.headersToDelete = append(ri.headersToDelete, key)
}
// Write 拦截写入操作(缓冲 body延迟 header 发送)
// Write 拦截写入操作(缓冲 body延迟 header 发送)。
//
// 如果未启用拦截模式,直接写入 ctx。
//
// 参数:
// - p: 要写入的数据
//
// 返回值:
// - int: 写入字节数
// - error: 写入错误
func (ri *ResponseInterceptor) Write(p []byte) (int, error) {
if !ri.intercepted {
// 未启用拦截,直接写入
@ -83,12 +166,22 @@ func (ri *ResponseInterceptor) Write(p []byte) (int, error) {
return len(p), nil
}
// WriteString 写入字符串
// WriteString 写入字符串。
//
// 参数:
// - s: 要写入的字符串
//
// 返回值:
// - int: 写入字节数
// - error: 写入错误
func (ri *ResponseInterceptor) WriteString(s string) (int, error) {
return ri.Write([]byte(s))
}
// SetBody 设置 body延迟发送
// SetBody 设置 body延迟发送
//
// 参数:
// - body: 响应体内容
func (ri *ResponseInterceptor) SetBody(body []byte) {
if !ri.intercepted {
ri.ctx.SetBody(body)
@ -97,12 +190,24 @@ func (ri *ResponseInterceptor) SetBody(body []byte) {
ri.bodyBuffer = body
}
// SetBodyString 设置字符串 body
// SetBodyString 设置字符串 body。
//
// 参数:
// - body: 响应体内容
func (ri *ResponseInterceptor) SetBodyString(body string) {
ri.SetBody([]byte(body))
}
// Flush 执行 header/body filter 并发送响应
// Flush 执行 header/body filter 并发送响应。
//
// 执行顺序:
// 1. 执行 header filter 回调
// 2. 应用 header 修改和删除
// 3. 执行 body filter 回调
// 4. 发送最终响应
//
// 返回值:
// - error: filter 执行失败时返回错误
func (ri *ResponseInterceptor) Flush() error {
if ri.headersWritten {
return nil // 已经发送过
@ -143,39 +248,56 @@ func (ri *ResponseInterceptor) Flush() error {
return nil
}
// Enable 启用拦截模式
// Enable 启用拦截模式
func (ri *ResponseInterceptor) Enable() {
ri.intercepted = true
}
// Disable 禁用拦截模式
// Disable 禁用拦截模式
func (ri *ResponseInterceptor) Disable() {
ri.intercepted = false
}
// IsEnabled 检查是否启用拦截
// IsEnabled 检查是否启用拦截。
//
// 返回值:
// - bool: true 表示启用
func (ri *ResponseInterceptor) IsEnabled() bool {
return ri.intercepted
}
// GetBufferedBody 获取当前缓冲的 body
// GetBufferedBody 获取当前缓冲的 body。
//
// 返回值:
// - []byte: 缓冲的 body 数据
func (ri *ResponseInterceptor) GetBufferedBody() []byte {
return ri.bodyBuffer
}
// ClearBody 清空 body 缓冲
// ClearBody 清空 body 缓冲
func (ri *ResponseInterceptor) ClearBody() {
ri.bodyBuffer = nil
}
// DelayedResponseWriter 延迟响应写入器
// 包装 fasthttp.RequestCtx 提供延迟写入能力
// DelayedResponseWriter 延迟响应写入器。
//
// 包装 fasthttp.RequestCtx 和 ResponseInterceptor提供延迟写入能力。
// 用于 Lua header_filter/body_filter 阶段的响应拦截和修改。
type DelayedResponseWriter struct {
ctx *fasthttp.RequestCtx
// ctx 关联的 fasthttp 请求上下文
ctx *fasthttp.RequestCtx
// interceptor 响应拦截器
interceptor *ResponseInterceptor
}
// NewDelayedResponseWriter 创建延迟响应写入器
// NewDelayedResponseWriter 创建延迟响应写入器。
//
// 参数:
// - ctx: fasthttp 请求上下文
//
// 返回值:
// - *DelayedResponseWriter: 初始化的写入器实例
func NewDelayedResponseWriter(ctx *fasthttp.RequestCtx) *DelayedResponseWriter {
return &DelayedResponseWriter{
ctx: ctx,
@ -183,22 +305,32 @@ func NewDelayedResponseWriter(ctx *fasthttp.RequestCtx) *DelayedResponseWriter {
}
}
// EnableFilterPhase 启用 filter phase
// EnableFilterPhase 启用 filter phase(启动拦截模式)。
func (drw *DelayedResponseWriter) EnableFilterPhase() {
drw.interceptor.Enable()
}
// DisableFilterPhase 禁用 filter phase
// DisableFilterPhase 禁用 filter phase
func (drw *DelayedResponseWriter) DisableFilterPhase() {
drw.interceptor.Disable()
}
// GetInterceptor 获取响应拦截器
// GetInterceptor 获取响应拦截器。
//
// 返回值:
// - *ResponseInterceptor: 关联的拦截器
func (drw *DelayedResponseWriter) GetInterceptor() *ResponseInterceptor {
return drw.interceptor
}
// HeaderFilter 执行 header filter 阶段
// HeaderFilter 注册 header filter 阶段的 Lua 脚本。
//
// 参数:
// - script: Lua 脚本
// - luaCtx: Lua 上下文
//
// 返回值:
// - error: 脚本执行失败时返回错误
func (drw *DelayedResponseWriter) HeaderFilter(script string, luaCtx *LuaContext) error {
if !drw.interceptor.IsEnabled() {
return nil
@ -211,7 +343,14 @@ func (drw *DelayedResponseWriter) HeaderFilter(script string, luaCtx *LuaContext
return nil
}
// BodyFilter 执行 body filter 阶段
// BodyFilter 注册 body filter 阶段的 Lua 脚本。
//
// 参数:
// - script: Lua 脚本
// - luaCtx: Lua 上下文
//
// 返回值:
// - error: 脚本执行失败时返回错误
func (drw *DelayedResponseWriter) BodyFilter(script string, luaCtx *LuaContext) error {
if !drw.interceptor.IsEnabled() {
return nil
@ -229,59 +368,82 @@ func (drw *DelayedResponseWriter) BodyFilter(script string, luaCtx *LuaContext)
return nil
}
// Flush 刷新响应
// Flush 刷新响应(执行 filter 并发送)。
//
// 返回值:
// - error: 刷新失败时返回错误
func (drw *DelayedResponseWriter) Flush() error {
return drw.interceptor.Flush()
}
// Write 实现 io.Writer
// Write 实现 io.Writer 接口。
//
// 参数:
// - p: 要写入的数据
//
// 返回值:
// - int: 写入字节数
// - error: 写入错误
func (drw *DelayedResponseWriter) Write(p []byte) (int, error) {
return drw.interceptor.Write(p)
}
// WriteString 写入字符串
// WriteString 写入字符串。
//
// 参数:
// - s: 要写入的字符串
//
// 返回值:
// - int: 写入字节数
// - error: 写入错误
func (drw *DelayedResponseWriter) WriteString(s string) (int, error) {
return drw.interceptor.WriteString(s)
}
// SetStatusCode 设置状态码
// SetStatusCode 设置状态码
func (drw *DelayedResponseWriter) SetStatusCode(code int) {
drw.interceptor.SetStatusCode(code)
}
// SetBody 设置 body
// SetBody 设置 body
func (drw *DelayedResponseWriter) SetBody(body []byte) {
drw.interceptor.SetBody(body)
}
// SetBodyString 设置字符串 body
// SetBodyString 设置字符串 body
func (drw *DelayedResponseWriter) SetBodyString(body string) {
drw.interceptor.SetBodyString(body)
}
// SetHeader 设置 header
// SetHeader 设置 header
func (drw *DelayedResponseWriter) SetHeader(key, value string) {
drw.interceptor.SetHeader(key, value)
}
// GetHeader 获取 header
// GetHeader 获取 header
func (drw *DelayedResponseWriter) GetHeader(key string) []byte {
return drw.interceptor.GetHeader(key)
}
// DelHeader 删除 header
// DelHeader 删除 header
func (drw *DelayedResponseWriter) DelHeader(key string) {
drw.interceptor.DelHeader(key)
}
// ResponseInterceptorPool 响应拦截器
// ResponseInterceptorPool 响应拦截器对象
var ResponseInterceptorPool = sync.Pool{
New: func() interface{} {
return &ResponseInterceptor{}
},
}
// AcquireResponseInterceptor 从池中获取拦截器
// AcquireResponseInterceptor 从池中获取拦截器并初始化。
//
// 参数:
// - ctx: fasthttp 请求上下文
//
// 返回值:
// - *ResponseInterceptor: 初始化后的拦截器
func AcquireResponseInterceptor(ctx *fasthttp.RequestCtx) *ResponseInterceptor {
ri, ok := ResponseInterceptorPool.Get().(*ResponseInterceptor)
if !ok {
@ -299,7 +461,9 @@ func AcquireResponseInterceptor(ctx *fasthttp.RequestCtx) *ResponseInterceptor {
return ri
}
// ReleaseResponseInterceptor 释放拦截器回池
// ReleaseResponseInterceptor 释放拦截器回池。
//
// 清理所有引用和回调,防止内存泄漏。
func ReleaseResponseInterceptor(ri *ResponseInterceptor) {
if ri == nil {
return
@ -314,55 +478,83 @@ func ReleaseResponseInterceptor(ri *ResponseInterceptor) {
ResponseInterceptorPool.Put(ri)
}
// Hijack 支持连接劫持(用于 WebSocket
// Hijack 支持连接劫持(用于 WebSocket
//
// 参数:
// - handler: 劫持后的处理函数
func (drw *DelayedResponseWriter) Hijack(handler fasthttp.HijackHandler) {
drw.ctx.Hijack(handler)
}
// Hijacked 检查是否已劫持
// Hijacked 检查是否已劫持。
//
// 返回值:
// - bool: true 表示已劫持
func (drw *DelayedResponseWriter) Hijacked() bool {
return drw.ctx.Hijacked()
}
// LocalAddr 获取本地地址
// LocalAddr 获取本地地址。
//
// 返回值:
// - net.Addr: 本地网络地址
func (drw *DelayedResponseWriter) LocalAddr() net.Addr {
return drw.ctx.LocalAddr()
}
// RemoteAddr 获取远程地址
// RemoteAddr 获取远程地址。
//
// 返回值:
// - net.Addr: 远程网络地址
func (drw *DelayedResponseWriter) RemoteAddr() net.Addr {
return drw.ctx.RemoteAddr()
}
// SetConnectionClose 设置连接关闭
// SetConnectionClose 设置响应头 Connection: close。
func (drw *DelayedResponseWriter) SetConnectionClose() {
drw.ctx.Response.SetConnectionClose()
}
// BodyWriter 返回 body 写入器
// BodyWriter 返回 body 写入器(适配 io.Writer
//
// 返回值:
// - io.Writer: body 写入器
func (drw *DelayedResponseWriter) BodyWriter() io.Writer {
return &responseWriterAdapter{interceptor: drw.interceptor}
}
// responseWriterAdapter 适配 io.Writer
// responseWriterAdapter 将 ResponseInterceptor 适配 io.Writer 接口。
type responseWriterAdapter struct {
interceptor *ResponseInterceptor
}
// Write 实现 io.Writer 接口。
func (rwa *responseWriterAdapter) Write(p []byte) (n int, err error) {
return rwa.interceptor.Write(p)
}
// ResponseStats 响应统计信息
// ResponseStats 响应统计信息
type ResponseStats struct {
BufferedBytes int
// BufferedBytes 缓冲的 body 字节数
BufferedBytes int
// HeadersModified 修改的 header 数量
HeadersModified int
HeadersDeleted int
BodyModified bool
StatusCode int
// HeadersDeleted 删除的 header 数量
HeadersDeleted int
// BodyModified body 是否被修改
BodyModified bool
// StatusCode 响应状态码
StatusCode int
}
// GetStats 获取响应统计
// GetStats 获取响应统计信息。
//
// 返回值:
// - ResponseStats: 当前统计快照
func (drw *DelayedResponseWriter) GetStats() ResponseStats {
return ResponseStats{
BufferedBytes: len(drw.interceptor.bodyBuffer),
@ -373,17 +565,23 @@ func (drw *DelayedResponseWriter) GetStats() ResponseStats {
}
}
// IsBodyBuffered 检查 body 是否被缓冲
// IsBodyBuffered 检查 body 是否被缓冲。
//
// 返回值:
// - bool: true 表示有缓冲数据
func (drw *DelayedResponseWriter) IsBodyBuffered() bool {
return len(drw.interceptor.bodyBuffer) > 0
}
// GetBufferedBodySize 获取缓冲的 body 大小
// GetBufferedBodySize 获取缓冲的 body 大小。
//
// 返回值:
// - int: 缓冲字节数
func (drw *DelayedResponseWriter) GetBufferedBodySize() int {
return len(drw.interceptor.bodyBuffer)
}
// Reset 重置写入器状态
// Reset 重置写入器状态
func (drw *DelayedResponseWriter) Reset() {
drw.interceptor.bodyBuffer = nil
drw.interceptor.headersWritten = false
@ -392,7 +590,13 @@ func (drw *DelayedResponseWriter) Reset() {
drw.interceptor.headersToDelete = make([]string, 0)
}
// SetBodyStream 设置 body 流
// SetBodyStream 设置 body 流。
//
// 流式 body 无法缓冲,在设置前应用 header filter。
//
// 参数:
// - bodyStream: body 数据源
// - bodySize: body 大小(-1 表示未知)
func (drw *DelayedResponseWriter) SetBodyStream(bodyStream io.Reader, bodySize int) {
if !drw.interceptor.IsEnabled() {
drw.ctx.SetBodyStream(bodyStream, bodySize)
@ -407,7 +611,15 @@ func (drw *DelayedResponseWriter) SetBodyStream(bodyStream io.Reader, bodySize i
drw.interceptor.headersWritten = true
}
// SendFile 发送文件
// SendFile 发送文件。
//
// 在发送前应用 header filter 和自定义 header。
//
// 参数:
// - path: 文件路径
//
// 返回值:
// - error: 发送失败时返回错误
func (drw *DelayedResponseWriter) SendFile(path string) error {
if !drw.interceptor.IsEnabled() {
drw.ctx.SendFile(path)
@ -432,7 +644,13 @@ func (drw *DelayedResponseWriter) SendFile(path string) error {
return nil
}
// Redirect 重定向
// Redirect 重定向。
//
// 在重定向前应用 header filter。
//
// 参数:
// - uri: 目标 URI
// - statusCode: HTTP 重定向状态码
func (drw *DelayedResponseWriter) Redirect(uri string, statusCode int) {
if !drw.interceptor.IsEnabled() {
drw.ctx.Redirect(uri, statusCode)
@ -446,7 +664,7 @@ func (drw *DelayedResponseWriter) Redirect(uri string, statusCode int) {
drw.interceptor.headersWritten = true
}
// bufferPool body 缓冲区
// bufferPool body 缓冲区对象
var bufferPool = sync.Pool{
New: func() interface{} {
buf := make([]byte, 0, 4096) // 4KB 初始容量
@ -454,7 +672,10 @@ var bufferPool = sync.Pool{
},
}
// acquireBuffer 获取缓冲区
// acquireBuffer 获取缓冲区。
//
// 返回值:
// - []byte: 可复用的缓冲区
func acquireBuffer() []byte {
buf, ok := bufferPool.Get().(*[]byte)
if !ok {
@ -463,7 +684,9 @@ func acquireBuffer() []byte {
return *buf
}
// releaseBuffer 释放缓冲区
// releaseBuffer 释放缓冲区回池。
//
// 只回收容量不超过 64KB 的缓冲区,避免池过大。
func releaseBuffer(buf []byte) {
if buf != nil && cap(buf) <= 65536 { // 只回收小缓冲区
buf = buf[:0]
@ -471,15 +694,32 @@ func releaseBuffer(buf []byte) {
}
}
// BufferedWriter 带缓冲的写入器
// BufferedWriter 带缓冲的写入器。
//
// 支持自动刷新(达到 maxSize 时自动调用 flushFunc和手动刷新。
// 使用对象池分配底层缓冲区。
type BufferedWriter struct {
// flushFunc 刷新回调
flushFunc func([]byte) error
buf []byte
maxSize int
// buf 缓冲区
buf []byte
// maxSize 自动刷新的最大大小
maxSize int
// autoFlush 是否启用自动刷新
autoFlush bool
}
// NewBufferedWriter 创建缓冲写入器
// NewBufferedWriter 创建缓冲写入器。
//
// 参数:
// - maxSize: 触发自动刷新的最大缓冲区大小
// - flushFunc: 刷新回调函数
//
// 返回值:
// - *BufferedWriter: 初始化的写入器
func NewBufferedWriter(maxSize int, flushFunc func([]byte) error) *BufferedWriter {
return &BufferedWriter{
buf: acquireBuffer(),
@ -489,7 +729,16 @@ func NewBufferedWriter(maxSize int, flushFunc func([]byte) error) *BufferedWrite
}
}
// Write 写入数据
// Write 写入数据到缓冲区。
//
// 如果缓冲区不足,自动扩容。如果启用 autoFlush 且达到 maxSize自动刷新。
//
// 参数:
// - p: 要写入的数据
//
// 返回值:
// - int: 写入字节数
// - error: 刷新失败时返回错误
func (bw *BufferedWriter) Write(p []byte) (int, error) {
if bw.buf == nil {
bw.buf = acquireBuffer()
@ -520,7 +769,10 @@ func (bw *BufferedWriter) Write(p []byte) (int, error) {
return len(p), nil
}
// Flush 刷新缓冲区
// Flush 刷新缓冲区。
//
// 返回值:
// - error: 刷新失败时返回错误
func (bw *BufferedWriter) Flush() error {
if bw.flushFunc == nil || len(bw.buf) == 0 {
return nil
@ -532,7 +784,10 @@ func (bw *BufferedWriter) Flush() error {
return nil
}
// Close 关闭写入器
// Close 关闭写入器,刷新剩余数据并回收缓冲区。
//
// 返回值:
// - error: 刷新失败时返回错误
func (bw *BufferedWriter) Close() error {
err := bw.Flush()
if bw.buf != nil {
@ -542,12 +797,18 @@ func (bw *BufferedWriter) Close() error {
return err
}
// Size 返回当前缓冲区大小
// Size 返回当前缓冲区大小。
//
// 返回值:
// - int: 缓冲区字节数
func (bw *BufferedWriter) Size() int {
return len(bw.buf)
}
// Bytes 返回当前缓冲区内容(不消费)
// Bytes 返回当前缓冲区内容(不消费)。
//
// 返回值:
// - []byte: 缓冲区内容
func (bw *BufferedWriter) Bytes() []byte {
return bw.buf
}

View File

@ -1,4 +1,17 @@
// Package lua 提供 Lua 中间件实现
// Package lua 提供 Lua 中间件实现。
//
// 该文件包含 fasthttp 中间件的 Lua 集成,包括:
// - LuaMiddleware单阶段 Lua 中间件
// - MultiPhaseLuaMiddleware多阶段 Lua 中间件,支持在请求生命周期不同阶段执行不同脚本
//
// 中间件执行顺序(从外到内):
// rewrite -> access -> content -> header_filter -> body_filter -> log
//
// 注意事项:
// - 中间件在协程创建失败时记录错误并继续执行下一处理器
// - ngx.exit/ngx.redirect 导致的终止视为正常行为,不返回 500 错误
//
// 作者xfy
package lua
import (
@ -9,27 +22,60 @@ import (
"github.com/valyala/fasthttp"
)
// LuaMiddleware Lua 中间件配置
// LuaMiddleware 单阶段 Lua 中间件。
//
// 包装 fasthttp.RequestHandler在执行实际请求处理前运行指定的 Lua 脚本。
// 脚本在独立的协程中执行,拥有自己的沙箱环境。
type LuaMiddleware struct {
engine *LuaEngine
// engine Lua 引擎实例
engine *LuaEngine
// scriptPath Lua 脚本文件路径
scriptPath string
name string
phase Phase
timeout time.Duration
enabled bool
// name 中间件名称
name string
// phase 执行阶段
phase Phase
// timeout 执行超时(当前未使用超时控制)
timeout time.Duration
// enabled 是否启用
enabled bool
}
// LuaMiddlewareConfig Lua 中间件配置
// LuaMiddlewareConfig Lua 中间件配置参数。
type LuaMiddlewareConfig struct {
// ScriptPath Lua 脚本文件路径(必填)
ScriptPath string
Name string
Phase Phase
Timeout time.Duration
Enabled bool
// Name 中间件名称(为空时自动生成)
Name string
// Phase 执行阶段(默认为 PhaseContent
Phase Phase
// Timeout 执行超时(默认为 30 秒)
Timeout time.Duration
// Enabled 是否启用(默认 true
Enabled bool
// EnabledSet 是否显式设置了 Enabled用于区分零值和显式 false
EnabledSet bool
}
// DefaultLuaMiddlewareConfig 默认配置
// DefaultLuaMiddlewareConfig 返回 Lua 中间件的默认配置。
//
// 该函数提供一组合理的默认值,适用于大多数场景:
// - Phase: PhaseContent内容阶段执行
// - Timeout: 30 秒
// - Enabled: true默认启用
//
// 返回值:
// - LuaMiddlewareConfig: 包含默认值的配置结构体
func DefaultLuaMiddlewareConfig() LuaMiddlewareConfig {
return LuaMiddlewareConfig{
Phase: PhaseContent,
@ -38,7 +84,20 @@ func DefaultLuaMiddlewareConfig() LuaMiddlewareConfig {
}
}
// NewLuaMiddleware 创建 Lua 中间件
// NewLuaMiddleware 创建 Lua 中间件实例。
//
// 参数:
// - engine: Lua 引擎实例
// - config: 中间件配置
//
// 返回值:
// - *LuaMiddleware: 中间件实例
// - error: 参数验证失败时返回错误
//
// 注意事项:
// - ScriptPath 不能为空
// - engine 不能为 nil
// - Timeout 为零时自动设置为 30 秒默认值
func NewLuaMiddleware(engine *LuaEngine, config LuaMiddlewareConfig) (*LuaMiddleware, error) {
if engine == nil {
return nil, fmt.Errorf("lua engine is required")
@ -75,12 +134,26 @@ func NewLuaMiddleware(engine *LuaEngine, config LuaMiddlewareConfig) (*LuaMiddle
}, nil
}
// Name 返回中间件名称
// Name 返回中间件名称
func (m *LuaMiddleware) Name() string {
return m.name
}
// Process 包装请求处理器
// Process 包装请求处理器,注入 Lua 脚本执行逻辑。
//
// 执行流程:
// 1. 检查中间件是否启用,未启用则直接调用 next
// 2. 创建 Lua 上下文并设置阶段
// 3. 初始化协程(失败时记录错误并继续)
// 4. 执行 Lua 脚本文件
// 5. 处理 ngx.exit/ngx.redirect 导致的终止(视为正常行为)
// 6. 非 ngx.exit 错误时设置 500 响应
// 7. 刷新输出缓冲
// 8. 如果脚本调用了 ngx.exit不继续执行 next
// 9. 否则继续执行后续处理器
//
// 返回值:
// - fasthttp.RequestHandler: 包装后的处理器
func (m *LuaMiddleware) Process(next fasthttp.RequestHandler) fasthttp.RequestHandler {
return func(ctx *fasthttp.RequestCtx) {
// 检查是否启用
@ -176,20 +249,30 @@ func (m *LuaMiddleware) IsEnabled() bool {
return m.enabled
}
// MultiPhaseLuaMiddleware 多阶段 Lua 中间件
// 支持在不同阶段执行不同的脚本
// MultiPhaseLuaMiddleware 多阶段 Lua 中间件。
//
// 支持在不同请求处理阶段执行不同的 Lua 脚本。
// 阶段按逆序包装,确保执行顺序为:
// rewrite -> access -> content -> header_filter -> body_filter -> log
type MultiPhaseLuaMiddleware struct {
// Lua 引擎
// engine Lua 引擎实例
engine *LuaEngine
// 各阶段脚本配置
// phases 各阶段对应的单阶段中间件
phases map[Phase]*LuaMiddleware
// 名称
// name 中间件名称
name string
}
// NewMultiPhaseLuaMiddleware 创建多阶段 Lua 中间件
// NewMultiPhaseLuaMiddleware 创建多阶段 Lua 中间件。
//
// 参数:
// - engine: Lua 引擎实例
// - name: 中间件名称
//
// 返回值:
// - *MultiPhaseLuaMiddleware: 初始化的多阶段中间件
func NewMultiPhaseLuaMiddleware(engine *LuaEngine, name string) *MultiPhaseLuaMiddleware {
return &MultiPhaseLuaMiddleware{
engine: engine,
@ -198,12 +281,20 @@ func NewMultiPhaseLuaMiddleware(engine *LuaEngine, name string) *MultiPhaseLuaMi
}
}
// Name 返回中间件名称
// Name 返回中间件名称
func (m *MultiPhaseLuaMiddleware) Name() string {
return m.name
}
// AddPhase 添加阶段脚本
// AddPhase 为指定阶段添加 Lua 脚本。
//
// 参数:
// - phase: 处理阶段
// - scriptPath: 脚本文件路径
// - timeout: 执行超时
//
// 返回值:
// - error: 中间件创建失败时返回错误
func (m *MultiPhaseLuaMiddleware) AddPhase(phase Phase, scriptPath string, timeout time.Duration) error {
config := LuaMiddlewareConfig{
ScriptPath: scriptPath,
@ -223,7 +314,12 @@ func (m *MultiPhaseLuaMiddleware) AddPhase(phase Phase, scriptPath string, timeo
return nil
}
// Process 包装请求处理器
// Process 包装请求处理器,按逆序添加各阶段中间件。
//
// 执行顺序(从先到后):
// rewrite -> access -> content -> header_filter -> body_filter -> log
//
// 通过在包装链中逆序注册(从 log 开始),确保实际执行时先执行 rewrite。
func (m *MultiPhaseLuaMiddleware) Process(next fasthttp.RequestHandler) fasthttp.RequestHandler {
handler := next

View File

@ -1,4 +1,18 @@
// Package lua 提供 Lua 中间件配置
// Package lua 提供 Lua 中间件配置解析与验证。
//
// 该文件定义从配置文件YAML加载的中间件配置结构包括
// - MiddlewareConfig完整的中间件配置含脚本列表、全局设置、启用标记
// - ScriptConfig单个脚本的配置路径、阶段、超时、启用标记
// - GlobalLuaSettings全局 Lua 引擎设置(并发、缓存、栈大小等)
// - ParsePhase字符串到 Phase 常量的转换
// - ToEngineConfig将配置文件的设置转换为引擎配置
//
// 注意事项:
// - Phase 值必须为rewrite、access、content、log、header_filter、body_filter
// - 超时时间至少为 1 秒
// - 最大并发协程数至少为 1
//
// 作者xfy
package lua
import (

View File

@ -1,4 +1,13 @@
// Package lua 提供 Lua 引擎的 Mock 实现,用于测试
// Package lua 提供 Lua 引擎的 Mock 实现,用于测试。
//
// 该文件提供 LuaEngine 和 LuaCoroutine 的 Mock 版本,通过函数指针
// 注入自定义行为,便于单元测试中模拟 Lua 脚本执行。
//
// 使用方式:
// - 设置 ExecuteFunc 等字段来自定义方法行为
// - 未设置的函数指针返回零值stub 模式)
//
// 作者xfy
package lua
import (
@ -9,26 +18,64 @@ import (
glua "github.com/yuin/gopher-lua"
)
// MockLuaEngine 是 LuaEngine 的 Mock 实现
// MockLuaEngine 是 LuaEngine 的 Mock 实现。
//
// 通过注入函数指针模拟 LuaEngine 的所有公开方法,
// 未注入的方法返回零值或 nilstub 模式)。
type MockLuaEngine struct {
ExecuteFunc func(script string) error
ExecuteFileFunc func(path string) error
NewCoroutineFunc func(ctx *fasthttp.RequestCtx) (*MockCoroutine, error)
CloseFunc func()
StatsFunc func() EngineStats
ActiveCoroutinesFunc func() int32
CodeCacheFunc func() *CodeCache
SharedDictManagerFunc func() *SharedDictManager
TimerManagerFunc func() *TimerManager
LocationManagerFunc func() *LocationManager
CreateSharedDictFunc func(name string, maxItems int) *SharedDict
// ExecuteFunc 模拟 Execute 方法
ExecuteFunc func(script string) error
// ExecuteFileFunc 模拟 ExecuteFile 方法
ExecuteFileFunc func(path string) error
// NewCoroutineFunc 模拟 NewCoroutine 方法
NewCoroutineFunc func(ctx *fasthttp.RequestCtx) (*MockCoroutine, error)
// CloseFunc 模拟 Close 方法
CloseFunc func()
// StatsFunc 模拟 Stats 方法
StatsFunc func() EngineStats
// ActiveCoroutinesFunc 模拟 ActiveCoroutines 方法
ActiveCoroutinesFunc func() int32
// CodeCacheFunc 模拟 CodeCache 方法
CodeCacheFunc func() *CodeCache
// SharedDictManagerFunc 模拟 SharedDictManager 方法
SharedDictManagerFunc func() *SharedDictManager
// TimerManagerFunc 模拟 TimerManager 方法
TimerManagerFunc func() *TimerManager
// LocationManagerFunc 模拟 LocationManager 方法
LocationManagerFunc func() *LocationManager
// CreateSharedDictFunc 模拟 CreateSharedDict 方法
CreateSharedDictFunc func(name string, maxItems int) *SharedDict
// InitSchedulerLStateFunc 模拟 InitSchedulerLState 方法
InitSchedulerLStateFunc func() error
SchedulerLoopFunc func()
EnqueueCallbackFunc func(entry *CallbackEntry) bool
CloseSchedulerFunc func()
// SchedulerLoopFunc 模拟 SchedulerLoop 方法
SchedulerLoopFunc func()
// EnqueueCallbackFunc 模拟 EnqueueCallback 方法
EnqueueCallbackFunc func(entry *CallbackEntry) bool
// CloseSchedulerFunc 模拟 CloseScheduler 方法
CloseSchedulerFunc func()
}
// Execute 执行脚本
// Execute 执行脚本Mock
//
// 参数:
// - script: Lua 脚本
//
// 返回值:
// - error: ExecuteFunc 的结果,未注入时返回 nil
func (m *MockLuaEngine) Execute(script string) error {
if m.ExecuteFunc != nil {
return m.ExecuteFunc(script)
@ -36,7 +83,13 @@ func (m *MockLuaEngine) Execute(script string) error {
return nil // stub
}
// ExecuteFile 执行文件
// ExecuteFile 执行文件Mock
//
// 参数:
// - path: 脚本文件路径
//
// 返回值:
// - error: ExecuteFileFunc 的结果,未注入时返回 nil
func (m *MockLuaEngine) ExecuteFile(path string) error {
if m.ExecuteFileFunc != nil {
return m.ExecuteFileFunc(path)
@ -44,7 +97,14 @@ func (m *MockLuaEngine) ExecuteFile(path string) error {
return nil // stub
}
// NewCoroutine 创建协程
// NewCoroutine 创建协程Mock
//
// 参数:
// - req: fasthttp 请求上下文
//
// 返回值:
// - *MockCoroutine: 模拟协程
// - error: NewCoroutineFunc 的结果
func (m *MockLuaEngine) NewCoroutine(req *fasthttp.RequestCtx) (*MockCoroutine, error) {
if m.NewCoroutineFunc != nil {
return m.NewCoroutineFunc(req)
@ -52,14 +112,17 @@ func (m *MockLuaEngine) NewCoroutine(req *fasthttp.RequestCtx) (*MockCoroutine,
return &MockCoroutine{}, nil
}
// Close 关闭引擎
// Close 关闭引擎Mock
func (m *MockLuaEngine) Close() {
if m.CloseFunc != nil {
m.CloseFunc()
}
}
// Stats 返回统计
// Stats 返回统计Mock
//
// 返回值:
// - EngineStats: StatsFunc 的结果,未注入时返回零值
func (m *MockLuaEngine) Stats() EngineStats {
if m.StatsFunc != nil {
return m.StatsFunc()
@ -67,7 +130,10 @@ func (m *MockLuaEngine) Stats() EngineStats {
return EngineStats{}
}
// ActiveCoroutines 返回活跃协程数
// ActiveCoroutines 返回活跃协程数Mock
//
// 返回值:
// - int32: ActiveCoroutinesFunc 的结果,未注入时返回 0
func (m *MockLuaEngine) ActiveCoroutines() int32 {
if m.ActiveCoroutinesFunc != nil {
return m.ActiveCoroutinesFunc()
@ -75,7 +141,10 @@ func (m *MockLuaEngine) ActiveCoroutines() int32 {
return 0
}
// CodeCache 返回字节码缓存
// CodeCache 返回字节码缓存Mock
//
// 返回值:
// - *CodeCache: CodeCacheFunc 的结果,未注入时返回 nil
func (m *MockLuaEngine) CodeCache() *CodeCache {
if m.CodeCacheFunc != nil {
return m.CodeCacheFunc()
@ -83,7 +152,10 @@ func (m *MockLuaEngine) CodeCache() *CodeCache {
return nil
}
// SharedDictManager 返回共享字典管理器
// SharedDictManager 返回共享字典管理器Mock
//
// 返回值:
// - *SharedDictManager: SharedDictManagerFunc 的结果,未注入时返回 nil
func (m *MockLuaEngine) SharedDictManager() *SharedDictManager {
if m.SharedDictManagerFunc != nil {
return m.SharedDictManagerFunc()
@ -91,7 +163,10 @@ func (m *MockLuaEngine) SharedDictManager() *SharedDictManager {
return nil
}
// TimerManager 返回定时器管理器
// TimerManager 返回定时器管理器Mock
//
// 返回值:
// - *TimerManager: TimerManagerFunc 的结果,未注入时返回 nil
func (m *MockLuaEngine) TimerManager() *TimerManager {
if m.TimerManagerFunc != nil {
return m.TimerManagerFunc()
@ -99,7 +174,10 @@ func (m *MockLuaEngine) TimerManager() *TimerManager {
return nil
}
// LocationManager 返回 location 管理器
// LocationManager 返回 location 管理器Mock
//
// 返回值:
// - *LocationManager: LocationManagerFunc 的结果,未注入时返回 nil
func (m *MockLuaEngine) LocationManager() *LocationManager {
if m.LocationManagerFunc != nil {
return m.LocationManagerFunc()
@ -107,7 +185,14 @@ func (m *MockLuaEngine) LocationManager() *LocationManager {
return nil
}
// CreateSharedDict 创建共享字典
// CreateSharedDict 创建共享字典Mock
//
// 参数:
// - name: 字典名称
// - maxItems: 最大条目数
//
// 返回值:
// - *SharedDict: CreateSharedDictFunc 的结果,未注入时返回 nil
func (m *MockLuaEngine) CreateSharedDict(name string, maxItems int) *SharedDict {
if m.CreateSharedDictFunc != nil {
return m.CreateSharedDictFunc(name, maxItems)
@ -115,7 +200,10 @@ func (m *MockLuaEngine) CreateSharedDict(name string, maxItems int) *SharedDict
return nil
}
// InitSchedulerLState 初始化调度器 LState
// InitSchedulerLState 初始化调度器 LStateMock
//
// 返回值:
// - error: InitSchedulerLStateFunc 的结果,未注入时返回 nil
func (m *MockLuaEngine) InitSchedulerLState() error {
if m.InitSchedulerLStateFunc != nil {
return m.InitSchedulerLStateFunc()
@ -123,14 +211,20 @@ func (m *MockLuaEngine) InitSchedulerLState() error {
return nil
}
// SchedulerLoop 调度器循环
// SchedulerLoop 调度器循环Mock
func (m *MockLuaEngine) SchedulerLoop() {
if m.SchedulerLoopFunc != nil {
m.SchedulerLoopFunc()
}
}
// EnqueueCallback 将回调加入调度队列
// EnqueueCallback 将回调加入调度队列Mock
//
// 参数:
// - entry: 回调条目
//
// 返回值:
// - bool: EnqueueCallbackFunc 的结果,未注入时返回 false
func (m *MockLuaEngine) EnqueueCallback(entry *CallbackEntry) bool {
if m.EnqueueCallbackFunc != nil {
return m.EnqueueCallbackFunc(entry)
@ -138,33 +232,65 @@ func (m *MockLuaEngine) EnqueueCallback(entry *CallbackEntry) bool {
return false
}
// CloseScheduler 关闭调度器
// CloseScheduler 关闭调度器Mock
func (m *MockLuaEngine) CloseScheduler() {
if m.CloseSchedulerFunc != nil {
m.CloseSchedulerFunc()
}
}
// MockCoroutine 是 LuaCoroutine 的 Mock 实现
// MockCoroutine 是 LuaCoroutine 的 Mock 实现。
//
// 通过注入函数指针模拟 LuaCoroutine 的核心方法,
// 同时包含模拟字段供测试验证。
type MockCoroutine struct {
ExecuteFunc func(script string) error
ExecuteFileFunc func(path string) error
SetupSandboxFunc func() error
CloseFunc func()
HandleYieldFunc func(values []glua.LValue) ([]glua.LValue, error)
// ExecuteFunc 模拟 Execute 方法
ExecuteFunc func(script string) error
// 模拟字段
CreatedAt time.Time
// ExecuteFileFunc 模拟 ExecuteFile 方法
ExecuteFileFunc func(path string) error
// SetupSandboxFunc 模拟 SetupSandbox 方法
SetupSandboxFunc func() error
// CloseFunc 模拟 Close 方法
CloseFunc func()
// HandleYieldFunc 模拟 handleYield 方法
HandleYieldFunc func(values []glua.LValue) ([]glua.LValue, error)
// CreatedAt 协程创建时间
CreatedAt time.Time
// ExecutionContext 执行上下文
ExecutionContext context.Context
Engine *MockLuaEngine
Co *glua.LState
Cancel context.CancelFunc
RequestCtx *fasthttp.RequestCtx
OutputBuffer []byte
Exited bool
// Engine 所属引擎
Engine *MockLuaEngine
// Co 底层 Lua 协程
Co *glua.LState
// Cancel 取消函数
Cancel context.CancelFunc
// RequestCtx fasthttp 请求上下文
RequestCtx *fasthttp.RequestCtx
// OutputBuffer 输出缓冲
OutputBuffer []byte
// Exited 退出标记
Exited bool
}
// Execute 执行脚本
// Execute 执行脚本Mock
//
// 参数:
// - script: Lua 脚本
//
// 返回值:
// - error: ExecuteFunc 的结果,未注入时返回 nil
func (c *MockCoroutine) Execute(script string) error {
if c.ExecuteFunc != nil {
return c.ExecuteFunc(script)
@ -172,7 +298,13 @@ func (c *MockCoroutine) Execute(script string) error {
return nil
}
// ExecuteFile 执行文件
// ExecuteFile 执行文件Mock
//
// 参数:
// - path: 脚本文件路径
//
// 返回值:
// - error: ExecuteFileFunc 的结果,未注入时返回 nil
func (c *MockCoroutine) ExecuteFile(path string) error {
if c.ExecuteFileFunc != nil {
return c.ExecuteFileFunc(path)
@ -180,7 +312,10 @@ func (c *MockCoroutine) ExecuteFile(path string) error {
return nil
}
// SetupSandbox 设置沙箱
// SetupSandbox 设置沙箱Mock
//
// 返回值:
// - error: SetupSandboxFunc 的结果,未注入时返回 nil
func (c *MockCoroutine) SetupSandbox() error {
if c.SetupSandboxFunc != nil {
return c.SetupSandboxFunc()
@ -188,7 +323,7 @@ func (c *MockCoroutine) SetupSandbox() error {
return nil
}
// Close 关闭协程
// Close 关闭协程Mock
func (c *MockCoroutine) Close() {
if c.CloseFunc != nil {
c.CloseFunc()

View File

@ -1,22 +1,51 @@
// Package lua 提供 Lua API 注册辅助函数
// Package lua 提供 Lua API 注册辅助函数。
//
// 该文件提供批量注册 API 方法到 Lua 表的工具函数,包括:
// - RegisterAPIMethods批量注册方法到 Lua 表
// - RegisterUnsafeAPI注册不可用于 timer context 的安全桩函数
//
// 注意事项:
// - RegisterUnsafeAPI 用于在 timer callback 等受限上下文中,
// 将不可用的 API 替换为返回错误的桩函数
//
// 作者xfy
package lua
import glua "github.com/yuin/gopher-lua"
// APIMethod 表示一个 Lua API 方法
// APIMethod 表示一个 Lua API 方法
type APIMethod struct {
Func func(*glua.LState) int
// Name 方法名(在 Lua 表中暴露的名称)
Name string
// Func Lua 函数实现
Func func(*glua.LState) int
}
// RegisterAPIMethods 批量注册 API 方法到 Lua 表
// RegisterAPIMethods 批量注册 API 方法到 Lua 表。
//
// 遍历方法列表,将每个方法注册为 Lua 函数并设置到目标表中。
//
// 参数:
// - L: Lua 状态
// - tbl: 目标 Lua 表
// - methods: 要注册的方法列表
func RegisterAPIMethods(L *glua.LState, tbl *glua.LTable, methods []APIMethod) {
for _, m := range methods {
tbl.RawSetString(m.Name, L.NewFunction(m.Func))
}
}
// RegisterUnsafeAPI 注册不可用于 timer context 的安全桩
// RegisterUnsafeAPI 注册不可用于 timer context 的安全桩。
//
// 在 timer callback 等受限上下文中,某些 API如 ngx.req、ngx.resp不可用。
// 此函数将指定 API 的所有方法替换为返回错误的桩函数。
//
// 参数:
// - L: Lua 状态
// - ngx: ngx 表(父表)
// - apiName: API 子模块名称(如 "req"、"resp"
// - methods: 要替换为桩的方法名列表
func RegisterUnsafeAPI(L *glua.LState, ngx *glua.LTable, apiName string, methods []string) {
tbl := L.NewTable()
for _, m := range methods {

View File

@ -1,31 +1,73 @@
// Package lua 提供 Lua 脚本嵌入能力
// Package lua 提供 Lua 脚本嵌入能力。
//
// 该文件实现共享内存字典SharedDict包括
// - SharedDict并发安全的 key-value 存储,带 LRU 淘汰策略
// - 过期机制:支持 TTL 过期,惰性删除 + 主动清理
// - 数值操作Incr 支持原子自增
//
// 注意事项:
// - 所有公开方法均为并发安全(使用 sync.Mutex
// - 容量满时优先淘汰过期条目,其次淘汰 LRU 最久未使用的条目
//
// 作者xfy
package lua
import (
"container/list"
"fmt"
"sync"
"time"
)
// SharedDict 共享内存字典
// 支持并发安全的 key-value 存储,带 LRU 汰出策略
// SharedDict 共享内存字典。
//
// 提供并发安全的 key-value 存储,兼容 ngx.shared.DICT API。
// 特性:
// - 支持 TTL 过期(惰性删除 + FlushExpired 主动清理)
// - LRU 淘汰策略(容量满时淘汰最久未使用的条目)
// - 所有公开方法均为并发安全
type SharedDict struct {
data map[string]*sharedDictEntry
lruList *list.List
name string
// data 键值存储映射
data map[string]*sharedDictEntry
// lruList LRU 链表,用于淘汰策略
lruList *list.List
// 字典名称
name string
// 最大条目数
maxItems int
mu sync.Mutex
// 互斥锁
mu sync.Mutex
}
// sharedDictEntry 字典条目
// sharedDictEntry 字典条目。
//
// 存储单个 key-value 对及其过期信息和 LRU 链表引用。
type sharedDictEntry struct {
// expiredAt 过期时间,零值表示永不过期
expiredAt time.Time
element *list.Element
key string
value string
// element LRU 链表中的节点引用
element *list.Element
// 键名
key string
// 值(字符串类型)
value string
}
// NewSharedDict 创建共享字典
// NewSharedDict 创建新的共享字典实例。
//
// 参数:
// - name: 字典名称(用于标识)
// - maxItems: 最大条目数(达到上限时触发 LRU 淘汰)
//
// 返回值:
// - *SharedDict: 初始化的字典实例
func NewSharedDict(name string, maxItems int) *SharedDict {
return &SharedDict{
name: name,
@ -35,8 +77,14 @@ func NewSharedDict(name string, maxItems int) *SharedDict {
}
}
// Get 获取值
// 返回 value, expired, err
// Get 获取指定键的值。
//
// 返回值:
// - value: 存储的值,不存在或过期时返回空字符串
// - expired: 是否存在但已过期true=存在但过期false=不存在或未过期)
// - err: 错误信息(当前实现始终返回 nil
//
// 注意:访问成功时会更新 LRU 位置(移到链表前端)。
func (d *SharedDict) Get(key string) (string, bool, error) {
d.mu.Lock()
defer d.mu.Unlock()
@ -59,8 +107,19 @@ func (d *SharedDict) Get(key string) (string, bool, error) {
return entry.value, false, nil
}
// Set 设置值
// 返回 ok, err (ok=false 表示容量满且无法淘汰)
// Set 设置键值对。
//
// 如果键已存在,更新其值和过期时间。
// 如果是新键且容量已满,先尝试淘汰过期条目,再淘汰 LRU 条目。
//
// 参数:
// - key: 键名
// - value: 值
// - ttl: 过期时间,零值表示永不过期
//
// 返回值:
// - ok: true 表示设置成功false 表示容量满且无法淘汰
// - err: 错误信息(当前实现始终返回 nil
func (d *SharedDict) Set(key, value string, ttl time.Duration) (bool, error) {
d.mu.Lock()
defer d.mu.Unlock()
@ -108,8 +167,18 @@ func (d *SharedDict) Set(key, value string, ttl time.Duration) (bool, error) {
return true, nil
}
// Add 添加值(仅在不存在时设置)
// 返回 ok, err (ok=false 表示已存在或容量满)
// Add 添加键值对(仅在键不存在时设置)。
//
// 与 Set 的区别如果键已存在包括已过期的条目Add 会返回 false。
//
// 参数:
// - key: 键名
// - value: 值
// - ttl: 过期时间
//
// 返回值:
// - ok: true 表示添加成功false 表示已存在或容量满
// - err: 错误信息
func (d *SharedDict) Add(key, value string, ttl time.Duration) (bool, error) {
d.mu.Lock()
defer d.mu.Unlock()
@ -147,8 +216,18 @@ func (d *SharedDict) Add(key, value string, ttl time.Duration) (bool, error) {
return true, nil
}
// Incr 自增数值
// 返回 new_value, err
// Incr 将指定键的值作为整数自增。
//
// 如果键不存在,创建初始值为 0 后再自增。
// 如果值不是纯数字字符串,返回 0非错误
//
// 参数:
// - key: 键名
// - increment: 自增量(可为负数)
//
// 返回值:
// - newValue: 自增后的值
// - err: 错误信息(当前实现始终返回 nil
func (d *SharedDict) Incr(key string, increment int) (int, error) {
d.mu.Lock()
defer d.mu.Unlock()
@ -178,7 +257,7 @@ func (d *SharedDict) Incr(key string, increment int) (int, error) {
var current int
for _, c := range entry.value {
if c < '0' || c > '9' {
return 0, nil // 不是数值
return 0, fmt.Errorf("not a number")
}
current = current*10 + int(c-'0')
}
@ -190,7 +269,7 @@ func (d *SharedDict) Incr(key string, increment int) (int, error) {
return newValue, nil
}
// Delete 删除条目
// Delete 删除指定键。
func (d *SharedDict) Delete(key string) error {
d.mu.Lock()
defer d.mu.Unlock()
@ -201,7 +280,7 @@ func (d *SharedDict) Delete(key string) error {
return nil
}
// FlushAll 清空所有条目
// FlushAll 清空字典中的所有条目
func (d *SharedDict) FlushAll() error {
d.mu.Lock()
defer d.mu.Unlock()
@ -211,8 +290,10 @@ func (d *SharedDict) FlushAll() error {
return nil
}
// FlushExpired 清除所有过期条目
// 返回清除的条目数
// FlushExpired 清除所有过期条目。
//
// 返回值:
// - int: 被清除的条目数
func (d *SharedDict) FlushExpired() int {
d.mu.Lock()
defer d.mu.Unlock()
@ -220,27 +301,32 @@ func (d *SharedDict) FlushExpired() int {
return d.evictExpired()
}
// Size 返回当前条目数
// Size 返回当前条目数(包括已过期的条目)。
func (d *SharedDict) Size() int {
d.mu.Lock()
defer d.mu.Unlock()
return len(d.data)
}
// FreeSlots 返回剩余容量
// FreeSlots 返回剩余可添加的条目数。
func (d *SharedDict) FreeSlots() int {
d.mu.Lock()
defer d.mu.Unlock()
return d.maxItems - len(d.data)
}
// deleteEntry 删除条目(内部方法,已持有锁)
// deleteEntry 删除指定条目(内部方法,已持有锁)
func (d *SharedDict) deleteEntry(entry *sharedDictEntry) {
d.lruList.Remove(entry.element)
delete(d.data, entry.key)
}
// evictExpired 淘汰过期条目(内部方法,已持有锁)
// evictExpired 淘汰所有过期条目(内部方法,需已持有锁)。
//
// 从 LRU 链表尾部(最久未使用)开始扫描,删除过期条目。
//
// 返回值:
// - int: 被淘汰的条目数
func (d *SharedDict) evictExpired() int {
now := time.Now()
count := 0
@ -278,7 +364,10 @@ func (d *SharedDict) evictExpired() int {
return count
}
// evictLRU 淘汰 LRU 最久未使用的条目(内部方法,已持有锁)
// evictLRU 淘汰 LRU 最久未使用的条目(内部方法,需已持有锁)。
//
// 返回值:
// - bool: true 表示成功淘汰false 表示链表为空
func (d *SharedDict) evictLRU() bool {
if d.lruList.Len() == 0 {
return false

View File

@ -1,4 +1,20 @@
// Package lua 提供 Cosocket 管理功能
// Package lua 提供 Cosocket 管理功能。
//
// 该文件实现 TCP Cosocket 管理器,包括:
// - SocketStateSocket 生命周期状态机
// - SocketOperation单个 Socket 操作的封装,支持异步等待
// - CosocketManager操作生命周期管理、超时检测、统计追踪
//
// 特性:
// - 原子操作标记完成状态,避免竞态条件
// - 后台清理循环定期检测并取消超时操作
// - 统计信息使用 atomic 操作保证并发安全
//
// 注意事项:
// - 操作一旦标记完成CompareAndSwap不可重复完成
// - 管理器关闭时会取消所有未完成的操作
//
// 作者xfy
package lua
import (
@ -9,9 +25,12 @@ import (
"time"
)
// SocketState 表示 socket 操作状态
// SocketState 表示 Socket 操作状态。
//
// 状态机流转Idle -> Connecting -> Connected -> Sending/Receiving -> Closing -> Closed
type SocketState int
// Socket 生命周期状态常量
const (
// SocketStateIdle 空闲状态
SocketStateIdle SocketState = iota
@ -31,6 +50,7 @@ const (
SocketStateError
)
// String 返回状态的字符串表示
func (s SocketState) String() string {
switch s {
case SocketStateIdle:
@ -57,6 +77,7 @@ func (s SocketState) String() string {
// OperationType 操作类型
type OperationType string
// 操作类型常量
const (
// OpConnect 连接操作
OpConnect OperationType = "connect"
@ -68,27 +89,60 @@ const (
OpClose OperationType = "close"
)
// SocketOperation 表示一个 socket 操作
// SocketOperation 表示一个 Socket 操作。
//
// 封装单个异步操作的生命周期,包括创建、执行、完成和等待。
// 使用 atomic 操作标记完成状态,通过 Done channel 通知等待方。
type SocketOperation struct {
CreatedAt time.Time
// ID 操作唯一标识
ID uint64
// Type 操作类型
Type OperationType
// State 当前 Socket 状态
State SocketState
// Socket 关联的 TCP Socket
Socket *TCPSocket
// Timeout 操作超时时间
Timeout time.Duration
// CreatedAt 操作创建时间
CreatedAt time.Time
// LastActivity 最后活动时间(用于超时检测)
LastActivity time.Time
Error error
Result interface{}
Socket *TCPSocket
Done chan struct{}
Type OperationType
ID uint64
State SocketState
Timeout time.Duration
completed int32
// Result 操作结果
Result interface{}
// Error 操作错误
Error error
// Done 完成信号 channel操作完成时关闭
Done chan struct{}
// completed 原子标记1=已完成0=未完成
completed int32
}
// IsCompleted 检查操作是否已完成
// IsCompleted 检查操作是否已完成。
//
// 返回值:
// - bool: true 表示已完成
func (op *SocketOperation) IsCompleted() bool {
return atomic.LoadInt32(&op.completed) == 1
}
// Complete 标记操作完成
// Complete 标记操作完成。
//
// 使用 CompareAndSwap 确保只完成一次,完成后关闭 Done channel 通知等待方。
//
// 参数:
// - result: 操作结果
// - err: 操作错误nil 表示成功)
func (op *SocketOperation) Complete(result interface{}, err error) {
if atomic.CompareAndSwapInt32(&op.completed, 0, 1) {
op.Result = result
@ -97,7 +151,16 @@ func (op *SocketOperation) Complete(result interface{}, err error) {
}
}
// Wait 等待操作完成
// Wait 等待操作完成。
//
// 阻塞直到操作完成或上下文取消。
//
// 参数:
// - ctx: 取消上下文
//
// 返回值:
// - interface{}: 操作结果
// - error: 操作错误或上下文取消错误
func (op *SocketOperation) Wait(ctx context.Context) (interface{}, error) {
select {
case <-op.Done:
@ -107,52 +170,81 @@ func (op *SocketOperation) Wait(ctx context.Context) (interface{}, error) {
}
}
// Touch 更新活动时间
// Touch 更新活动时间(用于超时检测)
func (op *SocketOperation) Touch() {
op.LastActivity = time.Now()
}
// CosocketStats Cosocket 统计信息
// CosocketStats Cosocket 统计信息。
//
// 包含操作和 Socket 的创建、活跃、超时、错误等统计。
type CosocketStats struct {
// 总操作数
// TotalOperations 总操作数
TotalOperations uint64
// 活跃操作数
// ActiveOperations 当前活跃操作数
ActiveOperations uint64
// 超时操作数
// TimeoutOperations 超时操作数
TimeoutOperations uint64
// 错误操作数
// ErrorOperations 错误操作数
ErrorOperations uint64
// 当前 socket 数
// ActiveSockets 当前活跃 Socket 数
ActiveSockets uint64
// 总创建 socket
// TotalSocketsCreated 累计创建的 Socket 总
TotalSocketsCreated uint64
// 总关闭 socket
// TotalSocketsClosed 累计关闭的 Socket 总
TotalSocketsClosed uint64
}
// CosocketManager Cosocket 管理器
// CosocketManager Cosocket 管理器。
//
// 负责管理 Socket 操作的生命周期,包括:
// - 创建和跟踪异步操作
// - 检测并清理超时操作
// - 统计操作和 Socket 的使用情况
type CosocketManager struct {
ctx context.Context
operations map[uint64]*SocketOperation
timeoutChecker *time.Ticker
cancel context.CancelFunc
stats CosocketStats
nextID uint64
defaultTimeout time.Duration
// ctx 上下文(用于控制清理循环)
ctx context.Context
// cancel 取消函数
cancel context.CancelFunc
// operations 进行中的操作映射ID -> 操作)
operations map[uint64]*SocketOperation
// mu 读写锁
mu sync.RWMutex
// nextID 下一个操作 ID
nextID uint64
// defaultTimeout 默认超时时间
defaultTimeout time.Duration
// timeoutChecker 超时检查定时器
timeoutChecker *time.Ticker
// cleanupInterval 清理间隔
cleanupInterval time.Duration
mu sync.RWMutex
// stats 统计信息
stats CosocketStats
}
// DefaultCosocketManager 全局默认管理器
// DefaultCosocketManager 全局默认 Cosocket 管理器
var DefaultCosocketManager = NewCosocketManager()
// NewCosocketManager 创建新的 Cosocket 管理器
// NewCosocketManager 创建新的 Cosocket 管理器。
//
// 启动后台清理循环,每 30 秒检查一次超时操作。
//
// 返回值:
// - *CosocketManager: 初始化的管理器实例
func NewCosocketManager() *CosocketManager {
ctx, cancel := context.WithCancel(context.Background())
cm := &CosocketManager{
@ -171,7 +263,17 @@ func NewCosocketManager() *CosocketManager {
return cm
}
// StartOperation 开始一个新的 socket 操作
// StartOperation 开始一个新的 Socket 操作。
//
// 创建操作实例,分配唯一 ID注册到管理器中。
//
// 参数:
// - socket: 关联的 TCP Socket
// - opType: 操作类型
// - timeout: 超时时间,零值时使用默认超时
//
// 返回值:
// - *SocketOperation: 新创建的操作实例
func (cm *CosocketManager) StartOperation(socket *TCPSocket, opType OperationType, timeout time.Duration) *SocketOperation {
if timeout <= 0 {
timeout = cm.defaultTimeout
@ -201,7 +303,14 @@ func (cm *CosocketManager) StartOperation(socket *TCPSocket, opType OperationTyp
return op
}
// CompleteOperation 完成操作
// CompleteOperation 完成指定 ID 的操作。
//
// 从管理器中移除操作,标记完成,更新统计。
//
// 参数:
// - id: 操作 ID
// - result: 操作结果
// - err: 操作错误
func (cm *CosocketManager) CompleteOperation(id uint64, result interface{}, err error) {
cm.mu.Lock()
op, exists := cm.operations[id]
@ -219,14 +328,20 @@ func (cm *CosocketManager) CompleteOperation(id uint64, result interface{}, err
}
}
// GetOperation 获取操作
// GetOperation 获取指定 ID 的操作。
//
// 参数:
// - id: 操作 ID
//
// 返回值:
// - *SocketOperation: 操作实例,不存在时返回 nil
func (cm *CosocketManager) GetOperation(id uint64) *SocketOperation {
cm.mu.RLock()
defer cm.mu.RUnlock()
return cm.operations[id]
}
// cleanupLoop 清理循环
// cleanupLoop 清理循环,定期检测超时操作。
func (cm *CosocketManager) cleanupLoop() {
for {
select {
@ -238,7 +353,9 @@ func (cm *CosocketManager) cleanupLoop() {
}
}
// cleanup 清理超时操作
// cleanup 清理超时操作。
//
// 扫描所有未完成的操作,标记超过 LastActivity + Timeout 的操作为超时。
func (cm *CosocketManager) cleanup() {
now := time.Now()
timeoutOps := make([]*SocketOperation, 0)
@ -257,7 +374,10 @@ func (cm *CosocketManager) cleanup() {
}
}
// Stats 获取统计信息
// Stats 获取 Cosocket 统计信息。
//
// 返回值:
// - CosocketStats: 当前统计快照
func (cm *CosocketManager) Stats() CosocketStats {
return CosocketStats{
TotalOperations: atomic.LoadUint64(&cm.stats.TotalOperations),
@ -270,12 +390,17 @@ func (cm *CosocketManager) Stats() CosocketStats {
}
}
// SetDefaultTimeout 设置默认超时
// SetDefaultTimeout 设置默认超时时间。
//
// 参数:
// - timeout: 新的默认超时
func (cm *CosocketManager) SetDefaultTimeout(timeout time.Duration) {
cm.defaultTimeout = timeout
}
// Close 关闭管理器
// Close 关闭 Cosocket 管理器。
//
// 停止清理循环,取消所有未完成的操作。
func (cm *CosocketManager) Close() {
cm.cancel()
cm.timeoutChecker.Stop()
@ -294,19 +419,27 @@ func (cm *CosocketManager) Close() {
}
}
// TrackSocketCreated 跟踪 socket 创建
// TrackSocketCreated 跟踪 Socket 创建(更新统计)。
func (cm *CosocketManager) TrackSocketCreated() {
atomic.AddUint64(&cm.stats.TotalSocketsCreated, 1)
atomic.AddUint64(&cm.stats.ActiveSockets, 1)
}
// TrackSocketClosed 跟踪 socket 关闭
// TrackSocketClosed 跟踪 Socket 关闭(更新统计)。
func (cm *CosocketManager) TrackSocketClosed() {
atomic.AddUint64(&cm.stats.TotalSocketsClosed, 1)
atomic.AddUint64(&cm.stats.ActiveSockets, ^uint64(0))
}
// TCPAddr 解析 TCP 地址
// TCPAddr 解析 TCP 地址。
//
// 参数:
// - host: 主机地址
// - port: 端口号
//
// 返回值:
// - *net.TCPAddr: 解析后的 TCP 地址
// - error: 解析失败时返回错误
func (cm *CosocketManager) TCPAddr(host string, port int) (*net.TCPAddr, error) {
return &net.TCPAddr{
IP: net.ParseIP(host),