lolly/internal/lua/context.go
xfy 1c3e04afdb docs(lua): 为 Lua API 模块添加标准化 godoc 注释
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 11:23:03 +08:00

249 lines
6.9 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

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

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