bunkerweb/lib/resty/session/dshm.lua
Théophile Diot d8ee65aa70 Squashed 'src/deps/src/lua-resty-session/' changes from 8b5f8752f..5f2aed616
5f2aed616 chore(*): release 4.0.5
f4980f0e1 fix(dshm/memcached): add a missing return parameter as otherwise pool parameters are not respected

git-subtree-dir: src/deps/src/lua-resty-session
git-subtree-split: 5f2aed616d16fa7ca04dc40e23d6941740cd634d
2023-09-15 14:22:59 +02:00

348 lines
8.9 KiB
Lua

---
-- Distributed Shared Memory (DSHM) backend for session library
--
-- @module resty.session.dshm
local buffer = require "string.buffer"
local utils = require "resty.session.utils"
local dshm = require "resty.dshm"
local meta_get_latest = utils.meta_get_latest
local meta_get_value = utils.meta_get_value
local get_name = utils.get_name
local setmetatable = setmetatable
local error = error
local pairs = pairs
local null = ngx.null
local max = math.max
local DEFAULT_HOST = "127.0.0.1"
local DEFAULT_PORT = 4321
local SESSIONS_BUFFER = buffer.new(128)
-- not safe for concurrent access
local function update_meta(dshmc, meta_key, key, exp, current_time)
local metadata = dshmc:get(meta_key)
local sessions = metadata and meta_get_latest(metadata, current_time) or {}
SESSIONS_BUFFER:reset()
sessions[key] = exp > 0 and exp or nil
local max_expiry = current_time
for s, e in pairs(sessions) do
SESSIONS_BUFFER:put(meta_get_value(s, e))
max_expiry = max(max_expiry, e)
end
local ser = SESSIONS_BUFFER:get()
if #ser > 0 then
return dshmc:set(meta_key, ser, max_expiry - current_time)
end
return dshmc:delete(meta_key)
end
local function READ_METADATA(self, dshmc, name, audience, subject, current_time)
local meta_key = get_name(self, name, audience, subject)
local res = dshmc:get(meta_key)
if not res then
return nil, "not found"
end
return meta_get_latest(res, current_time)
end
local function SET(self, dshmc, name, key, value, ttl, current_time, old_key, stale_ttl, metadata, remember)
local inferred_key = get_name(self, name, key)
if not metadata and not old_key then
return dshmc:set(inferred_key, value, ttl)
end
local ok, err = dshmc:set(inferred_key, value, ttl)
if err then
return nil, err
end
local old_name = old_key and get_name(self, name, old_key)
if old_name then
if remember then
dshmc:delete(old_name)
else
dshmc:touch(old_name, stale_ttl)
end
end
if metadata then
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])
update_meta(dshmc, meta_key, key, current_time + ttl, current_time)
if old_key then
update_meta(dshmc, meta_key, old_key, 0, current_time)
end
end
end
return ok
end
local function GET(self, dshmc, name, key)
local res, err = dshmc:get(get_name(self, name, key))
if err then
return nil, err
end
return res
end
local function DELETE(self, dshmc, name, key, current_time, metadata)
local key_name = get_name(self, name, key)
local ok, err = dshmc:delete(key_name)
if not metadata then
return ok, err
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])
update_meta(dshmc, meta_key, key, 0, current_time)
end
return ok, err
end
local function exec(self, func, ...)
local dshmc = dshm:new()
local connect_timeout = self.connect_timeout
local send_timeout = self.send_timeout
local read_timeout = self.read_timeout
if connect_timeout or send_timeout or read_timeout then
dshmc.sock:settimeouts(connect_timeout, send_timeout, read_timeout)
end
local ok, err = dshmc:connect(self.host, self.port, self.options)
if not ok then
return nil, err
end
if self.ssl and dshmc:get_reused_times() == 0 then
ok, err = dshmc.sock:sslhandshake(false, self.server_name, self.ssl_verify)
if not ok then
dshmc:close()
return nil, err
end
end
ok, err = func(self, dshmc, ...)
if err then
dshmc:close()
return nil, err
end
if not dshmc:set_keepalive(self.keepalive_timeout) then
dshmc:close()
end
if ok == null then
ok = nil
end
return ok, err
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(...)
return exec(self, SET, ...)
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(...)
return exec(self, GET, ...)
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(...)
return exec(self, DELETE, ...)
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(...)
return exec(self, READ_METADATA, ...)
end
local storage = {}
---
-- Configuration
-- @section configuration
---
-- Distributed shared memory storage backend configuration
-- @field prefix The prefix for the keys stored in DSHM.
-- @field suffix The suffix for the keys stored in DSHM.
-- @field host The host to connect (defaults to `"127.0.0.1"`).
-- @field port The port to connect (defaults to `4321`).
-- @field connect_timeout Controls the default timeout value used in TCP/unix-domain socket object's `connect` method.
-- @field send_timeout Controls the default timeout value used in TCP/unix-domain socket object's `send` method.
-- @field read_timeout Controls the default timeout value used in TCP/unix-domain socket object's `receive` method.
-- @field keepalive_timeout Controls the default maximal idle time of the connections in the connection pool.
-- @field pool A custom name for the connection pool being used.
-- @field pool_size The size of the connection pool.
-- @field backlog A queue size to use when the connection pool is full (configured with @pool_size).
-- @field ssl Enable SSL (defaults to `false`).
-- @field ssl_verify Verify server certificate (defaults to `nil`).
-- @field server_name The server name for the new TLS extension Server Name Indication (SNI).
-- @table configuration
---
-- Constructors
-- @section constructors
---
-- Create a distributed shared memory storage.
--
-- This creates a new distributed shared memory storage instance.
--
-- @function module.new
-- @tparam[opt] table configuration DSHM storage @{configuration}
-- @treturn table DSHM storage instance
function storage.new(configuration)
local prefix = configuration and configuration.prefix
local suffix = configuration and configuration.suffix
local host = configuration and configuration.host or DEFAULT_HOST
local port = configuration and configuration.port or DEFAULT_PORT
local connect_timeout = configuration and configuration.connect_timeout
local send_timeout = configuration and configuration.send_timeout
local read_timeout = configuration and configuration.read_timeout
local keepalive_timeout = configuration and configuration.keepalive_timeout
local pool = configuration and configuration.pool
local pool_size = configuration and configuration.pool_size
local backlog = configuration and configuration.backlog
local ssl = configuration and configuration.ssl
local ssl_verify = configuration and configuration.ssl_verify
local server_name = configuration and configuration.server_name
if pool or pool_size or backlog then
return setmetatable({
prefix = prefix,
suffix = suffix,
host = host,
port = port,
connect_timeout = connect_timeout,
send_timeout = send_timeout,
read_timeout = read_timeout,
keepalive_timeout = keepalive_timeout,
ssl = ssl,
ssl_verify = ssl_verify,
server_name = server_name,
options = {
pool = pool,
pool_size = pool_size,
backlog = backlog,
}
}, metatable)
end
return setmetatable({
prefix = prefix,
suffix = suffix,
host = host,
port = port,
connect_timeout = connect_timeout,
send_timeout = send_timeout,
read_timeout = read_timeout,
keepalive_timeout = keepalive_timeout,
ssl = ssl,
ssl_verify = ssl_verify,
server_name = server_name,
}, metatable)
end
return storage