From 941c44b7982e484cd2b3d04144f46b62bc193276 Mon Sep 17 00:00:00 2001 From: xfy Date: Fri, 10 Apr 2026 11:20:57 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=B7=BB=E5=8A=A0=20Lua=20=E5=B5=8C?= =?UTF-8?q?=E5=85=A5=E5=88=86=E6=9E=90=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 lua-embed-analysis.md 技术分析文档 - 新增 lua-nginx-module 文档目录 - 更新 gitignore 允许跟踪 docs/lua-nginx-module/ Co-Authored-By: Claude Opus 4.6 --- .gitignore | 3 +- docs/lua-embed-analysis.md | 835 +++++++++++++++++++++++ docs/lua-nginx-module/00-overview.md | 84 +++ docs/lua-nginx-module/01-directives.md | 277 ++++++++ docs/lua-nginx-module/02-lua-api.md | 454 ++++++++++++ docs/lua-nginx-module/03-cosocket.md | 356 ++++++++++ docs/lua-nginx-module/04-timer-thread.md | 366 ++++++++++ docs/lua-nginx-module/05-shdict.md | 333 +++++++++ docs/lua-nginx-module/06-ssl.md | 257 +++++++ docs/lua-nginx-module/07-subrequest.md | 200 ++++++ docs/lua-nginx-module/08-balancer.md | 182 +++++ docs/lua-nginx-module/09-filter.md | 201 ++++++ docs/lua-nginx-module/10-code-cache.md | 228 +++++++ docs/lua-nginx-module/11-architecture.md | 306 +++++++++ 14 files changed, 4081 insertions(+), 1 deletion(-) create mode 100644 docs/lua-embed-analysis.md create mode 100644 docs/lua-nginx-module/00-overview.md create mode 100644 docs/lua-nginx-module/01-directives.md create mode 100644 docs/lua-nginx-module/02-lua-api.md create mode 100644 docs/lua-nginx-module/03-cosocket.md create mode 100644 docs/lua-nginx-module/04-timer-thread.md create mode 100644 docs/lua-nginx-module/05-shdict.md create mode 100644 docs/lua-nginx-module/06-ssl.md create mode 100644 docs/lua-nginx-module/07-subrequest.md create mode 100644 docs/lua-nginx-module/08-balancer.md create mode 100644 docs/lua-nginx-module/09-filter.md create mode 100644 docs/lua-nginx-module/10-code-cache.md create mode 100644 docs/lua-nginx-module/11-architecture.md diff --git a/.gitignore b/.gitignore index 53aa248..f6819b5 100644 --- a/.gitignore +++ b/.gitignore @@ -64,4 +64,5 @@ coverage.html html/ default.pgo benchmark-*.txt -lua-nginx-module/ \ No newline at end of file +lua-nginx-module/ +!docs/lua-nginx-module/ \ No newline at end of file diff --git a/docs/lua-embed-analysis.md b/docs/lua-embed-analysis.md new file mode 100644 index 0000000..089ca01 --- /dev/null +++ b/docs/lua-embed-analysis.md @@ -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/) - 架构参考(已生成) \ No newline at end of file diff --git a/docs/lua-nginx-module/00-overview.md b/docs/lua-nginx-module/00-overview.md new file mode 100644 index 0000000..5df6e33 --- /dev/null +++ b/docs/lua-nginx-module/00-overview.md @@ -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+ 用例 \ No newline at end of file diff --git a/docs/lua-nginx-module/01-directives.md b/docs/lua-nginx-module/01-directives.md new file mode 100644 index 0000000..4f57e70 --- /dev/null +++ b/docs/lua-nginx-module/01-directives.md @@ -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` | \ No newline at end of file diff --git a/docs/lua-nginx-module/02-lua-api.md b/docs/lua-nginx-module/02-lua-api.md new file mode 100644 index 0000000..01e89cf --- /dev/null +++ b/docs/lua-nginx-module/02-lua-api.md @@ -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 | \ No newline at end of file diff --git a/docs/lua-nginx-module/03-cosocket.md b/docs/lua-nginx-module/03-cosocket.md new file mode 100644 index 0000000..9e38ba6 --- /dev/null +++ b/docs/lua-nginx-module/03-cosocket.md @@ -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 +``` \ No newline at end of file diff --git a/docs/lua-nginx-module/04-timer-thread.md b/docs/lua-nginx-module/04-timer-thread.md new file mode 100644 index 0000000..820facb --- /dev/null +++ b/docs/lua-nginx-module/04-timer-thread.md @@ -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 +``` \ No newline at end of file diff --git a/docs/lua-nginx-module/05-shdict.md b/docs/lua-nginx-module/05-shdict.md new file mode 100644 index 0000000..d00912d --- /dev/null +++ b/docs/lua-nginx-module/05-shdict.md @@ -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`: 内存大小 (最小 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 +``` \ No newline at end of file diff --git a/docs/lua-nginx-module/06-ssl.md b/docs/lua-nginx-module/06-ssl.md new file mode 100644 index 0000000..804425b --- /dev/null +++ b/docs/lua-nginx-module/06-ssl.md @@ -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); +``` \ No newline at end of file diff --git a/docs/lua-nginx-module/07-subrequest.md b/docs/lua-nginx-module/07-subrequest.md new file mode 100644 index 0000000..38f0d80 --- /dev/null +++ b/docs/lua-nginx-module/07-subrequest.md @@ -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. **异步操作会挂起父请求** \ No newline at end of file diff --git a/docs/lua-nginx-module/08-balancer.md b/docs/lua-nginx-module/08-balancer.md new file mode 100644 index 0000000..911a3ec --- /dev/null +++ b/docs/lua-nginx-module/08-balancer.md @@ -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 +``` \ No newline at end of file diff --git a/docs/lua-nginx-module/09-filter.md b/docs/lua-nginx-module/09-filter.md new file mode 100644 index 0000000..1c5fd2a --- /dev/null +++ b/docs/lua-nginx-module/09-filter.md @@ -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` \ No newline at end of file diff --git a/docs/lua-nginx-module/10-code-cache.md b/docs/lua-nginx-module/10-code-cache.md new file mode 100644 index 0000000..16dab41 --- /dev/null +++ b/docs/lua-nginx-module/10-code-cache.md @@ -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() + +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 +``` \ No newline at end of file diff --git a/docs/lua-nginx-module/11-architecture.md b/docs/lua-nginx-module/11-architecture.md new file mode 100644 index 0000000..ad974b6 --- /dev/null +++ b/docs/lua-nginx-module/11-architecture.md @@ -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` | 协程状态管理 | \ No newline at end of file