diff options
Diffstat (limited to 'server/resty/session/storage/memcache.lua')
-rw-r--r-- | server/resty/session/storage/memcache.lua | 303 |
1 files changed, 303 insertions, 0 deletions
diff --git a/server/resty/session/storage/memcache.lua b/server/resty/session/storage/memcache.lua new file mode 100644 index 0000000..da44ba7 --- /dev/null +++ b/server/resty/session/storage/memcache.lua @@ -0,0 +1,303 @@ +local memcached = require "resty.memcached" +local setmetatable = setmetatable +local tonumber = tonumber +local concat = table.concat +local sleep = ngx.sleep +local null = ngx.null +local var = ngx.var + +local function enabled(value) + if value == nil then + return nil + end + + return value == true + or value == "1" + or value == "true" + or value == "on" +end + +local function ifnil(value, default) + if value == nil then + return default + end + + return enabled(value) +end + +local defaults = { + prefix = var.session_memcache_prefix or "sessions", + socket = var.session_memcache_socket, + host = var.session_memcache_host or "127.0.0.1", + uselocking = enabled(var.session_memcache_uselocking or true), + connect_timeout = tonumber(var.session_memcache_connect_timeout, 10), + read_timeout = tonumber(var.session_memcache_read_timeout, 10), + send_timeout = tonumber(var.session_memcache_send_timeout, 10), + port = tonumber(var.session_memcache_port, 10) or 11211, + spinlockwait = tonumber(var.session_memcache_spinlockwait, 10) or 150, + maxlockwait = tonumber(var.session_memcache_maxlockwait, 10) or 30, + pool = { + name = var.session_memcache_pool_name, + timeout = tonumber(var.session_memcache_pool_timeout, 10), + size = tonumber(var.session_memcache_pool_size, 10), + backlog = tonumber(var.session_memcache_pool_backlog, 10), + }, +} + +local storage = {} + +storage.__index = storage + +function storage.new(session) + local config = session.memcache or defaults + local pool = config.pool or defaults.pool + local locking = ifnil(config.uselocking, defaults.uselocking) + + local connect_timeout = tonumber(config.connect_timeout, 10) or defaults.connect_timeout + + local memcache = memcached:new() + if memcache.set_timeouts then + local send_timeout = tonumber(config.send_timeout, 10) or defaults.send_timeout + local read_timeout = tonumber(config.read_timeout, 10) or defaults.read_timeout + + if connect_timeout then + if send_timeout and read_timeout then + memcache:set_timeouts(connect_timeout, send_timeout, read_timeout) + else + memcache:set_timeout(connect_timeout) + end + end + + elseif memcache.set_timeout and connect_timeout then + memcache:set_timeout(connect_timeout) + end + + local self = { + memcache = memcache, + prefix = config.prefix or defaults.prefix, + uselocking = locking, + spinlockwait = tonumber(config.spinlockwait, 10) or defaults.spinlockwait, + maxlockwait = tonumber(config.maxlockwait, 10) or defaults.maxlockwait, + pool_timeout = tonumber(pool.timeout, 10) or defaults.pool.timeout, + connect_opts = { + pool = pool.name or defaults.pool.name, + pool_size = tonumber(pool.size, 10) or defaults.pool.size, + backlog = tonumber(pool.backlog, 10) or defaults.pool.backlog, + }, + } + + local socket = config.socket or defaults.socket + if socket and socket ~= "" then + self.socket = socket + else + self.host = config.host or defaults.host + self.port = config.port or defaults.port + end + + return setmetatable(self, storage) +end + +function storage:connect() + local socket = self.socket + if socket then + return self.memcache:connect(socket, self.connect_opts) + end + return self.memcache:connect(self.host, self.port, self.connect_opts) +end + +function storage:set_keepalive() + return self.memcache:set_keepalive(self.pool_timeout) +end + +function storage:key(id) + return concat({ self.prefix, id }, ":" ) +end + +function storage:lock(key) + if not self.uselocking or self.locked then + return true + end + + if not self.token then + self.token = var.request_id + end + + local lock_key = concat({ key, "lock" }, "." ) + local lock_ttl = self.maxlockwait + 1 + local attempts = (1000 / self.spinlockwait) * self.maxlockwait + local waittime = self.spinlockwait / 1000 + + for _ = 1, attempts do + local ok = self.memcache:add(lock_key, self.token, lock_ttl) + if ok then + self.locked = true + return true + end + + sleep(waittime) + end + + return false, "unable to acquire a session lock" +end + +function storage:unlock(key) + if not self.uselocking or not self.locked then + return true + end + + local lock_key = concat({ key, "lock" }, "." ) + local token = self:get(lock_key) + + if token == self.token then + self.memcache:delete(lock_key) + self.locked = nil + end +end + +function storage:get(key) + local data, err = self.memcache:get(key) + if not data then + return nil, err + end + + if data == null then + return nil + end + + return data +end + +function storage:set(key, data, ttl) + return self.memcache:set(key, data, ttl) +end + +function storage:expire(key, ttl) + return self.memcache:touch(key, ttl) +end + +function storage:delete(key) + return self.memcache:delete(key) +end + +function storage:open(id, keep_lock) + local ok, err = self:connect() + if not ok then + return nil, err + end + + local key = self:key(id) + + ok, err = self:lock(key) + if not ok then + self:set_keepalive() + return nil, err + end + + local data + data, err = self:get(key) + + if err or not data or not keep_lock then + self:unlock(key) + end + + self:set_keepalive() + + return data, err +end + +function storage:start(id) + if not self.uselocking or not self.locked then + return true + end + + local ok, err = self:connect() + if not ok then + return nil, err + end + + local key = self:key(id) + + ok, err = self:lock(key) + + self:set_keepalive() + + return ok, err +end + +function storage:save(id, ttl, data, close) + local ok, err = self:connect() + if not ok then + return nil, err + end + + local key = self:key(id) + + ok, err = self:set(key, data, ttl) + + if close then + self:unlock(key) + end + + self:set_keepalive() + + if not ok then + return nil, err + end + + return true +end + +function storage:close(id) + if not self.uselocking or not self.locked then + return true + end + + local ok, err = self:connect() + if not ok then + return nil, err + end + + local key = self:key(id) + + self:unlock(key) + self:set_keepalive() + + return true +end + +function storage:destroy(id) + local ok, err = self:connect() + if not ok then + return nil, err + end + + local key = self:key(id) + + ok, err = self:delete(key) + + self:unlock(key) + self:set_keepalive() + + return ok, err +end + +function storage:ttl(id, ttl, close) + local ok, err = self:connect() + if not ok then + return nil, err + end + + local key = self:key(id) + + ok, err = self:expire(key, ttl) + + if close then + self:unlock(key) + end + + self:set_keepalive() + + return ok, err +end + +return storage |