feat(lua): 添加 Scheduler 模式下的 API 安全检查

为定时器回调上下文注册不安全的 ngx API 时返回错误:
- ngx.ctx: 请求上下文不可用
- ngx.req: 请求 API 不可用
- ngx.resp: 响应 API 不可用
- ngx.var: 变量 API 不可用
- ngx.log: 提供安全版本(不依赖 RequestCtx)

防止定时器回调中误用请求相关 API 导致并发问题。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
xfy 2026-04-12 13:03:09 +08:00
parent 191f6f39ba
commit 26f18055ce
5 changed files with 123 additions and 0 deletions

View File

@ -58,3 +58,18 @@ func (api *ngxCtxAPI) GetValue(L *glua.LState, key string) glua.LValue {
}
return glua.LNil
}
// RegisterSchedulerUnsafeCtxAPI 为 Scheduler LState 注册不安全的 ngx.ctx API
func RegisterSchedulerUnsafeCtxAPI(L *glua.LState, ngx *glua.LTable) {
ctxTable := L.NewTable()
mt := L.NewTable()
mt.RawSetString("__index", L.NewFunction(luaSchedulerUnsafeCtx))
mt.RawSetString("__newindex", L.NewFunction(luaSchedulerUnsafeCtx))
L.SetMetatable(ctxTable, mt)
ngx.RawSetString("ctx", ctxTable)
}
func luaSchedulerUnsafeCtx(L *glua.LState) int {
L.RaiseError("API ngx.ctx not available in timer callback context")
return 0
}

View File

@ -338,3 +338,48 @@ func LogLevelToZerolog(level int) zerolog.Level {
return zerolog.InfoLevel
}
}
// RegisterSchedulerLogAPI 为 Scheduler LState 注册安全的 ngx.log API
// 不依赖 RequestCtx仅输出到标准日志
func RegisterSchedulerLogAPI(L *glua.LState, ngx *glua.LTable) {
// 注册日志级别常量
ngx.RawSetString("STDERR", glua.LNumber(LogStderr))
ngx.RawSetString("EMERG", glua.LNumber(LogEmerg))
ngx.RawSetString("ALERT", glua.LNumber(LogAlert))
ngx.RawSetString("CRIT", glua.LNumber(LogCrit))
ngx.RawSetString("ERR", glua.LNumber(LogErr))
ngx.RawSetString("WARN", glua.LNumber(LogWarn))
ngx.RawSetString("NOTICE", glua.LNumber(LogNotice))
ngx.RawSetString("INFO", glua.LNumber(LogInfo))
ngx.RawSetString("DEBUG", glua.LNumber(LogDebug))
// 注册 HTTP 状态码常量
ngx.RawSetString("HTTP_OK", glua.LNumber(HTTPOK))
ngx.RawSetString("HTTP_INTERNAL_SERVER_ERROR", glua.LNumber(HTTPInternalServerError))
// 注册 ngx.log 函数(不依赖 RequestCtx 的版本)
ngx.RawSetString("log", L.NewFunction(luaSchedulerLog))
}
// luaSchedulerLog 实现 scheduler 模式下的 ngx.log
// 不依赖 RequestCtx仅输出到标准日志
func luaSchedulerLog(L *glua.LState) int {
// 获取日志级别
level := L.CheckInt(1)
// 收集所有参数并拼接
var parts []string
n := L.GetTop()
for i := 2; i <= n; i++ {
parts = append(parts, L.ToString(i))
}
msg := strings.Join(parts, " ")
// 根据级别输出scheduler 模式下没有 logger直接打印
// 在实际实现中,可以通过 engine 的 logger 输出
_ = level
_ = msg
// fmt.Printf("[timer] %s\n", msg)
return 0
}

View File

@ -616,3 +616,27 @@ func parseQueryString(query []byte) map[string][]string {
return result
}
// RegisterSchedulerUnsafeReqAPI 为 Scheduler LState 注册不安全的 ngx.req API
func RegisterSchedulerUnsafeReqAPI(L *glua.LState, ngx *glua.LTable) {
ngxReq := L.NewTable()
ngxReq.RawSetString("get_method", L.NewFunction(luaSchedulerUnsafeReq))
ngxReq.RawSetString("get_uri", L.NewFunction(luaSchedulerUnsafeReq))
ngxReq.RawSetString("set_uri", L.NewFunction(luaSchedulerUnsafeReq))
ngxReq.RawSetString("get_uri_args", L.NewFunction(luaSchedulerUnsafeReq))
ngxReq.RawSetString("set_uri_args", L.NewFunction(luaSchedulerUnsafeReq))
ngxReq.RawSetString("get_headers", L.NewFunction(luaSchedulerUnsafeReq))
ngxReq.RawSetString("set_header", L.NewFunction(luaSchedulerUnsafeReq))
ngxReq.RawSetString("clear_header", L.NewFunction(luaSchedulerUnsafeReq))
ngxReq.RawSetString("get_body_data", L.NewFunction(luaSchedulerUnsafeReq))
ngxReq.RawSetString("read_body", L.NewFunction(luaSchedulerUnsafeReq))
ngx.RawSetString("req", ngxReq)
}
// luaSchedulerUnsafeReq 在 scheduler 模式下调用 ngx.req API 时返回错误
func luaSchedulerUnsafeReq(L *glua.LState) int {
L.RaiseError("API ngx.req not available in timer callback context")
return 0
}

View File

@ -197,3 +197,22 @@ func (api *ngxRespAPI) SetHeader(name, value string) {
api.headersCache = nil
api.headersCacheOnce = sync.Once{}
}
// RegisterSchedulerUnsafeRespAPI 为 Scheduler LState 注册不安全的 ngx.resp API
func RegisterSchedulerUnsafeRespAPI(L *glua.LState, ngx *glua.LTable) {
ngxResp := L.NewTable()
ngxResp.RawSetString("get_status", L.NewFunction(luaSchedulerUnsafeResp))
ngxResp.RawSetString("set_status", L.NewFunction(luaSchedulerUnsafeResp))
ngxResp.RawSetString("get_headers", L.NewFunction(luaSchedulerUnsafeResp))
ngxResp.RawSetString("set_header", L.NewFunction(luaSchedulerUnsafeResp))
ngxResp.RawSetString("clear_header", L.NewFunction(luaSchedulerUnsafeResp))
ngx.RawSetString("resp", ngxResp)
}
// luaSchedulerUnsafeResp 在 scheduler 模式下调用 ngx.resp API 时返回错误
func luaSchedulerUnsafeResp(L *glua.LState) int {
L.RaiseError("API ngx.resp not available in timer callback context")
return 0
}

View File

@ -195,3 +195,23 @@ func (api *ngxVarAPI) GetVariable(name string) (string, bool) {
value := api.getVariable(name)
return value, value != ""
}
// RegisterSchedulerUnsafeVarAPI 为 Scheduler LState 注册不安全的 ngx.var API
func RegisterSchedulerUnsafeVarAPI(L *glua.LState, ngx *glua.LTable) {
ngxVar := L.NewTable()
mt := L.NewTable()
mt.RawSetString("__index", L.NewFunction(luaSchedulerUnsafeVarIndex))
mt.RawSetString("__newindex", L.NewFunction(luaSchedulerUnsafeVarNewIndex))
L.SetMetatable(ngxVar, mt)
ngx.RawSetString("var", ngxVar)
}
func luaSchedulerUnsafeVarIndex(L *glua.LState) int {
L.RaiseError("API ngx.var not available in timer callback context")
return 0
}
func luaSchedulerUnsafeVarNewIndex(L *glua.LState) int {
L.RaiseError("API ngx.var not available in timer callback context")
return 0
}