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:
parent
4a93cf2b5c
commit
941c44b798
3
.gitignore
vendored
3
.gitignore
vendored
@ -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
835
docs/lua-embed-analysis.md
Normal 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/) - 架构参考(已生成)
|
||||
84
docs/lua-nginx-module/00-overview.md
Normal file
84
docs/lua-nginx-module/00-overview.md
Normal 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+ 用例
|
||||
277
docs/lua-nginx-module/01-directives.md
Normal file
277
docs/lua-nginx-module/01-directives.md
Normal 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` |
|
||||
454
docs/lua-nginx-module/02-lua-api.md
Normal file
454
docs/lua-nginx-module/02-lua-api.md
Normal 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 |
|
||||
356
docs/lua-nginx-module/03-cosocket.md
Normal file
356
docs/lua-nginx-module/03-cosocket.md
Normal 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
|
||||
```
|
||||
366
docs/lua-nginx-module/04-timer-thread.md
Normal file
366
docs/lua-nginx-module/04-timer-thread.md
Normal 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
|
||||
```
|
||||
333
docs/lua-nginx-module/05-shdict.md
Normal file
333
docs/lua-nginx-module/05-shdict.md
Normal 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
|
||||
```
|
||||
257
docs/lua-nginx-module/06-ssl.md
Normal file
257
docs/lua-nginx-module/06-ssl.md
Normal 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);
|
||||
```
|
||||
200
docs/lua-nginx-module/07-subrequest.md
Normal file
200
docs/lua-nginx-module/07-subrequest.md
Normal 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. **异步操作会挂起父请求**
|
||||
182
docs/lua-nginx-module/08-balancer.md
Normal file
182
docs/lua-nginx-module/08-balancer.md
Normal 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
|
||||
```
|
||||
201
docs/lua-nginx-module/09-filter.md
Normal file
201
docs/lua-nginx-module/09-filter.md
Normal 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`
|
||||
228
docs/lua-nginx-module/10-code-cache.md
Normal file
228
docs/lua-nginx-module/10-code-cache.md
Normal 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
|
||||
```
|
||||
306
docs/lua-nginx-module/11-architecture.md
Normal file
306
docs/lua-nginx-module/11-architecture.md
Normal 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` | 协程状态管理 |
|
||||
Loading…
x
Reference in New Issue
Block a user