lolly/internal/lua/api_var.go
xfy 26f18055ce 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>
2026-04-13 09:24:43 +08:00

218 lines
5.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package lua 提供 ngx.var API 实现
package lua
import (
"strconv"
"github.com/valyala/fasthttp"
glua "github.com/yuin/gopher-lua"
)
// ngxVarAPI ngx.var API 实现
type ngxVarAPI struct {
// 请求上下文
ctx *fasthttp.RequestCtx
// 变量存储(用于自定义变量)
store map[string]string
}
// newNgxVarAPI 创建 ngx.var API 实例
func newNgxVarAPI(ctx *fasthttp.RequestCtx) *ngxVarAPI {
return &ngxVarAPI{
ctx: ctx,
store: make(map[string]string),
}
}
// RegisterNgxVarAPI 在 Lua 状态机中注册 ngx.var API
// 使用元表实现动态读写ngx.var.key 和 ngx.var[key]
func RegisterNgxVarAPI(L *glua.LState, api *ngxVarAPI, ngxTable *glua.LTable) {
// 创建 ngx.var 表(使用元表实现动态访问)
ngxVar := L.NewTable()
// 创建元表
mt := L.NewTable()
// __index 元方法:读取变量
mt.RawSetString("__index", L.NewFunction(api.luaVarIndex))
// __newindex 元方法:设置变量
mt.RawSetString("__newindex", L.NewFunction(api.luaVarNewIndex))
// 设置元表
L.SetMetatable(ngxVar, mt)
// 将 ngx.var 添加到 ngx 表
ngxTable.RawSetString("var", ngxVar)
}
// luaVarIndex 实现 ngx.var[key] 读取
// Lua 调用: local value = ngx.var.key 或 ngx.var[key]
func (api *ngxVarAPI) luaVarIndex(L *glua.LState) int {
// 第一个参数是表本身ngx.var
// 第二个参数是键名
key := L.CheckString(2)
// 1. 先查自定义变量存储
if value, ok := api.store[key]; ok {
L.Push(glua.LString(value))
return 1
}
// 2. 从 fasthttp RequestCtx 获取变量
value := api.getVariable(key)
if value != "" {
L.Push(glua.LString(value))
return 1
}
// 3. 未找到变量,返回 nil
L.Push(glua.LNil)
return 1
}
// luaVarNewIndex 实现 ngx.var[key] = value 写入
// Lua 调用: ngx.var.key = value 或 ngx.var[key] = value
func (api *ngxVarAPI) luaVarNewIndex(L *glua.LState) int {
// 第一个参数是表本身ngx.var
// 第二个参数是键名
// 第三个参数是值
key := L.CheckString(2)
value := L.CheckString(3)
// 存储到自定义变量存储
api.store[key] = value
return 0
}
// getVariable 从 fasthttp RequestCtx 获取变量值
// 支持常见的 nginx 变量
func (api *ngxVarAPI) getVariable(name string) string {
if api.ctx == nil {
return ""
}
switch name {
// HTTP 请求相关
case "request_method":
return string(api.ctx.Method())
case "request_uri":
return string(api.ctx.RequestURI())
case "uri":
return string(api.ctx.URI().Path())
case "document_uri":
return string(api.ctx.URI().Path())
case "query_string", "args":
return string(api.ctx.URI().QueryString())
case "server_protocol", "protocol":
return string(api.ctx.Request.Header.Protocol())
case "scheme":
return string(api.ctx.URI().Scheme())
case "request_length":
return strconv.Itoa(api.ctx.Request.Header.ContentLength())
case "request_time":
// 简化实现,返回空字符串
return ""
// 请求头相关
case "http_host":
return string(api.ctx.Host())
case "http_user_agent", "http_user-agent":
return string(api.ctx.UserAgent())
case "http_referer":
return string(api.ctx.Referer())
case "http_accept":
return string(api.ctx.Request.Header.Peek("Accept"))
case "http_accept_encoding", "http_accept-encoding":
return string(api.ctx.Request.Header.Peek("Accept-Encoding"))
case "http_accept_language", "http_accept-language":
return string(api.ctx.Request.Header.Peek("Accept-Language"))
case "http_connection":
return string(api.ctx.Request.Header.Peek("Connection"))
case "http_content_type", "http_content-type":
return string(api.ctx.Request.Header.ContentType())
case "http_content_length", "http_content-length":
return string(api.ctx.Request.Header.Peek("Content-Length"))
// 客户端信息
case "remote_addr":
return api.ctx.RemoteAddr().String()
case "remote_port":
addr := api.ctx.RemoteAddr()
if addr != nil {
// 简化处理,实际可能需要解析端口
return ""
}
return ""
case "binary_remote_addr":
return ""
// 服务器信息
case "server_addr":
addr := api.ctx.LocalAddr()
if addr != nil {
return addr.String()
}
return ""
case "server_port":
return ""
case "server_name":
return string(api.ctx.Host())
// URI 参数
case "arg_":
// 获取所有参数
return string(api.ctx.URI().QueryString())
default:
// 检查是否是 arg_ 开头的参数
if len(name) > 4 && name[:4] == "arg_" {
paramName := name[4:]
return string(api.ctx.QueryArgs().Peek(paramName))
}
// 检查是否是 http_ 开头的请求头
if len(name) > 5 && name[:5] == "http_" {
headerName := name[5:]
return string(api.ctx.Request.Header.Peek(headerName))
}
return ""
}
}
// SetVariable 设置自定义变量Go 层调用)
func (api *ngxVarAPI) SetVariable(name, value string) {
api.store[name] = value
}
// GetVariable 获取变量值Go 层调用)
func (api *ngxVarAPI) GetVariable(name string) (string, bool) {
// 先查自定义变量
if value, ok := api.store[name]; ok {
return value, true
}
// 再查 fasthttp 变量
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
}