mirror of
https://github.com/bunkerity/bunkerweb
synced 2026-05-24 09:28:37 +00:00
git-subtree-dir: src/deps/src/lua-resty-session git-subtree-split: 8b5f8752f3046396c414c5b97850e784c07e1641
315 lines
7.2 KiB
Lua
315 lines
7.2 KiB
Lua
---
|
|
-- Shared Memory (SHM) backend for session library
|
|
--
|
|
-- @module resty.session.shm
|
|
|
|
|
|
local table_new = require "table.new"
|
|
local utils = require "resty.session.utils"
|
|
|
|
|
|
local meta_get_value = utils.meta_get_value
|
|
local meta_get_next = utils.meta_get_next
|
|
local get_name = utils.get_name
|
|
local errmsg = utils.errmsg
|
|
|
|
|
|
local setmetatable = setmetatable
|
|
local shared = ngx.shared
|
|
local random = math.random
|
|
local assert = assert
|
|
local error = error
|
|
local pairs = pairs
|
|
local max = math.max
|
|
local log = ngx.log
|
|
|
|
|
|
local WARN = ngx.WARN
|
|
|
|
|
|
local DEFAULT_ZONE = "sessions"
|
|
local CLEANUP_PROBABILITY = 0.1 -- 1 / 10
|
|
|
|
|
|
local function get_and_clean_metadata(dict, meta_key, current_time)
|
|
local size = dict:llen(meta_key)
|
|
if not size or size == 0 then
|
|
return
|
|
end
|
|
|
|
local max_expiry = current_time
|
|
local sessions = table_new(0, size)
|
|
|
|
for _ = 1, size do
|
|
local meta_value, err = dict:lpop(meta_key)
|
|
if not meta_value then
|
|
log(WARN, "[session] ", errmsg(err, "failed read meta value"))
|
|
break
|
|
end
|
|
|
|
local key, err, exp = meta_get_next(meta_value, 1)
|
|
if err then
|
|
return nil, err
|
|
end
|
|
|
|
if exp and exp > current_time then
|
|
sessions[key] = exp
|
|
max_expiry = max(max_expiry, exp)
|
|
|
|
else
|
|
sessions[key] = nil
|
|
end
|
|
end
|
|
|
|
for key, exp in pairs(sessions) do
|
|
local meta_value = meta_get_value(key, exp)
|
|
local ok, err = dict:rpush(meta_key, meta_value)
|
|
if not ok then
|
|
log(WARN, "[session] ", errmsg(err, "failed to update metadata"))
|
|
end
|
|
end
|
|
|
|
local exp = max_expiry - current_time
|
|
if exp > 0 then
|
|
local ok, err = dict:expire(meta_key, max_expiry - current_time)
|
|
if not ok and err ~= "not found" then
|
|
log(WARN, "[session] ", errmsg(err, "failed to touch metadata"))
|
|
end
|
|
|
|
else
|
|
dict:delete(meta_key)
|
|
end
|
|
|
|
return sessions
|
|
end
|
|
|
|
|
|
local function cleanup(dict, meta_key, current_time)
|
|
get_and_clean_metadata(dict, meta_key, current_time)
|
|
end
|
|
|
|
|
|
local function read_metadata(self, meta_key, current_time)
|
|
return get_and_clean_metadata(self.dict, meta_key, current_time)
|
|
end
|
|
|
|
---
|
|
-- Storage
|
|
-- @section instance
|
|
|
|
|
|
local metatable = {}
|
|
|
|
|
|
metatable.__index = metatable
|
|
|
|
|
|
function metatable.__newindex()
|
|
error("attempt to update a read-only table", 2)
|
|
end
|
|
|
|
|
|
---
|
|
-- Store session data.
|
|
--
|
|
-- @function instance:set
|
|
-- @tparam string name cookie name
|
|
-- @tparam string key session key
|
|
-- @tparam string value session value
|
|
-- @tparam number ttl session ttl
|
|
-- @tparam number current_time current time
|
|
-- @tparam[opt] string old_key old session id
|
|
-- @tparam string stale_ttl stale ttl
|
|
-- @tparam[opt] table metadata table of metadata
|
|
-- @tparam boolean remember whether storing persistent session or not
|
|
-- @treturn true|nil ok
|
|
-- @treturn string error message
|
|
function metatable:set(name, key, value, ttl, current_time, old_key, stale_ttl, metadata, remember)
|
|
local dict = self.dict
|
|
if not metadata and not old_key then
|
|
local ok, err = dict:set(get_name(self, name, key), value, ttl)
|
|
if not ok then
|
|
return nil, err
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
local old_name, old_ttl
|
|
if old_key then
|
|
old_name = get_name(self, name, old_key)
|
|
if not remember then
|
|
old_ttl = dict:ttl(old_name)
|
|
end
|
|
end
|
|
|
|
local ok, err = dict:set(get_name(self, name, key), value, ttl)
|
|
if not ok then
|
|
return nil, err
|
|
end
|
|
|
|
if old_name then
|
|
if remember then
|
|
dict:delete(old_name)
|
|
|
|
elseif (not old_ttl or old_ttl > stale_ttl) then
|
|
local ok, err = dict:expire(old_name, stale_ttl)
|
|
if not ok then
|
|
log(WARN, "[session] ", errmsg(err, "failed to touch old session"))
|
|
end
|
|
end
|
|
end
|
|
|
|
if not metadata then
|
|
return true
|
|
end
|
|
|
|
local audiences = metadata.audiences
|
|
local subjects = metadata.subjects
|
|
local count = #audiences
|
|
for i = 1, count do
|
|
local meta_key = get_name(self, name, audiences[i], subjects[i])
|
|
local meta_value = meta_get_value(key, current_time + ttl)
|
|
|
|
local ok, err = dict:rpush(meta_key, meta_value)
|
|
if not ok then
|
|
log(WARN, "[session] ", errmsg(err, "failed to update metadata"))
|
|
end
|
|
|
|
if old_key then
|
|
meta_value = meta_get_value(old_key, 0)
|
|
local ok, err = dict:rpush(meta_key, meta_value)
|
|
if not ok then
|
|
log(WARN, "[session] ", errmsg(err, "failed to update old metadata"))
|
|
end
|
|
end
|
|
|
|
-- no need to clean up every time we write
|
|
-- it is just beneficial when a key is used a lot
|
|
if random() < CLEANUP_PROBABILITY then
|
|
cleanup(dict, meta_key, current_time)
|
|
end
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
|
|
---
|
|
-- Retrieve session data.
|
|
--
|
|
-- @function instance:get
|
|
-- @tparam string name cookie name
|
|
-- @tparam string key session key
|
|
-- @treturn string|nil session data
|
|
-- @treturn string error message
|
|
function metatable:get(name, key)
|
|
local value, err = self.dict:get(get_name(self, name, key))
|
|
if not value then
|
|
return nil, err
|
|
end
|
|
|
|
return value
|
|
end
|
|
|
|
|
|
---
|
|
-- Delete session data.
|
|
--
|
|
-- @function instance:delete
|
|
-- @tparam string name cookie name
|
|
-- @tparam string key session key
|
|
-- @tparam[opt] table metadata session meta data
|
|
-- @treturn boolean|nil session data
|
|
-- @treturn string error message
|
|
function metatable:delete(name, key, current_time, metadata)
|
|
local dict = self.dict
|
|
|
|
dict:delete(get_name(self, name, key))
|
|
|
|
if not metadata then
|
|
return true
|
|
end
|
|
|
|
local audiences = metadata.audiences
|
|
local subjects = metadata.subjects
|
|
local count = #audiences
|
|
for i = 1, count do
|
|
local meta_key = get_name(self, name, audiences[i], subjects[i])
|
|
local meta_value = meta_get_value(key, 0)
|
|
|
|
local ok, err = dict:rpush(meta_key, meta_value)
|
|
if not ok then
|
|
if not ok then
|
|
log(WARN, "[session] ", errmsg(err, "failed to update metadata"))
|
|
end
|
|
end
|
|
|
|
cleanup(dict, meta_key, current_time)
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
|
|
---
|
|
-- Read session metadata.
|
|
--
|
|
-- @function instance:read_metadata
|
|
-- @tparam string name cookie name
|
|
-- @tparam string audience session key
|
|
-- @tparam string subject session key
|
|
-- @tparam number current_time current time
|
|
-- @treturn table|nil session metadata
|
|
-- @treturn string error message
|
|
function metatable:read_metadata(name, audience, subject, current_time)
|
|
local meta_key = get_name(self, name, audience, subject)
|
|
return read_metadata(self, meta_key, current_time)
|
|
end
|
|
|
|
|
|
local storage = {}
|
|
|
|
|
|
---
|
|
-- Configuration
|
|
-- @section configuration
|
|
|
|
|
|
---
|
|
-- Shared memory storage backend configuration
|
|
-- @field prefix Prefix for the keys stored in SHM.
|
|
-- @field suffix Suffix for the keys stored in SHM.
|
|
-- @field zone A name of shared memory zone (defaults to `sessions`).
|
|
-- @table configuration
|
|
|
|
|
|
---
|
|
-- Constructors
|
|
-- @section constructors
|
|
|
|
|
|
---
|
|
-- Create a SHM storage.
|
|
--
|
|
-- This creates a new shared memory storage instance.
|
|
--
|
|
-- @function module.new
|
|
-- @tparam[opt] table configuration shm storage @{configuration}
|
|
-- @treturn table shm storage instance
|
|
function storage.new(configuration)
|
|
local prefix = configuration and configuration.prefix
|
|
local suffix = configuration and configuration.suffix
|
|
local zone = configuration and configuration.zone or DEFAULT_ZONE
|
|
|
|
local dict = assert(shared[zone], "lua_shared_dict " .. zone .. " is missing")
|
|
|
|
return setmetatable({
|
|
prefix = prefix,
|
|
suffix = suffix,
|
|
dict = dict,
|
|
}, metatable)
|
|
end
|
|
|
|
|
|
return storage
|