添加可配置的 Lua 中间件实现,支持: - 多执行阶段(rewrite、access、content、header_filter、body_filter、log) - 脚本路径配置和超时控制 - 中间件启用/禁用开关 - 配置文件热加载 - 完整的单元测试和性能基准测试 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
288 lines
5.8 KiB
Markdown
288 lines
5.8 KiB
Markdown
# Lua Middleware 使用指南
|
||
|
||
## 概述
|
||
|
||
LuaMiddleware 提供了将 Lua 脚本嵌入 HTTP 请求处理流程的能力,支持在不同执行阶段运行自定义逻辑。
|
||
|
||
## 快速开始
|
||
|
||
### 创建 Lua 引擎
|
||
|
||
```go
|
||
import "rua.plus/lolly/internal/lua"
|
||
|
||
// 创建 Lua 引擎
|
||
engine, err := lua.NewEngine(lua.DefaultConfig())
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
defer engine.Close()
|
||
```
|
||
|
||
### 创建单阶段中间件
|
||
|
||
```go
|
||
config := lua.LuaMiddlewareConfig{
|
||
ScriptPath: "/path/to/script.lua",
|
||
Phase: lua.PhaseContent, // 内容生成阶段
|
||
Timeout: 30 * time.Second,
|
||
Name: "my-lua-middleware",
|
||
}
|
||
|
||
middleware, err := lua.NewLuaMiddleware(engine, config)
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
```
|
||
|
||
### 创建多阶段中间件
|
||
|
||
```go
|
||
multi := lua.NewMultiPhaseLuaMiddleware(engine, "multi-phase")
|
||
|
||
// 添加不同阶段的脚本
|
||
multi.AddPhase(lua.PhaseRewrite, "/scripts/rewrite.lua", 10*time.Second)
|
||
multi.AddPhase(lua.PhaseAccess, "/scripts/access.lua", 10*time.Second)
|
||
multi.AddPhase(lua.PhaseContent, "/scripts/content.lua", 10*time.Second)
|
||
multi.AddPhase(lua.PhaseLog, "/scripts/log.lua", 10*time.Second)
|
||
```
|
||
|
||
## 执行阶段
|
||
|
||
阶段按以下顺序执行(请求处理流程):
|
||
|
||
```
|
||
rewrite → access → content → header_filter → body_filter → log
|
||
```
|
||
|
||
| 阶段 | 常量 | 用途 |
|
||
|------|------|------|
|
||
| Rewrite | `PhaseRewrite` | URL 重写、请求修改 |
|
||
| Access | `PhaseAccess` | 访问控制、认证授权 |
|
||
| Content | `PhaseContent` | 内容生成(默认阶段) |
|
||
| Header Filter | `PhaseHeaderFilter` | 响应头过滤 |
|
||
| Body Filter | `PhaseBodyFilter` | 响应体过滤 |
|
||
| Log | `PhaseLog` | 日志记录 |
|
||
|
||
## 可用的 ngx API
|
||
|
||
在 Lua 脚本中可使用以下 nginx 风格 API:
|
||
|
||
### ngx.req - 请求操作
|
||
|
||
```lua
|
||
-- 获取请求方法
|
||
local method = ngx.req.get_method()
|
||
|
||
-- 获取请求头
|
||
local headers = ngx.req.get_headers()
|
||
local content_type = headers["Content-Type"]
|
||
|
||
-- 设置请求头
|
||
ngx.req.set_header("X-Custom", "value")
|
||
|
||
-- 获取请求体
|
||
local body = ngx.req.get_body_data()
|
||
|
||
-- 设置 URI
|
||
ngx.req.set_uri("/new/path")
|
||
```
|
||
|
||
### ngx.resp - 响应操作
|
||
|
||
```lua
|
||
-- 获取/设置状态码
|
||
local status = ngx.resp.get_status()
|
||
ngx.resp.set_status(404)
|
||
|
||
-- 设置响应头
|
||
ngx.resp.set_header("X-Response-Time", "100ms")
|
||
```
|
||
|
||
### ngx.var - 变量操作
|
||
|
||
```lua
|
||
-- 获取/设置变量
|
||
local uri = ngx.var.uri
|
||
ngx.var.custom_var = "value"
|
||
```
|
||
|
||
### ngx.ctx - 请求上下文
|
||
|
||
```lua
|
||
-- 在阶段间传递数据
|
||
ngx.ctx.user_id = "123"
|
||
ngx.ctx.auth_time = ngx.now()
|
||
```
|
||
|
||
### ngx.say/print/flush - 输出
|
||
|
||
```lua
|
||
-- 输出内容到响应体
|
||
ngx.say("Hello from Lua!")
|
||
ngx.print("No newline")
|
||
ngx.flush() -- 刷新缓冲
|
||
```
|
||
|
||
### ngx.exit - 终止请求
|
||
|
||
```lua
|
||
-- 终止请求处理,不再执行后续处理器
|
||
ngx.exit(200) -- 成功
|
||
ngx.exit(403) -- 禁止访问
|
||
ngx.exit(ngx.HTTP_NOT_FOUND) -- 404
|
||
```
|
||
|
||
### ngx.redirect - 重定向
|
||
|
||
```lua
|
||
-- HTTP 重定向
|
||
ngx.redirect("/new-location", 301)
|
||
ngx.redirect("https://example.com", 302)
|
||
```
|
||
|
||
## 配置文件格式
|
||
|
||
在 YAML 配置文件中添加 Lua 中间件配置:
|
||
|
||
```yaml
|
||
server:
|
||
lua:
|
||
enabled: true
|
||
global_settings:
|
||
max_concurrent_coroutines: 1000
|
||
coroutine_timeout: 30s
|
||
code_cache_size: 1000
|
||
enable_file_watch: true
|
||
max_execution_time: 30s
|
||
scripts:
|
||
- path: "/scripts/auth.lua"
|
||
phase: "access"
|
||
timeout: 10s
|
||
enabled: true
|
||
- path: "/scripts/transform.lua"
|
||
phase: "content"
|
||
timeout: 30s
|
||
enabled: true
|
||
```
|
||
|
||
## 错误处理
|
||
|
||
### 脚本执行错误
|
||
|
||
当 Lua 脚本执行出错时,中间件会返回 500 错误:
|
||
|
||
```lua
|
||
-- 这会导致 500 错误
|
||
error("something went wrong")
|
||
```
|
||
|
||
### ngx.exit 终止
|
||
|
||
`ngx.exit()` 通过抛出特殊错误终止执行,这是正常行为:
|
||
|
||
```lua
|
||
ngx.say("Processing...")
|
||
ngx.exit(200) -- 正常终止,返回 200
|
||
-- 此后的代码不会执行
|
||
ngx.say("Never reached")
|
||
```
|
||
|
||
### 启用/禁用控制
|
||
|
||
```go
|
||
// 动态启用/禁用
|
||
middleware.SetEnabled(false) // 禁用中间件
|
||
middleware.SetEnabled(true) // 启用中间件
|
||
|
||
// 检查状态
|
||
if middleware.IsEnabled() {
|
||
// 中间件已启用
|
||
}
|
||
```
|
||
|
||
## 性能考虑
|
||
|
||
### 单请求开销
|
||
|
||
基准测试显示单请求 Lua 开销约 **0.1ms**,远低于 1ms 阈值:
|
||
|
||
```
|
||
BenchmarkLuaMiddlewareOverhead-8 10000 99.885µs
|
||
```
|
||
|
||
### 最佳实践
|
||
|
||
1. **字节码缓存**:脚本编译后缓存,避免重复编译
|
||
2. **协程复用**:请求级协程从引擎池获取
|
||
3. **避免阻塞**:使用 `ngx.sleep()` 时注意超时
|
||
4. **限制脚本大小**:大脚本增加编译时间
|
||
|
||
## 示例脚本
|
||
|
||
### 访问控制(access phase)
|
||
|
||
```lua
|
||
-- auth.lua
|
||
local token = ngx.req.get_headers()["Authorization"]
|
||
if not token then
|
||
ngx.exit(401)
|
||
return
|
||
end
|
||
|
||
-- 验证 token
|
||
if token ~= "valid-token" then
|
||
ngx.exit(403)
|
||
return
|
||
end
|
||
|
||
-- 记录认证信息
|
||
ngx.ctx.user = "authenticated"
|
||
```
|
||
|
||
### 响应头注入(header_filter phase)
|
||
|
||
```lua
|
||
-- headers.lua
|
||
ngx.resp.set_header("X-Server", "lolly")
|
||
ngx.resp.set_header("X-Request-Id", ngx.var.request_id)
|
||
```
|
||
|
||
### 日志记录(log phase)
|
||
|
||
```lua
|
||
-- log.lua
|
||
local log_data = {
|
||
uri = ngx.var.uri,
|
||
method = ngx.req.get_method(),
|
||
status = ngx.resp.get_status(),
|
||
duration = ngx.now() - ngx.ctx.start_time
|
||
}
|
||
|
||
-- 写入日志文件或发送到日志服务
|
||
ngx.log(ngx.INFO, "request completed: " .. ngx.json.encode(log_data))
|
||
```
|
||
|
||
## 安全限制
|
||
|
||
默认配置下,以下 Lua 库被禁用:
|
||
|
||
- **os** - 操作系统访问
|
||
- **io** - 文件 I/O
|
||
- **load/loadfile** - 动态代码加载
|
||
|
||
可通过配置启用(谨慎使用):
|
||
|
||
```yaml
|
||
lua:
|
||
global_settings:
|
||
enable_os_lib: false # 安全
|
||
enable_io_lib: false # 安全
|
||
enable_load_lib: false # 安全
|
||
```
|
||
|
||
沙箱限制:
|
||
|
||
- 协程创建被拦截(防止无限协程)
|
||
- 全局表只读(防止污染全局环境)
|
||
- 危险函数移除(debug, coroutine.create 等) |