From ad177e9640786e1434b7398c601f9a353e01317f Mon Sep 17 00:00:00 2001 From: xfy Date: Mon, 20 Apr 2026 10:59:17 +0800 Subject: [PATCH] =?UTF-8?q?docs(lua):=20=E4=B8=BA=20Lua=20API=20=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E6=B7=BB=E5=8A=A0=E6=A0=87=E5=87=86=E5=8C=96=20godoc?= =?UTF-8?q?=20=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为所有 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 --- internal/lua/api_balancer.go | 73 ++++- internal/lua/api_ctx.go | 31 ++- internal/lua/api_location.go | 71 ++++- internal/lua/api_log.go | 39 ++- internal/lua/api_req.go | 21 +- internal/lua/api_resp.go | 20 +- internal/lua/api_shared_dict.go | 158 ++++++++--- internal/lua/api_socket_tcp.go | 98 +++++-- internal/lua/api_timer.go | 224 +++++++++++++--- internal/lua/api_var.go | 35 ++- internal/lua/cache.go | 162 ++++++++++-- internal/lua/config.go | 75 ++++-- internal/lua/context.go | 140 ++++++++-- internal/lua/coroutine.go | 146 ++++++++-- internal/lua/engine.go | 320 +++++++++++++++++----- internal/lua/filter_writer.go | 425 ++++++++++++++++++++++++------ internal/lua/middleware.go | 146 ++++++++-- internal/lua/middleware_config.go | 16 +- internal/lua/mock_engine.go | 233 ++++++++++++---- internal/lua/register.go | 39 ++- internal/lua/shared_dict.go | 149 ++++++++--- internal/lua/socket_manager.go | 227 ++++++++++++---- 22 files changed, 2330 insertions(+), 518 deletions(-) diff --git a/internal/lua/api_balancer.go b/internal/lua/api_balancer.go index 285ee4f..17fc7cf 100644 --- a/internal/lua/api_balancer.go +++ b/internal/lua/api_balancer.go @@ -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 "" diff --git a/internal/lua/api_ctx.go b/internal/lua/api_ctx.go index ad754db..2176a63 100644 --- a/internal/lua/api_ctx.go +++ b/internal/lua/api_ctx.go @@ -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 diff --git a/internal/lua/api_location.go b/internal/lua/api_location.go index fbf7790..95bb8bd 100644 --- a/internal/lua/api_location.go +++ b/internal/lua/api_location.go @@ -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() diff --git a/internal/lua/api_log.go b/internal/lua/api_log.go index a450aae..f4263c2 100644 --- a/internal/lua/api_log.go +++ b/internal/lua/api_log.go @@ -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.redirect:HTTP 重定向 +// - 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, diff --git a/internal/lua/api_req.go b/internal/lua/api_req.go index ea702c6..9d47b8f 100644 --- a/internal/lua/api_req.go +++ b/internal/lua/api_req.go @@ -1,5 +1,22 @@ -// Package lua 提供 ngx.req API 实现 -// 本文件实现双层 API 边界验证原型,用于测量直接映射层 vs 兼容层的性能差异 +// Package lua 提供 ngx.req API 实现。 +// +// 该文件实现请求操作相关的 Lua API,兼容 OpenResty/ngx_lua 语义。 +// 采用分层 API 设计: +// - 直接映射层(APILayerDirect):fasthttp 直接映射,零拷贝,最小开销 +// - 兼容层(APILayerCompatible):模拟 nginx 语义,增加解析开销 +// - 伪非阻塞层(APILayerPseudoNonBlocking):yield/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 ( diff --git a/internal/lua/api_resp.go b/internal/lua/api_resp.go index 99184cb..af3b50a 100644 --- a/internal/lua/api_resp.go +++ b/internal/lua/api_resp.go @@ -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 ( diff --git a/internal/lua/api_shared_dict.go b/internal/lua/api_shared_dict.go index 1226c4b..1ec7300 100644 --- a/internal/lua/api_shared_dict.go +++ b/internal/lua/api_shared_dict.go @@ -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) // 验证参数但不使用返回值 diff --git a/internal/lua/api_socket_tcp.go b/internal/lua/api_socket_tcp.go index f088969..b1b1400 100644 --- a/internal/lua/api_socket_tcp.go +++ b/internal/lua/api_socket_tcp.go @@ -1,4 +1,23 @@ -// Package lua 提供 Cosocket TCP API 实现 +// Package lua 提供 Cosocket TCP API 实现。 +// +// 该文件实现 ngx.socket.tcp 相关的 Lua API,兼容 OpenResty cosocket 语义。 +// 包括: +// - TCPSocket:TCP 连接封装,支持 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 { diff --git a/internal/lua/api_timer.go b/internal/lua/api_timer.go index 9f2485c..8173440 100644 --- a/internal/lua/api_timer.go +++ b/internal/lua/api_timer.go @@ -1,4 +1,22 @@ -// Package lua 提供 Lua 脚本嵌入能力 +// Package lua 提供 ngx.timer API 实现。 +// +// 该文件实现定时器相关的 Lua API,兼容 OpenResty/ngx_lua 语义。 +// 包括: +// - TimerManager:定时器管理器,支持延迟执行和取消 +// - TimerEntry:单个定时器条目,包含回调和参数 +// - TimerHandle:Lua 可见的定时器句柄(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() diff --git a/internal/lua/api_var.go b/internal/lua/api_var.go index ad5ad1d..af20790 100644 --- a/internal/lua/api_var.go +++ b/internal/lua/api_var.go @@ -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, diff --git a/internal/lua/cache.go b/internal/lua/cache.go index 3d800f3..15175a3 100644 --- a/internal/lua/cache.go +++ b/internal/lua/cache.go @@ -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() diff --git a/internal/lua/config.go b/internal/lua/config.go index 1944427..3c620e6 100644 --- a/internal/lua/config.go +++ b/internal/lua/config.go @@ -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, diff --git a/internal/lua/context.go b/internal/lua/context.go index adcb486..d41aeeb 100644 --- a/internal/lua/context.go +++ b/internal/lua/context.go @@ -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 返回写入的字节数和可能的错误 diff --git a/internal/lua/coroutine.go b/internal/lua/coroutine.go index 2b7e5ac..9e9fbaf 100644 --- a/internal/lua/coroutine.go +++ b/internal/lua/coroutine.go @@ -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.* API(req、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.var:nginx 变量访问和自定义变量 +// - ngx.ctx:请求级上下文 table +// - ngx.log:日志输出 +// - ngx.socket:TCP 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) diff --git a/internal/lua/engine.go b/internal/lua/engine.go index 2afcc77..93ef2f9 100644 --- a/internal/lua/engine.go +++ b/internal/lua/engine.go @@ -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. 注册安全的 API(ngx.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() diff --git a/internal/lua/filter_writer.go b/internal/lua/filter_writer.go index b3772b7..95f0db6 100644 --- a/internal/lua/filter_writer.go +++ b/internal/lua/filter_writer.go @@ -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 时应用所有修改并发送响应 +// +// 注意事项: +// - 流式 body(SetBodyStream)无法缓冲,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 } diff --git a/internal/lua/middleware.go b/internal/lua/middleware.go index 87ced11..ca953c2 100644 --- a/internal/lua/middleware.go +++ b/internal/lua/middleware.go @@ -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 diff --git a/internal/lua/middleware_config.go b/internal/lua/middleware_config.go index 5ad8013..c44d5c6 100644 --- a/internal/lua/middleware_config.go +++ b/internal/lua/middleware_config.go @@ -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 ( diff --git a/internal/lua/mock_engine.go b/internal/lua/mock_engine.go index 0050ad7..2a178ce 100644 --- a/internal/lua/mock_engine.go +++ b/internal/lua/mock_engine.go @@ -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 的所有公开方法, +// 未注入的方法返回零值或 nil(stub 模式)。 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 初始化调度器 LState(Mock)。 +// +// 返回值: +// - 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() diff --git a/internal/lua/register.go b/internal/lua/register.go index 16eb911..96a9d1b 100644 --- a/internal/lua/register.go +++ b/internal/lua/register.go @@ -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 { diff --git a/internal/lua/shared_dict.go b/internal/lua/shared_dict.go index fd724e5..4ca4ae9 100644 --- a/internal/lua/shared_dict.go +++ b/internal/lua/shared_dict.go @@ -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 diff --git a/internal/lua/socket_manager.go b/internal/lua/socket_manager.go index d5084b1..738047a 100644 --- a/internal/lua/socket_manager.go +++ b/internal/lua/socket_manager.go @@ -1,4 +1,20 @@ -// Package lua 提供 Cosocket 管理功能 +// Package lua 提供 Cosocket 管理功能。 +// +// 该文件实现 TCP Cosocket 管理器,包括: +// - SocketState:Socket 生命周期状态机 +// - 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),