为所有 Lua API 文件添加完整的包级和函数级文档注释: - api_balancer: 负载均衡 API(set_current_peer, set_more_tries 等) - api_ctx: 请求上下文存储 API(ngx.ctx) - api_location: 子请求捕获 API(ngx.location.capture) - api_log: 日志输出 API(ngx.log) - api_req: 请求对象 API - api_resp: 响应对象 API - api_shared_dict: 共享字典 API - api_socket_tcp: TCP socket API - api_timer: 定时器 API - api_var: 变量 API - engine: Lua 引擎核心 - context: 请求上下文管理 - coroutine: 协程调度器 - middleware: 中间件集成 - filter_writer: 响应过滤器 - cache: Lua 脚本缓存 - shared_dict: 共享字典实现 - socket_manager: socket 连接管理 注释格式遵循 Go 官方风格,包含功能说明、参数说明和注意事项。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
477 lines
12 KiB
Go
477 lines
12 KiB
Go
// Package lua 提供 ngx.shared.DICT API 实现。
|
||
//
|
||
// 该文件实现共享内存字典的 Lua API,兼容 OpenResty/ngx_lua 语义。
|
||
// 包括:
|
||
// - SharedDictManager:管理多个命名的 SharedDict 实例
|
||
// - Lua 元表注册:支持 dict:get/set/add/replace/incr/delete 等方法
|
||
// - dict:flush_all/flush_expired/get_keys/size/free_space 管理方法
|
||
//
|
||
// 安全说明:
|
||
// - 共享字典仅支持字符串键值对
|
||
// - incr 操作使用手动整数解析(不使用 strconv 依赖)
|
||
// - Scheduler 模式下共享字典仍然可用
|
||
//
|
||
// 作者:xfy
|
||
package lua
|
||
|
||
import (
|
||
"sync"
|
||
"time"
|
||
|
||
glua "github.com/yuin/gopher-lua"
|
||
)
|
||
|
||
// SharedDictManager 共享字典管理器。
|
||
//
|
||
// 管理多个命名的 SharedDict 实例,支持并发安全的创建、查询和清理操作。
|
||
type SharedDictManager struct {
|
||
// dicts 字典名称到实例的映射
|
||
dicts map[string]*SharedDict
|
||
|
||
// mu 读写锁
|
||
mu sync.RWMutex
|
||
}
|
||
|
||
// NewSharedDictManager 创建共享字典管理器实例。
|
||
//
|
||
// 返回值:
|
||
// - *SharedDictManager: 初始化的管理器实例
|
||
func NewSharedDictManager() *SharedDictManager {
|
||
return &SharedDictManager{
|
||
dicts: make(map[string]*SharedDict),
|
||
}
|
||
}
|
||
|
||
// CreateDict 创建或获取指定名称的共享字典。
|
||
//
|
||
// 如果字典已存在则直接返回,否则创建新字典。
|
||
//
|
||
// 参数:
|
||
// - name: 字典名称
|
||
// - maxItems: 最大条目数(LRU 淘汰阈值)
|
||
//
|
||
// 返回值:
|
||
// - *SharedDict: 共享字典实例
|
||
func (m *SharedDictManager) CreateDict(name string, maxItems int) *SharedDict {
|
||
m.mu.Lock()
|
||
defer m.mu.Unlock()
|
||
|
||
if dict, ok := m.dicts[name]; ok {
|
||
return dict
|
||
}
|
||
|
||
dict := NewSharedDict(name, maxItems)
|
||
m.dicts[name] = dict
|
||
return dict
|
||
}
|
||
|
||
// GetDict 获取指定名称的共享字典。
|
||
//
|
||
// 参数:
|
||
// - name: 字典名称
|
||
//
|
||
// 返回值:
|
||
// - *SharedDict: 字典实例,不存在时返回 nil
|
||
func (m *SharedDictManager) GetDict(name string) *SharedDict {
|
||
m.mu.RLock()
|
||
defer m.mu.RUnlock()
|
||
return m.dicts[name]
|
||
}
|
||
|
||
// Close 清理所有字典引用。
|
||
//
|
||
// 将 dicts 映射置为 nil,释放所有字典引用。
|
||
func (m *SharedDictManager) Close() {
|
||
m.mu.Lock()
|
||
defer m.mu.Unlock()
|
||
m.dicts = nil
|
||
}
|
||
|
||
// DictConfig 共享字典配置。
|
||
type DictConfig struct {
|
||
// Name 字典名称
|
||
Name string
|
||
|
||
// MaxItems 最大条目数
|
||
MaxItems int
|
||
}
|
||
|
||
// RegisterSharedDictAPI 注册 ngx.shared.DICT API 到 Lua 状态机。
|
||
//
|
||
// 在 ngx 表下创建 shared 子表和字典元表,
|
||
// 注册 get/set/add/replace/incr/delete/flush_all/flush_expired/get_keys/size/free_space 方法。
|
||
//
|
||
// 参数:
|
||
// - L: Lua 状态
|
||
// - manager: 共享字典管理器
|
||
// - ngx: ngx 全局表
|
||
func RegisterSharedDictAPI(L *glua.LState, manager *SharedDictManager, ngx *glua.LTable) {
|
||
// 创建 ngx.shared 表
|
||
shared := L.NewTable()
|
||
|
||
// ngx.shared.DICT - 返回字典 userdata
|
||
L.SetField(shared, "DICT", L.NewFunction(func(L *glua.LState) int {
|
||
name := L.CheckString(1)
|
||
|
||
dict := manager.GetDict(name)
|
||
if dict == nil {
|
||
L.Push(glua.LNil)
|
||
L.Push(glua.LString("shared dict not found: " + name))
|
||
return 2
|
||
}
|
||
|
||
// 返回字典 userdata
|
||
ud := L.NewUserData()
|
||
ud.Value = dict
|
||
L.SetMetatable(ud, L.GetTypeMetatable("ngx.shared.dict"))
|
||
L.Push(ud)
|
||
return 1
|
||
}))
|
||
|
||
L.SetField(ngx, "shared", shared)
|
||
|
||
// 创建字典类型元表
|
||
mt := L.NewTypeMetatable("ngx.shared.dict")
|
||
L.SetField(mt, "__index", L.NewFunction(dictIndex))
|
||
L.SetField(mt, "__newindex", L.NewFunction(dictNewIndex))
|
||
L.SetField(mt, "__tostring", L.NewFunction(dictToString))
|
||
|
||
// 注册字典方法
|
||
methods := L.NewTable()
|
||
L.SetField(methods, "get", L.NewFunction(dictGet))
|
||
L.SetField(methods, "set", L.NewFunction(dictSet))
|
||
L.SetField(methods, "add", L.NewFunction(dictAdd))
|
||
L.SetField(methods, "replace", L.NewFunction(dictReplace))
|
||
L.SetField(methods, "incr", L.NewFunction(dictIncr))
|
||
L.SetField(methods, "delete", L.NewFunction(dictDelete))
|
||
L.SetField(methods, "flush_all", L.NewFunction(dictFlushAll))
|
||
L.SetField(methods, "flush_expired", L.NewFunction(dictFlushExpired))
|
||
L.SetField(methods, "get_keys", L.NewFunction(dictGetKeys))
|
||
L.SetField(methods, "size", L.NewFunction(dictSize))
|
||
L.SetField(methods, "free_space", L.NewFunction(dictFreeSpace))
|
||
L.SetField(mt, "methods", methods)
|
||
}
|
||
|
||
// checkSharedDict 从 Lua 调用参数中验证并获取 SharedDict 实例。
|
||
//
|
||
// 参数:
|
||
// - L: Lua 状态
|
||
//
|
||
// 返回值:
|
||
// - *SharedDict: 字典实例,类型不正确时引发 Lua 错误
|
||
func checkSharedDict(L *glua.LState) *SharedDict {
|
||
ud := L.CheckUserData(1)
|
||
dict, ok := ud.Value.(*SharedDict)
|
||
if !ok {
|
||
L.RaiseError("invalid shared dict")
|
||
}
|
||
return dict
|
||
}
|
||
|
||
// dictIndex 字典 __index 元方法。
|
||
//
|
||
// 先检查是否为方法名(如 get、set 等),若是则返回方法;
|
||
// 否则作为 key 获取字典中的值。
|
||
func dictIndex(L *glua.LState) int {
|
||
dict := checkSharedDict(L)
|
||
|
||
key := L.CheckString(2)
|
||
|
||
// 检查是否是方法
|
||
|
||
ud, ok := L.Get(1).(*glua.LUserData)
|
||
if !ok {
|
||
L.RaiseError("expected userdata")
|
||
return 0
|
||
}
|
||
methods := L.GetField(ud.Metatable, "methods")
|
||
if method := L.GetField(methods, key); method != glua.LNil {
|
||
L.Push(method)
|
||
return 1
|
||
}
|
||
|
||
// 否则作为 key 获取值
|
||
value, expired, err := dict.Get(key)
|
||
if err != nil {
|
||
L.RaiseError("%s", err.Error())
|
||
return 0
|
||
}
|
||
if expired {
|
||
L.Push(glua.LNil)
|
||
L.Push(glua.LString("expired"))
|
||
return 2
|
||
}
|
||
if value == "" {
|
||
L.Push(glua.LNil)
|
||
return 1
|
||
}
|
||
L.Push(glua.LString(value))
|
||
return 1
|
||
}
|
||
|
||
// dictNewIndex 字典 __newindex 元方法。
|
||
//
|
||
// 处理 dict[key] = value 形式的赋值,设置永不过期的键值对。
|
||
func dictNewIndex(L *glua.LState) int {
|
||
dict := checkSharedDict(L)
|
||
|
||
key := L.CheckString(2)
|
||
value := L.CheckString(3)
|
||
|
||
ok, err := dict.Set(key, value, 0)
|
||
if err != nil {
|
||
L.RaiseError("%s", err.Error())
|
||
return 0
|
||
}
|
||
if !ok {
|
||
L.Push(glua.LFalse)
|
||
L.Push(glua.LString("no memory"))
|
||
return 2
|
||
}
|
||
L.Push(glua.LTrue)
|
||
return 1
|
||
}
|
||
|
||
// dictToString 字典 __tostring 元方法,返回 "ngx.shared.dict:{name}" 格式字符串。
|
||
func dictToString(L *glua.LState) int {
|
||
dict := checkSharedDict(L)
|
||
L.Push(glua.LString("ngx.shared.dict:" + dict.name))
|
||
return 1
|
||
}
|
||
|
||
// dictGet 实现 dict:get(key) 方法。
|
||
//
|
||
// 获取指定键的值,支持过期检测。
|
||
//
|
||
// 返回值(推入 Lua 栈):
|
||
// - value: 键对应的值,不存在或过期时返回 nil
|
||
// - flags: 标志位(当前始终返回 0)
|
||
// - err: 错误信息(不存在时不返回)
|
||
func dictGet(L *glua.LState) int {
|
||
dict := checkSharedDict(L)
|
||
|
||
key := L.CheckString(2)
|
||
|
||
value, expired, err := dict.Get(key)
|
||
if err != nil {
|
||
L.Push(glua.LNil)
|
||
L.Push(glua.LString(err.Error()))
|
||
return 2
|
||
}
|
||
if expired {
|
||
L.Push(glua.LNil)
|
||
L.Push(glua.LString("expired"))
|
||
return 2
|
||
}
|
||
if value == "" {
|
||
L.Push(glua.LNil)
|
||
return 1
|
||
}
|
||
L.Push(glua.LString(value))
|
||
L.Push(glua.LNumber(0)) // flags(暂不支持)
|
||
return 2
|
||
}
|
||
|
||
// dictSet 实现 dict:set(key, value, exptime?, flags?) 方法。
|
||
//
|
||
// 设置键值对,支持可选过期时间(秒)。
|
||
//
|
||
// 返回值(推入 Lua 栈):
|
||
// - ok: true 表示设置成功
|
||
// - err: 失败时的错误信息
|
||
func dictSet(L *glua.LState) int {
|
||
dict := checkSharedDict(L)
|
||
|
||
key := L.CheckString(2)
|
||
value := L.CheckString(3)
|
||
|
||
ttl := time.Duration(0)
|
||
if L.GetTop() >= 4 {
|
||
ttl = time.Duration(float64(L.CheckNumber(4)) * float64(time.Second))
|
||
}
|
||
|
||
// flags 参数暂不使用
|
||
|
||
ok, err := dict.Set(key, value, ttl)
|
||
if err != nil {
|
||
L.Push(glua.LFalse)
|
||
L.Push(glua.LString(err.Error()))
|
||
return 2
|
||
}
|
||
if !ok {
|
||
L.Push(glua.LFalse)
|
||
L.Push(glua.LString("no memory"))
|
||
return 2
|
||
}
|
||
L.Push(glua.LTrue)
|
||
return 1
|
||
}
|
||
|
||
// dictAdd 实现 dict:add(key, value, exptime?, flags?) 方法。
|
||
//
|
||
// 仅在键不存在时添加,存在时返回错误。
|
||
func dictAdd(L *glua.LState) int {
|
||
dict := checkSharedDict(L)
|
||
|
||
key := L.CheckString(2)
|
||
value := L.CheckString(3)
|
||
|
||
ttl := time.Duration(0)
|
||
if L.GetTop() >= 4 {
|
||
ttl = time.Duration(float64(L.CheckNumber(4)) * float64(time.Second))
|
||
}
|
||
|
||
ok, err := dict.Add(key, value, ttl)
|
||
if err != nil {
|
||
L.Push(glua.LFalse)
|
||
L.Push(glua.LString(err.Error()))
|
||
return 2
|
||
}
|
||
if !ok {
|
||
L.Push(glua.LFalse)
|
||
L.Push(glua.LString("exists"))
|
||
return 2
|
||
}
|
||
L.Push(glua.LTrue)
|
||
return 1
|
||
}
|
||
|
||
// dictReplace 实现 dict:replace(key, value, exptime?, flags?) 方法。
|
||
//
|
||
// 仅在键存在且未过期时替换值,不存在时返回 "not found" 错误。
|
||
func dictReplace(L *glua.LState) int {
|
||
dict := checkSharedDict(L)
|
||
|
||
key := L.CheckString(2)
|
||
value := L.CheckString(3)
|
||
|
||
ttl := time.Duration(0)
|
||
if L.GetTop() >= 4 {
|
||
ttl = time.Duration(float64(L.CheckNumber(4)) * float64(time.Second))
|
||
}
|
||
|
||
// 检查是否存在(Get 返回: value, expired, error)
|
||
// expired=true 表示存在但已过期,expired=false 表示不存在或存在且未过期
|
||
// 需要区分不存在和存在的情况
|
||
val, expired, _ := dict.Get(key)
|
||
// 如果 val 为空且 expired 为 false,表示 key 不存在
|
||
// 如果 expired 为 true,表示 key 存在但已过期(也算不存在)
|
||
if val == "" && !expired {
|
||
// key 不存在
|
||
L.Push(glua.LFalse)
|
||
L.Push(glua.LString("not found"))
|
||
return 2
|
||
}
|
||
if expired {
|
||
// key 存在但已过期,也算不存在
|
||
L.Push(glua.LFalse)
|
||
L.Push(glua.LString("not found"))
|
||
return 2
|
||
}
|
||
|
||
setOK, err := dict.Set(key, value, ttl)
|
||
if err != nil {
|
||
L.Push(glua.LFalse)
|
||
L.Push(glua.LString(err.Error()))
|
||
return 2
|
||
}
|
||
if !setOK {
|
||
L.Push(glua.LFalse)
|
||
L.Push(glua.LString("no memory"))
|
||
return 2
|
||
}
|
||
L.Push(glua.LTrue)
|
||
return 1
|
||
}
|
||
|
||
// dictIncr 实现 dict:incr(key, value) 方法。
|
||
//
|
||
// 将指定键的值作为整数自增,返回新值。
|
||
// 如果键不存在则创建初始值为 0 再自增。
|
||
// 如果值不是纯数字,返回 nil 和错误。
|
||
func dictIncr(L *glua.LState) int {
|
||
dict := checkSharedDict(L)
|
||
|
||
key := L.CheckString(2)
|
||
increment := int(L.CheckNumber(3))
|
||
|
||
newValue, err := dict.Incr(key, increment)
|
||
if err != nil {
|
||
L.Push(glua.LNil)
|
||
L.Push(glua.LString(err.Error()))
|
||
return 2
|
||
}
|
||
if newValue == 0 {
|
||
// Incr 返回 0 表示值不是纯数字字符串(非错误)
|
||
// 在 Lua 中返回 nil 表示操作失败
|
||
L.Push(glua.LNil)
|
||
L.Push(glua.LString("not a number"))
|
||
return 2
|
||
}
|
||
L.Push(glua.LNumber(newValue))
|
||
return 1
|
||
}
|
||
|
||
// dictDelete 实现 dict:delete(key) 方法。
|
||
//
|
||
// 删除指定键。
|
||
func dictDelete(L *glua.LState) int {
|
||
dict := checkSharedDict(L)
|
||
|
||
key := L.CheckString(2)
|
||
_ = dict.Delete(key) // Delete 返回错误,但在 Lua API 中忽略
|
||
L.Push(glua.LTrue)
|
||
return 1
|
||
}
|
||
|
||
// dictFlushAll 实现 dict:flush_all() 方法。
|
||
//
|
||
// 清空字典中的所有条目。
|
||
func dictFlushAll(L *glua.LState) int {
|
||
dict := checkSharedDict(L)
|
||
|
||
_ = dict.FlushAll() // FlushAll 返回错误,但在 Lua API 中忽略
|
||
return 0
|
||
}
|
||
|
||
// dictFlushExpired 实现 dict:flush_expired(max_count?) 方法。
|
||
//
|
||
// 清除所有过期条目,返回被清除的数量。
|
||
func dictFlushExpired(L *glua.LState) int {
|
||
dict := checkSharedDict(L)
|
||
|
||
count := dict.FlushExpired()
|
||
L.Push(glua.LNumber(count))
|
||
return 1
|
||
}
|
||
|
||
// dictGetKeys 实现 dict:get_keys(max_count?) 方法。
|
||
//
|
||
// 获取字典中的所有键。当前实现返回空表(待完善)。
|
||
func dictGetKeys(L *glua.LState) int {
|
||
checkSharedDict(L) // 验证参数但不使用返回值
|
||
|
||
// 暂不实现完整版,返回空表
|
||
keys := L.NewTable()
|
||
L.Push(keys)
|
||
return 1
|
||
}
|
||
|
||
// dictSize 获取条目数
|
||
// dict:size() -> count
|
||
func dictSize(L *glua.LState) int {
|
||
dict := checkSharedDict(L)
|
||
|
||
L.Push(glua.LNumber(dict.Size()))
|
||
return 1
|
||
}
|
||
|
||
// dictFreeSpace 获取剩余容量
|
||
// dict:free_space() -> slots
|
||
func dictFreeSpace(L *glua.LState) int {
|
||
dict := checkSharedDict(L)
|
||
|
||
L.Push(glua.LNumber(dict.FreeSlots()))
|
||
return 1
|
||
}
|