lolly/internal/lua/middleware.go
xfy bfab449241 feat(lua): 实现 Lua 中间件系统
添加可配置的 Lua 中间件实现,支持:
- 多执行阶段(rewrite、access、content、header_filter、body_filter、log)
- 脚本路径配置和超时控制
- 中间件启用/禁用开关
- 配置文件热加载
- 完整的单元测试和性能基准测试

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 13:34:34 +08:00

292 lines
6.6 KiB
Go
Raw 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 中间件实现
package lua
import (
"fmt"
"strings"
"time"
"github.com/valyala/fasthttp"
)
// LuaMiddleware Lua 中间件配置
type LuaMiddleware struct {
// Lua 引擎
engine *LuaEngine
// 脚本路径
scriptPath string
// 执行阶段
phase Phase
// 超时时间
timeout time.Duration
// 中间件名称
name string
// 是否启用
enabled bool
}
// LuaMiddlewareConfig Lua 中间件配置
type LuaMiddlewareConfig struct {
// 脚本路径
ScriptPath string
// 执行阶段 (默认 PhaseContent)
Phase Phase
// 超时时间 (默认 30s)
Timeout time.Duration
// 中间件名称 (默认 "lua-{phase}")
Name string
// 是否启用
Enabled bool
// EnabledSet 表示是否显式设置了 Enabled
// 当为 true 时使用 Enabled 的值,否则默认启用
EnabledSet bool
}
// DefaultLuaMiddlewareConfig 默认配置
func DefaultLuaMiddlewareConfig() LuaMiddlewareConfig {
return LuaMiddlewareConfig{
Phase: PhaseContent,
Timeout: 30 * time.Second,
Enabled: true,
}
}
// NewLuaMiddleware 创建 Lua 中间件
func NewLuaMiddleware(engine *LuaEngine, config LuaMiddlewareConfig) (*LuaMiddleware, error) {
if engine == nil {
return nil, fmt.Errorf("lua engine is required")
}
if config.ScriptPath == "" {
return nil, fmt.Errorf("script path is required")
}
// 设置默认值
if config.Timeout == 0 {
config.Timeout = 30 * time.Second
}
// Enabled 默认值处理:
// - EnabledSet 为 true 时,使用显式设置的 Enabled 值
// - EnabledSet 为 false 时(零值),默认启用
if !config.EnabledSet {
config.Enabled = true
}
// 生成默认名称
if config.Name == "" {
config.Name = fmt.Sprintf("lua-%s", config.Phase.String())
}
return &LuaMiddleware{
engine: engine,
scriptPath: config.ScriptPath,
phase: config.Phase,
timeout: config.Timeout,
name: config.Name,
enabled: config.Enabled,
}, nil
}
// Name 返回中间件名称
func (m *LuaMiddleware) Name() string {
return m.name
}
// Process 包装请求处理器
func (m *LuaMiddleware) Process(next fasthttp.RequestHandler) fasthttp.RequestHandler {
return func(ctx *fasthttp.RequestCtx) {
// 检查是否启用
if !m.enabled {
next(ctx)
return
}
// 创建 Lua 上下文
luaCtx := NewContext(m.engine, ctx)
luaCtx.SetPhase(m.phase)
// 初始化协程
if err := luaCtx.InitCoroutine(); err != nil {
// 协程创建失败,记录错误并继续
ctx.Error(fmt.Sprintf("lua coroutine init failed: %v", err), fasthttp.StatusInternalServerError)
luaCtx.Release()
next(ctx)
return
}
// 执行脚本
err := luaCtx.ExecuteFile(m.scriptPath)
// 检查是否为 ngx.exit/redirect 导致的终止(正常行为)
// 这些 API 通过 RaiseError 终止执行,错误消息包含 "ngx.exit" 或 "ngx.redirect"
isNgxExit := err != nil && (strings.Contains(err.Error(), "ngx.exit") ||
strings.Contains(err.Error(), "ngx.redirect"))
// 如果是 ngx.exit手动设置 Exited 标记
// 因为 setupNgxAPI 中 ngxLogAPI.luaCtx 为 nilExit() 只设置了 RequestCtx.StatusCode()
if isNgxExit {
luaCtx.Exited = true
}
// 只有非 ngx.exit/redirect 错误才设置错误响应
if err != nil && !isNgxExit && !luaCtx.Exited {
ctx.Error(fmt.Sprintf("lua execution failed: %v", err), fasthttp.StatusInternalServerError)
}
// 刷新输出缓冲
luaCtx.FlushOutput()
// 检查退出状态(在 Release 之前)
exited := luaCtx.Exited
// 释放资源
luaCtx.Release()
// 如果已退出,不再继续执行后续处理器
if exited {
return
}
// 继续执行后续处理器
next(ctx)
}
}
// SetEnabled 设置启用状态
func (m *LuaMiddleware) SetEnabled(enabled bool) {
m.enabled = enabled
}
// SetPhase 设置执行阶段
func (m *LuaMiddleware) SetPhase(phase Phase) {
m.phase = phase
m.name = fmt.Sprintf("lua-%s", phase.String())
}
// SetTimeout 设置超时时间
func (m *LuaMiddleware) SetTimeout(timeout time.Duration) {
m.timeout = timeout
}
// SetScriptPath 设置脚本路径
func (m *LuaMiddleware) SetScriptPath(path string) {
m.scriptPath = path
}
// GetPhase 获取执行阶段
func (m *LuaMiddleware) GetPhase() Phase {
return m.phase
}
// GetScriptPath 获取脚本路径
func (m *LuaMiddleware) GetScriptPath() string {
return m.scriptPath
}
// IsEnabled 检查是否启用
func (m *LuaMiddleware) IsEnabled() bool {
return m.enabled
}
// MultiPhaseLuaMiddleware 多阶段 Lua 中间件
// 支持在不同阶段执行不同的脚本
type MultiPhaseLuaMiddleware struct {
// Lua 引擎
engine *LuaEngine
// 各阶段脚本配置
phases map[Phase]*LuaMiddleware
// 名称
name string
}
// NewMultiPhaseLuaMiddleware 创建多阶段 Lua 中间件
func NewMultiPhaseLuaMiddleware(engine *LuaEngine, name string) *MultiPhaseLuaMiddleware {
return &MultiPhaseLuaMiddleware{
engine: engine,
phases: make(map[Phase]*LuaMiddleware),
name: name,
}
}
// Name 返回中间件名称
func (m *MultiPhaseLuaMiddleware) Name() string {
return m.name
}
// AddPhase 添加阶段脚本
func (m *MultiPhaseLuaMiddleware) AddPhase(phase Phase, scriptPath string, timeout time.Duration) error {
config := LuaMiddlewareConfig{
ScriptPath: scriptPath,
Phase: phase,
Timeout: timeout,
Name: fmt.Sprintf("%s-%s", m.name, phase.String()),
Enabled: true, // 多阶段配置默认启用
EnabledSet: true, // 显式设置
}
middleware, err := NewLuaMiddleware(m.engine, config)
if err != nil {
return err
}
m.phases[phase] = middleware
return nil
}
// Process 包装请求处理器
func (m *MultiPhaseLuaMiddleware) Process(next fasthttp.RequestHandler) fasthttp.RequestHandler {
handler := next
// 按逆序包装,确保执行顺序正确
// log -> body_filter -> header_filter -> content -> access -> rewrite
phaseOrder := []Phase{
PhaseLog,
PhaseBodyFilter,
PhaseHeaderFilter,
PhaseContent,
PhaseAccess,
PhaseRewrite,
}
for _, phase := range phaseOrder {
if middleware, ok := m.phases[phase]; ok {
handler = middleware.Process(handler)
}
}
return handler
}
// GetPhaseMiddleware 获取指定阶段的中间件
func (m *MultiPhaseLuaMiddleware) GetPhaseMiddleware(phase Phase) *LuaMiddleware {
return m.phases[phase]
}
// RemovePhase 移除阶段脚本
func (m *MultiPhaseLuaMiddleware) RemovePhase(phase Phase) {
delete(m.phases, phase)
}
// HasPhase 检查是否有指定阶段的脚本
func (m *MultiPhaseLuaMiddleware) HasPhase(phase Phase) bool {
return m.phases[phase] != nil
}
// PhaseCount 返回阶段数量
func (m *MultiPhaseLuaMiddleware) PhaseCount() int {
return len(m.phases)
}