docs: 添加 Lua 嵌入分析文档

- 新增 lua-embed-analysis.md 技术分析文档
- 新增 lua-nginx-module 文档目录
- 更新 gitignore 允许跟踪 docs/lua-nginx-module/

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
xfy 2026-04-10 11:20:57 +08:00
parent 4a93cf2b5c
commit 941c44b798
14 changed files with 4081 additions and 1 deletions

3
.gitignore vendored
View File

@ -64,4 +64,5 @@ coverage.html
html/
default.pgo
benchmark-*.txt
lua-nginx-module/
lua-nginx-module/
!docs/lua-nginx-module/

835
docs/lua-embed-analysis.md Normal file
View File

@ -0,0 +1,835 @@
# 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/) - 架构参考(已生成)

View File

@ -0,0 +1,84 @@
# lua-nginx-module 概述
本文档为 lolly 项目提供 lua-nginx-module 的完整功能参考,帮助理解 OpenResty 核心模块的架构和实现。
## 模块简介
lua-nginx-module 是 OpenResty 的核心模块,将 Lua/LuaJIT 嵌入到 Nginx 中,通过协程实现非阻塞 I/O让开发者可以用 Lua 编写高性能 Web 应用。
## 核心特性
| 特性 | 说明 |
|------|------|
| **非阻塞 I/O** | 通过 Lua 协程 + Nginx 事件循环实现 |
| **多阶段处理** | 支持 Nginx 11 个请求处理阶段 |
| **共享内存** | Worker 间共享数据字典 |
| **Cosocket** | 非阻塞 TCP/UDP socket API |
| **动态负载均衡** | Lua 控制上游服务器选择 |
| **SSL/TLS 扩展** | 动态证书、Session 缓存自定义 |
## 文档目录
| 文档 | 内容 |
|------|------|
| [01-directives.md](./01-directives.md) | 所有配置指令详解 |
| [02-lua-api.md](./02-lua-api.md) | ngx.* Lua API 参考 |
| [03-cosocket.md](./03-cosocket.md) | 非阻塞 Socket API |
| [04-timer-thread.md](./04-timer-thread.md) | 定时器和用户线程 |
| [05-shdict.md](./05-shdict.md) | 共享内存字典 |
| [06-ssl.md](./06-ssl.md) | SSL/TLS 功能 |
| [07-subrequest.md](./07-subrequest.md) | 内部子请求 |
| [08-balancer.md](./08-balancer.md) | 负载均衡 |
| [09-filter.md](./09-filter.md) | 过滤器链 |
| [10-code-cache.md](./10-code-cache.md) | 代码缓存与异常处理 |
| [11-architecture.md](./11-architecture.md) | 核心架构设计 |
## 源码结构
```
lua-nginx-module/
├── src/
│ ├── ngx_http_lua_module.c # 主模块定义
│ ├── ngx_http_lua_common.h # 核心数据结构
│ ├── ngx_http_lua_directive.c # 指令解析
│ ├── ngx_http_lua_util.c # 工具函数
│ ├── ngx_http_lua_ctx.c # 请求上下文
│ ├── ngx_http_lua_cache.c # 代码缓存
│ ├── ngx_http_lua_coroutine.c # 协程管理
│ │
│ # Phase Handlers
│ ├── ngx_http_lua_contentby.c
│ ├── ngx_http_lua_accessby.c
│ ├── ngx_http_lua_rewriteby.c
│ ├── ngx_http_lua_headerfilterby.c
│ ├── ngx_http_lua_bodyfilterby.c
│ │
│ # Network
│ ├── ngx_http_lua_socket_tcp.c
│ ├── ngx_http_lua_socket_udp.c
│ ├── ngx_http_lua_subrequest.c
│ │
│ # Memory
│ ├── ngx_http_lua_shdict.c
│ │
│ # SSL
│ ├── ngx_http_lua_ssl_certby.c
│ ├── ngx_http_lua_ssl_session_storeby.c
│ │
│ # Timer & Thread
│ ├── ngx_http_lua_timer.c
│ ├── ngx_http_lua_uthread.c
│ ├── ngx_http_lua_semaphore.c
│ │
│ └── api/
│ └── ngx_http_lua_api.h # 公共 API 头文件
├── t/ # 测试用例 (Test::Nginx)
└── util/ # 工具脚本
```
## 版本信息
- 版本: v0.10.29
- 发布日期: 2025-10-24
- 测试用例: 228 个文件, 10,000+ 用例

View File

@ -0,0 +1,277 @@
# lua-nginx-module 配置指令详解
本文档详细列出 lua-nginx-module 的所有配置指令,供 lolly 项目参考实现。
## 一、初始化类指令 (Initialization Phase)
| 指令 | 参数类型 | 配置层级 | 执行时机 | Nginx Phase |
|------|---------|---------|---------|-------------|
| `init_by_lua` | inline script | main | Master 进程启动时 | 配置加载阶段 |
| `init_by_lua_block` | block | main | Master 进程启动时 | 配置加载阶段 |
| `init_by_lua_file` | file path | main | Master 进程启动时 | 配置加载阶段 |
| `init_worker_by_lua` | inline script | main | Worker 进程启动时 | worker 初始化 |
| `init_worker_by_lua_block` | block | main | Worker 进程启动时 | worker 初始化 |
| `init_worker_by_lua_file` | file path | main | Worker 进程启动时 | worker 初始化 |
| `exit_worker_by_lua_block` | block | main | Worker 进程退出时 | worker 退出 |
| `exit_worker_by_lua_file` | file path | main | Worker 进程退出时 | worker 退出 |
### 使用示例
```nginx
# init_by_lua - 初始化 Lua VM加载共享模块
init_by_lua_block {
require "resty.core"
local cache = require "myapp.cache"
cache.init()
}
# init_worker_by_lua - Worker 级初始化(如定时任务)
init_worker_by_lua_block {
local timer = require "myapp.timer"
timer.start_heartbeat()
}
# exit_worker_by_lua - Worker 退出清理
exit_worker_by_lua_block {
local cleanup = require "myapp.cleanup"
cleanup.flush_pending_data()
}
```
---
## 二、变量设置类指令 (Variable Phase)
| 指令 | 参数类型 | 配置层级 | 执行时机 |
|------|---------|---------|---------|
| `set_by_lua` | inline script + args | server/if/location | 变量赋值时 |
| `set_by_lua_block` | block | server/if/location | 变量赋值时 |
| `set_by_lua_file` | file path + args | server/if/location | 变量赋值时 |
### 特点
- **同步执行**: 不能 yield必须立即返回
- **返回值**: 通过 `return` 返回结果赋给变量
- **参数传递**: 支持额外参数传递给 Lua 代码
### 使用示例
```nginx
location /set {
set $res "";
set_by_lua_block $res {
local a = tonumber(ngx.arg[1])
local b = tonumber(ngx.arg[2])
return a + b
} $arg_a $arg_b;
return 200 "Result: $res";
}
```
---
## 三、请求处理类指令 (Request Handling Phases)
### 3.1 Server Rewrite Phase
| 指令 | 参数类型 | 配置层级 |
|------|---------|---------|
| `server_rewrite_by_lua_block` | block | main/server |
| `server_rewrite_by_lua_file` | file path | main/server |
### 3.2 Rewrite Phase
| 指令 | 参数类型 | 配置层级 |
|------|---------|---------|
| `rewrite_by_lua` | inline script | main/server/location/if |
| `rewrite_by_lua_block` | block | main/server/location/if |
| `rewrite_by_lua_file` | file path | main/server/location/if |
### 3.3 Access Phase
| 指令 | 参数类型 | 配置层级 |
|------|---------|---------|
| `access_by_lua` | inline script | main/server/location/if |
| `access_by_lua_block` | block | main/server/location/if |
| `access_by_lua_file` | file path | main/server/location/if |
### 3.4 Precontent Phase
| 指令 | 参数类型 | 配置层级 |
|------|---------|---------|
| `precontent_by_lua_block` | block | main/server/location/if |
| `precontent_by_lua_file` | file path | main/server/location/if |
### 3.5 Content Phase
| 指令 | 参数类型 | 配置层级 |
|------|---------|---------|
| `content_by_lua` | inline script | location/if |
| `content_by_lua_block` | block | location/if |
| `content_by_lua_file` | file path | location/if |
### 3.6 Log Phase
| 指令 | 参数类型 | 配置层级 |
|------|---------|---------|
| `log_by_lua` | inline script | main/server/location/if |
| `log_by_lua_block` | block | main/server/location/if |
| `log_by_lua_file` | file path | main/server/location/if |
### 阶段执行顺序
```
请求进入
server_rewrite_by_lua*
rewrite_by_lua*
access_by_lua*
precontent_by_lua*
content_by_lua*
header_filter_by_lua* → body_filter_by_lua*
log_by_lua*
```
---
## 四、过滤类指令 (Filter Phase)
| 指令 | 参数类型 | 配置层级 | 说明 |
|------|---------|---------|------|
| `header_filter_by_lua` | inline script | main/server/location/if | 响应头过滤 |
| `header_filter_by_lua_block` | block | main/server/location/if | 响应头过滤 |
| `header_filter_by_lua_file` | file path | main/server/location/if | 响应头过滤 |
| `body_filter_by_lua` | inline script | main/server/location/if | 响应体过滤 |
| `body_filter_by_lua_block` | block | main/server/location/if | 响应体过滤 |
| `body_filter_by_lua_file` | file path | main/server/location/if | 响应体过滤 |
### 特点
- **同步执行**: 不能 yield
- **ngx.arg 访问**: `ngx.arg[1]` 数据块, `ngx.arg[2]` EOF 标记
---
## 五、负载均衡类指令 (Upstream/Balancer)
| 指令 | 参数类型 | 配置层级 | 说明 |
|------|---------|---------|------|
| `balancer_by_lua_block` | block | upstream | 选择后端服务器 |
| `balancer_by_lua_file` | file path | upstream | 选择后端服务器 |
| `balancer_keepalive` | number | upstream | 连接池配置 |
---
## 六、SSL/TLS 类指令 (SSL Phase)
### 6.1 服务器端 SSL
| 指令 | 参数类型 | 配置层级 | 执行时机 |
|------|---------|---------|---------|
| `ssl_client_hello_by_lua_block` | block | main/server | SSL Client Hello |
| `ssl_client_hello_by_lua_file` | file path | main/server | SSL Client Hello |
| `ssl_certificate_by_lua_block` | block | main/server | SSL 证书阶段 |
| `ssl_certificate_by_lua_file` | file path | main/server | SSL 证书阶段 |
| `ssl_session_store_by_lua_block` | block | main/server | SSL 会话存储 |
| `ssl_session_store_by_lua_file` | file path | main/server | SSL 会话存储 |
| `ssl_session_fetch_by_lua_block` | block | main/server | SSL 会话获取 |
| `ssl_session_fetch_by_lua_file` | file path | main/server | SSL 会话获取 |
### 6.2 代理 SSL (Proxy SSL)
| 指令 | 参数类型 | 配置层级 |
|------|---------|---------|
| `proxy_ssl_certificate_by_lua_block` | block | location/if |
| `proxy_ssl_certificate_by_lua_file` | file path | location/if |
| `proxy_ssl_verify_by_lua_block` | block | location/if |
| `proxy_ssl_verify_by_lua_file` | file path | location/if |
---
## 七、配置控制类指令
| 指令 | 参数类型 | 配置层级 | 默认值 | 说明 |
|------|---------|---------|--------|------|
| `lua_code_cache` | on/off | main/server/location/if | on | Lua 代码缓存开关 |
| `lua_need_request_body` | on/off | main/server/location/if | off | 强制读取请求体 |
| `lua_transform_underscores_in_response_headers` | on/off | main/server/location/if | on | 转换下划线为连字符 |
| `lua_socket_log_errors` | on/off | main/server/location/if | on | socket 错误日志 |
---
## 八、Lua 环境配置指令
| 指令 | 参数类型 | 配置层级 | 说明 |
|------|---------|---------|------|
| `lua_package_path` | path | main | Lua 模块搜索路径 |
| `lua_package_cpath` | path | main | Lua C 模块搜索路径 |
| `lua_shared_dict` | name size | main | 共享内存字典 |
| `lua_regex_cache_max_entries` | number | main | 正则缓存条目数(默认1024) |
| `lua_regex_match_limit` | number | main | 正则匹配限制 |
| `lua_max_pending_timers` | number | main | 最大挂起定时器数(默认1024) |
| `lua_max_running_timers` | number | main | 最大运行定时器数(默认256) |
| `lua_thread_cache_max_entries` | number | main | 线程缓存条目数 |
| `lua_worker_thread_vm_pool_size` | number | main | Worker 线程 VM 池大小(默认10) |
---
## 九、Socket 配置指令
| 指令 | 参数类型 | 配置层级 | 默认值 | 说明 |
|------|---------|---------|--------|------|
| `lua_socket_keepalive_timeout` | time | main/server/location/if | 60s | 连接保活超时 |
| `lua_socket_connect_timeout` | time | main/server/location/if | 60s | 连接超时 |
| `lua_socket_send_timeout` | time | main/server/location/if | 60s | 发送超时 |
| `lua_socket_read_timeout` | time | main/server/location/if | 60s | 读取超时 |
| `lua_socket_send_lowat` | size | main/server/location/if | 0 | 发送低水位 |
| `lua_socket_buffer_size` | size | main/server/location/if | pagesize | 缓冲区大小 |
| `lua_socket_pool_size` | number | main/server/location/if | 30 | 连接池大小 |
---
## 十、SSL 配置指令
| 指令 | 参数类型 | 配置层级 | 说明 |
|------|---------|---------|------|
| `lua_ssl_protocols` | [SSLv2\|SSLv3\|TLSv1...] | main/server/location | SSL 协议版本 |
| `lua_ssl_ciphers` | ciphers | main/server/location | SSL 加密套件 |
| `lua_ssl_verify_depth` | number | main/server/location | 验证深度 |
| `lua_ssl_certificate` | path | main/server/location | SSL 证书 |
| `lua_ssl_certificate_key` | path | main/server/location | SSL 证书密钥 |
| `lua_ssl_trusted_certificate` | path | main/server/location | 可信证书 |
| `lua_ssl_crl` | path | main/server/location | 证书吊销列表 |
| `lua_ssl_key_log` | path | main/server/location | SSL 密钥日志 |
| `lua_ssl_conf_command` | key value | main/server/location | SSL 配置命令 |
---
## 十一、其他指令
| 指令 | 参数类型 | 配置层级 | 默认值 | 说明 |
|------|---------|---------|--------|------|
| `lua_load_resty_core` | on/off | main | - | 加载 resty.core (已废弃) |
| `lua_capture_error_log` | size | main | - | 错误日志捕获 |
| `lua_sa_restart` | on/off | main | on | SA_RESTART 信号处理 |
| `lua_http10_buffering` | on/off | main/server/location/if | on | HTTP/1.0 缓冲 |
| `lua_check_client_abort` | on/off | main/server/location/if | off | 检测客户端中断 |
| `lua_use_default_type` | on/off | main/server/location/if | on | 使用默认 Content-Type |
---
## 指令参数类型说明
| 类型 | 说明 | 示例 |
|------|------|------|
| inline script | 直接嵌入 Lua 代码字符串 | `content_by_lua "ngx.say('hello')";` |
| block | 使用 `{ }` 包裹的 Lua 代码块 | `content_by_lua_block { ngx.say('hello') }` |
| file path | 外部 Lua 文件路径 | `content_by_lua_file /path/to/script.lua;` |
| time | 时间值,支持单位 (s/ms) | `60s`, `5000ms` |
| size | 大小值,支持单位 (k/m/g) | `10m`, `1g` |
| path | 文件系统路径 | `/usr/local/openresty/lualib` |

View File

@ -0,0 +1,454 @@
# lua-nginx-module ngx.* Lua API 参考
本文档详细列出 lua-nginx-module 暴露给 Lua 的所有 API。
## API 注入点
所有 API 通过 `ngx_http_lua_inject_ngx_api` 函数注入到 `ngx` 全局表。
**源文件**: `src/ngx_http_lua_util.c:835`
### 注入函数列表
| 注入函数 | 文件 | 说明 |
|---------|------|------|
| `ngx_http_lua_inject_arg_api` | ngx_http_lua_util.c | 命令行参数 |
| `ngx_http_lua_inject_http_consts` | ngx_http_lua_consts.c | HTTP 方法/状态码常量 |
| `ngx_http_lua_inject_core_consts` | ngx_http_lua_consts.c | 核心常量 |
| `ngx_http_lua_inject_log_api` | ngx_http_lua_log.c | 日志 API |
| `ngx_http_lua_inject_output_api` | ngx_http_lua_output.c | 输出控制 |
| `ngx_http_lua_inject_control_api` | ngx_http_lua_control.c | 控制流 |
| `ngx_http_lua_inject_subrequest_api` | ngx_http_lua_subrequest.c | 子请求 |
| `ngx_http_lua_inject_req_api` | ngx_http_lua_util.c | 请求 API |
| `ngx_http_lua_inject_resp_header_api` | ngx_http_lua_headers.c | 响应头 |
| `ngx_http_lua_inject_shdict_api` | ngx_http_lua_shdict.c | 共享字典 |
| `ngx_http_lua_inject_socket_tcp_api` | ngx_http_lua_socket_tcp.c | TCP socket |
| `ngx_http_lua_inject_socket_udp_api` | ngx_http_lua_socket_udp.c | UDP socket |
| `ngx_http_lua_inject_uthread_api` | ngx_http_lua_uthread.c | 用户线程 |
| `ngx_http_lua_inject_timer_api` | ngx_http_lua_timer.c | 定时器 |
| `ngx_http_lua_inject_coroutine_api` | ngx_http_lua_coroutine.c | 协程 |
---
## 一、ngx.req.* - 请求操作 API
### 1.1 URI 操作
#### `ngx.req.set_uri(uri, jump?, binary?)`
设置请求 URI。
- **参数**:
- `uri` (string): 新的 URI
- `jump` (boolean, 可选): 是否执行内部跳转
- `binary` (boolean, 可选): 是否二进制安全
- **返回值**: 无或 yield
- **适用阶段**: rewrite, server_rewrite
- **示例**:
```lua
ngx.req.set_uri("/new/path", true) -- 内部跳转
ngx.req.set_uri("/api/v2" .. ngx.var.request_uri) -- 重写
```
### 1.2 参数操作
#### `ngx.req.get_uri_args(max_args?)`
获取 URL 查询参数。
- **参数**: `max_args` (number, 可选, 默认 100)
- **返回值**: table (参数键值对,多值参数为数组)
- **示例**:
```lua
local args = ngx.req.get_uri_args()
-- URL: /test?foo=bar&foo=baz&name=john
-- args.foo = {"bar", "baz"}
-- args.name = "john"
```
#### `ngx.req.get_post_args(max_args?)`
获取 POST 表单参数。
- **参数**: `max_args` (number, 可选, 默认 100)
- **返回值**: table
- **前提**: 需要 `lua_need_request_body on` 或先调用 `ngx.req.read_body()`
#### `ngx.req.set_uri_args(args)`
设置 URL 查询参数。
- **参数**: `args` (string/number/table)
- **示例**:
```lua
ngx.req.set_uri_args({foo = "bar", page = 1})
ngx.req.set_uri_args("foo=bar&page=1")
```
### 1.3 请求头操作
#### `ngx.req.get_headers(max_headers?, raw?)`
获取请求头。
- **参数**:
- `max_headers` (number, 可选)
- `raw` (boolean, 可选): 是否保留原始大小写
- **返回值**: table
- **示例**:
```lua
local headers = ngx.req.get_headers()
local content_type = headers["Content-Type"]
local host = headers.host or headers.Host
```
#### `ngx.req.set_header(header_name, header_value)`
设置请求头。
- **参数**:
- `header_name` (string)
- `header_value` (string/table/nil)
- **示例**:
```lua
ngx.req.set_header("X-Custom", "value")
ngx.req.set_header("X-Multi", {"val1", "val2"})
ngx.req.set_header("X-Remove", nil) -- 删除
```
#### `ngx.req.clear_header(header_name)`
清除请求头。
### 1.4 请求体操作
#### `ngx.req.read_body()`
异步读取请求体。
- **返回值**: 无或 yield
- **适用阶段**: rewrite, access, content
#### `ngx.req.get_body_data(max?)`
获取请求体数据。
- **参数**: `max` (number, 可选)
- **返回值**: string 或 nil
#### `ngx.req.get_body_file()`
获取请求体临时文件路径。
- **返回值**: string 或 nil
#### `ngx.req.discard_body()`
丢弃请求体。
#### `ngx.req.set_body_data(data)`
设置请求体数据。
#### `ngx.req.set_body_file(file_path, auto_clean?)`
设置请求体文件。
### 1.5 其他请求信息
#### `ngx.req.http_version()`
获取 HTTP 版本。
- **返回值**: number (0.9, 1.0, 1.1, 2.0, 3.0) 或 nil
#### `ngx.req.raw_header(no_request_line?)`
获取原始请求头字符串。
- **参数**: `no_request_line` (boolean)
- **返回值**: string
#### `ngx.req.get_method()`
获取请求方法。
- **返回值**: string ("GET", "POST", etc.)
#### `ngx.req.set_method(method)`
设置请求方法。
#### `ngx.req.is_internal()`
判断是否内部请求。
- **返回值**: boolean
---
## 二、ngx.resp.* - 响应操作 API
#### `ngx.resp.get_headers(max_headers?, raw?)`
获取响应头。
- **参数**:
- `max_headers` (number, 可选)
- `raw` (boolean, 可选)
- **返回值**: table
---
## 三、输出控制 API
### `ngx.say(...)`
输出内容并附加换行符。
- **参数**: 可变参数 (string/number/boolean/table/nil)
- **返回值**: 1 (成功) 或 nil, err
- **适用阶段**: rewrite, access, content, precontent
- **示例**:
```lua
ngx.say("Hello, ", "World!")
ngx.say({name = "test", value = 123}) -- 输出 table
```
### `ngx.print(...)`
输出内容,不附加换行符。
### `ngx.flush(wait?)`
刷新输出缓冲区。
- **参数**: `wait` (boolean, 可选)
- **返回值**: 1, nil+err, 或 yield
### `ngx.eof()`
发送 EOF结束响应。
### `ngx.send_headers()`
显式发送响应头。
### `ngx.exit(status)`
结束请求处理。
- **参数**: `status` (number) - HTTP 状态码或 ngx.ERROR 等
- **示例**:
```lua
ngx.exit(ngx.HTTP_NOT_FOUND) -- 404
ngx.exit(ngx.HTTP_OK) -- 正常结束
ngx.exit(ngx.ERROR) -- 错误
```
---
## 四、日志 API
### `ngx.log(level, ...)`
记录日志。
- **参数**:
- `level`: 日志级别常量
- `...`: 可变参数
- **示例**:
```lua
ngx.log(ngx.ERR, "error message: ", err)
ngx.log(ngx.INFO, "request from: ", ngx.var.remote_addr)
```
### 日志级别常量
| 常量 | 值 | 说明 |
|------|---|------|
| `ngx.STDERR` | 0 | 标准错误 |
| `ngx.EMERG` | 1 | 紧急 |
| `ngx.ALERT` | 2 | 警报 |
| `ngx.CRIT` | 3 | 严重 |
| `ngx.ERR` | 4 | 错误 |
| `ngx.WARN` | 5 | 警告 |
| `ngx.NOTICE` | 6 | 通知 |
| `ngx.INFO` | 7 | 信息 |
| `ngx.DEBUG` | 8 | 调试 |
### `print(...)`
全局函数,等效于 `ngx.log(ngx.NOTICE, ...)`
---
## 五、控制流 API
### `ngx.exec(uri, args?)`
内部重定向。
- **参数**:
- `uri` (string): 目标 URI
- `args` (string/table/nil): 参数
- **示例**:
```lua
ngx.exec("/internal/api", {foo = "bar"})
```
### `ngx.redirect(uri, status?)`
HTTP 重定向。
- **参数**:
- `uri` (string): 目标 URL
- `status` (number): HTTP 状态码 (默认 302)
- **可选状态**: 301, 302, 303, 307, 308
- **示例**:
```lua
ngx.redirect("https://example.com/new")
ngx.redirect("/login", ngx.HTTP_MOVED_TEMPORARILY)
```
### `ngx.on_abort(callback)`
注册客户端断开连接回调。
---
## 六、子请求 API
### `ngx.location.capture(uri, options?)`
发起单个子请求。
- **参数**:
- `uri` (string): 内部 URI
- `options` (table, 可选):
- `method`: HTTP 方法常量
- `args`: 参数字符串/table
- `body`: 请求体
- `headers`: 请求头 table
- `vars`: 变量 table
- `share_all_vars`: boolean
- `copy_all_vars`: boolean
- `always_forward_body`: boolean
- `ctx`: Lua table
- **返回值**: table {status, header, body, truncated}
- **适用阶段**: rewrite, access, content
- **示例**:
```lua
local res = ngx.location.capture("/api/users", {
method = ngx.HTTP_POST,
body = '{"name":"john"}',
headers = {["Content-Type"] = "application/json"}
})
ngx.say("Status: ", res.status)
ngx.say("Body: ", res.body)
```
### `ngx.location.capture_multi(requests)`
并发发起多个子请求。
- **参数**: `requests` (array of {uri, options?})
- **返回值**: array of response tables
- **示例**:
```lua
local res1, res2 = ngx.location.capture_multi({
{"/api/users"},
{"/api/products", {method = ngx.HTTP_GET}}
})
```
---
## 七、变量访问 API
### `ngx.var.VARIABLE_NAME`
读写 nginx 变量。
- **读取**: `local value = ngx.var.host`
- **写入**: `ngx.var.my_var = "value"`
- **删除**: `ngx.var.my_var = nil`
**内部实现**: 通过 FFI 函数 `ngx_http_lua_ffi_var_get``ngx_http_lua_ffi_var_set`
---
## 八、请求上下文 API
### `ngx.ctx`
当前请求的 Lua 表,可在请求各阶段共享数据。
- **特点**:
- 每个请求独立
- 子请求有独立的 ctx除非显式传递
- 请求结束时自动清理
- **示例**:
```lua
-- access_by_lua
ngx.ctx.user_id = 123
-- content_by_lua (同一请求)
ngx.say("User: ", ngx.ctx.user_id)
```
---
## 九、睡眠 API
### `ngx.sleep(seconds)`
非阻塞睡眠。
- **参数**: `seconds` (number) - 睡眠秒数,支持小数
- **返回值**: yield
- **示例**:
```lua
ngx.sleep(0.1) -- 睡眠 100ms
ngx.sleep(1) -- 睡眠 1s
```
---
## 十、常量定义
### 核心常量
| 常量 | 值 | 说明 |
|------|---|------|
| `ngx.OK` | 0 | 成功 |
| `ngx.ERROR` | -1 | 错误 |
| `ngx.AGAIN` | -2 | 再次 |
| `ngx.DONE` | -4 | 完成 |
| `ngx.DECLINED` | -5 | 拒绝 |
| `ngx.null` | lightuserdata | NULL 值 |
### HTTP 状态码常量
| 常量 | 值 |
|------|---|
| `ngx.HTTP_OK` | 200 |
| `ngx.HTTP_CREATED` | 201 |
| `ngx.HTTP_NO_CONTENT` | 204 |
| `ngx.HTTP_BAD_REQUEST` | 400 |
| `ngx.HTTP_UNAUTHORIZED` | 401 |
| `ngx.HTTP_FORBIDDEN` | 403 |
| `ngx.HTTP_NOT_FOUND` | 404 |
| `ngx.HTTP_INTERNAL_SERVER_ERROR` | 500 |
| `ngx.HTTP_SERVICE_UNAVAILABLE` | 503 |
### HTTP 方法常量
| 常量 | 值 |
|------|---|
| `ngx.HTTP_GET` | 2 |
| `ngx.HTTP_POST` | 8 |
| `ngx.HTTP_PUT` | 16 |
| `ngx.HTTP_DELETE` | 32 |
| `ngx.HTTP_HEAD` | 4 |
| `ngx.HTTP_PATCH` | 2048 |

View File

@ -0,0 +1,356 @@
# lua-nginx-module Cosocket 非阻塞 Socket API
本文档详细说明 lua-nginx-module 的非阻塞 Socket API (Cosocket)。
## 核心文件
| 文件路径 | 描述 |
|---------|------|
| `src/ngx_http_lua_socket_tcp.c` | TCP socket 实现 (~7000 行) |
| `src/ngx_http_lua_socket_tcp.h` | TCP socket 头文件 |
| `src/ngx_http_lua_socket_udp.c` | UDP socket 实现 (~1700 行) |
| `src/ngx_http_lua_socket_udp.h` | UDP socket 头文件 |
---
## 一、TCP Socket API
### 1.1 创建 Socket
#### `ngx.socket.tcp()` / `ngx.socket.stream()`
创建 TCP socket 对象。
- **返回值**: TCP socket 对象
- **示例**:
```lua
local sock = ngx.socket.tcp()
```
### 1.2 连接
#### `sock:connect(host, port, options?)`
建立 TCP 连接。
- **参数**:
- `host` (string): 主机名或 IP 地址
- `port` (number): 端口号
- `options` (table, 可选): 连接选项
- **参数格式**:
```lua
-- IP + 端口
sock:connect("127.0.0.1", 80)
-- Unix Domain Socket
sock:connect("unix:/path/to/socket")
-- 带连接池选项
sock:connect("127.0.0.1", 80, {
pool_size = 10, -- 连接池大小
backlog = 5, -- 等待队列长度
pool = "my_pool" -- 自定义池名称
})
```
- **返回值**: 1 (成功) 或 nil, err
- **适用阶段**: rewrite, access, content
### 1.3 发送数据
#### `sock:send(data)`
发送数据。
- **参数**: `data` (string/number/boolean/table)
- **返回值**: bytes_sent (成功) 或 nil, err
- **示例**:
```lua
local bytes, err = sock:send("GET / HTTP/1.1\r\n\r\n")
local bytes, err = sock:send({ "line1", "line2" })
```
### 1.4 接收数据
#### `sock:receive(pattern?)`
接收数据。
- **参数**: `pattern` (string/number, 可选)
- **接收模式**:
| 模式 | 说明 |
|------|------|
| `nil``"*l"` | 读取一行 (默认) |
| `"*a"` | 读取所有数据直到 EOF |
| `number` | 读取指定字节数 |
- **返回值**: data (成功) 或 nil, err, partial
- **示例**:
```lua
local line = sock:receive() -- 读取一行
local data = sock:receive("*a") -- 读取全部
local data = sock:receive(1024) -- 读取 1024 字节
```
#### `sock:receiveany(max_bytes)`
接收最多指定字节数的数据。
- **参数**: `max_bytes` (number)
- **返回值**: data 或 nil, err
#### `sock:receiveuntil(pattern, options?)`
创建迭代器读取直到匹配模式。
- **参数**:
- `pattern` (string): 结束模式
- `options` (table): `{inclusive = true/false}`
- **返回值**: iterator 函数
- **示例**:
```lua
local reader = sock:receiveuntil("\r\n\r\n")
local data, err, partial = reader()
local data4 = reader(4) -- 读取 4 字节
```
### 1.5 超时设置
#### `sock:settimeout(ms)`
设置所有操作的超时时间。
#### `sock:settimeouts(connect_timeout, send_timeout, read_timeout)`
分别设置连接、发送、读取超时。
- **参数**: 毫秒数
- **示例**:
```lua
sock:settimeout(5000) -- 5 秒超时
sock:settimeouts(1000, 2000, 5000) -- 连接 1s, 发送 2s, 读取 5s
```
### 1.6 连接池管理
#### `sock:setkeepalive(timeout?, pool_size?)`
将连接放回连接池。
- **参数**:
- `timeout` (number, 可选): 最大空闲时间,毫秒
- `pool_size` (number, 可选): 池大小
- **返回值**: 1 或 nil, err
- **示例**:
```lua
local ok, err = sock:setkeepalive(60000, 100)
```
#### `sock:getreusedtimes()`
获取连接被复用的次数。
- **返回值**: number
- **示例**:
```lua
local times = sock:getreusedtimes()
if times > 100 then
-- 连接复用次数过多,关闭重建
sock:close()
end
```
### 1.7 关闭
#### `sock:close()`
关闭连接。
- **返回值**: 1 或 nil, err
### 1.8 SSL 握手
#### `sock:sslhandshake(session?, server_name?, verify?, ...)`
执行 SSL 握手。
- **参数**:
- `session` (userdata, 可选): SSL 会话对象
- `server_name` (string, 可选): SNI 主机名
- `verify` (boolean, 可选): 是否验证证书
- **返回值**: session 或 nil, err
- **示例**:
```lua
local session, err = sock:sslhandshake(nil, "example.com", true)
```
### 1.9 Socket 选项
#### `sock:setoption(option, value)`
设置 socket 选项 (FFI 接口)。
| 选项 | 说明 |
|------|------|
| `ngx.HTTP_LUA_SOCKOPT_KEEPALIVE` | SO_KEEPALIVE |
| `ngx.HTTP_LUA_SOCKOPT_TCP_NODELAY` | TCP_NODELAY |
| `ngx.HTTP_LUA_SOCKOPT_SNDBUF` | SO_SNDBUF |
| `ngx.HTTP_LUA_SOCKOPT_RCVBUF` | SO_RCVBUF |
| `ngx.HTTP_LUA_SOCKOPT_REUSEADDR` | SO_REUSEADDR |
---
## 二、UDP Socket API
### 2.1 创建 Socket
#### `ngx.socket.udp()`
创建 UDP socket 对象。
### 2.2 设置对端
#### `sock:setpeername(host, port)`
设置目标地址。
- **参数**:
- `host` (string): 主机名或 IP
- `port` (number): 端口号
- **示例**:
```lua
local sock = ngx.socket.udp()
sock:setpeername("127.0.0.1", 53)
```
### 2.3 发送
#### `sock:send(data)`
发送数据报。
- **参数**: `data` (string)
- **返回值**: 1 或 nil, err
### 2.4 接收
#### `sock:receive(size?)`
接收数据报。
- **参数**: `size` (number, 可选) - 最大 65536 字节
- **返回值**: data 或 nil, err
### 2.5 绑定本地地址
#### `sock:bind(address)`
绑定本地地址。
---
## 三、内部实现机制
### 3.1 非阻塞原理
Cosocket 通过以下机制实现非阻塞:
1. **协程挂起**: 调用 `lua_yield()` 挂起 Lua 协程
2. **事件注册**: 向 Nginx 事件循环注册读写事件
3. **恢复执行**: 事件触发时通过 `resume_handler` 恢复协程
### 3.2 连接池实现
```
连接池结构:
┌─────────────────────────────────────┐
│ pool = "host:port" │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐│
│ │conn 1 │→│conn 2 │→│conn N ││
│ └─────────┘ └─────────┘ └─────────┘│
│ free_queue │
└─────────────────────────────────────┘
```
**关键数据结构**:
```c
typedef struct {
ngx_queue_t queue; // 空闲队列链接
ngx_connection_t *connection; // 缓存的连接
ngx_str_t host;
ngx_uint_t port;
// ...
} ngx_http_lua_socket_pool_item_t;
```
### 3.3 请求 Socket
#### `ngx.req.socket(raw?)`
获取请求的 raw socket。
- **参数**: `raw` (boolean) - 是否原始模式
- **返回值**: cosocket 对象
- **示例**:
```lua
local sock, err = ngx.req.socket()
sock:receive() -- 接收客户端数据
sock:send(data) -- 发送响应
```
---
## 四、最佳实践
### 4.1 连接池使用
```lua
local function query_redis()
local sock = ngx.socket.tcp()
sock:settimeout(1000)
-- 使用连接池
local ok, err = sock:connect("127.0.0.1", 6379, {pool = "redis"})
if not ok then
return nil, err
end
local bytes, err = sock:send("PING\r\n")
local data, err = sock:receive()
-- 放回连接池而非关闭
sock:setkeepalive(60000, 100)
return data
end
```
### 4.2 错误处理
```lua
local function safe_request()
local sock = ngx.socket.tcp()
local ok, err = sock:connect("127.0.0.1", 80)
if not ok then
ngx.log(ngx.ERR, "connect failed: ", err)
return nil, err
end
local bytes, err = sock:send("GET / HTTP/1.0\r\n\r\n")
if not bytes then
sock:close()
return nil, err
end
local reader = sock:receiveuntil("\r\n\r\n")
local headers, err = reader()
if not headers then
sock:close()
return nil, err
end
sock:setkeepalive()
return headers
end
```

View File

@ -0,0 +1,366 @@
# lua-nginx-module 定时器和用户线程 API
本文档详细说明 lua-nginx-module 的定时器和用户线程功能。
---
## 一、Timer API
### 核心文件
- `src/ngx_http_lua_timer.c` - 定时器核心实现
- `src/ngx_http_lua_timer.h` - 定时器头文件
### 1.1 `ngx.timer.at(delay, callback, ...)`
创建一次性定时器。
- **参数**:
- `delay` (number): 延迟秒数,支持小数
- `callback` (function): 回调函数
- `...`: 传递给回调的额外参数
- **返回值**: timer_id (成功) 或 nil, err
- **回调参数**: `premature`, `...` (额外参数)
- **示例**:
```lua
local function handler(premature, data)
if premature then
return -- Worker 正在退出
end
ngx.log(ngx.INFO, "Timer fired: ", data)
end
local ok, err = ngx.timer.at(5, handler, "hello")
if not ok then
ngx.log(ngx.ERR, "failed to create timer: ", err)
end
```
### 1.2 `ngx.timer.every(delay, callback, ...)`
创建周期性定时器。
- **参数**: 同 `ngx.timer.at`
- **返回值**: timer_id 或 nil, err
- **示例**:
```lua
local function heartbeat()
-- 定期执行任务
local redis = require "resty.redis"
-- ...
end
ngx.timer.every(30, heartbeat) -- 每 30 秒执行
```
### 1.3 `ngx.timer.running_count()`
获取当前正在执行的定时器数量。
- **返回值**: number
### 1.4 `ngx.timer.pending_count()`
获取等待执行的定时器数量。
- **返回值**: number
### 1.5 定时器限制
| 配置指令 | 默认值 | 说明 |
|---------|--------|------|
| `lua_max_pending_timers` | 1024 | 最大挂起定时器数 |
| `lua_max_running_timers` | 256 | 最大运行定时器数 |
### 1.6 内部实现
**数据结构**:
```c
typedef struct {
void **main_conf;
void **srv_conf;
void **loc_conf;
lua_State *co; // 定时器回调协程
ngx_pool_t *pool;
int co_ref;
unsigned delay:31; // 周期性定时器的延迟
unsigned premature:1; // 是否被提前终止
} ngx_http_lua_timer_ctx_t;
```
**执行机制**:
1. 创建 `ngx_event_t` + `ngx_http_lua_timer_ctx_t`
2. 使用 `ngx_add_timer(ev, delay)` 加入 Nginx 红黑树定时器
3. 定时器到期时,`ngx_http_lua_timer_handler` 执行
4. 创建 fake connection/request 模拟请求上下文
---
## 二、Coroutine API
### 核心文件
- `src/ngx_http_lua_coroutine.c` - 协程核心实现
- `src/ngx_http_lua_coroutine.h` - 协程头文件
### 2.1 协程状态
```c
typedef enum {
NGX_HTTP_LUA_CO_RUNNING = 0, // 运行中
NGX_HTTP_LUA_CO_SUSPENDED = 1, // 挂起
NGX_HTTP_LUA_CO_NORMAL = 2, // 正常(被 resume 的协程)
NGX_HTTP_LUA_CO_DEAD = 3, // 已结束
NGX_HTTP_LUA_CO_ZOMBIE = 4 // 僵尸(父协程已结束)
} ngx_http_lua_co_status_e;
```
### 2.2 标准 Coroutine API
lua-nginx-module 扩展了 Lua 标准 coroutine 库:
| API | 说明 |
|-----|------|
| `coroutine.create(func)` | 创建新协程 |
| `coroutine.wrap(func)` | 创建包装协程 |
| `coroutine.resume(co, ...)` | 恢复协程执行 |
| `coroutine.yield(...)` | 挂起当前协程 |
| `coroutine.status(co)` | 获取协程状态 |
### 2.3 设计特点
- 所有用户协程都在主协程中创建
- 确保总是 yield 到主 Lua 线程
- 使用 `ngx_http_lua_co_ctx_t` 管理协程上下文
---
## 三、User Thread (Light Thread) API
### 核心文件
- `src/ngx_http_lua_uthread.c` - 用户线程实现
- `src/ngx_http_lua_uthread.h` - 用户线程头文件
### 3.1 概念说明
Light Thread (uthread) 是一种特殊的协程:
- 通过 `ngx.thread.*` API 操作
- 父子关系严格:只有父协程可以 wait/kill 子线程
- 使用 `is_uthread` 标记区分
### 3.2 API
#### `ngx.thread.spawn(func, ...)`
创建轻量级线程。
- **参数**:
- `func` (function): 线程函数
- `...`: 传递给函数的参数
- **返回值**: thread 对象
- **示例**:
```lua
local function fetch(url)
local sock = ngx.socket.tcp()
-- ... 网络操作
return result
end
local thread1 = ngx.thread.spawn(fetch, "http://api1")
local thread2 = ngx.thread.spawn(fetch, "http://api2")
```
#### `ngx.thread.wait(thread1, thread2, ...)`
等待线程结束。
- **参数**: 一个或多个 thread 对象
- **返回值**: success, res_or_err, thread
- **示例**:
```lua
local ok, res = ngx.thread.wait(thread1, thread2)
if ok then
ngx.say("Result: ", res)
end
```
#### `ngx.thread.kill(thread)`
终止线程。
- **参数**: thread 对象
- **返回值**: 1 或 nil, err
### 3.3 判断宏
```c
#define ngx_http_lua_is_thread(ctx) \
((ctx)->cur_co_ctx->is_uthread || (ctx)->cur_co_ctx == &(ctx)->entry_co_ctx)
#define ngx_http_lua_is_entry_thread(ctx) \
((ctx)->cur_co_ctx == &(ctx)->entry_co_ctx)
#define ngx_http_lua_coroutine_alive(coctx) \
((coctx)->co_status != NGX_HTTP_LUA_CO_DEAD \
&& (coctx)->co_status != NGX_HTTP_LUA_CO_ZOMBIE)
```
---
## 四、Semaphore API
### 核心文件
- `src/ngx_http_lua_semaphore.c` - 信号量实现
- `src/ngx_http_lua_semaphore.h` - 信号量头文件
### 4.1 数据结构
```c
typedef struct ngx_http_lua_sema_s {
ngx_queue_t wait_queue; // 等待队列
int resource_count; // 资源计数
unsigned wait_count; // 等待计数
} ngx_http_lua_sema_t;
```
### 4.2 API
#### `ngx.semaphore.new(n)`
创建信号量。
- **参数**: `n` (number) - 初始资源数
- **返回值**: semaphore 对象 或 nil, err
- **示例**:
```lua
local semaphore = require "ngx.semaphore"
local sem, err = semaphore.new(0)
```
#### `sem:post(n?)`
释放资源。
- **参数**: `n` (number, 可选, 默认 1) - 释放数量
- **示例**:
```lua
sem:post(1) -- 释放 1 个资源
```
#### `sem:wait(timeout)`
等待资源。
- **参数**: `timeout` (number) - 超时秒数
- **返回值**: 1 或 nil, err
- **示例**:
```lua
local ok, err = sem:wait(5) -- 最多等待 5 秒
if not ok then
ngx.log(ngx.ERR, "wait timeout: ", err)
end
```
#### `sem:count()`
获取可用资源数。
- **返回值**: number
### 4.3 使用示例
```lua
local semaphore = require "ngx.semaphore"
local sem = semaphore.new(0)
-- 生产者
local function producer()
for i = 1, 10 do
ngx.sleep(0.1)
sem:post(1)
ngx.log(ngx.INFO, "produced: ", i)
end
end
-- 消费者
local function consumer()
for i = 1, 10 do
sem:wait(1)
ngx.log(ngx.INFO, "consumed: ", i)
end
end
ngx.thread.spawn(producer)
ngx.thread.spawn(consumer)
```
---
## 五、关系图
```
ngx.timer.* ─────────────────────────────┐
│ │
▼ │
ngx_event_t (Nginx 定时器红黑树) │
│ │
▼ │
lua_State (协程) ◄── coroutine.create │
│ │ │
│ ▼ │
│ ngx_http_lua_co_ctx_t │
│ │ │
└─────────► ngx.thread.spawn ─────────►│
(uthread/light thread) │
│ │
▼ │
ngx.semaphore.new │
│ │
▼ │
ngx_http_lua_sema_t ───────┘
```
---
## 六、最佳实践
### 6.1 定时器清理
```lua
local timer_id
local function cleanup()
-- Worker 退出时清理
ngx.log(ngx.INFO, "cleaning up...")
end
timer_id = ngx.timer.every(60, function(premature)
if premature then
cleanup()
return
end
-- 正常任务
end)
```
### 6.2 并发控制
```lua
local semaphore = require "ngx.semaphore"
local sem = semaphore.new(3) -- 最大 3 个并发
local function limited_task()
sem:wait(10) -- 等待获取资源
-- 执行任务
sem:post(1) -- 释放资源
end
for i = 1, 10 do
ngx.thread.spawn(limited_task)
end
```

View File

@ -0,0 +1,333 @@
# lua-nginx-module 共享内存字典 (shdict)
本文档详细说明 lua-nginx-module 的共享内存字典功能。
---
## 一、核心文件
| 文件 | 说明 |
|------|------|
| `src/ngx_http_lua_shdict.c` | shdict 核心实现 |
| `src/ngx_http_lua_shdict.h` | 数据结构定义 |
| `src/ngx_http_lua_directive.c` | 配置指令处理 |
---
## 二、数据结构
### 2.1 节点结构
```c
typedef struct {
u_char color; /* 红黑树颜色 */
uint8_t value_type; /* 值类型 */
u_short key_len; /* key 长度 */
uint32_t value_len; /* value 长度 */
uint64_t expires; /* 过期时间 (毫秒) */
ngx_queue_t queue; /* LRU 队列节点 */
uint32_t user_flags; /* 用户自定义标志 */
u_char data[1]; /* key + value 数据 */
} ngx_http_lua_shdict_node_t;
```
### 2.2 值类型枚举
```c
enum {
SHDICT_TNIL = 0, /* 空值 */
SHDICT_TBOOLEAN = 1, /* 布尔值 */
SHDICT_TNUMBER = 3, /* 数字 */
SHDICT_TSTRING = 4, /* 字符串 */
SHDICT_TLIST = 5, /* 列表 */
};
```
### 2.3 共享上下文
```c
typedef struct {
ngx_rbtree_t rbtree; /* 红黑树根 */
ngx_rbtree_node_t sentinel; /* 红黑树哨兵 */
ngx_queue_t lru_queue; /* LRU 队列头 */
} ngx_http_lua_shdict_shctx_t;
typedef struct {
ngx_http_lua_shdict_shctx_t *sh; /* 共享上下文 */
ngx_slab_pool_t *shpool; /* slab 分配器 */
ngx_str_t name; /* 字典名称 */
} ngx_http_lua_shdict_ctx_t;
```
---
## 三、配置
### `lua_shared_dict <name> <size>`
定义共享内存字典。
- **参数**:
- `name`: 字典名称
- `size`: 内存大小 (最小 8KB)
- **配置层级**: main
- **示例**:
```nginx
lua_shared_dict cache 10m;
lua_shared_dict sessions 100m;
lua_shared_dict counters 1m;
```
---
## 四、Lua API
### 4.1 获取字典对象
```lua
local dict = ngx.shared.cache
```
### 4.2 基础操作
#### `dict:get(key)`
获取值。
- **参数**: `key` (string)
- **返回值**: value, err 或 nil
- **示例**:
```lua
local val, err = ngx.shared.cache:get("user:123")
if val == nil and err then
ngx.log(ngx.ERR, "get failed: ", err)
end
```
#### `dict:get_stale(key)`
获取值(允许过期数据)。
#### `dict:set(key, value, exptime?, flags?)`
设置值。
- **参数**:
- `key` (string)
- `value` (string/number/boolean/nil)
- `exptime` (number, 可选): 过期时间秒数
- `flags` (number, 可选): 用户标志
- **返回值**: success, err, forcible
- **示例**:
```lua
local dict = ngx.shared.cache
local ok, err, forcible = dict:set("key", "value", 60)
if forcible then
ngx.log(ngx.WARN, "forced to evict old items")
end
```
#### `dict:safe_set(key, value, exptime?, flags?)`
安全设置值(不会淘汰有效项)。
#### `dict:add(key, value, exptime?, flags?)`
仅在 key 不存在时添加。
#### `dict:safe_add(key, value, exptime?, flags?)`
安全添加(不会淘汰有效项)。
#### `dict:replace(key, value, exptime?, flags?)`
仅在 key 存在时替换。
#### `dict:delete(key)`
删除 key。
### 4.3 数值操作
#### `dict:incr(key, value, init?)`
原子递增。
- **参数**:
- `key` (string)
- `value` (number): 增量
- `init` (number, 可选): key 不存在时的初始值
- **返回值**: new_value, err 或 nil
- **示例**:
```lua
local dict = ngx.shared.counters
local new_val, err = dict:incr("hits", 1, 0)
ngx.say("Hits: ", new_val)
```
### 4.4 列表操作
#### `dict:lpush(key, value)`
左侧推入列表。
#### `dict:rpush(key, value)`
右侧推入列表。
#### `dict:lpop(key)`
左侧弹出列表。
#### `dict:rpop(key)`
右侧弹出列表。
#### `dict:llen(key)`
获取列表长度。
- **示例**:
```lua
local dict = ngx.shared.queue
dict:rpush("jobs", "job1")
dict:rpush("jobs", "job2")
local job = dict:lpop("jobs") -- "job1"
local len = dict:llen("jobs") -- 1
```
### 4.5 过期管理
#### `dict:ttl(key)`
获取剩余 TTL。
- **返回值**: ttl, err 或 nil
#### `dict:expire(key, exptime)`
设置过期时间。
- **参数**:
- `key` (string)
- `exptime` (number): 秒数
- **返回值**: success, err
#### `dict:flush_all()`
标记所有项为过期。
#### `dict:flush_expired(max_count?)`
清除过期项。
- **参数**: `max_count` (number, 可选)
### 4.6 容量信息
#### `dict:capacity()`
获取总容量。
- **返回值**: number (bytes)
#### `dict:free_space()`
获取空闲空间。
- **返回值**: number (bytes)
#### `dict:get_keys(max_count?)`
获取所有 key。
- **参数**: `max_count` (number, 可选, 默认 1024)
- **返回值**: table of keys
---
## 五、内部实现
### 5.1 Slab 分配器
使用 Nginx 的 slab 分配器管理共享内存:
- `ngx_slab_alloc_locked()` / `ngx_slab_alloc()`
- `ngx_slab_free_locked()` / `ngx_slab_free()`
### 5.2 红黑树索引
用于快速查找:
- Key 哈希 → 红黑树节点
- 冲突处理:`ngx_memn2cmp` 比较实际 key
### 5.3 LRU 队列
实现最近最少使用淘汰:
- 新访问项移到队列头
- 淘汰时从队列尾移除
### 5.4 并发控制
使用互斥锁保护共享数据:
```c
ngx_shmtx_lock(&ctx->shpool->mutex);
// 操作共享数据
ngx_shmtx_unlock(&ctx->shpool->mutex);
```
---
## 六、使用示例
### 6.1 简单缓存
```lua
local function get_with_cache(key, fetch_func, ttl)
local dict = ngx.shared.cache
local value = dict:get(key)
if value then
return value
end
value = fetch_func()
dict:set(key, value, ttl)
return value
end
```
### 6.2 分布式锁
```lua
local function acquire_lock(lock_name, timeout, expiry)
local dict = ngx.shared.locks
local ok, err = dict:add(lock_name, 1, expiry)
if ok then
return true
end
return false, err
end
local function release_lock(lock_name)
local dict = ngx.shared.locks
dict:delete(lock_name)
end
```
### 6.3 限流计数
```lua
local function rate_limit(key, limit, window)
local dict = ngx.shared.limits
local count, err = dict:incr(key, 1, 0, window)
if count > limit then
return false -- 超限
end
return true
end
```

View File

@ -0,0 +1,257 @@
# lua-nginx-module SSL/TLS 功能
本文档详细说明 lua-nginx-module 的 SSL/TLS 相关功能。
---
## 一、核心文件
| 文件 | 说明 |
|------|------|
| `src/ngx_http_lua_ssl_certby.c` | 动态证书选择 |
| `src/ngx_http_lua_ssl_session_storeby.c` | Session 存储回调 |
| `src/ngx_http_lua_ssl_session_fetchby.c` | Session 获取回调 |
| `src/ngx_http_lua_ssl_client_helloby.c` | Client Hello 处理 |
| `src/ngx_http_lua_ssl.c` | SSL 上下文初始化 |
---
## 二、ssl_certificate_by_lua - 动态证书选择
### 2.1 功能概述
在 SSL 握手前执行 Lua 代码,动态选择证书。常用于:
- SNI 多域名证书选择
- 动态证书加载
- Let's Encrypt 自动证书
### 2.2 OpenSSL 回调集成
```c
int ngx_http_lua_ssl_cert_handler(ngx_ssl_conn_t *ssl_conn, void *data)
{
// 1. 获取连接上下文
// 2. 创建 fake connection/request
// 3. 调用 Lua handler
// 4. 返回结果 (1=成功, 0=失败)
}
```
### 2.3 FFI API
| 函数 | 功能 |
|------|------|
| `ngx_http_lua_ffi_ssl_clear_certs` | 清除当前证书 |
| `ngx_http_lua_ffi_set_cert` | 设置证书链 |
| `ngx_http_lua_ffi_set_priv_key` | 设置私钥 |
| `ngx_http_lua_ffi_ssl_server_name` | 获取 SNI 服务器名 |
| `ngx_http_lua_ffi_ssl_raw_server_addr` | 获取服务器地址 |
| `ngx_http_lua_ffi_ssl_client_random` | 获取客户端随机数 |
| `ngx_http_lua_ffi_ssl_verify_client` | 启用客户端证书验证 |
### 2.4 证书解析 API
```lua
local ssl = require "ngx.ssl"
-- PEM 转 DER
local der_cert, err = ssl.cert_pem_to_der(pem_cert)
local der_key, err = ssl.priv_key_pem_to_der(pem_key)
-- 设置证书
local ok, err = ssl.set_der_cert(der_cert)
local ok, err = ssl.set_der_priv_key(der_key)
```
### 2.5 使用示例
```nginx
server {
listen 443 ssl;
server_name ~^(.+)\.example\.com$;
ssl_certificate_by_lua_block {
local ssl = require "ngx.ssl"
-- 获取 SNI
local server_name, err = ssl.server_name()
if not server_name then
ngx.log(ngx.ERR, "no SNI")
return ngx.exit(ngx.ERROR)
end
-- 动态加载证书
local cert = load_cert_from_redis(server_name)
if cert then
ssl.set_der_cert(cert.der_cert)
ssl.set_der_priv_key(cert.der_key)
end
}
ssl_certificate /fallback.crt;
ssl_certificate_key /fallback.key;
}
```
---
## 三、SSL Session 缓存
### 3.1 ssl_session_store_by_lua
存储 Session 到外部存储。
```nginx
ssl_session_store_by_lua_block {
local ssl_session = require "ngx.ssl.session"
local id, err = ssl_session.get_session_id()
local data, err = ssl_session.get_serialized_session()
-- 存储到 Redis
local redis = require "resty.redis"
local red = redis:new()
red:connect("127.0.0.1", 6379)
red:setex("ssl:sess:" .. id, 3600, data)
}
```
### 3.2 ssl_session_fetch_by_lua
从外部存储获取 Session。
```nginx
ssl_session_fetch_by_lua_block {
local ssl_session = require "ngx.ssl.session"
local id, err = ssl_session.get_session_id()
local redis = require "resty.redis"
local red = redis:new()
red:connect("127.0.0.1", 6379)
local data = red:get("ssl:sess:" .. id)
if data then
ssl_session.set_serialized_session(data)
end
}
```
### 3.3 异步支持
Session fetch 支持异步操作:
```c
#ifdef SSL_ERROR_PENDING_SESSION
return SSL_magic_pending_session_ptr(); // 挂起,等待异步完成
#endif
```
---
## 四、ssl_client_hello_by_lua
### 4.1 功能概述
在 Client Hello 消息处理后执行,可用于:
- 基于客户端能力选择协议
- 访问控制
- 早期拒绝
### 4.2 要求
- **OpenSSL 1.1.1+**
- 使用 `SSL_ERROR_WANT_CLIENT_HELLO_CB` 回调机制
### 4.3 使用示例
```nginx
ssl_client_hello_by_lua_block {
local ssl = require "ngx.ssl"
-- 获取支持的协议版本
local version = ssl.get_tls1_version()
-- 拒绝旧版 TLS
if version < 0x0303 then -- TLS 1.2
return ngx.exit(ngx.ERROR)
end
}
```
---
## 五、Socket SSL
### 5.1 SSL 握手
```lua
local sock = ngx.socket.tcp()
sock:connect("example.com", 443)
-- SSL 握手
local session, err = sock:sslhandshake(
nil, -- session 复用对象
"example.com", -- SNI
true -- 验证证书
)
if not session then
ngx.log(ngx.ERR, "SSL handshake failed: ", err)
return
end
```
### 5.2 SSL 配置指令
| 指令 | 说明 |
|------|------|
| `lua_ssl_protocols` | SSL 协议版本 |
| `lua_ssl_ciphers` | 加密套件 |
| `lua_ssl_verify_depth` | 验证深度 |
| `lua_ssl_trusted_certificate` | CA 证书 |
---
## 六、Proxy SSL
### 6.1 `proxy_ssl_certificate_by_lua`
动态设置代理 SSL 客户端证书。
### 6.2 `proxy_ssl_verify_by_lua`
自定义代理 SSL 验证。
---
## 七、FFI API 参考
### 证书操作
```c
// PEM 解析
void *ngx_http_lua_ffi_parse_pem_cert(const u_char *pem, size_t len, char **err);
void *ngx_http_lua_ffi_parse_pem_priv_key(const u_char *pem, size_t len, char **err);
// DER 解析
void *ngx_http_lua_ffi_parse_der_cert(const char *data, size_t len, char **err);
void *ngx_http_lua_ffi_parse_der_priv_key(const char *data, size_t len, char **err);
// 释放
void ngx_http_lua_ffi_free_cert(void *cdata);
void ngx_http_lua_ffi_free_priv_key(void *cdata);
```
### SSL 信息获取
```c
int ngx_http_lua_ffi_ssl_server_name(ngx_http_request_t *r,
const char **name, size_t *namelen, char **err);
int ngx_http_lua_ffi_ssl_raw_server_addr(ngx_http_request_t *r,
const char **addr, size_t *addrlen, int *port, char **err);
int ngx_http_lua_ffi_ssl_client_random(ngx_http_request_t *r,
unsigned char *out, size_t *outlen, char **err);
```

View File

@ -0,0 +1,200 @@
# lua-nginx-module Subrequest 内部请求
本文档详细说明 lua-nginx-module 的子请求功能。
---
## 一、核心文件
| 文件 | 说明 |
|------|------|
| `src/ngx_http_lua_subrequest.c` | 子请求实现 |
| `src/ngx_http_lua_capturefilter.c` | 响应捕获过滤器 |
---
## 二、API 概述
### `ngx.location.capture(uri, options?)`
发起单个子请求。
### `ngx.location.capture_multi(requests)`
并发发起多个子请求。
**关键发现**: `capture` 实际上是 `capture_multi` 的薄包装。
---
## 三、支持的 HTTP 方法
| 常量 | 方法 |
|------|------|
| `ngx.HTTP_GET` | GET |
| `ngx.HTTP_POST` | POST |
| `ngx.HTTP_PUT` | PUT |
| `ngx.HTTP_DELETE` | DELETE |
| `ngx.HTTP_HEAD` | HEAD |
| `ngx.HTTP_PATCH` | PATCH |
| `ngx.HTTP_OPTIONS` | OPTIONS |
---
## 四、选项参数
```lua
local res = ngx.location.capture(uri, {
method = ngx.HTTP_POST, -- HTTP 方法
args = "foo=bar", -- 参数字符串或 table
body = '{"data":1}', -- 请求体
headers = { -- 请求头
["Content-Type"] = "application/json"
},
vars = { -- 变量
upstream = "backend"
},
share_all_vars = false, -- 共享所有变量
copy_all_vars = false, -- 复制所有变量
always_forward_body = false, -- 始终转发请求体
ctx = {} -- 传递上下文
})
```
---
## 五、响应结构
```lua
local res = ngx.location.capture("/api")
-- res = {
-- status = 200, -- HTTP 状态码
-- header = {...}, -- 响应头 table
-- body = "...", -- 响应体
-- truncated = false -- 是否被截断
-- }
```
---
## 六、使用示例
### 6.1 基本用法
```lua
local res = ngx.location.capture("/internal/users")
if res.status == 200 then
local users = cjson.decode(res.body)
ngx.say("Users: ", #users)
end
```
### 6.2 POST 请求
```lua
local res = ngx.location.capture("/api/create", {
method = ngx.HTTP_POST,
body = '{"name":"john"}',
headers = {
["Content-Type"] = "application/json"
}
})
```
### 6.3 并发请求
```lua
local res1, res2, res3 = ngx.location.capture_multi({
{"/api/users"},
{"/api/products", {method = ngx.HTTP_GET}},
{"/api/orders", {args = "status=pending"}}
})
ngx.say("Users: ", res1.status)
ngx.say("Products: ", res2.status)
ngx.say("Orders: ", res3.status)
```
---
## 七、内部实现
### 7.1 数据存储结构
```c
struct ngx_http_lua_co_ctx_s {
ngx_int_t *sr_statuses; // 子请求状态码数组
ngx_http_headers_out_t **sr_headers; // 子请求响应头数组
ngx_str_t *sr_bodies; // 子请求响应体数组
uint8_t *sr_flags; // 子请求标志位数组
unsigned nsubreqs; // 子请求总数
unsigned pending_subreqs; // 待处理子请求数
};
```
### 7.2 响应捕获过滤器
```c
// 头部过滤器
static ngx_int_t ngx_http_lua_capture_header_filter(ngx_http_request_t *r)
{
if (ctx && ctx->capture) {
r->filter_need_in_memory = 1; // 强制内存缓冲
return NGX_OK; // 拦截,不发送到客户端
}
}
// 响应体过滤器
static ngx_int_t ngx_http_lua_capture_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
// 将响应体复制到 ctx->body 链表
}
```
### 7.3 响应组装
```c
static void ngx_http_lua_handle_subreq_responses(...)
{
for (index = 0; index < coctx->nsubreqs; index++) {
lua_createtable(co, 0, 4); // 创建响应表
// status
lua_pushinteger(co, coctx->sr_statuses[index]);
lua_setfield(co, -2, "status");
// truncated
if (coctx->sr_flags[index] & NGX_HTTP_LUA_SUBREQ_TRUNCATED) {
lua_pushboolean(co, 1);
lua_setfield(co, -2, "truncated");
}
// body
lua_pushlstring(co, body_str->data, body_str->len);
lua_setfield(co, -2, "body");
// header
// ...
}
}
```
---
## 八、请求体传递规则
| 条件 | 行为 |
|------|------|
| 指定了 `body` 选项 | 使用自定义请求体 |
| GET/DELETE 等方法 | 不转发原请求体 |
| 其他情况 | 深拷贝原请求体 |
---
## 九、注意事项
1. **子请求必须是内部 location**(以 `/` 开头或使用 `internal` 指令)
2. **嵌套深度有限制**Nginx 默认 50
3. **不能跨 server 块**
4. **异步操作会挂起父请求**

View File

@ -0,0 +1,182 @@
# lua-nginx-module Balancer 负载均衡
本文档详细说明 lua-nginx-module 的负载均衡功能。
---
## 一、核心文件
| 文件 | 说明 |
|------|------|
| `src/ngx_http_lua_balancer.c` | Balancer 实现 |
| `src/ngx_http_lua_balancer.h` | 头文件定义 |
---
## 二、配置指令
### `balancer_by_lua_block` / `balancer_by_lua_file`
在 upstream 块中定义负载均衡逻辑。
```nginx
upstream backend {
server 0.0.0.1; # 占位符
balancer_by_lua_block {
local balancer = require "ngx.balancer"
-- 动态选择后端
local host = pick_backend()
local ok, err = balancer.set_current_peer(host, 8080)
}
}
server {
location / {
proxy_pass http://backend;
}
}
```
---
## 三、Balancer API
### 3.1 `set_current_peer(host, port)`
设置当前请求的目标服务器。
- **参数**:
- `host` (string): IP 地址或主机名
- `port` (number): 端口号
- **返回值**: ok 或 nil, err
- **示例**:
```lua
local balancer = require "ngx.balancer"
local ok, err = balancer.set_current_peer("192.168.1.100", 8080)
```
### 3.2 `set_more_tries(count)`
设置额外重试次数。
- **参数**: `count` (number)
- **示例**:
```lua
balancer.set_more_tries(3) -- 额外尝试 3 次
```
### 3.3 `get_last_peer_address()`
获取上次请求的 peer 地址。
- **返回值**: host, port 或 nil, err
### 3.4 `set_timeouts(connect, send, read)`
设置超时时间。
- **参数**: 毫秒数
- **示例**:
```lua
balancer.set_timeouts(1000, 2000, 5000)
```
### 3.5 `enable_keepalive(timeout, max_requests)`
启用 keepalive。
- **参数**:
- `timeout` (number): 空闲超时毫秒
- `max_requests` (number): 最大请求数
- **示例**:
```lua
balancer.enable_keepalive(60000, 100)
```
### 3.6 `bind_to_local_addr(addr)`
绑定本地出站地址。
---
## 四、内部实现
### 4.1 回调替换
Balancer 替换 upstream 的标准回调:
| 原回调 | 替换为 |
|--------|--------|
| `peer.get` | `ngx_http_lua_balancer_get_peer` |
| `peer.free` | `ngx_http_lua_balancer_free_peer` |
| `peer.notify` | `ngx_http_lua_balancer_notify_peer` |
### 4.2 Keepalive Pool
```c
typedef struct {
ngx_queue_t queue; // 空闲队列
ngx_connection_t *connection; // 缓存的连接
ngx_str_t host;
ngx_uint_t port;
} ngx_http_lua_balancer_ka_item_t;
```
---
## 五、使用示例
### 5.1 简单轮询
```lua
local balancer = require "ngx.balancer"
local backends = {
{host = "192.168.1.1", port = 8080},
{host = "192.168.1.2", port = 8080},
{host = "192.168.1.3", port = 8080},
}
local index = 0
local function pick_backend()
index = (index % #backends) + 1
return backends[index]
end
-- balancer_by_lua_block 中
local backend = pick_backend()
balancer.set_current_peer(backend.host, backend.port)
```
### 5.2 一致性哈希
```lua
local balancer = require "ngx.balancer"
local resty_chash = require "resty.chash"
local ring = resty_chash:new(backends)
local server = ring:find(ngx.var.request_uri)
balancer.set_current_peer(server.host, server.port)
```
### 5.3 健康检查集成
```lua
local balancer = require "ngx.balancer"
local healthcheck = require "resty.healthcheck"
local checker = healthcheck.new({...})
local function pick_healthy_backend()
for _, backend in ipairs(backends) do
if checker:get_status(backend.host, backend.port) then
return backend
end
end
return nil
end
```

View File

@ -0,0 +1,201 @@
# lua-nginx-module Filter 过滤器链
本文档详细说明 lua-nginx-module 的 header/body filter 功能。
---
## 一、核心文件
| 文件 | 说明 |
|------|------|
| `src/ngx_http_lua_headerfilterby.c` | Header filter 实现 |
| `src/ngx_http_lua_bodyfilterby.c` | Body filter 实现 |
| `src/ngx_http_lua_capturefilter.c` | Capture filter (子请求) |
---
## 二、Header Filter
### 2.1 配置
```nginx
header_filter_by_lua_block {
ngx.header["X-Custom"] = "value"
ngx.header["X-Request-ID"] = ngx.var.request_id
}
```
### 2.2 特点
- **同步执行**: 不能 yield
- **修改响应头**: 通过 `ngx.header` API
- **继续链**: 自动调用下一个 filter
### 2.3 内部实现
```c
// Filter chain 注册
ngx_http_next_header_filter = ngx_http_top_header_filter;
ngx_http_top_header_filter = ngx_http_lua_header_filter;
```
---
## 三、Body Filter
### 3.1 配置
```nginx
body_filter_by_lua_block {
local chunk = ngx.arg[1] -- 当前数据块
local eof = ngx.arg[2] -- EOF 标记
-- 修改响应体
if chunk then
ngx.arg[1] = chunk:gsub("old", "new")
end
}
```
### 3.2 ngx.arg API
| 索引 | 说明 |
|------|------|
| `ngx.arg[1]` | 当前数据块 (string/nil) |
| `ngx.arg[2]` | EOF 标记 (boolean) |
### 3.3 操作方式
```lua
-- 替换数据
ngx.arg[1] = "new content"
-- 丢弃当前块
ngx.arg[1] = nil
-- 设置 EOF
ngx.arg[2] = true
-- 清除 EOF
ngx.arg[2] = false
```
### 3.4 内部实现
**ngx.arg 元表**:
```c
lua_createtable(L, 0, 2);
lua_pushcfunction(L, ngx_http_lua_param_set);
lua_setfield(L, -2, "__newindex");
lua_setmetatable(L, -2);
```
**Buffer 管理**:
```c
// 输入 buffers
ctx->filter_in_bufs
// Busy buffers
ctx->filter_busy_bufs
// EOF 标记
ctx->seen_last_in_filter
```
---
## 四、Filter Chain 机制
### 4.1 架构图
```
全局变量: ngx_http_top_header_filter
|
[Lua Header Filter]
| --ngx_http_next_header_filter-->
[下一个 Filter] --> ...
全局变量: ngx_http_top_body_filter
|
[Lua Body Filter]
| --ngx_http_next_body_filter-->
[下一个 Filter] --> ...
```
### 4.2 注册流程
```c
// 在 ngx_http_lua_init() 中
ngx_http_lua_header_filter_init();
ngx_http_lua_body_filter_init();
ngx_http_lua_capture_filter_init();
```
---
## 五、使用示例
### 5.1 添加安全头
```nginx
header_filter_by_lua_block {
ngx.header["X-Frame-Options"] = "DENY"
ngx.header["X-Content-Type-Options"] = "nosniff"
ngx.header["X-XSS-Protection"] = "1; mode=block"
}
```
### 5.2 响应体压缩
```nginx
body_filter_by_lua_block {
local chunk = ngx.arg[1]
if chunk then
-- 简单的 gzip 压缩模拟
ngx.arg[1] = ngx.encode_base64(chunk)
end
}
```
### 5.3 内容替换
```nginx
body_filter_by_lua_block {
local chunk = ngx.arg[1]
if chunk then
ngx.arg[1] = chunk:gsub("%{%{.(.-)%}%}", function(var)
return ngx.var[var] or ""
end)
end
}
```
### 5.4 流量统计
```nginx
body_filter_by_lua_block {
local chunk = ngx.arg[1]
local eof = ngx.arg[2]
if chunk then
-- 累计响应大小
ngx.ctx.response_size = (ngx.ctx.response_size or 0) + #chunk
end
if eof then
-- 记录总大小
ngx.log(ngx.INFO, "Response size: ", ngx.ctx.response_size)
end
}
```
---
## 六、注意事项
1. **不能 yield**: filter 代码必须同步完成
2. **性能敏感**: 每个响应块都会执行,避免重操作
3. **chunk-by-chunk**: body filter 逐块处理,可能需要缓冲
4. **EOF 标记**: 最后一个块的 `ngx.arg[2]``true`

View File

@ -0,0 +1,228 @@
# lua-nginx-module 代码缓存与异常处理
本文档详细说明 lua-nginx-module 的代码缓存和异常处理机制。
---
## 一、代码缓存机制
### 1.1 核心文件
| 文件 | 说明 |
|------|------|
| `src/ngx_http_lua_cache.c` | 代码缓存实现 |
| `src/ngx_http_lua_clfactory.c` | Closure factory 实现 |
| `src/ngx_http_lua_directive.c` | `lua_code_cache` 指令处理 |
### 1.2 lua_code_cache 指令
```nginx
lua_code_cache on; # 默认,代码缓存
lua_code_cache off; # 开发模式,每次重载
```
**影响**:
- **on**: 每个 worker 共享一个 Lua VM代码加载一次并缓存
- **off**: 每个请求创建独立 Lua VM代码每次重载
### 1.3 缓存键生成
**Inline Script**:
- 格式: `nhli_{MD5(src)}`
- 宏: `NGX_HTTP_LUA_INLINE_TAG`
```c
u_char *ngx_http_lua_gen_chunk_cache_key(ngx_conf_t *cf,
const char *tag, const u_char *src, size_t src_len)
{
// key = tag + "_" + "nhli_" + MD5(src)
}
```
**File Script**:
- 格式: `nhlf_{MD5(file_path)}`
- 宏: `NGX_HTTP_LUA_FILE_TAG`
### 1.4 Closure Factory 模式
**非 LuaJIT 环境**:
```c
#define CLFACTORY_BEGIN_CODE "return function() "
#define CLFACTORY_END_CODE "\nend"
```
用户代码被包装为:
```lua
return function()
<user_code>
end
```
- **缓存**: 工厂函数被缓存
- **执行**: 每次调用工厂创建新 closure隔离 upvalue
**LuaJIT 环境**:
- 直接缓存编译后的函数
- 不需要工厂包装
### 1.5 缓存查找/存储流程
**查找**:
```c
static ngx_int_t ngx_http_lua_cache_load_code(...)
{
// 1. 获取 Registry 中的缓存表
lua_pushlightuserdata(L, code_cache_key);
lua_rawget(L, LUA_REGISTRYINDEX);
// 2. 查找缓存
lua_getfield(L, -1, key);
if (lua_isfunction(L, -1)) {
// 命中
#ifdef OPENRESTY_LUAJIT
return NGX_OK;
#else
// 调用工厂生成新 closure
lua_pcall(L, 0, 1, 0);
#endif
}
return NGX_DECLINED; // 未命中
}
```
**存储**:
```c
static ngx_int_t ngx_http_lua_cache_store_code(...)
{
// 1. 获取缓存表
lua_pushlightuserdata(L, code_cache_key);
lua_rawget(L, LUA_REGISTRYINDEX);
// 2. 存储函数
lua_pushvalue(L, -2); // 复制 closure
lua_setfield(L, -2, key); // 存入缓存表
}
```
---
## 二、异常处理机制
### 2.1 核心文件
| 文件 | 说明 |
|------|------|
| `src/ngx_http_lua_exception.c` | 异常处理实现 |
| `src/ngx_http_lua_exception.h` | 宏定义 |
### 2.2 setjmp/longjmp 机制
```c
#define NGX_LUA_EXCEPTION_TRY if (setjmp(ngx_http_lua_exception) == 0)
#define NGX_LUA_EXCEPTION_CATCH else
#define NGX_LUA_EXCEPTION_THROW(x) longjmp(ngx_http_lua_exception, (x))
```
### 2.3 Panic Handler
```c
static int ngx_http_lua_atpanic(lua_State *L)
{
// 1. 输出错误日志
// 2. 通过 longjmp 恢复 nginx 执行
NGX_LUA_EXCEPTION_THROW(1);
}
```
### 2.4 使用模式
```c
ngx_http_lua_exception_init();
NGX_LUA_EXCEPTION_TRY {
// 执行 Lua 代码
rc = lua_pcall(L, 0, 1, 0);
} NGX_LUA_EXCEPTION_CATCH {
// 捕获异常
ngx_log_error(NGX_LOG_ERR, log, 0, "Lua VM panic");
}
```
---
## 三、VM 管理
### 3.1 VM 初始化
```c
ngx_int_t ngx_http_lua_init_vm(lua_State **L, ...)
{
// 1. 创建新 Lua 状态机
*L = luaL_newstate();
// 2. 加载标准库
luaL_openlibs(*L);
// 3. 注入 ngx.* API
ngx_http_lua_inject_ngx_api(*L, lmcf, log);
// 4. 设置 panic handler
lua_atpanic(*L, ngx_http_lua_atpanic);
}
```
### 3.2 VM 状态缓存
`lua_code_cache off` 模式下的 VM 状态缓存:
```c
typedef struct {
lua_State *lua; // Lua VM
ngx_pool_t *pool;
// ...
} ngx_http_lua_vm_state_t;
```
---
## 四、最佳实践
### 4.1 开发环境
```nginx
lua_code_cache off; # 代码实时生效
location /reload {
content_by_lua_block {
-- 无需重新加载配置
}
}
```
### 4.2 生产环境
```nginx
lua_code_cache on;
# 使用信号重载代码
# nginx -s reload
```
### 4.3 错误处理
```lua
local ok, err = pcall(function()
-- 可能出错的代码
end)
if not ok then
ngx.log(ngx.ERR, "Error: ", err)
ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
```

View File

@ -0,0 +1,306 @@
# lua-nginx-module 核心架构设计
本文档为 lolly 项目提供 lua-nginx-module 的核心架构参考。
---
## 一、核心数据结构
### 1.1 主配置结构 (ngx_http_lua_main_conf_t)
**文件**: `src/ngx_http_lua_common.h:233`
```c
struct ngx_http_lua_main_conf_s {
lua_State *lua; // Lua VM 实例
ngx_hash_t shm_zones; // 共享内存区域
ngx_array_t *shdict_zones; // shdict 区域
// Phase handlers
ngx_http_handler_pt init_handler;
ngx_http_handler_pt init_worker_handler;
ngx_http_handler_pt exit_worker_handler;
// 线程缓存
ngx_queue_t free_lua_threads;
ngx_queue_t cached_lua_threads;
// 功能开关
unsigned requires_rewrite:1;
unsigned requires_access:1;
unsigned requires_log:1;
// ...
};
```
### 1.2 请求上下文 (ngx_http_lua_ctx_t)
**文件**: `src/ngx_http_lua_common.h:628`
```c
struct ngx_http_lua_ctx_s {
ngx_http_request_t *request; // 请求对象
// 协程管理
ngx_http_lua_co_ctx_t *cur_co_ctx; // 当前协程
ngx_http_lua_co_ctx_t entry_co_ctx; // 入口协程
ngx_queue_t user_co_ctx; // 用户协程列表
// 输出缓冲
ngx_chain_t *out;
ngx_chain_t *free_bufs;
ngx_chain_t *busy_bufs;
// 请求体
ngx_chain_t *body;
ngx_chain_t *filter_in_bufs;
// 状态
unsigned context; // 当前 phase
unsigned exited:1;
unsigned eof:1;
// ...
};
```
### 1.3 协程上下文 (ngx_http_lua_co_ctx_t)
**文件**: `src/ngx_http_lua_common.h:550`
```c
struct ngx_http_lua_co_ctx_s {
lua_State *co; // Lua 协程
ngx_http_lua_co_status_e co_status; // 状态
// 父子关系
ngx_http_lua_co_ctx_t *parent_co_ctx;
ngx_queue_t zombie_child_threads;
// 子请求数据
ngx_int_t *sr_statuses;
ngx_http_headers_out_t **sr_headers;
ngx_str_t *sr_bodies;
// 挂起状态
unsigned sleep:1;
unsigned sem_wait:1;
// ...
};
```
---
## 二、Nginx 集成机制
### 2.1 Directive 注册
**文件**: `src/ngx_http_lua_module.c:114-804`
```c
static ngx_command_t ngx_http_lua_cmds[] = {
{ ngx_string("content_by_lua_block"),
NGX_HTTP_LOC_CONF|NGX_CONF_BLOCK,
ngx_http_lua_content_by_lua_block,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_lua_loc_conf_t, content_handler),
NULL },
// ... 更多指令
};
```
### 2.2 Handler 注册
**文件**: `src/ngx_http_lua_module.c:839-945`
```c
static ngx_int_t ngx_http_lua_init(ngx_conf_t *cf)
{
// 动态注册 phase handlers
if (lmcf->requires_rewrite) {
h = ngx_array_push(&cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers);
*h = ngx_http_lua_rewrite_handler;
}
// Filter chain 注册
ngx_http_lua_header_filter_init();
ngx_http_lua_body_filter_init();
}
```
### 2.3 Filter Chain
```c
// Header filter
ngx_http_next_header_filter = ngx_http_top_header_filter;
ngx_http_top_header_filter = ngx_http_lua_header_filter;
// Body filter
ngx_http_next_body_filter = ngx_http_top_body_filter;
ngx_http_top_body_filter = ngx_http_lua_body_filter;
```
---
## 三、请求处理流程
```
nginx 请求
ngx_http_lua_content_handler
ngx_http_lua_content_run
ngx_http_lua_run_thread ←───┐
↓ │
lua_pcall / lua_resume │
↓ │
用户 Lua 代码 │
│ │
├── lua_yield ───────────┘ (NGX_AGAIN)
│ ↓
│ 事件注册
│ ↓
│ resume_handler
│ ↓
│ 恢复执行
└── return
NGX_OK / NGX_ERROR
```
---
## 四、协程驱动架构
### 4.1 Yield/Resume 机制
```c
// Yield
lua_yield(L, nresults); // 挂起 Lua 协程
ctx->resume_handler = ...; // 设置恢复函数
return NGX_AGAIN; // 让出 nginx 控制权
// Resume
resume_handler(r); // 事件触发时调用
lua_resume(L, nargs); // 恢复 Lua 协程
```
### 4.2 非阻塞 I/O 模式
```
ngx.sleep(1)
├── 添加 nginx 定时器
├── lua_yield
└── return NGX_AGAIN
nginx 处理其他请求
定时器到期
ngx_http_lua_sleep_handler
lua_resume
用户代码继续
```
---
## 五、内存管理策略
### 5.1 双重内存管理
| 场景 | 使用 |
|------|------|
| 请求临时数据 | `ngx_palloc(r->pool)` / `ngx_pfree()` |
| 长期对象 | `lua_newuserdata()` (Lua GC) |
### 5.2 请求对象获取
```c
#ifdef OPENRESTY_LUAJIT
// 使用 exdata (更快)
r = lua_getexdata(L);
#else
// 全局变量
lua_getglobal(L, "__ngx_req");
r = lua_touserdata(L, -1);
#endif
```
---
## 六、设计模式总结
| 模式 | 说明 |
|------|------|
| **Phase-based** | Nginx 11 个 phase每个可注册 Lua handler |
| **协程驱动** | 非阻塞 I/O 通过 yield/resume 与 nginx 事件循环协作 |
| **VM 共享** | per-worker 单 VM协程实现请求隔离 |
| **Closure Factory** | 非 LuaJIT 环境隔离 upvalue |
| **Filter Chain** | 插入式 header/body 过滤 |
---
## 七、关键设计决策
### 7.1 为什么用协程?
- **同步代码风格**: 开发者写同步代码,框架处理异步
- **非阻塞**: yield 时 nginx 可以处理其他请求
- **简单错误处理**: 使用标准 Lua 错误机制
### 7.2 为什么单 VM
- **内存效率**: 代码只加载一次
- **共享状态**: 全局变量和缓存自然共享
- **性能**: 无 VM 切换开销
### 7.3 为什么用 FFI
- **性能**: 绕过 Lua C API 开销
- **简洁**: 直接调用 C 函数
- **灵活**: 可动态加载
---
## 八、lolly 实现参考
### 8.1 核心模块实现顺序
1. **VM 管理**: Lua 状态机创建、初始化、销毁
2. **Context 管理**: 请求上下文、协程上下文
3. **Phase Handlers**: 各阶段的 handler 注册和执行
4. **Yield/Resume**: 协程挂起和恢复机制
5. **API 注入**: ngx.* 命名空间构建
### 8.2 需要实现的 C API
```c
// VM 管理
lua_State *create_vm();
void destroy_vm(lua_State *L);
// 协程管理
int run_thread(lua_State *L, lua_State *co);
int yield_thread(lua_State *co, int nresults);
int resume_thread(lua_State *co, int nargs);
// 请求对象
ngx_http_request_t *get_request(lua_State *L);
void set_request(lua_State *L, ngx_http_request_t *r);
// Phase 执行
int run_phase_handler(ngx_http_request_t *r, int phase);
```
### 8.3 数据结构映射
| lua-nginx-module | lolly 参考 |
|------------------|-----------|
| `ngx_http_lua_main_conf_t` | 全局配置VM 管理 |
| `ngx_http_lua_loc_conf_t` | Location 配置handler 存储 |
| `ngx_http_lua_ctx_t` | 请求级上下文 |
| `ngx_http_lua_co_ctx_t` | 协程状态管理 |