// Package lua 提供 ngx.var API 实现。 // // 该文件实现 nginx 变量访问的 Lua API,兼容 OpenResty/ngx_lua 语义。 // 支持: // - 标准 nginx 变量:request_method、request_uri、uri、query_string 等 // - 请求头变量:http_host、http_user_agent、http_content_type 等 // - 客户端信息:remote_addr、server_addr 等 // - arg_ 前缀变量:arg_name 用于访问查询参数 // - http_ 前缀变量:http_xxx 用于访问任意请求头 // - 自定义变量存储:支持通过 store map 读写自定义变量 // // 实现说明: // - 通过元表(__index/__newindex)实现动态变量读写 // - 读取优先级:自定义变量 > fasthttp 内置变量 // - 写入始终存储到自定义变量 store 中 // // 作者:xfy package lua import ( "strconv" "strings" "github.com/valyala/fasthttp" glua "github.com/yuin/gopher-lua" ) // argPrefix 是 arg_ 变量的前缀,用于获取查询参数 const argPrefix = "arg_" // ngxVarAPI 封装 nginx 变量访问 API。 // // 支持读取 fasthttp 请求中的标准 nginx 变量, // 以及读写自定义变量(存储于 store map 中)。 type ngxVarAPI struct { // ctx 关联的 fasthttp 请求上下文 ctx *fasthttp.RequestCtx // store 自定义变量存储,支持 Lua 脚本读写自定义变量 store map[string]string } // newNgxVarAPI 创建 ngx.var API 实例。 // // 参数: // - ctx: fasthttp 请求上下文 // // 返回值: // - *ngxVarAPI: 初始化的 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 字段,避免重复写入 if existing := ngxTable.RawGetString("var"); existing != glua.LNil { return } // 创建 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 获取变量 // 某些变量需要返回数值类型(如 request_length) lv := api.getVariableLua(key) if lv != nil { L.Push(lv) return 1 } // 3. 未找到变量,返回 nil L.Push(glua.LNil) return 1 } // luaVarNewIndex 实现 ngx.var[key] = value 写入 // Lua 调用: ngx.var.key = value 或 ngx.var[key] = value // 注意:Lua 的 nil 会被转换为空字符串存储 func (api *ngxVarAPI) luaVarNewIndex(L *glua.LState) int { // 第一个参数是表本身(ngx.var) // 第二个参数是键名 // 第三个参数是值 key := L.CheckString(2) value := L.OptString(3, "") // 存储到自定义变量存储(nil 会转换为空字符串) api.store[key] = value return 0 } // getVariableLua 从 fasthttp RequestCtx 获取变量值,返回 Lua 类型 // 支持常见的 nginx 变量,某些变量返回数值类型 func (api *ngxVarAPI) getVariableLua(name string) glua.LValue { if api.ctx == nil { return nil } switch name { // HTTP 请求相关 - 数值类型 case "request_length": return glua.LNumber(api.ctx.Request.Header.ContentLength()) // HTTP 请求相关 - 字符串类型 case "request_method": return glua.LString(string(api.ctx.Method())) case "request_uri": return glua.LString(string(api.ctx.RequestURI())) case "uri": return glua.LString(string(api.ctx.URI().Path())) case "document_uri": return glua.LString(string(api.ctx.URI().Path())) case "query_string", "args": return glua.LString(string(api.ctx.URI().QueryString())) case "server_protocol", "protocol": return glua.LString(string(api.ctx.Request.Header.Protocol())) case "scheme": return glua.LString(string(api.ctx.URI().Scheme())) case "request_time": // 简化实现,返回空字符串 return glua.LString("") // 请求头相关 case "http_host": return glua.LString(string(api.ctx.Host())) case "http_user_agent", "http_user-agent": return glua.LString(string(api.ctx.UserAgent())) case "http_referer": return glua.LString(string(api.ctx.Referer())) case "http_accept": return glua.LString(string(api.ctx.Request.Header.Peek("Accept"))) case "http_accept_encoding", "http_accept-encoding": return glua.LString(string(api.ctx.Request.Header.Peek("Accept-Encoding"))) case "http_accept_language", "http_accept-language": return glua.LString(string(api.ctx.Request.Header.Peek("Accept-Language"))) case "http_connection": return glua.LString(string(api.ctx.Request.Header.Peek("Connection"))) case "http_content_type", "http_content-type": return glua.LString(string(api.ctx.Request.Header.ContentType())) case "http_content_length", "http_content-length": return glua.LString(string(api.ctx.Request.Header.Peek("Content-Length"))) // 客户端信息 case "remote_addr": return glua.LString(api.ctx.RemoteAddr().String()) case "remote_port": addr := api.ctx.RemoteAddr() if addr != nil { addrStr := addr.String() // IPv6 地址格式: [ip]:port,IPv4 格式: ip:port // 统一使用 LastIndex(":") 可正确处理两种格式 if idx := strings.LastIndex(addrStr, ":"); idx >= 0 { return glua.LString(addrStr[idx+1:]) } } return glua.LString("") case "binary_remote_addr": return glua.LString("") // 服务器信息 case "server_addr": addr := api.ctx.LocalAddr() if addr != nil { return glua.LString(addr.String()) } return glua.LString("") case "server_port": addr := api.ctx.LocalAddr() if addr != nil { addrStr := addr.String() if idx := strings.LastIndex(addrStr, ":"); idx >= 0 { return glua.LString(addrStr[idx+1:]) } } return glua.LString("") case "server_name": return glua.LString(string(api.ctx.Host())) // URI 参数 case argPrefix: // 获取所有参数 return glua.LString(string(api.ctx.URI().QueryString())) default: // 检查是否是 arg_ 开头的参数 if len(name) > len(argPrefix) && name[:len(argPrefix)] == argPrefix { paramName := name[4:] return glua.LString(string(api.ctx.QueryArgs().Peek(paramName))) } // 检查是否是 http_ 开头的请求头 if len(name) > 5 && name[:5] == "http_" { headerName := name[5:] return glua.LString(string(api.ctx.Request.Header.Peek(headerName))) } return nil } } // getVariable 从 fasthttp RequestCtx 获取变量值(字符串形式) // 用于 Go 层调用,内部复用 getVariableLua 实现 func (api *ngxVarAPI) getVariable(name string) string { lv := api.getVariableLua(name) if lv == nil { return "" } switch v := lv.(type) { case glua.LString: return string(v) case glua.LNumber: return strconv.Itoa(int(v)) default: return lv.String() } } // 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 != "" }