docs(lua): 为 Lua API 模块添加标准化 godoc 注释
为所有 Lua API 文件添加完整的包级和函数级文档注释: - api_balancer: 负载均衡 API(set_current_peer, set_more_tries 等) - api_ctx: 请求上下文存储 API(ngx.ctx) - api_location: 子请求捕获 API(ngx.location.capture) - api_log: 日志输出 API(ngx.log) - api_req: 请求对象 API - api_resp: 响应对象 API - api_shared_dict: 共享字典 API - api_socket_tcp: TCP socket API - api_timer: 定时器 API - api_var: 变量 API - engine: Lua 引擎核心 - context: 请求上下文管理 - coroutine: 协程调度器 - middleware: 中间件集成 - filter_writer: 响应过滤器 - cache: Lua 脚本缓存 - shared_dict: 共享字典实现 - socket_manager: socket 连接管理 注释格式遵循 Go 官方风格,包含功能说明、参数说明和注意事项。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
5f5717d6a4
commit
ad177e9640
@ -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 ""
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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) // 验证参数但不使用返回值
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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 返回写入的字节数和可能的错误
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user