refactor(docs): 重构文档目录结构,nginx 文档移至子目录

将 docs/ 根目录下的 nginx 相关文档统一移动到 docs/nginx/ 子目录,
提高文档组织性和可维护性。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
xfy 2026-04-16 10:48:14 +08:00
parent 0a332e8cef
commit 972eab4267
41 changed files with 0 additions and 1068 deletions

View File

@ -1,233 +0,0 @@
# 配置参考文档
## 目录
- [变量系统](#变量系统)
- [DNS 解析器](#dns-解析器)
- [访问日志格式](#访问日志格式)
---
## 变量系统
Lolly 支持 nginx 风格的变量系统,可用于访问日志格式、代理请求头和 URL 重写规则。
### 内置变量
| 变量名 | 说明 | 示例值 |
|--------|------|--------|
| `$host` | 请求的主机名Host 头) | `example.com` |
| `$remote_addr` | 客户端 IP 地址 | `192.168.1.1` |
| `$remote_port` | 客户端端口 | `54321` |
| `$request_uri` | 原始请求 URI包含查询参数 | `/api/users?page=1` |
| `$uri` | 解码后的 URI 路径 | `/api/users` |
| `$args` | 查询参数字符串 | `page=1&limit=10` |
| `$request_method` | HTTP 请求方法 | `GET`, `POST` |
| `$scheme` | 协议 | `http`, `https` |
| `$server_name` | 服务器名称 | `localhost` |
| `$server_port` | 服务器端口 | `8080` |
| `$status` | HTTP 响应状态码 | `200`, `404` |
| `$body_bytes_sent` | 发送的响应体字节数 | `1024` |
| `$request_time` | 请求处理时间(秒) | `0.050` |
| `$time_local` | 本地时间 | `08/Apr/2026:11:04:58 +0800` |
| `$time_iso8601` | ISO8601 格式时间 | `2026-04-08T11:04:58+08:00` |
| `$request_id` | 唯一请求标识符 | `uuid` |
### 动态 HTTP 头变量
`$http_` 开头的变量用于获取 HTTP 请求头值:
- `$http_user_agent` - User-Agent 头
- `$http_referer` - Referer 头
- `$http_x_forwarded_for` - X-Forwarded-For 头
- 其他任意请求头:`$http_header_name`
### 变量格式
支持两种格式:
1. **简单格式**: `$var`
```
$host $uri
```
2. **花括号格式**: `${var}`
```
${host}:8080
${scheme}://${host}${uri}
```
### 在代理请求头中使用变量
```yaml
proxy:
- path: /api
targets:
- url: http://backend:8080
headers:
set_request:
X-Real-IP: "$remote_addr"
X-Forwarded-Host: "$host"
X-Request-ID: "$request_id"
```
### 在访问日志中使用变量
```yaml
logging:
access:
format: '$remote_addr - $remote_user [$time_local] "$request_method $uri $scheme" $status $body_bytes_sent'
```
### 自定义变量
```yaml
variables:
set:
app_name: "lolly"
version: "1.0.0"
```
**注意**:
- `$request_id` 为内置变量,自动为每个请求生成唯一 UUID无需配置
- 自定义变量名不能与内置变量冲突
- 变量名只允许字母、数字、下划线
---
## DNS 解析器
Lolly 内置 DNS 解析器,支持动态解析后端服务域名。
### 配置选项
```yaml
resolver:
enabled: true # 是否启用
addresses: # DNS 服务器地址列表
- "8.8.8.8:53"
- "8.8.4.4:53"
valid: 30s # 缓存有效期TTL
timeout: 5s # DNS 查询超时
ipv4: true # 查询 IPv4 地址
ipv6: false # 查询 IPv6 地址
cache_size: 1024 # 缓存最大条目数
```
### 功能特性
- **DNS 缓存**: 按 TTL 缓存解析结果,减少 DNS 查询延迟
- **后台刷新**: 自动在 TTL/2 时刷新缓存,避免过期
- **故障转移**: 解析失败时使用缓存 IP 继续服务
- **健康检查**: 首次解析失败标记目标不健康
### 使用场景
当后端目标使用域名时DNS 解析器自动生效:
```yaml
proxy:
- path: /api
targets:
- url: http://backend.example.com:8080 # 使用域名
weight: 1
```
### 监控指标
通过状态端点获取 DNS 解析统计:
- `CacheHits` - 缓存命中次数
- `CacheMisses` - 缓存未命中次数
- `CacheEntries` - 当前缓存条目数
- `ResolveErrors` - 解析错误次数
- `AverageLatency` - 平均解析延迟
---
## 访问日志格式
### nginx 兼容格式
Lolly 默认提供 nginx 兼容的访问日志格式:
```yaml
logging:
access:
format: '$remote_addr - $remote_user [$time] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent"'
```
示例输出:
```
192.168.1.1 - - [08/Apr/2026:11:04:58 +0800] "GET /api/users HTTP/1.1" 200 1024 "-" "Mozilla/5.0"
```
### JSON 格式
设置格式为 `json` 输出结构化日志:
```yaml
logging:
access:
format: 'json'
```
示例输出:
```json
{
"remote_addr": "192.168.1.1",
"request": "GET /api/users HTTP/1.1",
"status": 200,
"body_bytes_sent": 1024,
"http_user_agent": "Mozilla/5.0"
}
```
### 自定义格式
使用变量创建自定义格式:
```yaml
logging:
access:
format: '$remote_addr $request_method $uri $status $request_time'
```
---
## 完整配置示例
```yaml
server:
listen: ":8080"
name: "localhost"
proxy:
- path: /api
targets:
- url: http://backend.example.com:8080
headers:
set_request:
X-Real-IP: "$remote_addr"
X-Forwarded-Host: "$host"
X-Request-ID: "$request_id"
resolver:
enabled: true
addresses:
- "8.8.8.8:53"
valid: 30s
timeout: 5s
variables:
set:
app_name: "lolly"
logging:
access:
format: '$remote_addr - $remote_user [$time_local] "$request_method $uri $scheme" $status $body_bytes_sent'
path: "/var/log/lolly/access.log"
error:
level: "info"
path: "/var/log/lolly/error.log"
```

View File

@ -1,835 +0,0 @@
# Golang Lua 运行时嵌入分析
本文档分析 Go 语言嵌入 Lua 运行时的方案,为 lolly 项目实现类似 lua-nginx-module 功能提供技术参考。
---
## 一、主流 Lua 运行时方案对比
### 1.1 可选方案
| 方案 | 语言 | 性能 | Lua版本 | 特点 |
|------|------|------|---------|------|
| **gopher-lua** | 纯 Go | ~Python3 | Lua 5.1 + goto | 原生 Go 实现goroutine/channel 集成 |
| **go-lua** | Go + C | ~原生Lua | Lua 5.1 | CGO 调用 C Lua性能接近原生 |
| **luaJIT (CGO)** | C | 极高 | LuaJIT 2.1 | 最快FFI 强大,但 CGO 开销 |
| **glua** (Shopify) | Go + C | 高 | Lua 5.2/5.3 | Shopify 废弃,不推荐 |
### 1.2 详细对比
#### gopher-lua
**优势**:
- **纯 Go 实现**: 无 CGO 依赖,交叉编译友好
- **原生并发**: `LChannel` 类型直接操作 Go channel
- **Context 支持**: `SetContext(ctx)` 实现超时取消
- **字节码复用**: `FunctionProto` 跨 LState 共享
- **安全沙箱**: `SkipOpenLibs` 精细控制标准库加载
**劣势**:
- 性能约 Python3 级别,低于原生 Lua/LuaJIT
- 不支持 Lua 5.2+ 特性bit32, utf8 等)
- GC 压力较大(大量 LValue 对象)
**适用场景**:
- 需要纯 Go、交叉编译
- 性能要求中等
- 需要与 Go goroutine/channel 深度集成
#### go-lua (CGO binding)
**优势**:
- 性能接近原生 Lua通过 CGO 直接调用 C API
- 支持 Lua 5.1 标准库完整功能
**劣势**:
- CGO 依赖,交叉编译复杂
- Go-C 边界开销(每次调用 ~50ns
- 协程与 goroutine 交互困难C 栈问题)
**适用场景**:
- 性能关键场景
- 已有 C Lua 生态依赖
- 可接受 CGO 复杂度
#### LuaJIT via CGO
**优势**:
- **极致性能**: JIT 编译,接近 C 速度
- **FFI 强大**: 直接调用 C 函数无开销
- **内存高效**: 更小的内存占用
**劣势**:
- LuaJIT 2.1 开发停滞
- CGO 集成复杂
- JIT 在某些环境受限(容器、安全限制)
- Go-LuaJIT 协程映射困难
**适用场景**:
- 性能极致要求
- 已有 OpenResty/LuaJIT 生态
- 运行环境可控
### 1.3 推荐选择
**对于 lolly 项目,推荐 gopher-lua**:
1. **纯 Go**: 与项目技术栈一致,交叉编译无障碍
2. **并发集成**: 天然支持 goroutine/channel契合 Go HTTP 服务器架构
3. **性能足够**: Python3 级性能对脚本处理场景已够用
4. **成熟稳定**: yuin/gopher-lua 维护活跃,社区成熟
---
## 二、gopher-lua 核心 API
### 2.1 LState 状态机
```go
import "github.com/yuin/gopher-lua"
// 创建 VM
L := lua.NewState(lua.Options{
SkipOpenLibs: true, // 安全:禁用默认库
IncludeGoStackTrace: true, // Panic 时输出 Go 调用栈
})
defer L.Close()
// 执行脚本
L.DoString("print('hello')")
L.DoFile("script.lua")
// Context 控制(超时)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
L.SetContext(ctx)
L.DoString("while true do end") // 5秒后自动取消
```
### 2.2 栈操作
```go
// 基本栈操作
L.Push(lua.LNumber(42)) // 压入数字
L.Push(lua.LString("hello")) // 压入字符串
v := L.Get(-1) // 获取栈顶
L.Pop(2) // 弹出 2 个
// 全局变量
L.SetGlobal("myvar", lua.LNumber(100))
val := L.GetGlobal("myvar")
// Table 操作
tbl := L.NewTable()
L.SetField(tbl, "name", lua.LString("lolly"))
L.SetField(tbl, "version", lua.LString("0.2.0"))
L.SetGlobal("config", tbl)
```
### 2.3 函数注册
```go
// Go 函数签名: func(L *lua.LState) int (返回压栈结果数)
func Double(L *lua.LState) int {
n := L.CheckInt(1) // 获取第1个参数
L.Push(lua.LNumber(n * 2)) // 压入结果
return 1 // 返回结果数
}
// 注册到全局
L.SetGlobal("double", L.NewFunction(Double))
// 批量注册到模块
mod := L.NewTable()
L.SetFuncs(mod, map[string]lua.LGFunction{
"double": Double,
"add": Add,
"sub": Sub,
})
L.SetGlobal("mathx", mod)
// 闭包(带 upvalue
counter := 0
L.SetGlobal("counter", L.NewClosure(func(L *lua.LState) int {
counter++
L.Push(lua.LNumber(counter))
return 1
}))
```
### 2.4 调用 Lua 函数
```go
// 受保护调用(推荐)
err := L.CallByParam(lua.P{
Fn: L.GetGlobal("myFunc"), // 函数
NRet: 1, // 期望返回值
Protect: true, // 拦截 panic
}, lua.LNumber(10), lua.LString("arg"))
if err != nil {
// 错误处理
}
ret := L.Get(-1) // 获取返回值
L.Pop(1) // 清理栈
```
### 2.5 模块加载
```go
// 预加载自定义模块
L.PreloadModule("lolly", func(L *lua.LState) int {
mod := L.NewTable()
L.SetFuncs(mod, map[string]lua.LGFunction{
"say": SayHello,
"log": LogMessage,
"sleep": Sleep,
})
L.Push(mod)
return 1 // 返回模块表
})
// Lua 中使用
L.DoString(`
local lolly = require("lolly")
lolly.say("hello")
`)
```
---
## 三、协程支持
### 3.1 协程 API
```go
// 创建协程线程
co, cancel := L.NewThread() // 共享全局状态
// 获取 Lua 协程函数
fn := L.GetGlobal("coroutine_func").(*lua.LFunction)
// Resume恢复执行
state, err, values := L.Resume(co, fn, lua.LNumber(10))
// state: lua.ResumeOK / lua.ResumeYield / lua.ResumeError
// values: yield 返回的值列表
// 检查状态
status := L.Status(co) // "suspended" / "running" / "normal" / "dead"
```
### 3.2 Yield 模式
Lua 侧:
```lua
function async_task()
print("start")
coroutine.yield("waiting") -- 挂起,返回值
print("continue")
return "done"
end
```
Go 侧:
```go
co, _ := L.NewThread()
fn := L.GetGlobal("async_task").(*lua.LFunction)
// 第一次 resume
st, err, vals := L.Resume(co, fn)
if st == lua.ResumeYield {
fmt.Println("yielded:", vals[0]) // "waiting"
}
// 第二次 resume恢复
st, err, vals = L.Resume(co)
if st == lua.ResumeOK {
fmt.Println("done:", vals[0]) // "done"
}
```
### 3.3 与 Go Channel 集成
gopher-lua 提供 `LChannel` 类型,让 Lua 操作 Go channel:
```go
// 创建 Go channel
ch := make(chan string, 10)
// 传递给 Lua
luaCh := lua.LChannel{Channel: ch}
L.SetGlobal("mychannel", luaCh)
// Lua 中操作
L.DoString(`
-- 发送
mychannel:send("hello")
-- 接收(阻塞)
local msg = mychannel:receive()
print(msg)
`)
```
---
## 四、错误处理
### 4.1 ApiError 结构
```go
type ApiError struct {
Type ApiErrorType // Run/Syntax/Panic/Memory/File
Object LValue // Lua 错误对象
StackTrace string // 调用栈
Cause error // 底层错误
}
```
### 4.2 Panic Handler
```go
// 注册 panic handler类似 lua-nginx-module
L.SetPanic(func(L *lua.LState) {
// 捕获 panic记录日志
log.Error("Lua panic: ", L.Get(-1))
// 可以选择重建 VM 或返回错误
})
// 使用 PCall 保护调用
err := L.PCall(0, 0, nil)
if err != nil {
apiErr := err.(*lua.ApiError)
log.Error("Lua error: ", apiErr.StackTrace)
}
```
---
## 五、字节码缓存与复用
### 5.1 编译与复用
```go
// 编译脚本为字节码(可跨 VM 复用)
proto, err := lua.CompileString("function foo() return 42 end", "foo.lua")
// 多个 LState 共享字节码
L1 := lua.NewState()
fn1 := L1.NewFunctionFromProto(proto)
L2 := lua.NewState()
fn2 := L2.NewFunctionFromProto(proto) // 无需重新编译
```
### 5.2 缓存设计(类似 lua-nginx-module
```go
type CodeCache struct {
mu sync.RWMutex
protos map[string]*lua.FunctionProto // MD5(key) -> proto
}
func (c *CodeCache) GetOrCompile(src string) (*lua.FunctionProto, error) {
key := md5Key(src)
c.mu.RLock()
proto, ok := c.protos[key]
c.mu.RUnlock()
if ok {
return proto, nil
}
// 编译并缓存
proto, err := lua.CompileString(src, key)
if err != nil {
return nil, err
}
c.mu.Lock()
c.protos[key] = proto
c.mu.Unlock()
return proto, nil
}
```
---
## 六、与 lolly 项目集成设计
### 6.1 架构映射
| lua-nginx-module | lolly (Go) 实现 |
|------------------|----------------|
| `ngx_http_lua_main_conf_t` | `LuaWorker` 结构,持有 VM |
| `ngx_http_lua_ctx_t` | `LuaRequestCtx`,请求上下文 |
| `ngx_http_lua_co_ctx_t` | `LuaCoroutine`,协程状态 |
| Phase Handlers | Middleware 集成点 |
| Filter Chain | Response Filter 中间件 |
### 6.2 核心结构设计
```go
// internal/lua/engine.go
package lua
import (
"context"
"sync"
glua "github.com/yuin/gopher-lua"
)
// LuaWorker - worker 级单 VM对应 ngx_http_lua_main_conf_t
type LuaWorker struct {
L *glua.LState // 主 VM
codeCache *CodeCache // 字节码缓存
coroPool *CoroutinePool // 协程池
modules map[string]glua.LGFunction // 已加载模块
mu sync.RWMutex
}
// LuaRequestCtx - 请求上下文(对应 ngx_http_lua_ctx_t
type LuaRequestCtx struct {
Worker *LuaWorker
Request *fasthttp.RequestCtx // fasthttp 请求
Coroutine *LuaCoroutine // 当前协程
Variables map[string]string // ngx.var
Output []byte // ngx.say 输出缓冲
Phase Phase // 当前阶段
Ctx context.Context // Go context
}
// LuaCoroutine - 协程状态(对应 ngx_http_lua_co_ctx_t
type LuaCoroutine struct {
Thread *glua.LState // 协程线程
Status CoroutineStatus // running/suspended/dead
Parent *LuaCoroutine // 父协程
ResumeFunc func() // 恢复回调(类似 resume_handler
}
// Phase - 处理阶段
type Phase int
const (
PhaseInit Phase = iota
PhaseAccess
PhaseContent
PhaseLog
PhaseHeaderFilter
PhaseBodyFilter
)
```
### 6.3 Worker 级单 VM 初始化
```go
// internal/lua/worker.go
func NewLuaWorker() *LuaWorker {
// 创建 VM安全模式
L := glua.NewState(glua.Options{
SkipOpenLibs: true,
})
worker := &LuaWorker{
L: L,
codeCache: NewCodeCache(),
coroPool: NewCoroutinePool(L),
modules: make(map[string]glua.LGFunction),
}
// 加载必要标准库
worker.loadSafeLibs()
// 注册 lolly.* API
worker.registerLollyAPI()
return worker
}
func (w *LuaWorker) loadSafeLibs() {
// 只加载安全的库(禁用 os, io 等危险库)
w.L.CallByParam(glua.P{
Fn: w.L.NewFunction(glua.OpenBase),
Protect: true,
})
w.L.CallByParam(glua.P{
Fn: w.L.NewFunction(glua.OpenTable),
Protect: true,
})
w.L.CallByParam(glua.P{
Fn: w.L.NewFunction(glua.OpenString),
Protect: true,
})
w.L.CallByParam(glua.P{
Fn: w.L.NewFunction(glua.OpenMath),
Protect: true,
})
}
```
### 6.4 lolly.* API 注册
```go
// internal/lua/api.go
func (w *LuaWorker) registerLollyAPI() {
// 创建 lolly 模块表
lollyMod := w.L.NewTable()
// 注册核心 API
w.L.SetFuncs(lollyMod, map[string]glua.LGFunction{
// 输出
"say": w.apiSay,
"print": w.apiPrint,
// 请求
"req": w.apiRequest,
"get_uri": w.apiGetURI,
"get_arg": w.apiGetArg,
"get_header": w.apiGetHeader,
// 响应
"resp": w.apiResponse,
"set_header": w.apiSetHeader,
"set_status": w.apiSetStatus,
// 变量
"var": w.apiVar,
"set_var": w.apiSetVar,
// 控制流
"exit": w.apiExit,
"sleep": w.apiSleep, // 异步 sleep
"throw": w.apiThrow,
// 日志
"log": w.apiLog,
"err": w.apiLogErr,
"warn": w.apiLogWarn,
"info": w.apiLogInfo,
})
w.L.SetGlobal("lolly", lollyMod)
// 兼容 nginx 命名(可选)
w.L.SetGlobal("ngx", lollyMod)
}
// apiSay - 输出内容
func (w *LuaWorker) apiSay(L *glua.LState) int {
ctx := getRequestCtx(L) // 从 LState 获取请求上下文
str := L.CheckString(1)
ctx.Output = append(ctx.Output, str...)
return 0
}
// apiGetURI - 获取请求 URI
func (w *LuaWorker) apiGetURI(L *glua.LState) int {
ctx := getRequestCtx(L)
uri := string(ctx.Request.URI().Path())
L.Push(glua.LString(uri))
return 1
}
// apiSleep - 异步睡眠yield 实现)
func (w *LuaWorker) apiSleep(L *glua.LState) int {
ctx := getRequestCtx(L)
ms := L.CheckInt(1)
// 创建定时器yield 当前协程
ctx.Coroutine.ResumeFunc = func() {
// 定时器到期后 resume
ctx.Worker.ResumeCoroutine(ctx.Coroutine)
}
// 注册定时器
go func() {
time.Sleep(time.Duration(ms) * time.Millisecond)
ctx.Coroutine.ResumeFunc()
}()
// Yield
L.Yield(glua.LNumber(ms))
return 0
}
```
### 6.5 请求上下文绑定
```go
// internal/lua/context.go
// 请求开始时绑定上下文
func (w *LuaWorker) NewRequestCtx(req *fasthttp.RequestCtx) *LuaRequestCtx {
ctx := &LuaRequestCtx{
Worker: w,
Request: req,
Variables: make(map[string]string),
Phase: PhaseAccess,
Ctx: req,
}
// 创建请求协程
ctx.Coroutine = w.coroPool.Acquire()
ctx.Coroutine.Parent = nil
// 绑定到 LState使用 exdata 模式)
// gopher-lua 不支持 exdata使用全局变量
ctx.Coroutine.Thread.SetGlobal("__lolly_req", glua.LUserData{
Value: ctx,
Metatable: w.L.NewTable(), // 可设置 __index 方法
})
return ctx
}
// 从 LState 获取请求上下文
func getRequestCtx(L *glua.LState) *LuaRequestCtx {
ud := L.GetGlobal("__lolly_req")
if ud == glua.LNil {
return nil
}
return ud.(*glua.LUserData).Value.(*LuaRequestCtx)
}
```
### 6.6 Yield/Resume 与 Go 异步集成
**关键设计**: Lua yield → Go channel → 恢复执行
```go
// internal/lua/coroutine.go
type CoroutinePool struct {
L *glua.LState
free chan *LuaCoroutine
resume chan *LuaCoroutine // 恢复队列
}
func (p *CoroutinePool) Acquire() *LuaCoroutine {
select {
case co := <-p.free:
return co
default:
// 创建新协程
thread, _ := p.L.NewThread()
return &LuaCoroutine{
Thread: thread,
Status: StatusSuspended,
}
}
}
// 执行脚本(支持 yield
func (ctx *LuaRequestCtx) RunScript(script string) error {
// 获取或编译字节码
proto, err := ctx.Worker.codeCache.GetOrCompile(script)
if err != nil {
return err
}
fn := ctx.Coroutine.Thread.NewFunctionFromProto(proto)
// 开始执行
state, err, _ := ctx.Worker.L.Resume(ctx.Coroutine.Thread, fn)
for state == glua.ResumeYield {
// 协程 yield等待恢复信号
ctx.Coroutine.Status = StatusSuspended
// 等待 ResumeFunc 触发
select {
case <-ctx.Worker.coroPool.resume:
// 恢复执行
state, err, _ = ctx.Worker.L.Resume(ctx.Coroutine.Thread)
case <-ctx.Ctx.Done():
// 请求超时/取消
return ctx.Ctx.Err()
}
}
if state == glua.ResumeError {
return err
}
return nil
}
```
### 6.7 中间件集成
```go
// internal/middleware/lua_middleware.go
package middleware
import (
"rua.plus/lolly/internal/lua"
"github.com/valyala/fasthttp"
)
type LuaMiddleware struct {
worker *lua.LuaWorker
config LuaConfig
}
func (m *LuaMiddleware) Process(next fasthttp.RequestHandler) fasthttp.RequestHandler {
return func(ctx *fasthttp.RequestCtx) {
// 创建 Lua 请求上下文
luaCtx := m.worker.NewRequestCtx(ctx)
// 执行 access_by_lua
if m.config.AccessScript != "" {
if err := luaCtx.RunScript(m.config.AccessScript); err != nil {
ctx.Error("Lua error: "+err.Error(), 500)
return
}
}
// 执行 content_by_lua如果有
if m.config.ContentScript != "" {
luaCtx.Phase = lua.PhaseContent
if err := luaCtx.RunScript(m.config.ContentScript); err != nil {
ctx.Error("Lua error: "+err.Error(), 500)
return
}
// 输出 Lua 内容
ctx.Write(luaCtx.Output)
return
}
// 继续下一个 handler
next(ctx)
// 执行 log_by_lua
if m.config.LogScript != "" {
luaCtx.Phase = lua.PhaseLog
luaCtx.RunScript(m.config.LogScript)
}
}
}
```
---
## 七、实现路线图
### 7.1 阶段一基础嵌入Week 1-2
| 任务 | 文件 | 说明 |
|------|------|------|
| 添加 gopher-lua 依赖 | `go.mod` | `github.com/yuin/gopher-lua` |
| 创建 LuaWorker | `internal/lua/worker.go` | 单 VM 管理 |
| 代码缓存 | `internal/lua/cache.go` | 字节码缓存 |
| 基础 API 注入 | `internal/lua/api.go` | say/print/get_uri 等 |
### 7.2 阶段二请求集成Week 3-4
| 任务 | 文件 | 说明 |
|------|------|------|
| LuaRequestCtx | `internal/lua/context.go` | 请求上下文绑定 |
| LuaMiddleware | `internal/middleware/lua_middleware.go` | 中间件集成 |
| 配置支持 | `internal/config/lua.go` | Lua 指令解析 |
### 7.3 阶段三异步支持Week 5-6
| 任务 | 文件 | 说明 |
|------|------|------|
| CoroutinePool | `internal/lua/coroutine.go` | 协程池 |
| Yield/Resume | `internal/lua/coroutine.go` | 异步 sleep 等 |
| LChannel 集成 | `internal/lua/channel.go` | Go channel 操作 |
### 7.4 阶段四高级功能Week 7-10
| 任务 | 说明 |
|------|------|
| 共享内存 (shdict) | Go sync.Map + Lua Table API |
| Cosocket | 非阻塞 TCP socket |
| 子请求 | 内部 location capture |
| Timer | ngx.timer.at 实现 |
| Balancer | 动态负载均衡 |
---
## 八、性能考量
### 8.1 性能优化点
| 优化 | 方法 |
|------|------|
| **字节码缓存** | 预编译脚本,跨请求复用 |
| **协程池** | 预创建协程,避免频繁创建销毁 |
| **减少 GC** | 使用 LTable 而非大量小对象 |
| **并行执行** | 多 worker 独立 VM无锁竞争 |
### 8.2 性能基准
```go
// 建议基准测试
func BenchmarkLuaSimple(b *testing.B) {
L := lua.NewState()
defer L.Close()
L.DoString("function test() return 1 + 1 end")
b.ResetTimer()
for i := 0; i < b.N; i++ {
L.CallByParam(lua.P{Fn: L.GetGlobal("test")})
}
}
func BenchmarkLuaWithCtx(b *testing.B) {
// 带请求上下文的基准
}
```
---
## 九、安全考虑
### 9.1 安全沙箱
```go
// 禁用危险库
L := lua.NewState(lua.Options{SkipOpenLibs: true})
// 只加载安全库
safeLibs := []glua.LGFunction{
glua.OpenBase, // 基础
glua.OpenTable, // 表操作
glua.OpenString, // 字符串
glua.OpenMath, // 数学
// 禁用: OpenOS, OpenIO, OpenPackage (部分)
}
```
### 9.2 资源限制
```go
// Context 超时
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
L.SetContext(ctx)
// 内存限制(通过 Options
L := lua.NewState(lua.Options{
RegistryMaxSize: 1024 * 1024, // 限制注册表大小
})
// CPU 限制(通过 goroutine 监控)
go func() {
time.Sleep(5 * time.Second)
cancel() // 强制取消
}()
```
---
## 十、参考资源
- [gopher-lua GitHub](https://github.com/yuin/gopher-lua) - 主仓库
- [gopher-lua API 文档](https://pkg.go.dev/github.com/yuin/gopher-lua) - GoDoc
- [Lua 5.1 手册](https://www.lua.org/manual/5.1/) - 语言参考
- [lua-nginx-module 文档](../lua-nginx-module/) - 架构参考(已生成)