├── .gitignore ├── .luacheckrc ├── Makefile ├── .busted ├── dist.ini ├── config.ld ├── .github └── workflows │ ├── integration_tests.yaml │ └── unit_tests.yaml ├── dev └── Dockerfile ├── .luacov ├── LICENSE ├── lua-resty-session-4.1.5-1.rockspec ├── t └── 01-cookies.t ├── lib └── resty │ └── session │ ├── file.lua │ ├── redis │ ├── common.lua │ ├── sentinel.lua │ └── cluster.lua │ ├── file │ ├── thread.lua │ └── utils.lua │ ├── shm.lua │ ├── redis.lua │ ├── dshm.lua │ ├── postgres.lua │ ├── memcached.lua │ └── mysql.lua ├── spec ├── 02-file-utils_spec.lua ├── 01-utils_spec.lua ├── 04-storage-1_spec.lua └── 05-storage-2_spec.lua └── docs ├── index.html ├── modules ├── resty.session.file-thread.html ├── resty.session.file.utils.html ├── resty.session.redis.common.html ├── resty.session.shm.html ├── resty.session.file.html └── resty.session.file.thread.html └── ldoc.css /.gitignore: -------------------------------------------------------------------------------- 1 | /.project 2 | -------------------------------------------------------------------------------- /.luacheckrc: -------------------------------------------------------------------------------- 1 | std = "ngx_lua" 2 | redefined = false 3 | max_line_length = false 4 | files["lib/resty/session/file.lua"] = { ignore = {"143"}} -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: lint test docs 2 | 3 | lint: 4 | @luacheck -q ./lib 5 | 6 | unit: 7 | busted --exclude-tags=noci --coverage 8 | 9 | unit-all: 10 | busted --coverage 11 | 12 | prove: 13 | prove 14 | 15 | docs: 16 | ldoc . 17 | -------------------------------------------------------------------------------- /.busted: -------------------------------------------------------------------------------- 1 | return { 2 | default = { 3 | lua = "resty --shdict 'sessions 1m' --main-conf 'thread_pool default threads=32 max_queue=65536;'", 4 | verbose = true, 5 | coverage = true, 6 | output = "gtest", 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /dist.ini: -------------------------------------------------------------------------------- 1 | name = lua-resty-session 2 | abstract = Session Library for OpenResty - Flexible and Secure 3 | author = Aapo Talvensaari (@bungle), Samuele Illuminati (@samugi) 4 | is_original = yes 5 | license = 2bsd 6 | repo_link = https://github.com/bungle/lua-resty-session 7 | requires = luajit, nginx, ngx_http_lua, fffonion/lua-resty-openssl >= 1.5.0, hamishforbes/lua-ffi-zlib >= 0.5 8 | -------------------------------------------------------------------------------- /config.ld: -------------------------------------------------------------------------------- 1 | project = "resty.session" 2 | description = "Session Library for OpenResty" 3 | full_description = "`lua-resty-session` is a secure, and flexible session library for OpenResty" 4 | title = "Session Library for OpenResty Documentation" 5 | dir = "docs" 6 | use_markdown_titles = true 7 | package = "session" 8 | format = "discount" 9 | sort_modules=true 10 | file = "./lib/resty" 11 | -------------------------------------------------------------------------------- /.github/workflows/integration_tests.yaml: -------------------------------------------------------------------------------- 1 | name: integration_tests 2 | on: [pull_request] 3 | 4 | jobs: 5 | test: 6 | strategy: 7 | fail-fast: false 8 | matrix: 9 | luaVersion: ["luajit-openresty"] 10 | 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - name: Setup environment 16 | run: docker build dev/ -t resty-session 17 | 18 | - name: Run tests 19 | run: docker run -v $PWD:/test -w /test resty-session bash -c "luarocks --lua-version 5.1 make && make prove" 20 | -------------------------------------------------------------------------------- /dev/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openresty/openresty:1.27.1.2-0-noble 2 | 3 | ENV DEBIAN_FRONTEND noninteractive 4 | ENV TEST_NGINX_BINARY openresty 5 | 6 | USER root 7 | RUN apt-get update && apt-get install -y gcc git cpanminus lua5.1 liblua5.1-dev luarocks 8 | 9 | RUN luarocks --lua-version 5.1 install busted 10 | RUN luarocks --lua-version 5.1 install pgmoon 11 | RUN luarocks --lua-version 5.1 install lua-resty-rsa 12 | RUN luarocks --lua-version 5.1 install lua-resty-redis-connector 13 | RUN luarocks --lua-version 5.1 install lua-resty-redis-cluster 14 | RUN luarocks --lua-version 5.1 install inspect 15 | RUN luarocks --lua-version 5.1 install LuaCov 16 | 17 | RUN cpanm --notest Test::Nginx 18 | -------------------------------------------------------------------------------- /.luacov: -------------------------------------------------------------------------------- 1 | return { 2 | 3 | -- filename to store stats collected 4 | ["statsfile"] = "luacov.stats.out", 5 | 6 | -- filename to store report 7 | ["reportfile"] = "luacov.report.out", 8 | 9 | -- Run reporter on completion? (won't work for ticks) 10 | runreport = true, 11 | 12 | -- Patterns for files to include when reporting 13 | -- all will be included if nothing is listed 14 | -- (exclude overrules include, do not include 15 | -- the .lua extension, path separator is always '/') 16 | ["include"] = { 'resty/session' }, 17 | 18 | -- Patterns for files to exclude when reporting 19 | -- all will be included if nothing is listed 20 | -- (exclude overrules include, do not include 21 | -- the .lua extension, path separator is always '/') 22 | ["exclude"] = { 23 | "luacov$", 24 | "luacov/reporter$", 25 | "luacov/defaults$", 26 | "luacov/runner$", 27 | "luacov/stats$", 28 | "luacov/tick$", 29 | }, 30 | 31 | } -------------------------------------------------------------------------------- /.github/workflows/unit_tests.yaml: -------------------------------------------------------------------------------- 1 | name: unit_tests 2 | on: [pull_request] 3 | 4 | jobs: 5 | test: 6 | strategy: 7 | fail-fast: false 8 | matrix: 9 | luaVersion: ["luajit-openresty"] 10 | 11 | services: 12 | redis: 13 | image: bitnami/redis 14 | env: 15 | REDIS_PASSWORD: password 16 | ports: 17 | - 6379:6379 18 | options: >- 19 | --health-cmd "redis-cli ping" 20 | --health-interval 10s 21 | --health-timeout 5s 22 | --health-retries 5 23 | memcached: 24 | image: memcached 25 | ports: 26 | - 11211:11211 27 | 28 | runs-on: ubuntu-latest 29 | steps: 30 | - uses: actions/checkout@v4 31 | 32 | - name: Setup environment 33 | run: docker build dev/ -t resty-session 34 | 35 | - name: Run tests 36 | run: docker run --network=host -v $PWD:/test -w /test resty-session bash -c "luarocks --lua-version 5.1 make && make unit" 37 | 38 | - name: Generate report 39 | run: docker run --network=host -v $PWD:/test -w /test resty-session bash -c "luarocks --lua-version 5.1 make && luacov" 40 | 41 | - name: Print report summary 42 | run: docker run --network=host -v $PWD:/test -w /test resty-session sed -n '/Summary/,$p' luacov.report.out 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 – 2025 Aapo Talvensaari, 2022 – 2025 Samuele Illuminati 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /lua-resty-session-4.1.5-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-session" 2 | version = "4.1.5-1" 3 | source = { 4 | url = "git+https://github.com/bungle/lua-resty-session.git", 5 | tag = "v4.1.5", 6 | } 7 | description = { 8 | summary = "Session Library for OpenResty - Flexible and Secure", 9 | detailed = "lua-resty-session is a secure, and flexible session library for OpenResty.", 10 | homepage = "https://github.com/bungle/lua-resty-session", 11 | maintainer = "Aapo Talvensaari , Samuele Illuminati ", 12 | license = "BSD", 13 | } 14 | dependencies = { 15 | "lua >= 5.1", 16 | "lua-ffi-zlib >= 0.5", 17 | "lua-resty-openssl >= 1.5.0", 18 | } 19 | build = { 20 | type = "builtin", 21 | modules = { 22 | ["resty.session"] = "lib/resty/session.lua", 23 | ["resty.session.dshm"] = "lib/resty/session/dshm.lua", 24 | ["resty.session.file"] = "lib/resty/session/file.lua", 25 | ["resty.session.file.thread"] = "lib/resty/session/file/thread.lua", 26 | ["resty.session.file.utils"] = "lib/resty/session/file/utils.lua", 27 | ["resty.session.memcached"] = "lib/resty/session/memcached.lua", 28 | ["resty.session.mysql"] = "lib/resty/session/mysql.lua", 29 | ["resty.session.postgres"] = "lib/resty/session/postgres.lua", 30 | ["resty.session.redis"] = "lib/resty/session/redis.lua", 31 | ["resty.session.redis.cluster"] = "lib/resty/session/redis/cluster.lua", 32 | ["resty.session.redis.sentinel"] = "lib/resty/session/redis/sentinel.lua", 33 | ["resty.session.redis.common"] = "lib/resty/session/redis/common.lua", 34 | ["resty.session.shm"] = "lib/resty/session/shm.lua", 35 | ["resty.session.utils"] = "lib/resty/session/utils.lua", 36 | }, 37 | } 38 | -------------------------------------------------------------------------------- /t/01-cookies.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket; 2 | repeat_each(2); 3 | 4 | $ENV{TEST_NGINX_NXSOCK} ||= html_dir(); 5 | plan tests => repeat_each() * blocks() * 3 + 4; 6 | 7 | run_tests(); 8 | 9 | __DATA__ 10 | 11 | === TEST 1: session cookie is returned 12 | --- http_config 13 | init_by_lua_block { 14 | require("resty.session").init({ 15 | storage = "cookie", 16 | }) 17 | } 18 | --- config 19 | location = /test { 20 | content_by_lua_block { 21 | local session = require "resty.session".new() 22 | local ok, err = session:save() 23 | ngx.say(ok and "yay" or err) 24 | } 25 | } 26 | 27 | --- request 28 | GET /test 29 | --- response_body 30 | yay 31 | --- response_headers_like 32 | Set-Cookie: .*session=.+;.* 33 | --- error_code: 200 34 | --- no_error_log 35 | [error] 36 | 37 | 38 | === TEST 2: remember cookie is returned when remember=true 39 | --- http_config 40 | init_by_lua_block { 41 | require("resty.session").init({ 42 | remember = true, 43 | storage = "cookie", 44 | }) 45 | } 46 | --- config 47 | location = /test { 48 | content_by_lua_block { 49 | local session = require "resty.session".new() 50 | local ok, err = session:save() 51 | ngx.say(ok and "yay" or err) 52 | } 53 | } 54 | 55 | --- request 56 | GET /test 57 | --- response_body 58 | yay 59 | --- response_headers_like 60 | Set-Cookie: .*remember=.+;.* 61 | --- error_code: 200 62 | --- no_error_log 63 | [error] 64 | 65 | 66 | === TEST 3: session.open() opens a session from a valid cookie and data is 67 | extracted correctly 68 | --- http_config 69 | init_by_lua_block { 70 | require("resty.session").init({ 71 | secret = "RaJKp8UQW1", 72 | storage = "cookie", 73 | audience = "my_application", 74 | idling_timeout = 0, 75 | rolling_timeout = 0, 76 | absolute_timeout = 0, 77 | }) 78 | } 79 | --- config 80 | location = /test { 81 | content_by_lua_block { 82 | local session = require "resty.session".open() 83 | local sub = session:get_subject() 84 | local aud = session:get_audience() 85 | local quote = session:get("quote") 86 | ngx.say(sub .. "|" .. aud .. "|" .. quote) 87 | } 88 | } 89 | 90 | --- request 91 | GET /test 92 | --- more_headers 93 | Cookie: session=AQAAS3ZGU0k8tUKsWSci9Fb6PM5xbm469FlR5g_B5HWZ6KYGSOZjAAAAAABcAABTCuHjqpE7B6Ux7m4GCylZAAAAzcWnTvzG51whooR_4QQwDgGdMOOa5W7tG4JWiDFU3zuYLFzakWEi-y-ogrwTpnt24zQXP_uJK7r5lMPNzRSMJM9H1a_MIegzEMm-QSgVRaoZVJq3Oo; Path=/; SameSite=Lax; HttpOnly 94 | --- response_body 95 | Lua Fan|my_application|Lorem ipsum dolor sit amet 96 | --- error_code: 200 97 | --- no_error_log 98 | [error] 99 | 100 | 101 | === TEST 4: clear_request_cookie() clears session Cookie from request to 102 | upstream 103 | --- http_config 104 | init_by_lua_block { 105 | require("resty.session").init({ 106 | secret = "RaJKp8UQW1", 107 | storage = "cookie", 108 | audience = "my_application", 109 | idling_timeout = 0, 110 | rolling_timeout = 0, 111 | absolute_timeout = 0, 112 | }) 113 | } 114 | 115 | server { 116 | listen unix:/$TEST_NGINX_NXSOCK/nginx.sock; 117 | location /t { 118 | content_by_lua_block { 119 | local headers = ngx.req.get_headers() 120 | ngx.say("session_cookie: [", tostring(headers["Cookie"]), "]") 121 | } 122 | } 123 | } 124 | 125 | --- config 126 | location = /test { 127 | access_by_lua_block { 128 | local session = require "resty.session".open() 129 | session:clear_request_cookie() 130 | } 131 | proxy_pass http://unix:/$TEST_NGINX_NXSOCK/nginx.sock; 132 | } 133 | 134 | --- request 135 | GET /test 136 | --- more_headers 137 | Cookie: session=AQAAS3ZGU0k8tUKsWSci9Fb6PM5xbm469FlR5g_B5HWZ6KYGSOZjAAAAAABcAABTCuHjqpE7B6Ux7m4GCylZAAAAzcWnTvzG51whooR_4QQwDgGdMOOa5W7tG4JWiDFU3zuYLFzakWEi-y-ogrwTpnt24zQXP_uJK7r5lMPNzRSMJM9H1a_MIegzEMm-QSgVRaoZVJq3Oo; Path=/; SameSite=Lax; HttpOnly 138 | --- response_body 139 | session_cookie: [Path=/; SameSite=Lax; HttpOnly] 140 | --- error_code: 200 141 | --- no_error_log 142 | [error] 143 | -------------------------------------------------------------------------------- /lib/resty/session/file.lua: -------------------------------------------------------------------------------- 1 | --- 2 | -- File storage backend for session library. 3 | -- 4 | -- @module resty.session.file 5 | 6 | 7 | local file_utils = require "resty.session.file.utils" 8 | 9 | 10 | local run_worker_thread = file_utils.run_worker_thread 11 | 12 | 13 | local setmetatable = setmetatable 14 | local error = error 15 | local byte = string.byte 16 | 17 | 18 | local SLASH_BYTE = byte("/") 19 | 20 | 21 | local THREAD_MODULE = "resty.session.file.thread" 22 | 23 | 24 | local DEFAULT_POOL = "default" 25 | local DEFAULT_PATH do 26 | local path = os.tmpname() 27 | local pos 28 | for i = #path, 1, -1 do 29 | if byte(path, i) == SLASH_BYTE then 30 | pos = i 31 | break 32 | end 33 | end 34 | 35 | DEFAULT_PATH = path:sub(1, pos) 36 | end 37 | 38 | 39 | local function run_thread(self, func, ...) 40 | local ok, res, err = run_worker_thread(self.pool, THREAD_MODULE, func, self.path, self.prefix, self.suffix, ...) 41 | if not ok then 42 | return nil, res 43 | end 44 | 45 | return res, err 46 | end 47 | 48 | 49 | --- 50 | -- Storage 51 | -- @section instance 52 | 53 | 54 | local metatable = {} 55 | 56 | 57 | metatable.__index = metatable 58 | 59 | 60 | function metatable.__newindex() 61 | error("attempt to update a read-only table", 2) 62 | end 63 | 64 | 65 | --- 66 | -- Store session data. 67 | -- 68 | -- @function instance:set 69 | -- @tparam string name cookie name 70 | -- @tparam string key session key 71 | -- @tparam string value session value 72 | -- @tparam number ttl session ttl 73 | -- @tparam number current_time current time 74 | -- @tparam[opt] string old_key old session id 75 | -- @tparam string stale_ttl stale ttl 76 | -- @tparam[opt] table metadata table of metadata 77 | -- @treturn true|nil ok 78 | -- @treturn string error message 79 | function metatable:set(...) 80 | return run_thread(self, "set", ...) 81 | end 82 | 83 | 84 | --- 85 | -- Retrieve session data. 86 | -- 87 | -- @function instance:get 88 | -- @tparam string name cookie name 89 | -- @tparam string key session key 90 | -- @treturn string|nil session data 91 | -- @treturn string error message 92 | function metatable:get(...) 93 | return run_thread(self, "get", ...) 94 | end 95 | 96 | 97 | --- 98 | -- Delete session data. 99 | -- 100 | -- @function instance:delete 101 | -- @tparam string name cookie name 102 | -- @tparam string key session key 103 | -- @tparam[opt] table metadata session meta data 104 | -- @treturn boolean|nil session data 105 | -- @treturn string error message 106 | function metatable:delete(...) 107 | return run_thread(self, "delete", ...) 108 | end 109 | 110 | 111 | --- 112 | -- Read session metadata. 113 | -- 114 | -- @function instance:read_metadata 115 | -- @tparam string name cookie name 116 | -- @tparam string audience session key 117 | -- @tparam string subject session key 118 | -- @tparam number current_time current time 119 | -- @treturn table|nil session metadata 120 | -- @treturn string error message 121 | function metatable:read_metadata(...) 122 | return run_thread(self, "read_metadata", ...) 123 | end 124 | 125 | 126 | local storage = {} 127 | 128 | 129 | --- 130 | -- Configuration 131 | -- @section configuration 132 | 133 | 134 | --- 135 | -- File storage backend configuration 136 | -- @field prefix File prefix for session file. 137 | -- @field suffix File suffix (or extension without `.`) for session file. 138 | -- @field pool Name of the thread pool under which file writing happens (available on Linux only). 139 | -- @field path Path (or directory) under which session files are created. 140 | -- @table configuration 141 | 142 | 143 | --- 144 | -- Constructors 145 | -- @section constructors 146 | 147 | 148 | --- 149 | -- Create a file storage. 150 | -- 151 | -- This creates a new file storage instance. 152 | -- 153 | -- @function module.new 154 | -- @tparam[opt] table configuration file storage @{configuration} 155 | -- @treturn table file storage instance 156 | function storage.new(configuration) 157 | local prefix = configuration and configuration.prefix 158 | local suffix = configuration and configuration.suffix 159 | 160 | local pool = configuration and configuration.pool or DEFAULT_POOL 161 | local path = configuration and configuration.path or DEFAULT_PATH 162 | 163 | if byte(path, -1) ~= SLASH_BYTE then 164 | path = path .. "/" 165 | end 166 | 167 | return setmetatable({ 168 | prefix = prefix ~= "" and prefix or nil, 169 | suffix = suffix ~= "" and suffix or nil, 170 | pool = pool, 171 | path = path, 172 | }, metatable) 173 | end 174 | 175 | 176 | return storage 177 | -------------------------------------------------------------------------------- /lib/resty/session/redis/common.lua: -------------------------------------------------------------------------------- 1 | --- 2 | -- Common Redis functions shared between Redis, 3 | -- Redis Cluster and Redis Sentinel implementations. 4 | -- 5 | -- @module resty.session.redis.common 6 | 7 | 8 | local utils = require "resty.session.utils" 9 | 10 | 11 | local get_name = utils.get_name 12 | local ipairs = ipairs 13 | 14 | 15 | --- 16 | -- Store session data. 17 | -- 18 | -- @function module.SET 19 | -- @tparam table storage the storage 20 | -- @tparam table red the redis instance 21 | -- @tparam string name the cookie name 22 | -- @tparam string key session key 23 | -- @tparam string value session value 24 | -- @tparam number ttl session ttl 25 | -- @tparam number current_time current time 26 | -- @tparam[opt] string old_key old session id 27 | -- @tparam string stale_ttl stale ttl 28 | -- @tparam[opt] table metadata table of metadata 29 | -- @treturn true|nil ok 30 | -- @treturn string error message 31 | local function SET(storage, red, name, key, value, ttl, current_time, old_key, stale_ttl, metadata) 32 | if not metadata and not old_key then 33 | return red:set(get_name(storage, name, key), value, "EX", ttl) 34 | end 35 | 36 | local old_name 37 | local old_ttl 38 | if old_key then 39 | old_name = get_name(storage, name, old_key) 40 | -- redis < 7.0 41 | old_ttl = red:ttl(old_name) 42 | end 43 | 44 | red:init_pipeline() 45 | red:set(get_name(storage, name, key), value, "EX", ttl) 46 | 47 | -- redis < 7.0 48 | if old_name and (not old_ttl or old_ttl > stale_ttl) then 49 | red:expire(old_name, stale_ttl) 50 | end 51 | 52 | -- redis >= 7.0 53 | --if old_key then 54 | -- red:expire(get_name(storage, name, old_key), stale_ttl, "LT") 55 | --end 56 | 57 | if metadata then 58 | local audiences = metadata.audiences 59 | local subjects = metadata.subjects 60 | local score = current_time - 1 61 | local new_score = current_time + ttl 62 | local count = #audiences 63 | for i = 1, count do 64 | local meta_key = get_name(storage, name, audiences[i], subjects[i]) 65 | red:zremrangebyscore(meta_key, 0, score) 66 | red:zadd(meta_key, new_score, key) 67 | if old_key then 68 | red:zrem(meta_key, old_key) 69 | end 70 | red:expire(meta_key, ttl) 71 | end 72 | end 73 | 74 | return red:commit_pipeline() 75 | end 76 | 77 | 78 | --- 79 | -- Retrieve session data. 80 | -- 81 | -- @function module.GET 82 | -- @tparam table storage the storage 83 | -- @tparam table red the redis instance 84 | -- @tparam string name cookie name 85 | -- @tparam string key session key 86 | -- @treturn string|nil session data 87 | -- @treturn string error message 88 | local function GET(storage, red, name, key) 89 | return red:get(get_name(storage, name, key)) 90 | end 91 | 92 | 93 | --- 94 | -- Delete session data. 95 | -- 96 | -- @function module.UNLINK 97 | -- @tparam table storage the storage 98 | -- @tparam table red the redis instance 99 | -- @tparam string name cookie name 100 | -- @tparam string key session key 101 | -- @tparam number current_time current time 102 | -- @tparam[opt] table metadata session meta data 103 | -- @treturn boolean|nil session data 104 | -- @treturn string error message 105 | local function UNLINK(storage, red, name, key, current_time, metadata) 106 | if not metadata then 107 | return red:unlink(get_name(storage, name, key)) 108 | end 109 | 110 | red:init_pipeline() 111 | red:unlink(get_name(storage, name, key)) 112 | local audiences = metadata.audiences 113 | local subjects = metadata.subjects 114 | local score = current_time - 1 115 | local count = #audiences 116 | for i = 1, count do 117 | local meta_key = get_name(storage, name, audiences[i], subjects[i]) 118 | red:zremrangebyscore(meta_key, 0, score) 119 | red:zrem(meta_key, key) 120 | end 121 | 122 | return red:commit_pipeline() 123 | end 124 | 125 | 126 | --- 127 | -- Read session metadata. 128 | -- 129 | -- @function module.READ_METADATA 130 | -- @tparam table storage the storage 131 | -- @tparam table red the redis instance 132 | -- @tparam string name cookie name 133 | -- @tparam string audience session key 134 | -- @tparam string subject session key 135 | -- @tparam number current_time current time 136 | -- @treturn table|nil session metadata 137 | -- @treturn string error message 138 | local function READ_METADATA(storage, red, name, audience, subject, current_time) 139 | local meta_key = get_name(storage, name, audience, subject) 140 | local res, err = red:zrange(meta_key, current_time, "+inf", "BYSCORE", "WITHSCORES") 141 | if not res then 142 | return nil, err 143 | end 144 | 145 | local sessions = {} 146 | for i, v in ipairs(res) do 147 | if i % 2 ~= 0 then 148 | sessions[v] = res[i + 1] 149 | end 150 | end 151 | return sessions 152 | end 153 | 154 | 155 | return { 156 | SET = SET, 157 | GET = GET, 158 | UNLINK = UNLINK, 159 | READ_METADATA = READ_METADATA, 160 | } 161 | -------------------------------------------------------------------------------- /spec/02-file-utils_spec.lua: -------------------------------------------------------------------------------- 1 | local file_utils = require "resty.session.file.utils" 2 | 3 | 4 | local fmt = string.format 5 | local describe = describe 6 | local assert = assert 7 | local it = it 8 | 9 | 10 | describe("Testing file utils", function() 11 | describe("validate file name", function() 12 | local validate_file_name = file_utils.validate_file_name 13 | local name = "name" 14 | local sid1 = "ABCdef123_-iJqwertYUIOpasdfgHJKLzxcvbnmJuis" 15 | local aud_sub1 = "some-aud:some-sub" 16 | local simple_prefix = "pref" 17 | local special_prefix = "@-_-" 18 | local simple_suffix = "suff" 19 | local special_suffix = "-_-@" 20 | local filename_inv1 = "some.conf" 21 | local filename_inv2 = "abc" 22 | local filename_inv3 = name.."_".. "abc" 23 | 24 | it("validation fails with invalid files with prefix and suffix", function() 25 | assert.is_false(validate_file_name(simple_prefix, simple_suffix, name, filename_inv1)) 26 | assert.is_false(validate_file_name(simple_prefix, simple_suffix, name, filename_inv2)) 27 | assert.is_false(validate_file_name(simple_prefix, simple_suffix, name, filename_inv3)) 28 | end) 29 | 30 | 31 | it("validation fails with invalid files with prefix only", function() 32 | assert.is_false(validate_file_name(simple_prefix, nil, name, filename_inv1)) 33 | assert.is_false(validate_file_name(simple_prefix, nil, name, filename_inv2)) 34 | assert.is_false(validate_file_name(simple_prefix, nil, name, filename_inv3)) 35 | end) 36 | 37 | it("validation fails with invalid files with suffix only", function() 38 | assert.is_false(validate_file_name(nil, simple_suffix, name, filename_inv1)) 39 | assert.is_false(validate_file_name(nil, simple_suffix, name, filename_inv2)) 40 | assert.is_false(validate_file_name(nil, simple_suffix, name, filename_inv3)) 41 | end) 42 | 43 | it("validation fails with invalid files with no prefix or suffix", function() 44 | assert.is_false(validate_file_name(nil, nil, name, filename_inv1)) 45 | assert.is_false(validate_file_name(nil, nil, name, filename_inv2)) 46 | assert.is_false(validate_file_name(nil, nil, name, filename_inv3)) 47 | end) 48 | 49 | it("validation passes with prefix and suffix", function() 50 | local filename_sess = fmt("%s_%s_%s.%s", simple_prefix, name, sid1, simple_suffix) 51 | local filename_meta = fmt("%s_%s_%s.%s", simple_prefix, name, aud_sub1, simple_suffix) 52 | 53 | assert.is_true(validate_file_name(simple_prefix, simple_suffix, name, filename_sess)) 54 | assert.is_true(validate_file_name(simple_prefix, simple_suffix, name, filename_meta)) 55 | assert.is_false(validate_file_name(simple_prefix, simple_suffix, name, filename_inv1)) 56 | assert.is_false(validate_file_name(simple_prefix, simple_suffix, name, filename_inv2)) 57 | end) 58 | 59 | it("validation passes with special prefix and suffix", function() 60 | local sp_filename_sess = fmt("%s_%s_%s.%s", special_prefix, name, sid1, special_suffix) 61 | local sp_filename_meta = fmt("%s_%s_%s.%s", special_prefix, name, aud_sub1, special_suffix) 62 | 63 | assert.is_true(validate_file_name(special_prefix, special_suffix, name, sp_filename_sess)) 64 | assert.is_true(validate_file_name(special_prefix, special_suffix, name, sp_filename_meta)) 65 | end) 66 | 67 | 68 | it("validation passes with prefix", function() 69 | local filename_sess = fmt("%s_%s_%s", simple_prefix, name, sid1) 70 | local filename_meta = fmt("%s_%s_%s", simple_prefix, name, aud_sub1) 71 | 72 | assert.is_true(validate_file_name(simple_prefix, nil, name, filename_sess)) 73 | assert.is_true(validate_file_name(simple_prefix, nil, name, filename_meta)) 74 | end) 75 | 76 | it("validation passes with special prefix", function() 77 | local sp_filename_sess = fmt("%s_%s_%s", special_prefix, name, sid1) 78 | local sp_filename_meta = fmt("%s_%s_%s", special_prefix, name, aud_sub1) 79 | 80 | assert.is_true(validate_file_name(special_prefix, nil, name, sp_filename_sess)) 81 | assert.is_true(validate_file_name(special_prefix, nil, name, sp_filename_meta)) 82 | end) 83 | 84 | it("#only validation passes with suffix", function() 85 | local filename_sess = fmt("%s_%s.%s", name, sid1, simple_suffix) 86 | local filename_meta = fmt("%s_%s.%s", name, aud_sub1, simple_suffix) 87 | 88 | assert.is_true(validate_file_name(nil, simple_suffix, name, filename_sess)) 89 | assert.is_true(validate_file_name(nil, simple_suffix, name, filename_meta)) 90 | end) 91 | 92 | it("validation passes with special suffix", function() 93 | local sp_filename_sess = fmt("%s_%s.%s", name, sid1, special_suffix) 94 | local sp_filename_meta = fmt("%s_%s.%s", name, aud_sub1, special_suffix) 95 | 96 | assert.is_true(validate_file_name(nil, special_suffix, name, sp_filename_sess)) 97 | assert.is_true(validate_file_name(nil, special_suffix, name, sp_filename_meta)) 98 | end) 99 | 100 | it("validation passes with no prefix or suffix", function() 101 | local filename_sess = fmt("%s_%s", name, sid1) 102 | local filename_meta = fmt("%s_%s", name, aud_sub1) 103 | 104 | assert.is_true(validate_file_name(nil, nil, name, filename_sess)) 105 | assert.is_true(validate_file_name(nil, nil, name, filename_meta)) 106 | end) 107 | end) 108 | end) 109 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | Session Library for OpenResty Documentation 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 52 | 53 |
54 | 55 | 56 |

Session Library for OpenResty

57 |

lua-resty-session is a secure, and flexible session library for OpenResty

58 | 59 |

Modules

60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 |
resty.sessionSession library.
resty.session.dshmDistributed Shared Memory (DSHM) backend for session library
resty.session.fileFile storage backend for session library.
resty.session.file.threadFile storage backend worker thread module
resty.session.file.utilsFile storage utilities
resty.session.memcachedMemcached backend for session library
resty.session.mysqlMySQL / MariaDB backend for session library
resty.session.postgresPostgres backend for session library.
resty.session.redisRedis backend for session library
resty.session.redis.clusterRedis Cluster backend for session library
resty.session.redis.commonCommon Redis functions shared between Redis, 104 | Redis Cluster and Redis Sentinel implementations.
resty.session.redis.sentinelRedis Sentinel backend for session library
resty.session.shmShared Memory (SHM) backend for session library
resty.session.utilsCommon utilities for session library and storage backends
119 | 120 |
121 |
122 |
123 | generated by LDoc 1.5.0 124 | Last updated 2025-11-24 15:25:35 125 |
126 |
127 | 128 | 129 | -------------------------------------------------------------------------------- /docs/modules/resty.session.file-thread.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | Session Library for OpenResty Documentation 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 56 | 57 |
58 | 59 |

Module resty.session.file-thread

60 |

File storage backend worker thread module

61 |

62 |

63 | 64 | 65 |

Functions

66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 |
set (path, content)Store data in file.
get (path)Read data from a file.
delete (path)Delete a file.
80 | 81 |
82 |
83 | 84 | 85 |

Functions

86 | 87 |
88 |
89 | 90 | set (path, content) 91 |
92 |
93 | Store data in file. 94 | 95 | 96 |

Parameters:

97 |
    98 |
  • path 99 | string 100 |
     file path
    101 | 
    102 | 103 |
  • 104 |
  • content 105 | string 106 | file content 107 |
  • 108 |
109 | 110 |

Returns:

111 |
    112 |
  1. 113 | true or nil 114 | ok
  2. 115 |
  3. 116 | string 117 | error message
  4. 118 |
119 | 120 | 121 | 122 | 123 |
124 |
125 | 126 | get (path) 127 |
128 |
129 | Read data from a file. 130 | 131 | 132 |

Parameters:

133 |
    134 |
  • path 135 | string 136 | file to read 137 |
  • 138 |
139 | 140 |

Returns:

141 |
    142 |
  1. 143 | string or nil 144 | content
  2. 145 |
  3. 146 | string 147 | error message
  4. 148 |
149 | 150 | 151 | 152 | 153 |
154 |
155 | 156 | delete (path) 157 |
158 |
159 | Delete a file. 160 | 161 | 162 |

Parameters:

163 |
    164 |
  • path 165 | string 166 | file to read 167 |
  • 168 |
169 | 170 |

Returns:

171 |
    172 |
  1. 173 | string or nil 174 | ok
  2. 175 |
  3. 176 | string 177 | error message
  4. 178 |
179 | 180 | 181 | 182 | 183 |
184 |
185 | 186 | 187 |
188 |
189 |
190 | generated by LDoc 1.4.6 191 | Last updated 2022-12-23 14:06:58 192 |
193 |
194 | 195 | 196 | -------------------------------------------------------------------------------- /spec/01-utils_spec.lua: -------------------------------------------------------------------------------- 1 | local utils = require "resty.session.utils" 2 | 3 | 4 | local describe = describe 5 | local tostring = tostring 6 | local random = math.random 7 | local assert = assert 8 | local ipairs = ipairs 9 | local match = string.match 10 | local it = it 11 | 12 | 13 | describe("Testing utils", function() 14 | describe("pack/unpack data", function() 15 | local function range_max(bytes) 16 | if bytes == 8 then 17 | bytes = bytes - 1 18 | end 19 | return 2 ^ (8 * bytes) - 1 20 | end 21 | 22 | it("bpack and bunpack produce consistent output", function() 23 | for _, i in ipairs{ 1, 2, 4, 8 } do 24 | local input = random(1, range_max(i)) 25 | local packed = utils.bpack(i, input) 26 | local unpacked = utils.bunpack(i, packed) 27 | 28 | assert.equals(i, #packed) 29 | assert.equals(unpacked, input) 30 | end 31 | end) 32 | end) 33 | 34 | describe("trim", function() 35 | it("characters are trimmed as expected", function() 36 | local input = " \t\t\r\n\n\v\f\f\fyay\ntrim!\f\f\v\n\r\t " 37 | local expected_output = "yay\ntrim!" 38 | local output = utils.trim(input) 39 | 40 | assert.equals(output, expected_output) 41 | end) 42 | end) 43 | 44 | describe("encode/decode json", function() 45 | it("produce consistent output", function() 46 | local input = { 47 | foo = "bar", 48 | test = 123 49 | } 50 | local encoded = utils.encode_json(input) 51 | local decoded = utils.decode_json(encoded) 52 | 53 | assert.same(decoded, input) 54 | end) 55 | end) 56 | 57 | describe("encode/decode base64url", function() 58 | it("produce consistent output", function() 59 | local input = "<<>>?!" 60 | local encoded = utils.encode_base64url(input) 61 | 62 | assert.is_nil(match(encoded, "[/=+]")) 63 | local decoded = utils.decode_base64url(encoded) 64 | assert.equals(input, decoded) 65 | end) 66 | end) 67 | 68 | describe("deflate/inflate", function() 69 | it("produce consistent output", function() 70 | local input = utils.rand_bytes(1024) 71 | local deflated = utils.deflate(input) 72 | local inflated = utils.inflate(deflated) 73 | 74 | assert.equals(input, inflated) 75 | end) 76 | end) 77 | 78 | describe("Derive keys, encrypt and decrypt", function() 79 | local ikm = "some key material" 80 | local nonce = "0000000000000000" 81 | local usage = "encryption" 82 | local size = 44 83 | 84 | it("derives key of expected size with derive_hkdf_sha256", function() 85 | local k_bytes, err = utils.derive_hkdf_sha256(ikm, nonce, usage, size) 86 | assert.is_not_nil(k_bytes) 87 | assert.is_nil(err) 88 | assert.equals(size, #tostring(k_bytes)) 89 | end) 90 | 91 | it("derives key of expected size with derive_pbkdf2_hmac_sha256", function() 92 | local k_bytes, err = utils.derive_pbkdf2_hmac_sha256(ikm, nonce, usage, size) 93 | assert.is_not_nil(k_bytes) 94 | assert.is_nil(err) 95 | assert.equals(size, #tostring(k_bytes)) 96 | end) 97 | 98 | it("derives a valid key and calculates mac with it", function() 99 | local key, err, mac 100 | key, err = utils.derive_hmac_sha256_key(ikm, nonce) 101 | assert.is_not_nil(key) 102 | assert.is_nil(err) 103 | assert.equals(32, #key) 104 | 105 | mac, err = utils.hmac_sha256(key, "some message") 106 | assert.is_not_nil(mac) 107 | assert.is_nil(err) 108 | end) 109 | 110 | it("successfully derives key and iv; encryption and decryption succeeds", function() 111 | local data = { 112 | foo = "bar", 113 | tab = { "val" }, 114 | num = 123, 115 | } 116 | local aad = "some_aad" 117 | local input_data = utils.encode_json(data) 118 | 119 | local key, err, iv, ciphertext, tag, plaintext 120 | for _, slow in ipairs({ true, false }) do 121 | key, err, iv = utils.derive_aes_gcm_256_key_and_iv(ikm, nonce, slow) 122 | assert.is_not_nil(key) 123 | assert.is_not_nil(iv) 124 | assert.is_nil(err) 125 | 126 | ciphertext, err, tag = utils.encrypt_aes_256_gcm(key, iv, input_data, aad) 127 | assert.is_not_nil(ciphertext) 128 | assert.is_not_nil(tag) 129 | assert.is_nil(err) 130 | 131 | plaintext, err = utils.decrypt_aes_256_gcm(key, iv, ciphertext, aad, tag) 132 | assert.is_not_nil(plaintext) 133 | assert.is_nil(err) 134 | 135 | assert.equals(plaintext, input_data) 136 | end 137 | end) 138 | end) 139 | describe("load_storage", function() 140 | -- "dshm" is disabled as it currently cannot be checked by CI 141 | for _, strategy in ipairs({ "memcached", "mysql", "postgres", "redis" }) do 142 | it("respects pool parameters #" .. strategy, function() 143 | local storage = assert(utils.load_storage(strategy, { 144 | [strategy] = { 145 | pool = "doge", 146 | pool_size = 10, 147 | backlog = 20, 148 | }, 149 | })) 150 | 151 | assert.equal("doge", storage.options.pool) 152 | assert.equal(10, storage.options.pool_size) 153 | assert.equal(20, storage.options.backlog) 154 | end) 155 | end 156 | it("respects pool parameters #redis-sentinel", function() 157 | local storage = assert(utils.load_storage("redis", { 158 | redis = { 159 | pool = "doge", 160 | pool_size = 10, 161 | backlog = 20, 162 | sentinels = {}, 163 | }, 164 | })) 165 | 166 | assert.equal("doge", storage.connector.config.connection_options.pool) 167 | assert.equal(10, storage.connector.config.connection_options.pool_size) 168 | assert.equal(20, storage.connector.config.connection_options.backlog) 169 | end) 170 | it("respects pool parameters #redis-cluster", function() 171 | local storage = assert(utils.load_storage("redis", { 172 | redis = { 173 | pool = "doge", 174 | pool_size = 10, 175 | backlog = 20, 176 | nodes = {}, 177 | }, 178 | })) 179 | 180 | assert.equal("doge", storage.options.connect_opts.pool) 181 | assert.equal(10, storage.options.connect_opts.pool_size) 182 | assert.equal(20, storage.options.connect_opts.backlog) 183 | end) 184 | end) 185 | end) 186 | -------------------------------------------------------------------------------- /lib/resty/session/file/thread.lua: -------------------------------------------------------------------------------- 1 | --- 2 | -- File storage backend worker thread module 3 | -- 4 | -- @module resty.session.file.thread 5 | 6 | 7 | local file_utils = require "resty.session.file.utils" 8 | local utils = require "resty.session.utils" 9 | 10 | 11 | local get_modification = file_utils.get_modification 12 | local meta_get_key = file_utils.meta_get_key 13 | local file_create = file_utils.file_create 14 | local file_append = file_utils.file_append 15 | local file_delete = file_utils.file_delete 16 | local file_touch = file_utils.file_touch 17 | local file_read = file_utils.file_read 18 | local get_path = file_utils.get_path 19 | local cleanup = file_utils.cleanup 20 | 21 | 22 | local meta_get_latest = utils.meta_get_latest 23 | local meta_get_value = utils.meta_get_value 24 | 25 | 26 | local max = math.max 27 | 28 | 29 | local function update_meta(path, prefix, suffix, name, meta_key, key, exp) 30 | local meta_value = meta_get_value(key, exp) 31 | if not meta_value then 32 | return 33 | end 34 | 35 | local file_path = get_path(path, prefix, suffix, name, meta_key) 36 | file_append(file_path, meta_value) 37 | local current_expiry = get_modification(file_path) or 0 38 | 39 | local new_expiry = max(current_expiry, exp) 40 | file_touch(file_path, new_expiry) 41 | end 42 | 43 | 44 | --- 45 | -- Store session data. 46 | -- 47 | -- @function module.set 48 | -- @tparam string path the path where sessions are stored 49 | -- @tparam string prefix the prefix for session files 50 | -- @tparam string suffix the suffix for session files 51 | -- @tparam string name the cookie name 52 | -- @tparam string key session key 53 | -- @tparam string value session value 54 | -- @tparam number ttl session ttl 55 | -- @tparam number current_time current time 56 | -- @tparam[opt] string old_key old session id 57 | -- @tparam string stale_ttl stale ttl 58 | -- @tparam[opt] table metadata table of metadata 59 | -- @treturn table|nil session metadata 60 | -- @treturn string error message 61 | local function set(path, prefix, suffix, name, key, value, ttl, 62 | current_time, old_key, stale_ttl, metadata) 63 | local file_path = get_path(path, prefix, suffix, name, key) 64 | if not metadata and not old_key then 65 | local ok, err = file_create(file_path, value) 66 | 67 | if ok and current_time and ttl then 68 | file_touch(file_path, current_time + ttl) 69 | end 70 | 71 | cleanup(path, prefix, suffix, name, current_time) 72 | 73 | return ok, err 74 | end 75 | 76 | local old_ttl, old_file_path 77 | if old_key then 78 | old_file_path = get_path(path, prefix, suffix, name, old_key) 79 | local exp = get_modification(old_file_path) 80 | if exp then 81 | old_ttl = exp - current_time 82 | end 83 | end 84 | 85 | local ok, err = file_create(file_path, value) 86 | if ok and current_time and ttl then 87 | file_touch(file_path, current_time + ttl) 88 | end 89 | 90 | if old_file_path and (not old_ttl or old_ttl > stale_ttl) then 91 | file_touch(old_file_path, current_time + stale_ttl) 92 | end 93 | 94 | if metadata then 95 | local audiences = metadata.audiences 96 | local subjects = metadata.subjects 97 | local count = #audiences 98 | for i = 1, count do 99 | local meta_key = meta_get_key(audiences[i], subjects[i]) 100 | update_meta(path, prefix, suffix, name, meta_key, key, current_time + ttl) 101 | 102 | if old_key then 103 | update_meta(path, prefix, suffix, name, meta_key, old_key, 0) 104 | end 105 | end 106 | end 107 | 108 | cleanup(path, prefix, suffix, name, current_time) 109 | 110 | return ok, err 111 | end 112 | 113 | 114 | --- 115 | -- Retrieve session data. 116 | -- 117 | -- @function module.GET 118 | -- @tparam string path the path where sessions are stored 119 | -- @tparam string prefix the prefix for session files 120 | -- @tparam string suffix the suffix for session files 121 | -- @tparam string name cookie name 122 | -- @tparam string key session key 123 | -- @treturn string|nil session data 124 | -- @treturn string error message 125 | local function get(path, prefix, suffix, name, key, current_time) 126 | local file_path = get_path(path, prefix, suffix, name, key) 127 | 128 | -- TODO: do we want to check expiry here? 129 | -- The cookie header already has the info and has a MAC too. 130 | local exp = get_modification(file_path) 131 | if exp and exp < current_time then 132 | return nil, "expired" 133 | end 134 | 135 | return file_read(file_path) 136 | end 137 | 138 | 139 | --- 140 | -- Delete session data. 141 | -- 142 | -- @function module.delete 143 | -- @tparam string path the path where sessions are stored 144 | -- @tparam string prefix the prefix for session files 145 | -- @tparam string suffix the suffix for session files 146 | -- @tparam string name the cookie name 147 | -- @tparam string key session key 148 | -- @tparam number current_time current time 149 | -- @treturn table|nil session metadata 150 | -- @treturn string error message 151 | local function delete(path, prefix, suffix, name, key, current_time, metadata) 152 | local file_path = get_path(path, prefix, suffix, name, key) 153 | file_delete(file_path) 154 | 155 | if metadata then 156 | local audiences = metadata.audiences 157 | local subjects = metadata.subjects 158 | local count = #audiences 159 | for i = 1, count do 160 | local meta_key = meta_get_key(audiences[i], subjects[i]) 161 | update_meta(path, prefix, suffix, name, meta_key, key, 0) 162 | end 163 | end 164 | 165 | cleanup(path, prefix, suffix, name, current_time) 166 | 167 | return true 168 | end 169 | 170 | 171 | --- 172 | -- Read session metadata. 173 | -- 174 | -- @function module.read_metadata 175 | -- @tparam string path the path where sessions are stored 176 | -- @tparam string prefix the prefix for session files 177 | -- @tparam string suffix the suffix for session files 178 | -- @tparam string name the cookie name 179 | -- @tparam string audience session audience 180 | -- @tparam string subject session subject 181 | -- @tparam number current_time current time 182 | -- @treturn table|nil session metadata 183 | -- @treturn string error message 184 | local function read_metadata(path, prefix, suffix, name, audience, subject, current_time) 185 | local meta_key = meta_get_key(audience, subject) 186 | local file_path = get_path(path, prefix, suffix, name, meta_key) 187 | local metadata, err = file_read(file_path) 188 | if not metadata then 189 | return nil, err 190 | end 191 | 192 | return meta_get_latest(metadata, current_time) 193 | end 194 | 195 | 196 | return { 197 | set = set, 198 | get = get, 199 | delete = delete, 200 | read_metadata = read_metadata, 201 | } 202 | -------------------------------------------------------------------------------- /spec/04-storage-1_spec.lua: -------------------------------------------------------------------------------- 1 | --- 2 | -- Ensure to keep the tests consistent with those in 05-storage-1_spec.lua 3 | 4 | 5 | local utils = require "resty.session.utils" 6 | 7 | 8 | local before_each = before_each 9 | local after_each = after_each 10 | local lazy_setup = lazy_setup 11 | local describe = describe 12 | local ipairs = ipairs 13 | local assert = assert 14 | local sleep = ngx.sleep 15 | local time = ngx.time 16 | local it = it 17 | 18 | 19 | local storage_configs = { 20 | file = { 21 | suffix = "session", 22 | }, 23 | shm = { 24 | prefix = "sessions", 25 | connect_timeout = 10000, 26 | send_timeout = 10000, 27 | read_timeout = 10000, 28 | }, 29 | redis = { 30 | prefix = "sessions", 31 | password = "password", 32 | }, 33 | memcached = { 34 | prefix = "sessions", 35 | connect_timeout = 10000, 36 | send_timeout = 10000, 37 | read_timeout = 10000, 38 | }, 39 | } 40 | 41 | 42 | for _, st in ipairs({ 43 | "file", 44 | "shm", 45 | "redis", 46 | "memcached", 47 | }) do 48 | describe("Storage tests 1", function() 49 | local current_time 50 | local storage 51 | local long_ttl = 60 52 | local short_ttl = 2 53 | local key = "test_key_1iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii" 54 | local key1 = "test_key_2iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii" 55 | local key2 = "test_key_3iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii" 56 | local old_key = "old_key_iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii" 57 | local name = "test_name" 58 | local value = "test_value" 59 | 60 | lazy_setup(function() 61 | local conf = { 62 | remember = true, 63 | store_metadata = true, 64 | } 65 | conf[st] = storage_configs[st] 66 | storage = utils.load_storage(st, conf) 67 | assert.is_not_nil(storage) 68 | end) 69 | 70 | before_each(function() 71 | current_time = time() 72 | end) 73 | 74 | describe("[#" .. st .. "] storage: SET + GET", function() 75 | local audiences = { "foo", "bar" } 76 | local subjects = { "john", "jane" } 77 | 78 | local metadata = { 79 | audiences = audiences, 80 | subjects = subjects, 81 | } 82 | 83 | after_each(function() 84 | storage:delete(name, key, current_time, metadata) 85 | storage:delete(name, key1, current_time, metadata) 86 | storage:delete(name, key2, current_time, metadata) 87 | end) 88 | 89 | it("SET: simple set does not return errors, GET fetches value correctly", function() 90 | local ok = storage:set(name, key, value, long_ttl, current_time) 91 | assert.is_not_nil(ok) 92 | 93 | local v, err = storage:get(name, key, current_time) 94 | assert.is_not_nil(v) 95 | assert.is_nil(err) 96 | assert.equals(v, value) 97 | end) 98 | 99 | it("SET: with metadata and remember works correctly", function() 100 | local ok = storage:set(name, key, value, long_ttl, current_time, nil, nil, metadata) 101 | assert.is_not_nil(ok) 102 | 103 | sleep(1) 104 | 105 | local v, err = storage:get(name, key, time()) 106 | assert.is_not_nil(v) 107 | assert.is_nil(err) 108 | assert.equals(v, value) 109 | end) 110 | 111 | it("SET: with metadata (long ttl) correctly appends metadata to collection", function() 112 | local ok = storage:set(name, key, value, long_ttl, current_time, nil, nil, metadata) 113 | ok = ok and storage:set(name, key1, value, long_ttl, current_time, nil, nil, metadata) 114 | ok = ok and storage:set(name, key2, value, long_ttl, current_time, nil, nil, metadata) 115 | assert.is_not_nil(ok) 116 | 117 | sleep(1) 118 | 119 | for i = 1, #audiences do 120 | local meta_values = storage:read_metadata(name, audiences[i], subjects[i], time()) 121 | assert.is_not_nil(meta_values) 122 | assert.truthy(meta_values[key ]) 123 | assert.truthy(meta_values[key1]) 124 | assert.truthy(meta_values[key2]) 125 | end 126 | end) 127 | 128 | it("SET: with metadata (short ttl) correctly expires metadata", function() 129 | local ok = storage:set(name, key, value, short_ttl, current_time, nil, nil, metadata) 130 | 131 | sleep(short_ttl + 1) 132 | 133 | ok = ok and storage:set(name, key1, value, long_ttl, time(), nil, nil, metadata) 134 | assert.is_not_nil(ok) 135 | 136 | sleep(1) 137 | 138 | for i = 1, #audiences do 139 | local meta_values = storage:read_metadata(name, audiences[i], subjects[i], time()) 140 | assert.falsy(meta_values[key]) 141 | assert.truthy(meta_values[key1]) 142 | end 143 | end) 144 | 145 | it("SET: with old_key correctly applies stale ttl on old key", function() 146 | local stale_ttl = 1 147 | 148 | local ok = storage:set(name, old_key, value, long_ttl, current_time) 149 | assert.is_not_nil(ok) 150 | 151 | ok = storage:set(name, key, value, long_ttl, current_time, old_key, stale_ttl, nil) 152 | assert.is_not_nil(ok) 153 | 154 | sleep(3) 155 | 156 | local v = storage:get(name, old_key, time()) 157 | assert.is_nil(v) 158 | end) 159 | 160 | it("SET: ttl works as expected", function() 161 | local ok = storage:set(name, key, value, short_ttl, current_time) 162 | assert.is_not_nil(ok) 163 | 164 | sleep(3) 165 | 166 | local v = storage:get(name, key, time()) 167 | assert.is_nil(v) 168 | end) 169 | end) 170 | 171 | describe("[#" .. st .. "] storage: DELETE", function() 172 | local audiences = { "foo" } 173 | local subjects = { "john" } 174 | 175 | local metadata = { 176 | audiences = audiences, 177 | subjects = subjects, 178 | } 179 | 180 | it("deleted file is really deleted", function() 181 | local ok = storage:set(name, key, value, short_ttl, current_time) 182 | assert.is_not_nil(ok) 183 | 184 | storage:delete(name, key, current_time, nil) 185 | 186 | local v = storage:get(name, key, current_time) 187 | assert.is_nil(v) 188 | end) 189 | 190 | it("with metadata correctly deletes metadata collection", function() 191 | local ok = storage:set(name, key1, value, long_ttl, current_time, nil, nil, metadata) 192 | assert.is_not_nil(ok) 193 | 194 | sleep(1) 195 | 196 | for i = 1, #audiences do 197 | local meta_values = storage:read_metadata(name, audiences[i], subjects[i], time()) 198 | assert.truthy(meta_values[key1]) 199 | ok = storage:delete(name, key1, time(), metadata) 200 | assert.is_not_nil(ok) 201 | 202 | sleep(2) 203 | 204 | meta_values = storage:read_metadata(name, audiences[i], subjects[i], time()) or {} 205 | assert.falsy(meta_values[key1]) 206 | end 207 | end) 208 | end) 209 | end) 210 | end 211 | -------------------------------------------------------------------------------- /lib/resty/session/file/utils.lua: -------------------------------------------------------------------------------- 1 | --- 2 | -- File storage utilities 3 | -- 4 | -- @module resty.session.file.utils 5 | 6 | 7 | local lfs = require "lfs" 8 | 9 | 10 | local attributes = lfs.attributes 11 | local touch = lfs.touch 12 | local dir = lfs.dir 13 | 14 | 15 | 16 | local file_delete = os.remove 17 | local random = math.random 18 | local pcall = pcall 19 | local open = io.open 20 | local fmt = string.format 21 | 22 | 23 | local CLEANUP_PROBABILITY = 0.0005 -- 1 / 2000 24 | 25 | 26 | local run_worker_thread do 27 | run_worker_thread = ngx.run_worker_thread -- luacheck: ignore 28 | if not run_worker_thread then 29 | local require = require 30 | run_worker_thread = function(_, module, func, ...) 31 | local m = require(module) 32 | return pcall(m[func], ...) 33 | end 34 | end 35 | end 36 | 37 | 38 | local function file_touch(path, mtime) 39 | return touch(path, nil, mtime) 40 | end 41 | 42 | 43 | --- 44 | -- Store data in file. 45 | -- 46 | -- @function file_create 47 | -- @tparam string path file path 48 | -- @tparam string content file content 49 | -- @treturn true|nil ok 50 | -- @treturn string error message 51 | local function file_create(path, content) 52 | local file, err = open(path, "wb") 53 | if not file then 54 | return nil, err 55 | end 56 | 57 | local ok, err = file:write(content) 58 | 59 | file:close() 60 | 61 | if not ok then 62 | file_delete(path) 63 | return nil, err 64 | end 65 | 66 | return true 67 | end 68 | 69 | 70 | --- 71 | -- Append data in file. 72 | -- 73 | -- @function file_append 74 | -- @tparam string path file path 75 | -- @tparam string data file data 76 | -- @treturn true|nil ok 77 | -- @treturn string error message 78 | local function file_append(path, data) 79 | local file, err = open(path, "a") 80 | if not file then 81 | return nil, err 82 | end 83 | 84 | local ok, err = file:write(data) 85 | 86 | file:close() 87 | 88 | if not ok then 89 | file_delete(path) 90 | return nil, err 91 | end 92 | 93 | return true 94 | end 95 | 96 | 97 | --- 98 | -- Read data from a file. 99 | -- 100 | -- @function file_read 101 | -- @tparam string path file to read 102 | -- @treturn string|nil content 103 | -- @treturn string error message 104 | local function file_read(path) 105 | local file, err = open(path, "rb") 106 | if not file then 107 | return nil, err 108 | end 109 | 110 | local content, err = file:read("*a") 111 | 112 | file:close() 113 | 114 | if not content then 115 | return nil, err 116 | end 117 | 118 | return content 119 | end 120 | 121 | 122 | --- 123 | -- Generate the path for a file to be stored at. 124 | -- 125 | -- @tparam string path the path where sessions are stored 126 | -- @tparam string prefix the prefix for session files 127 | -- @tparam string suffix the suffix for session files 128 | -- @tparam string name the cookie name 129 | -- @tparam string key session key 130 | -- @treturn string path 131 | local function get_path(path, prefix, suffix, name, key) 132 | if prefix and suffix then 133 | return fmt("%s%s_%s_%s.%s", path, prefix, name, key, suffix) 134 | elseif prefix then 135 | return fmt("%s%s_%s_%s", path, prefix, name, key) 136 | elseif suffix then 137 | return fmt("%s%s_%s.%s", path, name, key, suffix) 138 | else 139 | return fmt("%s%s_%s", path, name, key) 140 | end 141 | end 142 | 143 | 144 | --- 145 | -- Get the value modification time of a file. 146 | -- 147 | -- @function utils.get_modification 148 | -- @tparam string path the path to the file 149 | local function get_modification(path) 150 | local attr = attributes(path) 151 | if not attr or attr.mode ~= "file" then 152 | return 153 | end 154 | 155 | return attr.modification or nil 156 | end 157 | 158 | 159 | --- 160 | -- Given an audience and a subject, generate a metadata key. 161 | -- 162 | -- @function utils.meta_get_key 163 | -- @tparam string audience session audience 164 | -- @tparam string subject session subject 165 | -- @treturn string metadata key 166 | local function meta_get_key(audience, subject) 167 | return fmt("%s:%s", audience, subject) 168 | end 169 | 170 | 171 | --- 172 | -- Validate a file name. 173 | -- Run a few checks to try to determine if the file is managed by this library 174 | -- 175 | -- @function utils.validate_file_name 176 | -- @tparam string prefix the prefix for session files 177 | -- @tparam string suffix the suffix for session files 178 | -- @tparam string name cookie name 179 | -- @tparam string filename the name of the file 180 | -- @treturn true|false whether the file is managed by the library or not 181 | local validate_file_name do 182 | local byte = string.byte 183 | local sub = string.sub 184 | local find = string.find 185 | 186 | local UNDERSCORE = byte("_") 187 | local DOT = byte(".") 188 | 189 | validate_file_name = function(prefix, suffix, name, filename) 190 | if filename == "." or filename == ".." then 191 | return false 192 | end 193 | 194 | local plen = 0 195 | if prefix then 196 | plen = #prefix 197 | if byte(filename, plen + 1) ~= UNDERSCORE or 198 | (plen > 0 and sub(filename, 1, plen) ~= prefix) then 199 | return false 200 | end 201 | end 202 | 203 | local slen = 0 204 | if suffix then 205 | slen = #suffix 206 | 207 | if byte(filename, -1 - slen) ~= DOT or 208 | (slen > 0 and sub(filename, -slen) ~= suffix) 209 | then 210 | return false 211 | end 212 | end 213 | 214 | local nlen = #name 215 | local name_start = plen == 0 and 1 or plen + 2 216 | local name_end = name_start + nlen - 1 217 | 218 | if byte(filename, name_end + 1) ~= UNDERSCORE or 219 | sub(filename, name_start, name_end) ~= name 220 | then 221 | return false 222 | end 223 | 224 | local rest 225 | if slen == 0 then 226 | rest = sub(filename, name_end + 2) 227 | else 228 | rest = sub(filename, name_end + 2, -2 - slen) 229 | end 230 | 231 | local rlen = #rest 232 | if rlen < 3 then 233 | return false 234 | end 235 | 236 | if rlen ~= 43 then 237 | local colon_pos = find(rest, ":", 2, true) 238 | if not colon_pos or colon_pos == 43 then 239 | return false 240 | end 241 | end 242 | 243 | return true 244 | end 245 | end 246 | 247 | 248 | --- 249 | -- Clean up expired session and metadata files. 250 | -- 251 | -- @function utils.cleanup 252 | -- @tparam string path the path where sessions are stored 253 | -- @tparam string prefix the prefix for session files 254 | -- @tparam string suffix the suffix for session files 255 | -- @tparam string name cookie name 256 | -- @tparam number current_time current time 257 | -- @treturn true|false whether clean up completed 258 | local function cleanup(path, prefix, suffix, name, current_time) 259 | if random() > CLEANUP_PROBABILITY then 260 | return false 261 | end 262 | 263 | local deleted = 0 264 | 265 | for file in dir(path) do 266 | if validate_file_name(prefix, suffix, name, file) then 267 | local exp = get_modification(path .. file) 268 | if exp and exp < current_time then 269 | file_delete(path .. file) 270 | deleted = deleted + 1 271 | end 272 | end 273 | end 274 | return true 275 | end 276 | 277 | 278 | return { 279 | validate_file_name = validate_file_name, 280 | run_worker_thread = run_worker_thread, 281 | get_modification = get_modification, 282 | meta_get_key = meta_get_key, 283 | file_create = file_create, 284 | file_append = file_append, 285 | file_delete = file_delete, 286 | file_touch = file_touch, 287 | file_read = file_read, 288 | get_path = get_path, 289 | cleanup = cleanup, 290 | } 291 | -------------------------------------------------------------------------------- /spec/05-storage-2_spec.lua: -------------------------------------------------------------------------------- 1 | --- 2 | -- For now these tests don't run on CI. 3 | -- Ensure to keep the tests consistent with those in 04-storage-1_spec.lua 4 | 5 | 6 | local utils = require "resty.session.utils" 7 | 8 | 9 | local before_each = before_each 10 | local after_each = after_each 11 | local lazy_setup = lazy_setup 12 | local describe = describe 13 | local ipairs = ipairs 14 | local assert = assert 15 | local sleep = ngx.sleep 16 | local time = ngx.time 17 | local it = it 18 | 19 | 20 | local storage_configs = { 21 | mysql = { 22 | username = "root", 23 | password = "password", 24 | database = "test", 25 | }, 26 | postgres = { 27 | username = "postgres", 28 | password = "password", 29 | database = "test", 30 | }, 31 | redis_sentinel = { 32 | prefix = "sessions", 33 | password = "password", 34 | sentinels = { 35 | { host = "127.0.0.1", port = "26379" } 36 | }, 37 | connect_timeout = 10000, 38 | send_timeout = 10000, 39 | read_timeout = 10000, 40 | }, 41 | redis_cluster = { 42 | password = "password", 43 | nodes = { 44 | { ip = "127.0.0.1", port = "6380" } 45 | }, 46 | name = "somecluster", 47 | lock_zone = "sessions", 48 | connect_timeout = 10000, 49 | send_timeout = 10000, 50 | read_timeout = 10000, 51 | }, 52 | dshm = { 53 | prefix = "sessions", 54 | connect_timeout = 10000, 55 | send_timeout = 10000, 56 | read_timeout = 10000, 57 | }, 58 | } 59 | 60 | 61 | local function storage_type(ty) 62 | if ty == "redis_cluster" or ty == "redis_sentinel" then 63 | return "redis" 64 | end 65 | return ty 66 | end 67 | 68 | 69 | for _, st in ipairs({ 70 | "mysql", 71 | "postgres", 72 | "redis_cluster", 73 | "redis_sentinel", 74 | "dshm" 75 | }) do 76 | describe("Storage tests 2 #noci", function() 77 | local current_time 78 | local storage 79 | local long_ttl = 60 80 | local short_ttl = 2 81 | local key = "test_key_1iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii" 82 | local key1 = "test_key_2iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii" 83 | local key2 = "test_key_3iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii" 84 | local old_key = "old_key_iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii" 85 | local name = "test_name" 86 | local value = "test_value" 87 | 88 | lazy_setup(function() 89 | local conf = { 90 | remember = true, 91 | store_metadata = true, 92 | } 93 | conf[storage_type(st)] = storage_configs[st] 94 | storage = utils.load_storage(storage_type(st), conf) 95 | assert.is_not_nil(storage) 96 | end) 97 | 98 | before_each(function() 99 | current_time = time() 100 | end) 101 | 102 | describe("[#" .. st .. "] storage: SET + GET", function() 103 | local audiences = { "foo", "bar" } 104 | local subjects = { "john", "jane" } 105 | 106 | local metadata = { 107 | audiences = audiences, 108 | subjects = subjects, 109 | } 110 | 111 | after_each(function() 112 | current_time = time() 113 | storage:delete(name, key, current_time, metadata) 114 | storage:delete(name, key1, current_time, metadata) 115 | storage:delete(name, key2, current_time, metadata) 116 | end) 117 | 118 | it("SET: simple set does not return errors, GET fetches value correctly", function() 119 | local ok = storage:set(name, key, value, long_ttl, current_time) 120 | assert.is_not_nil(ok) 121 | 122 | local v, err = storage:get(name, key, current_time) 123 | assert.is_not_nil(v) 124 | assert.is_nil(err) 125 | assert.equals(v, value) 126 | end) 127 | 128 | it("SET: with metadata and remember works correctly", function() 129 | local ok = storage:set(name, key, value, long_ttl, time(), nil, nil, metadata) 130 | assert.is_not_nil(ok) 131 | 132 | sleep(1) 133 | 134 | local v, err = storage:get(name, key, time()) 135 | assert.is_not_nil(v) 136 | assert.is_nil(err) 137 | assert.equals(v, value) 138 | end) 139 | 140 | it("SET: with metadata (long ttl) correctly appends metadata to collection", function() 141 | local ok = storage:set(name, key, value, long_ttl, current_time, nil, nil, metadata) 142 | ok = ok and storage:set(name, key1, value, long_ttl, current_time, nil, nil, metadata) 143 | ok = ok and storage:set(name, key2, value, long_ttl, current_time, nil, nil, metadata) 144 | assert.is_not_nil(ok) 145 | 146 | sleep(1) 147 | 148 | for i = 1, #audiences do 149 | local meta_values = storage:read_metadata(name, audiences[i], subjects[i], time()) 150 | assert.is_not_nil(meta_values) 151 | assert.truthy(meta_values[key ]) 152 | assert.truthy(meta_values[key1]) 153 | assert.truthy(meta_values[key2]) 154 | end 155 | end) 156 | 157 | it("SET: with metadata (short ttl) correctly expires metadata", function() 158 | local ok = storage:set(name, key, value, short_ttl, current_time, nil, nil, metadata) 159 | 160 | sleep(short_ttl + 1) 161 | 162 | ok = ok and storage:set(name, key1, value, long_ttl, time(), nil, nil, metadata) 163 | assert.is_not_nil(ok) 164 | 165 | sleep(1) 166 | 167 | for i = 1, #audiences do 168 | local meta_values = storage:read_metadata(name, audiences[i], subjects[i], time()) 169 | assert.falsy(meta_values[key]) 170 | assert.truthy(meta_values[key1]) 171 | end 172 | end) 173 | 174 | it("SET: with old_key correctly applies stale ttl on old key", function() 175 | local stale_ttl = 1 176 | 177 | local ok = storage:set(name, old_key, value, long_ttl, current_time) 178 | assert.is_not_nil(ok) 179 | 180 | ok = storage:set(name, key, value, long_ttl, current_time, old_key, stale_ttl, nil) 181 | assert.is_not_nil(ok) 182 | 183 | sleep(3) 184 | 185 | local v = storage:get(name, old_key, time()) 186 | assert.is_nil(v) 187 | end) 188 | 189 | it("SET: ttl works as expected", function() 190 | local ok = storage:set(name, key, value, short_ttl, current_time) 191 | assert.is_not_nil(ok) 192 | 193 | sleep(3) 194 | 195 | local v = storage:get(name, key, time()) 196 | assert.is_nil(v) 197 | end) 198 | end) 199 | 200 | describe("[#" .. st .. "] storage: DELETE", function() 201 | local audiences = { "foo" } 202 | local subjects = { "john" } 203 | 204 | local metadata = { 205 | audiences = audiences, 206 | subjects = subjects, 207 | } 208 | 209 | it("deleted file is really deleted", function() 210 | local ok = storage:set(name, key, value, short_ttl, current_time) 211 | assert.is_not_nil(ok) 212 | 213 | storage:delete(name, key, current_time) 214 | 215 | local v = storage:get(name, key, current_time) 216 | assert.is_nil(v) 217 | end) 218 | 219 | it("with metadata correctly deletes metadata collection", function() 220 | local ok = storage:set(name, key1, value, long_ttl, current_time, nil, nil, metadata) 221 | assert.is_not_nil(ok) 222 | 223 | sleep(1) 224 | 225 | for i = 1, #audiences do 226 | local meta_values = storage:read_metadata(name, audiences[i], subjects[i], time()) 227 | assert.truthy(meta_values[key1]) 228 | 229 | ok = storage:delete(name, key1, time(), metadata) 230 | assert.is_not_nil(ok) 231 | 232 | sleep(2) 233 | 234 | meta_values = storage:read_metadata(name, audiences[i], subjects[i], time()) or {} 235 | assert.falsy(meta_values[key1]) 236 | end 237 | end) 238 | end) 239 | end) 240 | end 241 | -------------------------------------------------------------------------------- /docs/ldoc.css: -------------------------------------------------------------------------------- 1 | /* BEGIN RESET 2 | 3 | Copyright (c) 2010, Yahoo! Inc. All rights reserved. 4 | Code licensed under the BSD License: 5 | http://developer.yahoo.com/yui/license.html 6 | version: 2.8.2r1 7 | */ 8 | html { 9 | color: #000; 10 | background: #FFF; 11 | } 12 | body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,p,blockquote,th,td { 13 | margin: 0; 14 | padding: 0; 15 | } 16 | table { 17 | border-collapse: collapse; 18 | border-spacing: 0; 19 | } 20 | fieldset,img { 21 | border: 0; 22 | } 23 | address,caption,cite,code,dfn,em,strong,th,var,optgroup { 24 | font-style: inherit; 25 | font-weight: inherit; 26 | } 27 | del,ins { 28 | text-decoration: none; 29 | } 30 | li { 31 | margin-left: 20px; 32 | } 33 | caption,th { 34 | text-align: left; 35 | } 36 | h1,h2,h3,h4,h5,h6 { 37 | font-size: 100%; 38 | font-weight: bold; 39 | } 40 | q:before,q:after { 41 | content: ''; 42 | } 43 | abbr,acronym { 44 | border: 0; 45 | font-variant: normal; 46 | } 47 | sup { 48 | vertical-align: baseline; 49 | } 50 | sub { 51 | vertical-align: baseline; 52 | } 53 | legend { 54 | color: #000; 55 | } 56 | input,button,textarea,select,optgroup,option { 57 | font-family: inherit; 58 | font-size: inherit; 59 | font-style: inherit; 60 | font-weight: inherit; 61 | } 62 | input,button,textarea,select {*font-size:100%; 63 | } 64 | /* END RESET */ 65 | 66 | body { 67 | margin-left: 1em; 68 | margin-right: 1em; 69 | font-family: arial, helvetica, geneva, sans-serif; 70 | background-color: #ffffff; margin: 0px; 71 | } 72 | 73 | code, tt { font-family: monospace; font-size: 1.1em; } 74 | span.parameter { font-family:monospace; } 75 | span.parameter:after { content:":"; } 76 | span.types:before { content:"("; } 77 | span.types:after { content:")"; } 78 | .type { font-weight: bold; font-style:italic } 79 | 80 | body, p, td, th { font-size: .95em; line-height: 1.2em;} 81 | 82 | p, ul { margin: 10px 0 0 0px;} 83 | 84 | strong { font-weight: bold;} 85 | 86 | em { font-style: italic;} 87 | 88 | h1 { 89 | font-size: 1.5em; 90 | margin: 20px 0 20px 0; 91 | } 92 | h2, h3, h4 { margin: 15px 0 10px 0; } 93 | h2 { font-size: 1.25em; } 94 | h3 { font-size: 1.15em; } 95 | h4 { font-size: 1.06em; } 96 | 97 | a:link { font-weight: bold; color: #004080; text-decoration: none; } 98 | a:visited { font-weight: bold; color: #006699; text-decoration: none; } 99 | a:link:hover { text-decoration: underline; } 100 | 101 | hr { 102 | color:#cccccc; 103 | background: #00007f; 104 | height: 1px; 105 | } 106 | 107 | blockquote { margin-left: 3em; } 108 | 109 | ul { list-style-type: disc; } 110 | 111 | p.name { 112 | font-family: "Andale Mono", monospace; 113 | padding-top: 1em; 114 | } 115 | 116 | pre { 117 | background-color: rgb(245, 245, 245); 118 | border: 1px solid #C0C0C0; /* silver */ 119 | padding: 10px; 120 | margin: 10px 0 10px 0; 121 | overflow: auto; 122 | font-family: "Andale Mono", monospace; 123 | } 124 | 125 | pre.example { 126 | font-size: .85em; 127 | } 128 | 129 | table.index { border: 1px #00007f; } 130 | table.index td { text-align: left; vertical-align: top; } 131 | 132 | #container { 133 | margin-left: 1em; 134 | margin-right: 1em; 135 | background-color: #f0f0f0; 136 | } 137 | 138 | #product { 139 | text-align: center; 140 | border-bottom: 1px solid #cccccc; 141 | background-color: #ffffff; 142 | } 143 | 144 | #product big { 145 | font-size: 2em; 146 | } 147 | 148 | #main { 149 | background-color: #f0f0f0; 150 | border-left: 2px solid #cccccc; 151 | } 152 | 153 | #navigation { 154 | float: left; 155 | width: 14em; 156 | vertical-align: top; 157 | background-color: #f0f0f0; 158 | overflow: visible; 159 | } 160 | 161 | #navigation h2 { 162 | background-color:#e7e7e7; 163 | font-size:1.1em; 164 | color:#000000; 165 | text-align: left; 166 | padding:0.2em; 167 | border-top:1px solid #dddddd; 168 | border-bottom:1px solid #dddddd; 169 | } 170 | 171 | #navigation ul 172 | { 173 | font-size:1em; 174 | list-style-type: none; 175 | margin: 1px 1px 10px 1px; 176 | } 177 | 178 | #navigation li { 179 | text-indent: -1em; 180 | display: block; 181 | margin: 3px 0px 0px 22px; 182 | } 183 | 184 | #navigation li li a { 185 | margin: 0px 3px 0px -1em; 186 | } 187 | 188 | #content { 189 | margin-left: 14em; 190 | padding: 1em; 191 | width: 700px; 192 | border-left: 2px solid #cccccc; 193 | border-right: 2px solid #cccccc; 194 | background-color: #ffffff; 195 | } 196 | 197 | #about { 198 | clear: both; 199 | padding: 5px; 200 | border-top: 2px solid #cccccc; 201 | background-color: #ffffff; 202 | } 203 | 204 | @media print { 205 | body { 206 | font: 12pt "Times New Roman", "TimeNR", Times, serif; 207 | } 208 | a { font-weight: bold; color: #004080; text-decoration: underline; } 209 | 210 | #main { 211 | background-color: #ffffff; 212 | border-left: 0px; 213 | } 214 | 215 | #container { 216 | margin-left: 2%; 217 | margin-right: 2%; 218 | background-color: #ffffff; 219 | } 220 | 221 | #content { 222 | padding: 1em; 223 | background-color: #ffffff; 224 | } 225 | 226 | #navigation { 227 | display: none; 228 | } 229 | pre.example { 230 | font-family: "Andale Mono", monospace; 231 | font-size: 10pt; 232 | page-break-inside: avoid; 233 | } 234 | } 235 | 236 | table.module_list { 237 | border-width: 1px; 238 | border-style: solid; 239 | border-color: #cccccc; 240 | border-collapse: collapse; 241 | } 242 | table.module_list td { 243 | border-width: 1px; 244 | padding: 3px; 245 | border-style: solid; 246 | border-color: #cccccc; 247 | } 248 | table.module_list td.name { background-color: #f0f0f0; min-width: 200px; } 249 | table.module_list td.summary { width: 100%; } 250 | 251 | 252 | table.function_list { 253 | border-width: 1px; 254 | border-style: solid; 255 | border-color: #cccccc; 256 | border-collapse: collapse; 257 | } 258 | table.function_list td { 259 | border-width: 1px; 260 | padding: 3px; 261 | border-style: solid; 262 | border-color: #cccccc; 263 | } 264 | table.function_list td.name { background-color: #f0f0f0; min-width: 200px; } 265 | table.function_list td.summary { width: 100%; } 266 | 267 | ul.nowrap { 268 | overflow:auto; 269 | white-space:nowrap; 270 | } 271 | 272 | dl.table dt, dl.function dt {border-top: 1px solid #ccc; padding-top: 1em;} 273 | dl.table dd, dl.function dd {padding-bottom: 1em; margin: 10px 0 0 20px;} 274 | dl.table h3, dl.function h3 {font-size: .95em;} 275 | 276 | /* stop sublists from having initial vertical space */ 277 | ul ul { margin-top: 0px; } 278 | ol ul { margin-top: 0px; } 279 | ol ol { margin-top: 0px; } 280 | ul ol { margin-top: 0px; } 281 | 282 | /* make the target distinct; helps when we're navigating to a function */ 283 | a:target + * { 284 | background-color: #FF9; 285 | } 286 | 287 | 288 | /* styles for prettification of source */ 289 | pre .comment { color: #558817; } 290 | pre .constant { color: #a8660d; } 291 | pre .escape { color: #844631; } 292 | pre .keyword { color: #aa5050; font-weight: bold; } 293 | pre .library { color: #0e7c6b; } 294 | pre .marker { color: #512b1e; background: #fedc56; font-weight: bold; } 295 | pre .string { color: #8080ff; } 296 | pre .number { color: #f8660d; } 297 | pre .function-name { color: #60447f; } 298 | pre .operator { color: #2239a8; font-weight: bold; } 299 | pre .preprocessor, pre .prepro { color: #a33243; } 300 | pre .global { color: #800080; } 301 | pre .user-keyword { color: #800080; } 302 | pre .prompt { color: #558817; } 303 | pre .url { color: #272fc2; text-decoration: underline; } 304 | 305 | -------------------------------------------------------------------------------- /lib/resty/session/redis/sentinel.lua: -------------------------------------------------------------------------------- 1 | --- 2 | -- Redis Sentinel backend for session library 3 | -- 4 | -- @module resty.session.redis.sentinel 5 | 6 | 7 | local common = require "resty.session.redis.common" 8 | local redis = require "resty.redis.connector" 9 | 10 | 11 | local setmetatable = setmetatable 12 | local error = error 13 | local null = ngx.null 14 | 15 | 16 | local SET = common.SET 17 | local GET = common.GET 18 | local UNLINK = common.UNLINK 19 | local READ_METADATA = common.READ_METADATA 20 | 21 | 22 | local function exec(self, func, ...) 23 | local red, err = self.connector:connect() 24 | if not red then 25 | return nil, err 26 | end 27 | 28 | local ok, err = func(self, red, ...) 29 | if err then 30 | red:close() 31 | return nil, err 32 | end 33 | 34 | if ok == null then 35 | ok = nil 36 | end 37 | 38 | self.connector:set_keepalive(red) 39 | 40 | return ok, err 41 | end 42 | 43 | 44 | --- 45 | -- Storage 46 | -- @section instance 47 | 48 | 49 | local metatable = {} 50 | 51 | 52 | metatable.__index = metatable 53 | 54 | 55 | function metatable.__newindex() 56 | error("attempt to update a read-only table", 2) 57 | end 58 | 59 | 60 | --- 61 | -- Store session data. 62 | -- 63 | -- @function instance:set 64 | -- @tparam string name cookie name 65 | -- @tparam string key session key 66 | -- @tparam string value session value 67 | -- @tparam number ttl session ttl 68 | -- @tparam number current_time current time 69 | -- @tparam[opt] string old_key old session id 70 | -- @tparam string stale_ttl stale ttl 71 | -- @tparam[opt] table metadata table of metadata 72 | -- @treturn true|nil ok 73 | -- @treturn string error message 74 | function metatable:set(...) 75 | return exec(self, SET, ...) 76 | end 77 | 78 | 79 | --- 80 | -- Retrieve session data. 81 | -- 82 | -- @function instance:get 83 | -- @tparam string name cookie name 84 | -- @tparam string key session key 85 | -- @treturn string|nil session data 86 | -- @treturn string error message 87 | function metatable:get(...) 88 | return exec(self, GET, ...) 89 | end 90 | 91 | 92 | --- 93 | -- Delete session data. 94 | -- 95 | -- @function instance:delete 96 | -- @tparam string name cookie name 97 | -- @tparam string key session key 98 | -- @tparam[opt] table metadata session meta data 99 | -- @treturn boolean|nil session data 100 | -- @treturn string error message 101 | function metatable:delete(...) 102 | return exec(self, UNLINK, ...) 103 | end 104 | 105 | 106 | --- 107 | -- Read session metadata. 108 | -- 109 | -- @function instance:read_metadata 110 | -- @tparam string name cookie name 111 | -- @tparam string audience session key 112 | -- @tparam string subject session key 113 | -- @tparam number current_time current time 114 | -- @treturn table|nil session metadata 115 | -- @treturn string error message 116 | function metatable:read_metadata(...) 117 | return exec(self, READ_METADATA, ...) 118 | end 119 | 120 | 121 | local storage = {} 122 | 123 | 124 | --- 125 | -- Configuration 126 | -- @section configuration 127 | 128 | 129 | --- 130 | -- Redis Sentinel storage backend configuration 131 | -- @field prefix Prefix for the keys stored in redis. 132 | -- @field suffix Suffix for the keys stored in redis. 133 | -- @field master Name of master. 134 | -- @field role `"master"` or `"slave"`. 135 | -- @field sentinels Redis Sentinels. 136 | -- @field sentinel_username Optional sentinel username. 137 | -- @field sentinel_password Optional sentinel password. 138 | -- @field username The database username to authenticate. 139 | -- @field password Password for authentication. 140 | -- @field database The database to connect. 141 | -- @field connect_timeout Controls the default timeout value used in TCP/unix-domain socket object's `connect` method. 142 | -- @field send_timeout Controls the default timeout value used in TCP/unix-domain socket object's `send` method. 143 | -- @field read_timeout Controls the default timeout value used in TCP/unix-domain socket object's `receive` method. 144 | -- @field keepalive_timeout Controls the default maximal idle time of the connections in the connection pool. 145 | -- @field pool A custom name for the connection pool being used. 146 | -- @field pool_size The size of the connection pool. 147 | -- @field backlog A queue size to use when the connection pool is full (configured with @pool_size). 148 | -- @field ssl Enable SSK (defaults to `false`). 149 | -- @field ssl_verify Verify server certificate (defaults to `nil`). 150 | -- @field server_name The server name for the new TLS extension Server Name Indication (SNI). 151 | -- @table configuration 152 | 153 | 154 | --- 155 | -- Sentinels 156 | -- 157 | -- An array of sentinels. 158 | -- 159 | -- @table sentinels 160 | 161 | 162 | --- 163 | -- Sentinel 164 | -- @field host The host to connect. 165 | -- @field port The port to connect. 166 | -- @table sentinel 167 | 168 | 169 | --- 170 | -- Constructors 171 | -- @section constructors 172 | 173 | 174 | --- 175 | -- Create a Redis Sentinel storage. 176 | -- 177 | -- This creates a new Redis Sentinel storage instance. 178 | -- 179 | -- @function module.new 180 | -- @tparam[opt] table configuration redis sentinel storage @{configuration} 181 | -- @treturn table redis sentinel storage instance 182 | function storage.new(configuration) 183 | local prefix = configuration and configuration.prefix 184 | local suffix = configuration and configuration.suffix 185 | 186 | local master = configuration and configuration.master 187 | local role = configuration and configuration.role 188 | local sentinels = configuration and configuration.sentinels 189 | local sentinel_username = configuration and configuration.sentinel_username 190 | local sentinel_password = configuration and configuration.sentinel_password 191 | 192 | local username = configuration and configuration.username 193 | local password = configuration and configuration.password 194 | local database = configuration and configuration.database 195 | 196 | local connect_timeout = configuration and configuration.connect_timeout 197 | local send_timeout = configuration and configuration.send_timeout 198 | local read_timeout = configuration and configuration.read_timeout 199 | local keepalive_timeout = configuration and configuration.keepalive_timeout 200 | 201 | local pool = configuration and configuration.pool 202 | local pool_size = configuration and configuration.pool_size 203 | local backlog = configuration and configuration.backlog 204 | local ssl = configuration and configuration.ssl 205 | local ssl_verify = configuration and configuration.ssl_verify 206 | local server_name = configuration and configuration.server_name 207 | 208 | local connector 209 | if ssl ~= nil or ssl_verify ~= nil or server_name or pool or pool_size or backlog then 210 | connector = redis.new({ 211 | master_name = master, 212 | role = role, 213 | sentinels = sentinels, 214 | sentinel_username = sentinel_username, 215 | sentinel_password = sentinel_password, 216 | username = username, 217 | password = password, 218 | db = database, 219 | connect_timeout = connect_timeout, 220 | send_timeout = send_timeout, 221 | read_timeout = read_timeout, 222 | keepalive_timeout = keepalive_timeout, 223 | keepalive_poolsize = pool_size, 224 | connection_options = { 225 | ssl = ssl, 226 | ssl_verify = ssl_verify, 227 | server_name = server_name, 228 | pool = pool, 229 | pool_size = pool_size, 230 | backlog = backlog, 231 | } 232 | }) 233 | else 234 | connector = redis.new({ 235 | master_name = master, 236 | role = role, 237 | sentinels = sentinels, 238 | sentinel_username = sentinel_username, 239 | sentinel_password = sentinel_password, 240 | username = username, 241 | password = password, 242 | db = database, 243 | connect_timeout = connect_timeout, 244 | send_timeout = send_timeout, 245 | read_timeout = read_timeout, 246 | keepalive_timeout = keepalive_timeout, 247 | keepalive_poolsize = pool_size, 248 | }) 249 | end 250 | 251 | return setmetatable({ 252 | prefix = prefix, 253 | suffix = suffix, 254 | connector = connector, 255 | }, metatable) 256 | end 257 | 258 | 259 | return storage 260 | -------------------------------------------------------------------------------- /lib/resty/session/shm.lua: -------------------------------------------------------------------------------- 1 | --- 2 | -- Shared Memory (SHM) backend for session library 3 | -- 4 | -- @module resty.session.shm 5 | 6 | 7 | local utils = require "resty.session.utils" 8 | 9 | 10 | local meta_get_value = utils.meta_get_value 11 | local meta_get_next = utils.meta_get_next 12 | local table_new = utils.table_new 13 | local get_name = utils.get_name 14 | local errmsg = utils.errmsg 15 | 16 | 17 | local setmetatable = setmetatable 18 | local shared = ngx.shared 19 | local random = math.random 20 | local assert = assert 21 | local error = error 22 | local pairs = pairs 23 | local max = math.max 24 | local log = ngx.log 25 | 26 | 27 | local WARN = ngx.WARN 28 | 29 | 30 | local DEFAULT_ZONE = "sessions" 31 | local CLEANUP_PROBABILITY = 0.1 -- 1 / 10 32 | 33 | 34 | local function get_and_clean_metadata(dict, meta_key, current_time) 35 | local size = dict:llen(meta_key) 36 | if not size or size == 0 then 37 | return 38 | end 39 | 40 | local max_expiry = current_time 41 | local sessions = table_new(0, size) 42 | 43 | for _ = 1, size do 44 | local meta_value, err = dict:lpop(meta_key) 45 | if not meta_value then 46 | log(WARN, "[session] ", errmsg(err, "failed read meta value")) 47 | break 48 | end 49 | 50 | local key, err, exp = meta_get_next(meta_value, 1) 51 | if err then 52 | return nil, err 53 | end 54 | 55 | if exp and exp > current_time then 56 | sessions[key] = exp 57 | max_expiry = max(max_expiry, exp) 58 | 59 | else 60 | sessions[key] = nil 61 | end 62 | end 63 | 64 | for key, exp in pairs(sessions) do 65 | local meta_value = meta_get_value(key, exp) 66 | local ok, err = dict:rpush(meta_key, meta_value) 67 | if not ok then 68 | log(WARN, "[session] ", errmsg(err, "failed to update metadata")) 69 | end 70 | end 71 | 72 | local exp = max_expiry - current_time 73 | if exp > 0 then 74 | local ok, err = dict:expire(meta_key, max_expiry - current_time) 75 | if not ok and err ~= "not found" then 76 | log(WARN, "[session] ", errmsg(err, "failed to touch metadata")) 77 | end 78 | 79 | else 80 | dict:delete(meta_key) 81 | end 82 | 83 | return sessions 84 | end 85 | 86 | 87 | local function cleanup(dict, meta_key, current_time) 88 | get_and_clean_metadata(dict, meta_key, current_time) 89 | end 90 | 91 | 92 | local function read_metadata(self, meta_key, current_time) 93 | return get_and_clean_metadata(self.dict, meta_key, current_time) 94 | end 95 | 96 | --- 97 | -- Storage 98 | -- @section instance 99 | 100 | 101 | local metatable = {} 102 | 103 | 104 | metatable.__index = metatable 105 | 106 | 107 | function metatable.__newindex() 108 | error("attempt to update a read-only table", 2) 109 | end 110 | 111 | 112 | --- 113 | -- Store session data. 114 | -- 115 | -- @function instance:set 116 | -- @tparam string name cookie name 117 | -- @tparam string key session key 118 | -- @tparam string value session value 119 | -- @tparam number ttl session ttl 120 | -- @tparam number current_time current time 121 | -- @tparam[opt] string old_key old session id 122 | -- @tparam string stale_ttl stale ttl 123 | -- @tparam[opt] table metadata table of metadata 124 | -- @treturn true|nil ok 125 | -- @treturn string error message 126 | function metatable:set(name, key, value, ttl, current_time, old_key, stale_ttl, metadata) 127 | local dict = self.dict 128 | if not metadata and not old_key then 129 | local ok, err = dict:set(get_name(self, name, key), value, ttl) 130 | if not ok then 131 | return nil, err 132 | end 133 | 134 | return true 135 | end 136 | 137 | local old_name, old_ttl 138 | if old_key then 139 | old_name = get_name(self, name, old_key) 140 | old_ttl = dict:ttl(old_name) 141 | end 142 | 143 | local ok, err = dict:set(get_name(self, name, key), value, ttl) 144 | if not ok then 145 | return nil, err 146 | end 147 | 148 | if old_name and (not old_ttl or old_ttl > stale_ttl) then 149 | local ok, err = dict:expire(old_name, stale_ttl) 150 | if not ok then 151 | log(WARN, "[session] ", errmsg(err, "failed to touch old session")) 152 | end 153 | end 154 | 155 | if not metadata then 156 | return true 157 | end 158 | 159 | local audiences = metadata.audiences 160 | local subjects = metadata.subjects 161 | local count = #audiences 162 | for i = 1, count do 163 | local meta_key = get_name(self, name, audiences[i], subjects[i]) 164 | local meta_value = meta_get_value(key, current_time + ttl) 165 | 166 | local ok, err = dict:rpush(meta_key, meta_value) 167 | if not ok then 168 | log(WARN, "[session] ", errmsg(err, "failed to update metadata")) 169 | end 170 | 171 | if old_key then 172 | meta_value = meta_get_value(old_key, 0) 173 | local ok, err = dict:rpush(meta_key, meta_value) 174 | if not ok then 175 | log(WARN, "[session] ", errmsg(err, "failed to update old metadata")) 176 | end 177 | end 178 | 179 | -- no need to clean up every time we write 180 | -- it is just beneficial when a key is used a lot 181 | if random() < CLEANUP_PROBABILITY then 182 | cleanup(dict, meta_key, current_time) 183 | end 184 | end 185 | 186 | return true 187 | end 188 | 189 | 190 | --- 191 | -- Retrieve session data. 192 | -- 193 | -- @function instance:get 194 | -- @tparam string name cookie name 195 | -- @tparam string key session key 196 | -- @treturn string|nil session data 197 | -- @treturn string error message 198 | function metatable:get(name, key) 199 | local value, err = self.dict:get(get_name(self, name, key)) 200 | if not value then 201 | return nil, err 202 | end 203 | 204 | return value 205 | end 206 | 207 | 208 | --- 209 | -- Delete session data. 210 | -- 211 | -- @function instance:delete 212 | -- @tparam string name cookie name 213 | -- @tparam string key session key 214 | -- @tparam[opt] table metadata session meta data 215 | -- @treturn boolean|nil session data 216 | -- @treturn string error message 217 | function metatable:delete(name, key, current_time, metadata) 218 | local dict = self.dict 219 | 220 | dict:delete(get_name(self, name, key)) 221 | 222 | if not metadata then 223 | return true 224 | end 225 | 226 | local audiences = metadata.audiences 227 | local subjects = metadata.subjects 228 | local count = #audiences 229 | for i = 1, count do 230 | local meta_key = get_name(self, name, audiences[i], subjects[i]) 231 | local meta_value = meta_get_value(key, 0) 232 | 233 | local ok, err = dict:rpush(meta_key, meta_value) 234 | if not ok then 235 | if not ok then 236 | log(WARN, "[session] ", errmsg(err, "failed to update metadata")) 237 | end 238 | end 239 | 240 | cleanup(dict, meta_key, current_time) 241 | end 242 | 243 | return true 244 | end 245 | 246 | 247 | --- 248 | -- Read session metadata. 249 | -- 250 | -- @function instance:read_metadata 251 | -- @tparam string name cookie name 252 | -- @tparam string audience session key 253 | -- @tparam string subject session key 254 | -- @tparam number current_time current time 255 | -- @treturn table|nil session metadata 256 | -- @treturn string error message 257 | function metatable:read_metadata(name, audience, subject, current_time) 258 | local meta_key = get_name(self, name, audience, subject) 259 | return read_metadata(self, meta_key, current_time) 260 | end 261 | 262 | 263 | local storage = {} 264 | 265 | 266 | --- 267 | -- Configuration 268 | -- @section configuration 269 | 270 | 271 | --- 272 | -- Shared memory storage backend configuration 273 | -- @field prefix Prefix for the keys stored in SHM. 274 | -- @field suffix Suffix for the keys stored in SHM. 275 | -- @field zone A name of shared memory zone (defaults to `sessions`). 276 | -- @table configuration 277 | 278 | 279 | --- 280 | -- Constructors 281 | -- @section constructors 282 | 283 | 284 | --- 285 | -- Create a SHM storage. 286 | -- 287 | -- This creates a new shared memory storage instance. 288 | -- 289 | -- @function module.new 290 | -- @tparam[opt] table configuration shm storage @{configuration} 291 | -- @treturn table shm storage instance 292 | function storage.new(configuration) 293 | local prefix = configuration and configuration.prefix 294 | local suffix = configuration and configuration.suffix 295 | local zone = configuration and configuration.zone or DEFAULT_ZONE 296 | 297 | local dict = assert(shared[zone], "lua_shared_dict " .. zone .. " is missing") 298 | 299 | return setmetatable({ 300 | prefix = prefix, 301 | suffix = suffix, 302 | dict = dict, 303 | }, metatable) 304 | end 305 | 306 | 307 | return storage 308 | -------------------------------------------------------------------------------- /lib/resty/session/redis.lua: -------------------------------------------------------------------------------- 1 | --- 2 | -- Redis backend for session library 3 | -- 4 | -- @module resty.session.redis 5 | 6 | 7 | local common = require "resty.session.redis.common" 8 | local redis = require "resty.redis" 9 | 10 | 11 | local setmetatable = setmetatable 12 | local null = ngx.null 13 | 14 | 15 | local DEFAULT_HOST = "127.0.0.1" 16 | local DEFAULT_PORT = 6379 17 | 18 | 19 | local SET = common.SET 20 | local GET = common.GET 21 | local UNLINK = common.UNLINK 22 | local READ_METADATA = common.READ_METADATA 23 | 24 | 25 | local function exec(self, func, ...) 26 | local red = redis:new() 27 | 28 | local connect_timeout = self.connect_timeout 29 | local send_timeout = self.send_timeout 30 | local read_timeout = self.read_timeout 31 | if connect_timeout or send_timeout or read_timeout then 32 | red:set_timeouts(connect_timeout, send_timeout, read_timeout) 33 | end 34 | 35 | local ok, err do 36 | local socket = self.socket 37 | if socket then 38 | ok, err = red:connect(socket, self.options) 39 | else 40 | ok, err = red:connect(self.host, self.port, self.options) 41 | end 42 | end 43 | if not ok then 44 | return nil, err 45 | end 46 | 47 | if self.force_auth == true or red:get_reused_times() == 0 then 48 | local password = self.password 49 | if password then 50 | local username = self.username 51 | if username then 52 | ok, err = red:auth(username, password) 53 | else 54 | ok, err = red:auth(password) 55 | end 56 | 57 | if not ok then 58 | red:close() 59 | return nil, err 60 | end 61 | end 62 | end 63 | 64 | local database = self.database 65 | if database then 66 | ok, err = red:select(database) 67 | if not ok then 68 | return nil, err 69 | end 70 | end 71 | 72 | ok, err = func(self, red, ...) 73 | if err then 74 | red:close() 75 | return nil, err 76 | end 77 | 78 | if not red:set_keepalive(self.keepalive_timeout) then 79 | red:close() 80 | end 81 | 82 | if ok == null then 83 | ok = nil 84 | end 85 | 86 | return ok, err 87 | end 88 | 89 | 90 | --- 91 | -- Storage 92 | -- @section instance 93 | 94 | 95 | local metatable = {} 96 | 97 | 98 | metatable.__index = metatable 99 | 100 | 101 | --- 102 | -- Store session data. 103 | -- 104 | -- @function instance:set 105 | -- @tparam string name cookie name 106 | -- @tparam string key session key 107 | -- @tparam string value session value 108 | -- @tparam number ttl session ttl 109 | -- @tparam number current_time current time 110 | -- @tparam[opt] string old_key old session id 111 | -- @tparam string stale_ttl stale ttl 112 | -- @tparam[opt] table metadata table of metadata 113 | -- @treturn true|nil ok 114 | -- @treturn string error message 115 | function metatable:set(...) 116 | return exec(self, SET, ...) 117 | end 118 | 119 | 120 | --- 121 | -- Retrieve session data. 122 | -- 123 | -- @function instance:get 124 | -- @tparam string name cookie name 125 | -- @tparam string key session key 126 | -- @treturn string|nil session data 127 | -- @treturn string error message 128 | function metatable:get(...) 129 | return exec(self, GET, ...) 130 | end 131 | 132 | 133 | --- 134 | -- Delete session data. 135 | -- 136 | -- @function instance:delete 137 | -- @tparam string name cookie name 138 | -- @tparam string key session key 139 | -- @tparam[opt] table metadata session meta data 140 | -- @treturn boolean|nil session data 141 | -- @treturn string error message 142 | function metatable:delete(...) 143 | return exec(self, UNLINK, ...) 144 | end 145 | 146 | 147 | --- 148 | -- Read session metadata. 149 | -- 150 | -- @function instance:read_metadata 151 | -- @tparam string name cookie name 152 | -- @tparam string audience session key 153 | -- @tparam string subject session key 154 | -- @tparam number current_time current time 155 | -- @treturn table|nil session metadata 156 | -- @treturn string error message 157 | function metatable:read_metadata(...) 158 | return exec(self, READ_METADATA, ...) 159 | end 160 | 161 | 162 | local storage = {} 163 | 164 | 165 | --- 166 | -- Configuration 167 | -- @section configuration 168 | 169 | 170 | --- 171 | -- Redis storage backend configuration 172 | -- @field prefix Prefix for the keys stored in Redis. 173 | -- @field suffix Suffix for the keys stored in Redis. 174 | -- @field host The host to connect (defaults to `"127.0.0.1"`). 175 | -- @field port The port to connect (defaults to `6379`). 176 | -- @field socket The socket file to connect to (defaults to `nil`). 177 | -- @field username The database username to authenticate. 178 | -- @field password Password for authentication. 179 | -- @field database The database to connect. 180 | -- @field connect_timeout Controls the default timeout value used in TCP/unix-domain socket object's `connect` method. 181 | -- @field send_timeout Controls the default timeout value used in TCP/unix-domain socket object's `send` method. 182 | -- @field read_timeout Controls the default timeout value used in TCP/unix-domain socket object's `receive` method. 183 | -- @field keepalive_timeout Controls the default maximal idle time of the connections in the connection pool. 184 | -- @field pool A custom name for the connection pool being used. 185 | -- @field pool_size The size of the connection pool. 186 | -- @field backlog A queue size to use when the connection pool is full (configured with @pool_size). 187 | -- @field ssl Enable SSL (defaults to `false`). 188 | -- @field ssl_verify Verify server certificate (defaults to `nil`). 189 | -- @field server_name The server name for the new TLS extension Server Name Indication (SNI). 190 | -- @table configuration 191 | 192 | 193 | --- 194 | -- Constructors 195 | -- @section constructors 196 | 197 | 198 | --- 199 | -- Create a Redis storage. 200 | -- 201 | -- This creates a new Redis storage instance. 202 | -- 203 | -- @function module.new 204 | -- @tparam[opt] table configuration redis storage @{configuration} 205 | -- @treturn table redis storage instance 206 | function storage.new(configuration) 207 | local prefix = configuration and configuration.prefix 208 | local suffix = configuration and configuration.suffix 209 | 210 | local host = configuration and configuration.host or DEFAULT_HOST 211 | local port = configuration and configuration.port or DEFAULT_PORT 212 | local socket = configuration and configuration.socket 213 | 214 | local username = configuration and configuration.username 215 | local password = configuration and configuration.password 216 | local database = configuration and configuration.database 217 | 218 | local connect_timeout = configuration and configuration.connect_timeout 219 | local send_timeout = configuration and configuration.send_timeout 220 | local read_timeout = configuration and configuration.read_timeout 221 | local keepalive_timeout = configuration and configuration.keepalive_timeout 222 | 223 | local pool = configuration and configuration.pool 224 | local pool_size = configuration and configuration.pool_size 225 | local backlog = configuration and configuration.backlog 226 | local ssl = configuration and configuration.ssl 227 | local ssl_verify = configuration and configuration.ssl_verify 228 | local server_name = configuration and configuration.server_name 229 | 230 | local force_auth = configuration and configuration.force_auth 231 | 232 | if ssl ~= nil or ssl_verify ~= nil or server_name or pool or pool_size or backlog then 233 | return setmetatable({ 234 | prefix = prefix, 235 | suffix = suffix, 236 | host = host, 237 | port = port, 238 | socket = socket, 239 | username = username, 240 | password = password, 241 | database = database, 242 | connect_timeout = connect_timeout, 243 | send_timeout = send_timeout, 244 | read_timeout = read_timeout, 245 | keepalive_timeout = keepalive_timeout, 246 | force_auth = force_auth, 247 | options = { 248 | ssl = ssl, 249 | ssl_verify = ssl_verify, 250 | server_name = server_name, 251 | pool = pool, 252 | pool_size = pool_size, 253 | backlog = backlog, 254 | } 255 | }, metatable) 256 | end 257 | 258 | return setmetatable({ 259 | prefix = prefix, 260 | suffix = suffix, 261 | host = host, 262 | port = port, 263 | socket = socket, 264 | username = username, 265 | password = password, 266 | database = database, 267 | connect_timeout = connect_timeout, 268 | send_timeout = send_timeout, 269 | read_timeout = read_timeout, 270 | keepalive_timeout = keepalive_timeout, 271 | force_auth = force_auth, 272 | }, metatable) 273 | end 274 | 275 | 276 | return storage 277 | -------------------------------------------------------------------------------- /lib/resty/session/redis/cluster.lua: -------------------------------------------------------------------------------- 1 | --- 2 | -- Redis Cluster backend for session library 3 | -- 4 | -- @module resty.session.redis.cluster 5 | 6 | 7 | local common = require "resty.session.redis.common" 8 | local redis = require "resty.rediscluster" 9 | 10 | 11 | local setmetatable = setmetatable 12 | local null = ngx.null 13 | 14 | 15 | local SET = common.SET 16 | local GET = common.GET 17 | local UNLINK = common.UNLINK 18 | local READ_METADATA = common.READ_METADATA 19 | 20 | 21 | local function exec(self, func, ...) 22 | local red, err = redis:new(self.options) 23 | if err then 24 | return nil, err 25 | end 26 | 27 | local ok, err = func(self, red, ...) 28 | if err then 29 | red:close() 30 | return nil, err 31 | end 32 | 33 | if ok == null then 34 | ok = nil 35 | end 36 | 37 | return ok, err 38 | end 39 | 40 | 41 | --- 42 | -- Storage 43 | -- @section instance 44 | 45 | 46 | local metatable = {} 47 | 48 | 49 | metatable.__index = metatable 50 | 51 | 52 | --- 53 | -- Store session data. 54 | -- 55 | -- @function instance:set 56 | -- @tparam string name cookie name 57 | -- @tparam string key session key 58 | -- @tparam string value session value 59 | -- @tparam number ttl session ttl 60 | -- @tparam number current_time current time 61 | -- @tparam[opt] string old_key old session id 62 | -- @tparam string stale_ttl stale ttl 63 | -- @tparam[opt] table metadata table of metadata 64 | -- @treturn true|nil ok 65 | -- @treturn string error message 66 | function metatable:set(...) 67 | return exec(self, SET, ...) 68 | end 69 | 70 | 71 | --- 72 | -- Retrieve session data. 73 | -- 74 | -- @function instance:get 75 | -- @tparam string name cookie name 76 | -- @tparam string key session key 77 | -- @treturn string|nil session data 78 | -- @treturn string error message 79 | function metatable:get(...) 80 | return exec(self, GET, ...) 81 | end 82 | 83 | 84 | --- 85 | -- Delete session data. 86 | -- 87 | -- @function instance:delete 88 | -- @tparam string name cookie name 89 | -- @tparam string key session key 90 | -- @tparam[opt] table metadata session meta data 91 | -- @treturn boolean|nil session data 92 | -- @treturn string error message 93 | function metatable:delete(...) 94 | return exec(self, UNLINK, ...) 95 | end 96 | 97 | 98 | --- 99 | -- Read session metadata. 100 | -- 101 | -- @function instance:read_metadata 102 | -- @tparam string name cookie name 103 | -- @tparam string audience session key 104 | -- @tparam string subject session key 105 | -- @tparam number current_time current time 106 | -- @treturn table|nil session metadata 107 | -- @treturn string error message 108 | function metatable:read_metadata(...) 109 | return exec(self, READ_METADATA, ...) 110 | end 111 | 112 | 113 | local storage = {} 114 | 115 | 116 | --- 117 | -- Configuration 118 | -- @section configuration 119 | 120 | 121 | --- 122 | -- Redis Cluster storage backend configuration 123 | -- @field prefix Prefix for the keys stored in redis. 124 | -- @field suffix Suffix for the keys stored in redis. 125 | -- @field name Redis cluster name. 126 | -- @field nodes Redis cluster nodes. 127 | -- @field lock_zone Shared dictionary name for locks. 128 | -- @field lock_prefix Shared dictionary name prefix for lock. 129 | -- @field max_redirections Maximum retry attempts for redirection. 130 | -- @field max_connection_attempts Maximum retry attempts for connection. 131 | -- @field max_connection_timeout Maximum connection timeout in total among the retries. 132 | -- @field username The database username to authenticate. 133 | -- @field password Password for authentication. 134 | -- @field connect_timeout Controls the default timeout value used in TCP/unix-domain socket object's `connect` method. 135 | -- @field send_timeout controls The default timeout value used in TCP/unix-domain socket object's `send` method. 136 | -- @field read_timeout controls The default timeout value used in TCP/unix-domain socket object's `receive` method. 137 | -- @field keepalive_timeout Controls the default maximal idle time of the connections in the connection pool. 138 | -- @field pool A custom name for the connection pool being used. 139 | -- @field pool_size The size of the connection pool. 140 | -- @field backlog A queue size to use when the connection pool is full (configured with @pool_size). 141 | -- @field ssl Enable SSL (defaults to `false`). 142 | -- @field ssl_verify Verify server certificate (defaults to `nil`). 143 | -- @field server_name The server name for the new TLS extension Server Name Indication (SNI). 144 | -- @table configuration 145 | 146 | 147 | --- 148 | -- Cluster Nodes 149 | -- 150 | -- An array of cluster nodes. 151 | -- 152 | -- @table nodes 153 | 154 | 155 | --- 156 | -- Cluster Node 157 | -- @field ip The IP address to connect (defaults to `"127.0.0.1"`). 158 | -- @field port The port to connect (defaults to `6379`). 159 | -- @table node 160 | 161 | 162 | --- 163 | -- Constructors 164 | -- @section constructors 165 | 166 | 167 | --- 168 | -- Create a Redis Cluster storage. 169 | -- 170 | -- This creates a new Redis Cluster storage instance. 171 | -- 172 | -- @function module.new 173 | -- @tparam[opt] table configuration redis cluster storage @{configuration} 174 | -- @treturn table redis cluster storage instance 175 | function storage.new(configuration) 176 | local prefix = configuration and configuration.prefix 177 | local suffix = configuration and configuration.suffix 178 | 179 | local name = configuration and configuration.name 180 | local nodes = configuration and configuration.nodes 181 | 182 | local lock_zone = configuration and configuration.lock_zone 183 | local lock_prefix = configuration and configuration.lock_prefix 184 | local max_redirections = configuration and configuration.max_redirections 185 | local max_connection_attempts = configuration and configuration.max_connection_attempts 186 | local max_connection_timeout = configuration and configuration.max_connection_timeout 187 | 188 | local username = configuration and configuration.username 189 | local password = configuration and configuration.password 190 | 191 | local connect_timeout = configuration and configuration.connect_timeout 192 | local send_timeout = configuration and configuration.send_timeout 193 | local read_timeout = configuration and configuration.read_timeout 194 | local keepalive_timeout = configuration and configuration.keepalive_timeout 195 | 196 | local pool = configuration and configuration.pool 197 | local pool_size = configuration and configuration.pool_size 198 | local backlog = configuration and configuration.backlog 199 | local ssl = configuration and configuration.ssl 200 | local ssl_verify = configuration and configuration.ssl_verify 201 | local server_name = configuration and configuration.server_name 202 | 203 | local force_auth = configuration and configuration.force_auth 204 | 205 | 206 | local auth 207 | if not username then 208 | if password then 209 | auth = password 210 | password = nil 211 | end 212 | end 213 | 214 | if ssl ~= nil or ssl_verify ~= nil or server_name or pool or pool_size or backlog then 215 | return setmetatable({ 216 | prefix = prefix, 217 | suffix = suffix, 218 | options = { 219 | name = name, 220 | dict_name = lock_zone, 221 | refresh_lock_key = lock_prefix, 222 | serv_list = nodes, 223 | connect_timeout = connect_timeout, 224 | send_timeout = send_timeout, 225 | read_timeout = read_timeout, 226 | keepalive_timeout = keepalive_timeout, 227 | keepalive_cons = pool_size, 228 | max_redirection = max_redirections, 229 | max_connection_attempts = max_connection_attempts, 230 | max_connection_timeout = max_connection_timeout, 231 | auth = auth, 232 | username = username, 233 | password = password, 234 | force_auth = force_auth, 235 | connect_opts = { 236 | ssl = ssl, 237 | ssl_verify = ssl_verify, 238 | server_name = server_name, 239 | pool = pool, 240 | pool_size = pool_size, 241 | backlog = backlog, 242 | }, 243 | }, 244 | }, metatable) 245 | end 246 | 247 | return setmetatable({ 248 | prefix = prefix, 249 | suffix = suffix, 250 | options = { 251 | name = name, 252 | dict_name = lock_zone, 253 | refresh_lock_key = lock_prefix, 254 | serv_list = nodes, 255 | connect_timeout = connect_timeout, 256 | send_timeout = send_timeout, 257 | read_timeout = read_timeout, 258 | keepalive_timeout = keepalive_timeout, 259 | keepalive_cons = pool_size, 260 | max_redirection = max_redirections, 261 | max_connection_attempts = max_connection_attempts, 262 | max_connection_timeout = max_connection_timeout, 263 | auth = auth, 264 | username = username, 265 | password = password, 266 | force_auth = force_auth, 267 | }, 268 | }, metatable) 269 | end 270 | 271 | 272 | return storage 273 | -------------------------------------------------------------------------------- /lib/resty/session/dshm.lua: -------------------------------------------------------------------------------- 1 | --- 2 | -- Distributed Shared Memory (DSHM) backend for session library 3 | -- 4 | -- @module resty.session.dshm 5 | 6 | 7 | local buffer = require "string.buffer" 8 | local utils = require "resty.session.utils" 9 | local dshm = require "resty.dshm" 10 | 11 | 12 | local meta_get_latest = utils.meta_get_latest 13 | local meta_get_value = utils.meta_get_value 14 | local get_name = utils.get_name 15 | 16 | 17 | local setmetatable = setmetatable 18 | local error = error 19 | local pairs = pairs 20 | local null = ngx.null 21 | local max = math.max 22 | 23 | 24 | local DEFAULT_HOST = "127.0.0.1" 25 | local DEFAULT_PORT = 4321 26 | 27 | 28 | local SESSIONS_BUFFER = buffer.new(128) 29 | 30 | 31 | -- not safe for concurrent access 32 | local function update_meta(dshmc, meta_key, key, exp, current_time) 33 | local metadata = dshmc:get(meta_key) 34 | local sessions = metadata and meta_get_latest(metadata, current_time) or {} 35 | 36 | SESSIONS_BUFFER:reset() 37 | 38 | sessions[key] = exp > 0 and exp or nil 39 | 40 | local max_expiry = current_time 41 | for s, e in pairs(sessions) do 42 | SESSIONS_BUFFER:put(meta_get_value(s, e)) 43 | max_expiry = max(max_expiry, e) 44 | end 45 | 46 | local ser = SESSIONS_BUFFER:get() 47 | 48 | if #ser > 0 then 49 | return dshmc:set(meta_key, ser, max_expiry - current_time) 50 | end 51 | 52 | return dshmc:delete(meta_key) 53 | end 54 | 55 | 56 | local function READ_METADATA(self, dshmc, name, audience, subject, current_time) 57 | local meta_key = get_name(self, name, audience, subject) 58 | local res = dshmc:get(meta_key) 59 | if not res then 60 | return nil, "not found" 61 | end 62 | 63 | return meta_get_latest(res, current_time) 64 | end 65 | 66 | 67 | local function SET(self, dshmc, name, key, value, ttl, current_time, old_key, stale_ttl, metadata) 68 | local inferred_key = get_name(self, name, key) 69 | 70 | if not metadata and not old_key then 71 | return dshmc:set(inferred_key, value, ttl) 72 | end 73 | 74 | local ok, err = dshmc:set(inferred_key, value, ttl) 75 | if err then 76 | return nil, err 77 | end 78 | 79 | local old_name = old_key and get_name(self, name, old_key) 80 | if old_name then 81 | dshmc:touch(old_name, stale_ttl) 82 | end 83 | 84 | if metadata then 85 | local audiences = metadata.audiences 86 | local subjects = metadata.subjects 87 | local count = #audiences 88 | for i = 1, count do 89 | local meta_key = get_name(self, name, audiences[i], subjects[i]) 90 | update_meta(dshmc, meta_key, key, current_time + ttl, current_time) 91 | 92 | if old_key then 93 | update_meta(dshmc, meta_key, old_key, 0, current_time) 94 | end 95 | end 96 | end 97 | 98 | return ok 99 | end 100 | 101 | 102 | local function GET(self, dshmc, name, key) 103 | local res, err = dshmc:get(get_name(self, name, key)) 104 | if err then 105 | return nil, err 106 | end 107 | return res 108 | end 109 | 110 | 111 | local function DELETE(self, dshmc, name, key, current_time, metadata) 112 | local key_name = get_name(self, name, key) 113 | local ok, err = dshmc:delete(key_name) 114 | 115 | if not metadata then 116 | return ok, err 117 | end 118 | 119 | local audiences = metadata.audiences 120 | local subjects = metadata.subjects 121 | local count = #audiences 122 | for i = 1, count do 123 | local meta_key = get_name(self, name, audiences[i], subjects[i]) 124 | update_meta(dshmc, meta_key, key, 0, current_time) 125 | end 126 | 127 | return ok, err 128 | end 129 | 130 | 131 | local function exec(self, func, ...) 132 | local dshmc = dshm:new() 133 | local connect_timeout = self.connect_timeout 134 | local send_timeout = self.send_timeout 135 | local read_timeout = self.read_timeout 136 | if connect_timeout or send_timeout or read_timeout then 137 | dshmc.sock:settimeouts(connect_timeout, send_timeout, read_timeout) 138 | end 139 | 140 | local ok, err = dshmc:connect(self.host, self.port, self.options) 141 | if not ok then 142 | return nil, err 143 | end 144 | 145 | if self.ssl and dshmc:get_reused_times() == 0 then 146 | ok, err = dshmc.sock:sslhandshake(false, self.server_name, self.ssl_verify) 147 | if not ok then 148 | dshmc:close() 149 | return nil, err 150 | end 151 | end 152 | 153 | ok, err = func(self, dshmc, ...) 154 | if err then 155 | dshmc:close() 156 | return nil, err 157 | end 158 | 159 | if not dshmc:set_keepalive(self.keepalive_timeout) then 160 | dshmc:close() 161 | end 162 | 163 | if ok == null then 164 | ok = nil 165 | end 166 | 167 | return ok, err 168 | end 169 | 170 | 171 | --- 172 | -- Storage 173 | -- @section instance 174 | 175 | 176 | local metatable = {} 177 | 178 | 179 | metatable.__index = metatable 180 | 181 | 182 | function metatable.__newindex() 183 | error("attempt to update a read-only table", 2) 184 | end 185 | 186 | 187 | --- 188 | -- Store session data. 189 | -- 190 | -- @function instance:set 191 | -- @tparam string name cookie name 192 | -- @tparam string key session key 193 | -- @tparam string value session value 194 | -- @tparam number ttl session ttl 195 | -- @tparam number current_time current time 196 | -- @tparam[opt] string old_key old session id 197 | -- @tparam string stale_ttl stale ttl 198 | -- @tparam[opt] table metadata table of metadata 199 | -- @treturn true|nil ok 200 | -- @treturn string error message 201 | function metatable:set(...) 202 | return exec(self, SET, ...) 203 | end 204 | 205 | 206 | --- 207 | -- Retrieve session data. 208 | -- 209 | -- @function instance:get 210 | -- @tparam string name cookie name 211 | -- @tparam string key session key 212 | -- @treturn string|nil session data 213 | -- @treturn string error message 214 | function metatable:get(...) 215 | return exec(self, GET, ...) 216 | end 217 | 218 | 219 | --- 220 | -- Delete session data. 221 | -- 222 | -- @function instance:delete 223 | -- @tparam string name cookie name 224 | -- @tparam string key session key 225 | -- @tparam[opt] table metadata session meta data 226 | -- @treturn boolean|nil session data 227 | -- @treturn string error message 228 | function metatable:delete(...) 229 | return exec(self, DELETE, ...) 230 | end 231 | 232 | 233 | --- 234 | -- Read session metadata. 235 | -- 236 | -- @function instance:read_metadata 237 | -- @tparam string name cookie name 238 | -- @tparam string audience session key 239 | -- @tparam string subject session key 240 | -- @tparam number current_time current time 241 | -- @treturn table|nil session metadata 242 | -- @treturn string error message 243 | function metatable:read_metadata(...) 244 | return exec(self, READ_METADATA, ...) 245 | end 246 | 247 | 248 | local storage = {} 249 | 250 | 251 | --- 252 | -- Configuration 253 | -- @section configuration 254 | 255 | 256 | --- 257 | -- Distributed shared memory storage backend configuration 258 | -- @field prefix The prefix for the keys stored in DSHM. 259 | -- @field suffix The suffix for the keys stored in DSHM. 260 | -- @field host The host to connect (defaults to `"127.0.0.1"`). 261 | -- @field port The port to connect (defaults to `4321`). 262 | -- @field connect_timeout Controls the default timeout value used in TCP/unix-domain socket object's `connect` method. 263 | -- @field send_timeout Controls the default timeout value used in TCP/unix-domain socket object's `send` method. 264 | -- @field read_timeout Controls the default timeout value used in TCP/unix-domain socket object's `receive` method. 265 | -- @field keepalive_timeout Controls the default maximal idle time of the connections in the connection pool. 266 | -- @field pool A custom name for the connection pool being used. 267 | -- @field pool_size The size of the connection pool. 268 | -- @field backlog A queue size to use when the connection pool is full (configured with @pool_size). 269 | -- @field ssl Enable SSL (defaults to `false`). 270 | -- @field ssl_verify Verify server certificate (defaults to `nil`). 271 | -- @field server_name The server name for the new TLS extension Server Name Indication (SNI). 272 | -- @table configuration 273 | 274 | 275 | --- 276 | -- Constructors 277 | -- @section constructors 278 | 279 | 280 | --- 281 | -- Create a distributed shared memory storage. 282 | -- 283 | -- This creates a new distributed shared memory storage instance. 284 | -- 285 | -- @function module.new 286 | -- @tparam[opt] table configuration DSHM storage @{configuration} 287 | -- @treturn table DSHM storage instance 288 | function storage.new(configuration) 289 | local prefix = configuration and configuration.prefix 290 | local suffix = configuration and configuration.suffix 291 | 292 | local host = configuration and configuration.host or DEFAULT_HOST 293 | local port = configuration and configuration.port or DEFAULT_PORT 294 | 295 | local connect_timeout = configuration and configuration.connect_timeout 296 | local send_timeout = configuration and configuration.send_timeout 297 | local read_timeout = configuration and configuration.read_timeout 298 | local keepalive_timeout = configuration and configuration.keepalive_timeout 299 | 300 | local pool = configuration and configuration.pool 301 | local pool_size = configuration and configuration.pool_size 302 | local backlog = configuration and configuration.backlog 303 | local ssl = configuration and configuration.ssl 304 | local ssl_verify = configuration and configuration.ssl_verify 305 | local server_name = configuration and configuration.server_name 306 | 307 | if pool or pool_size or backlog then 308 | return setmetatable({ 309 | prefix = prefix, 310 | suffix = suffix, 311 | host = host, 312 | port = port, 313 | connect_timeout = connect_timeout, 314 | send_timeout = send_timeout, 315 | read_timeout = read_timeout, 316 | keepalive_timeout = keepalive_timeout, 317 | ssl = ssl, 318 | ssl_verify = ssl_verify, 319 | server_name = server_name, 320 | options = { 321 | pool = pool, 322 | pool_size = pool_size, 323 | backlog = backlog, 324 | } 325 | }, metatable) 326 | end 327 | 328 | return setmetatable({ 329 | prefix = prefix, 330 | suffix = suffix, 331 | host = host, 332 | port = port, 333 | connect_timeout = connect_timeout, 334 | send_timeout = send_timeout, 335 | read_timeout = read_timeout, 336 | keepalive_timeout = keepalive_timeout, 337 | ssl = ssl, 338 | ssl_verify = ssl_verify, 339 | server_name = server_name, 340 | }, metatable) 341 | end 342 | 343 | 344 | return storage 345 | -------------------------------------------------------------------------------- /lib/resty/session/postgres.lua: -------------------------------------------------------------------------------- 1 | --- 2 | -- Postgres backend for session library. 3 | -- 4 | -- @module resty.session.postgres 5 | 6 | 7 | --- 8 | -- Database 9 | -- @section database 10 | 11 | 12 | --- 13 | -- Sessions table. 14 | -- 15 | -- Database table that stores session data. 16 | -- 17 | -- @usage 18 | -- CREATE TABLE IF NOT EXISTS sessions ( 19 | -- sid TEXT PRIMARY KEY, 20 | -- name TEXT, 21 | -- data TEXT, 22 | -- exp TIMESTAMP WITH TIME ZONE 23 | -- ); 24 | -- CREATE INDEX ON sessions (exp); 25 | -- @table sessions 26 | 27 | 28 | --- 29 | -- Sessions metadata table. 30 | -- 31 | -- This is only needed if you want to store session metadata. 32 | -- 33 | -- @usage 34 | -- CREATE TABLE IF NOT EXISTS sessions_meta ( 35 | -- aud TEXT, 36 | -- sub TEXT, 37 | -- sid TEXT REFERENCES sessions (sid) ON DELETE CASCADE ON UPDATE CASCADE, 38 | -- PRIMARY KEY (aud, sub, sid) 39 | -- ); 40 | -- @table metadata 41 | 42 | 43 | local buffer = require "string.buffer" 44 | local pgmoon = require "pgmoon" 45 | 46 | 47 | local setmetatable = setmetatable 48 | local random = math.random 49 | local ipairs = ipairs 50 | local error = error 51 | local fmt = string.format 52 | 53 | 54 | local DEFAULT_HOST = "127.0.0.1" 55 | local DEFAULT_PORT = 5432 56 | local DEFAULT_TABLE = "sessions" 57 | 58 | 59 | local SET = "INSERT INTO %s (sid, name, data, exp) VALUES ('%s', '%s', '%s', TO_TIMESTAMP(%d) AT TIME ZONE 'UTC') ON CONFLICT (sid) DO UPDATE SET data = EXCLUDED.data, exp = EXCLUDED.exp" 60 | local SET_META_PREFIX = "INSERT INTO %s (aud, sub, sid) VALUES " 61 | local SET_META_VALUES = "('%s', '%s', '%s')" 62 | local SET_META_SUFFIX = " ON CONFLICT DO NOTHING" 63 | local GET_META = "SELECT sid, exp FROM %s JOIN %s USING (sid) WHERE aud = '%s' AND sub = '%s' AND exp >= TO_TIMESTAMP(%d)" 64 | local GET = "SELECT data FROM %s WHERE sid = '%s' AND exp >= TO_TIMESTAMP(%d) AT TIME ZONE 'UTC'" 65 | local EXPIRE = "UPDATE %s SET exp = TO_TIMESTAMP(%d) AT TIME ZONE 'UTC' WHERE sid = '%s' AND exp > TO_TIMESTAMP(%d) AT TIME ZONE 'UTC'" 66 | local DELETE = "DELETE FROM %s WHERE sid = '%s'" 67 | local CLEANUP = "DELETE FROM %s WHERE exp < TO_TIMESTAMP(%d)" 68 | 69 | 70 | local SQL = buffer.new() 71 | local STM_DELIM = ";\n" 72 | local VAL_DELIM = ", " 73 | 74 | 75 | local CLEANUP_PROBABILITY = 0.001 -- 1 / 1000 76 | 77 | 78 | local function exec(self, query) 79 | local pg = pgmoon.new(self.options) 80 | 81 | local connect_timeout = self.connect_timeout 82 | local send_timeout = self.send_timeout 83 | local read_timeout = self.read_timeout 84 | if connect_timeout or send_timeout or read_timeout then 85 | if pg.sock and pg.sock.settimeouts then 86 | pg.sock:settimeouts(connect_timeout, send_timeout, read_timeout) 87 | else 88 | pg:settimeout(connect_timeout) 89 | end 90 | end 91 | 92 | local ok, err = pg:connect() 93 | if not ok then 94 | return nil, err 95 | end 96 | 97 | ok, err = pg:query(query) 98 | 99 | if not pg:keepalive(self.keepalive_timeout) then 100 | pg:close() 101 | end 102 | 103 | return ok, err 104 | end 105 | 106 | 107 | --- 108 | -- Storage 109 | -- @section instance 110 | 111 | 112 | local metatable = {} 113 | 114 | 115 | metatable.__index = metatable 116 | 117 | 118 | function metatable.__newindex() 119 | error("attempt to update a read-only table", 2) 120 | end 121 | 122 | 123 | --- 124 | -- Store session data. 125 | -- 126 | -- @function instance:set 127 | -- @tparam string name cookie name 128 | -- @tparam string key session key 129 | -- @tparam string value session value 130 | -- @tparam number ttl session ttl 131 | -- @tparam number current_time current time 132 | -- @tparam[opt] string old_key old session id 133 | -- @tparam string stale_ttl stale ttl 134 | -- @tparam[opt] table metadata table of metadata 135 | -- @treturn true|nil ok 136 | -- @treturn string error message 137 | function metatable:set(name, key, value, ttl, current_time, old_key, stale_ttl, metadata) 138 | local table = self.table 139 | local exp = ttl + current_time 140 | 141 | if not metadata and not old_key then 142 | return exec(self, fmt(SET, table, key, name, value, exp)) 143 | end 144 | 145 | SQL:reset():putf(SET, table, key, name, value, exp) 146 | 147 | if old_key then 148 | local stale_exp = stale_ttl + current_time 149 | SQL:put(STM_DELIM):putf(EXPIRE, table, stale_exp, old_key, stale_exp) 150 | end 151 | 152 | local table_meta = self.table_meta 153 | if metadata then 154 | local audiences = metadata.audiences 155 | local subjects = metadata.subjects 156 | local count = #audiences 157 | 158 | SQL:put(STM_DELIM):putf(SET_META_PREFIX, table_meta) 159 | 160 | for i = 1, count do 161 | if i > 1 then 162 | SQL:put(VAL_DELIM) 163 | end 164 | SQL:putf(SET_META_VALUES, audiences[i], subjects[i], key) 165 | end 166 | 167 | SQL:putf(SET_META_SUFFIX) 168 | end 169 | 170 | if random() < CLEANUP_PROBABILITY then 171 | SQL:put(STM_DELIM):putf(CLEANUP, self.table, current_time) 172 | end 173 | 174 | return exec(self, SQL:get()) 175 | end 176 | 177 | 178 | --- 179 | -- Retrieve session data. 180 | -- 181 | -- @function instance:get 182 | -- @tparam string name cookie name 183 | -- @tparam string key session key 184 | -- @treturn string|nil session data 185 | -- @treturn string error message 186 | function metatable:get(name, key, current_time) -- luacheck: ignore 187 | local res, err = exec(self, fmt(GET, self.table, key, current_time)) 188 | if not res then 189 | return nil, err 190 | end 191 | 192 | local row = res[1] 193 | if not row then 194 | return nil 195 | end 196 | 197 | local data = row.data 198 | if not row.data then 199 | return nil 200 | end 201 | 202 | return data 203 | end 204 | 205 | 206 | --- 207 | -- Delete session data. 208 | -- 209 | -- @function instance:delete 210 | -- @tparam string name cookie name 211 | -- @tparam string key session key 212 | -- @tparam[opt] table metadata session meta data 213 | -- @treturn boolean|nil session data 214 | -- @treturn string error message 215 | function metatable:delete(name, key, current_time, metadata) -- luacheck: ignore 216 | SQL:reset():putf(DELETE, self.table, key) 217 | 218 | if random() < CLEANUP_PROBABILITY then 219 | SQL:put(STM_DELIM):putf(CLEANUP, self.table, current_time) 220 | end 221 | 222 | return exec(self, SQL:get()) 223 | end 224 | 225 | 226 | --- 227 | -- Read session metadata. 228 | -- 229 | -- @function instance:read_metadata 230 | -- @tparam string name cookie name 231 | -- @tparam string audience session key 232 | -- @tparam string subject session key 233 | -- @tparam number current_time current time 234 | -- @treturn table|nil session metadata 235 | -- @treturn string error message 236 | function metatable:read_metadata(name, audience, subject, current_time) -- luacheck: ignore 237 | local t = exec(self, fmt(GET_META, self.table_meta, self.table, audience, subject, current_time)) 238 | if not t then 239 | return nil, "not found" 240 | end 241 | 242 | local res = {} 243 | for _, v in ipairs(t) do 244 | local key = v.sid 245 | if key then 246 | res[key] = v.exp 247 | end 248 | end 249 | return res 250 | end 251 | 252 | 253 | local storage = {} 254 | 255 | 256 | --- 257 | -- Configuration 258 | -- @section configuration 259 | 260 | 261 | --- 262 | -- Postgres storage backend configuration 263 | -- @field host The host to connect (defaults to `"127.0.0.1"`). 264 | -- @field port The port to connect (defaults to `5432`). 265 | -- @field application Set the name of the connection as displayed in pg_stat_activity (defaults to `"pgmoon"`). 266 | -- @field username The database username to authenticate (defaults to `"postgres"`). 267 | -- @field password Password for authentication, may be required depending on server configuration. 268 | -- @field database The database name to connect. 269 | -- @field table_name Name of database table to which to store session data (can be `database schema` prefixed) (defaults to `"sessions"`). 270 | -- @field table_name_meta Name of database meta data table to which to store session meta data (can be `database schema` prefixed) (defaults to `"sessions_meta"`). 271 | -- @field connect_timeout Controls the default timeout value used in TCP/unix-domain socket object's `connect` method. 272 | -- @field send_timeout Controls the default timeout value used in TCP/unix-domain socket object's `send` method. 273 | -- @field read_timeout Controls the default timeout value used in TCP/unix-domain socket object's `receive` method. 274 | -- @field keepalive_timeout Controls the default maximal idle time of the connections in the connection pool. 275 | -- @field pool A custom name for the connection pool being used. 276 | -- @field pool_size The size of the connection pool. 277 | -- @field backlog A queue size to use when the connection pool is full (configured with @pool_size). 278 | -- @field ssl Enable SSL (defaults to `false`). 279 | -- @field ssl_verify Verify server certificate (defaults to `nil`). 280 | -- @field ssl_required Abort the connection if the server does not support SSL connections (defaults to `nil`). 281 | -- @table configuration 282 | 283 | 284 | --- 285 | -- Constructors 286 | -- @section constructors 287 | 288 | 289 | --- 290 | -- Create a Postgres storage. 291 | -- 292 | -- This creates a new Postgres storage instance. 293 | -- 294 | -- @function module.new 295 | -- @tparam[opt] table configuration postgres storage @{configuration} 296 | -- @treturn table postgres storage instance 297 | function storage.new(configuration) 298 | local host = configuration and configuration.host or DEFAULT_HOST 299 | local port = configuration and configuration.port or DEFAULT_PORT 300 | 301 | local application = configuration and configuration.application 302 | local username = configuration and configuration.username 303 | local password = configuration and configuration.password 304 | local database = configuration and configuration.database 305 | 306 | local table_name = configuration and configuration.table or DEFAULT_TABLE 307 | local table_name_meta = configuration and configuration.table_meta 308 | 309 | local connect_timeout = configuration and configuration.connect_timeout 310 | local send_timeout = configuration and configuration.send_timeout 311 | local read_timeout = configuration and configuration.read_timeout 312 | local keepalive_timeout = configuration and configuration.keepalive_timeout 313 | 314 | local pool = configuration and configuration.pool 315 | local pool_size = configuration and configuration.pool_size 316 | local backlog = configuration and configuration.backlog 317 | local ssl = configuration and configuration.ssl 318 | local ssl_verify = configuration and configuration.ssl_verify 319 | local ssl_required = configuration and configuration.ssl_required 320 | 321 | return setmetatable({ 322 | table = table_name, 323 | table_meta = table_name_meta or (table_name .. "_meta"), 324 | connect_timeout = connect_timeout, 325 | send_timeout = send_timeout, 326 | read_timeout = read_timeout, 327 | keepalive_timeout = keepalive_timeout, 328 | options = { 329 | host = host, 330 | port = port, 331 | application_name = application, 332 | user = username, 333 | password = password, 334 | database = database, 335 | socket_type = "nginx", 336 | pool = pool, 337 | pool_size = pool_size, 338 | backlog = backlog, 339 | ssl = ssl, 340 | ssl_verify = ssl_verify, 341 | ssl_required = ssl_required, 342 | } 343 | }, metatable) 344 | end 345 | 346 | 347 | return storage 348 | -------------------------------------------------------------------------------- /docs/modules/resty.session.file.utils.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | Session Library for OpenResty Documentation 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 59 | 60 |
61 | 62 |

Module resty.session.file.utils

63 |

File storage utilities

64 |

65 | 66 |

67 | 68 | 69 |

Functions

70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 |
file_create (path, content)Store data in file.
file_append (path, data)Append data in file.
file_read (path)Read data from a file.
get_modification (path)Get the value modification time of a file.
meta_get_key (audience, subject)Given an audience and a subject, generate a metadata key.
validate_file_name (prefix, suffix, name, filename)Validate a file name.
cleanup (path, prefix, suffix, name, current_time)Clean up expired session and metadata files.
100 | 101 |
102 |
103 | 104 | 105 |

Functions

106 | 107 |
108 |
109 | 110 | file_create (path, content) 111 |
112 |
113 | Store data in file. 114 | 115 | 116 |

Parameters:

117 |
    118 |
  • path 119 | string 120 | file path 121 |
  • 122 |
  • content 123 | string 124 | file content 125 |
  • 126 |
127 | 128 |

Returns:

129 |
    130 |
  1. 131 | true or nil 132 | ok
  2. 133 |
  3. 134 | string 135 | error message
  4. 136 |
137 | 138 | 139 | 140 | 141 |
142 |
143 | 144 | file_append (path, data) 145 |
146 |
147 | Append data in file. 148 | 149 | 150 |

Parameters:

151 |
    152 |
  • path 153 | string 154 | file path 155 |
  • 156 |
  • data 157 | string 158 | file data 159 |
  • 160 |
161 | 162 |

Returns:

163 |
    164 |
  1. 165 | true or nil 166 | ok
  2. 167 |
  3. 168 | string 169 | error message
  4. 170 |
171 | 172 | 173 | 174 | 175 |
176 |
177 | 178 | file_read (path) 179 |
180 |
181 | Read data from a file. 182 | 183 | 184 |

Parameters:

185 |
    186 |
  • path 187 | string 188 | file to read 189 |
  • 190 |
191 | 192 |

Returns:

193 |
    194 |
  1. 195 | string or nil 196 | content
  2. 197 |
  3. 198 | string 199 | error message
  4. 200 |
201 | 202 | 203 | 204 | 205 |
206 |
207 | 208 | get_modification (path) 209 |
210 |
211 | Get the value modification time of a file. 212 | 213 | 214 |

Parameters:

215 |
    216 |
  • path 217 | string 218 | the path to the file 219 |
  • 220 |
221 | 222 | 223 | 224 | 225 | 226 |
227 |
228 | 229 | meta_get_key (audience, subject) 230 |
231 |
232 | Given an audience and a subject, generate a metadata key. 233 | 234 | 235 |

Parameters:

236 |
    237 |
  • audience 238 | string 239 | session audience 240 |
  • 241 |
  • subject 242 | string 243 | session subject 244 |
  • 245 |
246 | 247 |

Returns:

248 |
    249 | 250 | string 251 | metadata key 252 |
253 | 254 | 255 | 256 | 257 |
258 |
259 | 260 | validate_file_name (prefix, suffix, name, filename) 261 |
262 |
263 | Validate a file name. 264 | Run a few checks to try to determine if the file is managed by this library 265 | 266 | 267 |

Parameters:

268 |
    269 |
  • prefix 270 | string 271 | the prefix for session files 272 |
  • 273 |
  • suffix 274 | string 275 | the suffix for session files 276 |
  • 277 |
  • name 278 | string 279 | cookie name 280 |
  • 281 |
  • filename 282 | string 283 | the name of the file 284 |
  • 285 |
286 | 287 |

Returns:

288 |
    289 | 290 | true or false 291 | whether the file is managed by the library or not 292 |
293 | 294 | 295 | 296 | 297 |
298 |
299 | 300 | cleanup (path, prefix, suffix, name, current_time) 301 |
302 |
303 | Clean up expired session and metadata files. 304 | 305 | 306 |

Parameters:

307 |
    308 |
  • path 309 | string 310 | the path where sessions are stored 311 |
  • 312 |
  • prefix 313 | string 314 | the prefix for session files 315 |
  • 316 |
  • suffix 317 | string 318 | the suffix for session files 319 |
  • 320 |
  • name 321 | string 322 | cookie name 323 |
  • 324 |
  • current_time 325 | number 326 | current time 327 |
  • 328 |
329 | 330 |

Returns:

331 |
    332 | 333 | true or false 334 | whether clean up completed 335 |
336 | 337 | 338 | 339 | 340 |
341 |
342 | 343 | 344 |
345 |
346 |
347 | generated by LDoc 1.5.0 348 | Last updated 2025-11-24 15:25:35 349 |
350 |
351 | 352 | 353 | -------------------------------------------------------------------------------- /lib/resty/session/memcached.lua: -------------------------------------------------------------------------------- 1 | --- 2 | -- Memcached backend for session library 3 | -- 4 | -- @module resty.session.memcached 5 | 6 | 7 | local memcached = require "resty.memcached" 8 | local buffer = require "string.buffer" 9 | local utils = require "resty.session.utils" 10 | 11 | 12 | local meta_get_latest = utils.meta_get_latest 13 | local meta_get_value = utils.meta_get_value 14 | local get_name = utils.get_name 15 | local errmsg = utils.errmsg 16 | 17 | 18 | local setmetatable = setmetatable 19 | local random = math.random 20 | local error = error 21 | local pairs = pairs 22 | local null = ngx.null 23 | local log = ngx.log 24 | local max = math.max 25 | 26 | 27 | local WARN = ngx.WARN 28 | 29 | 30 | local CLEANUP_PROBABILITY = 0.1 -- 1 / 10 31 | local SESSIONS_BUFFER = buffer.new(128) 32 | 33 | 34 | local function cleanup(memc, meta_key, current_time) 35 | local res, _, cas_unique, err = memc:gets(meta_key) 36 | if not res then 37 | return nil, err 38 | end 39 | 40 | local sessions = meta_get_latest(res, current_time) 41 | 42 | SESSIONS_BUFFER:reset() 43 | 44 | local max_expiry = current_time 45 | for key, exp in pairs(sessions) do 46 | SESSIONS_BUFFER:put(meta_get_value(key, exp)) 47 | max_expiry = max(max_expiry, exp) 48 | end 49 | 50 | local exp = max_expiry - current_time 51 | if exp > 0 then 52 | return memc:cas(meta_key, SESSIONS_BUFFER:get(), cas_unique, exp) 53 | end 54 | 55 | return memc:delete(meta_key) 56 | end 57 | 58 | 59 | local function read_metadata(self, memc, name, audience, subject, current_time) 60 | local meta_key = get_name(self, name, audience, subject) 61 | local res, _, err = memc:get(meta_key) 62 | if not res then 63 | return nil, err 64 | end 65 | 66 | return meta_get_latest(res, current_time) 67 | end 68 | 69 | 70 | -- TODO possible improvement: when available in the lib, use pipelines 71 | local function SET(self, memc, name, key, value, ttl, current_time, old_key, stale_ttl, metadata) 72 | local inferred_key = get_name(self, name, key) 73 | 74 | if not metadata and not old_key then 75 | return memc:set(inferred_key, value, ttl) 76 | end 77 | 78 | local ok, err = memc:set(inferred_key, value, ttl) 79 | if not ok then 80 | return nil, err 81 | end 82 | 83 | local old_name = old_key and get_name(self, name, old_key) 84 | if old_name then 85 | memc:touch(old_name, stale_ttl) 86 | end 87 | 88 | if not metadata then 89 | return true 90 | end 91 | 92 | local audiences = metadata.audiences 93 | local subjects = metadata.subjects 94 | local count = #audiences 95 | for i = 1, count do 96 | local meta_key = get_name(self, name, audiences[i], subjects[i]) 97 | local meta_value = meta_get_value(key, current_time + ttl) 98 | 99 | local added, err = memc:add(meta_key, meta_value) 100 | if not added then 101 | local appended, err2 = memc:append(meta_key, meta_value) 102 | if not appended then 103 | log(WARN, "[session] ", errmsg(err2 or err, "failed to store metadata")) 104 | end 105 | end 106 | 107 | if old_key then 108 | meta_value = meta_get_value(old_key, 0) 109 | local ok, err = memc:append(meta_key, meta_value) 110 | if not ok then 111 | log(WARN, "[session] ", errmsg(err, "failed to update metadata")) 112 | end 113 | end 114 | 115 | -- no need to clean up every time we write 116 | -- it is just beneficial when a key is used a lot 117 | if random() < CLEANUP_PROBABILITY then 118 | cleanup(memc, meta_key, current_time) 119 | end 120 | end 121 | 122 | return true 123 | end 124 | 125 | 126 | local function GET(self, memc, name, key) 127 | local res, _, err = memc:get(get_name(self, name, key)) 128 | if err then 129 | return nil, err 130 | end 131 | return res 132 | end 133 | 134 | 135 | local function DELETE(self, memc, name, key, current_time, metadata) 136 | local key_name = get_name(self, name, key) 137 | local ok, err = memc:delete(key_name) 138 | if not metadata then 139 | return ok, err 140 | end 141 | 142 | local audiences = metadata.audiences 143 | local subjects = metadata.subjects 144 | local count = #audiences 145 | for i = 1, count do 146 | local meta_key = get_name(self, name, audiences[i], subjects[i]) 147 | local meta_value = meta_get_value(key, 0) 148 | local ok, err = memc:append(meta_key, meta_value) 149 | if not ok and err ~= "NOT_STORED" then 150 | log(WARN, "[session] ", errmsg(err, "failed to update metadata")) 151 | end 152 | 153 | cleanup(memc, meta_key, current_time) 154 | end 155 | 156 | return ok, err 157 | end 158 | 159 | 160 | local DEFAULT_HOST = "127.0.0.1" 161 | local DEFAULT_PORT = 11211 162 | 163 | 164 | local function exec(self, func, ...) 165 | local memc = memcached:new() 166 | 167 | local connect_timeout = self.connect_timeout 168 | local send_timeout = self.send_timeout 169 | local read_timeout = self.read_timeout 170 | if connect_timeout or send_timeout or read_timeout then 171 | memc:set_timeouts(connect_timeout, send_timeout, read_timeout) 172 | end 173 | 174 | local ok, err do 175 | local socket = self.socket 176 | if socket then 177 | ok, err = memc:connect(socket, self.options) 178 | else 179 | ok, err = memc:connect(self.host, self.port, self.options) 180 | end 181 | end 182 | if not ok then 183 | return nil, err 184 | end 185 | 186 | if self.ssl and memc:get_reused_times() == 0 then 187 | ok, err = memc:sslhandshake(false, self.server_name, self.ssl_verify) 188 | if not ok then 189 | memc:close() 190 | return nil, err 191 | end 192 | end 193 | 194 | ok, err = func(self, memc, ...) 195 | 196 | if err then 197 | memc:close() 198 | return nil, err 199 | end 200 | 201 | if not memc:set_keepalive(self.keepalive_timeout) then 202 | memc:close() 203 | end 204 | 205 | if ok == null then 206 | ok = nil 207 | end 208 | 209 | return ok, err 210 | end 211 | 212 | 213 | --- 214 | -- Storage 215 | -- @section instance 216 | 217 | 218 | local metatable = {} 219 | 220 | 221 | metatable.__index = metatable 222 | 223 | 224 | function metatable.__newindex() 225 | error("attempt to update a read-only table", 2) 226 | end 227 | 228 | 229 | --- 230 | -- Store session data. 231 | -- 232 | -- @function instance:set 233 | -- @tparam string name cookie name 234 | -- @tparam string key session key 235 | -- @tparam string value session value 236 | -- @tparam number ttl session ttl 237 | -- @tparam number current_time current time 238 | -- @tparam[opt] string old_key old session id 239 | -- @tparam string stale_ttl stale ttl 240 | -- @tparam[opt] table metadata table of metadata 241 | -- @treturn true|nil ok 242 | -- @treturn string error message 243 | function metatable:set(...) 244 | return exec(self, SET, ...) 245 | end 246 | 247 | 248 | --- 249 | -- Retrieve session data. 250 | -- 251 | -- @function instance:get 252 | -- @tparam string name cookie name 253 | -- @tparam string key session key 254 | -- @treturn string|nil session data 255 | -- @treturn string error message 256 | function metatable:get(...) 257 | return exec(self, GET, ...) 258 | end 259 | 260 | 261 | --- 262 | -- Delete session data. 263 | -- 264 | -- @function instance:delete 265 | -- @tparam string name cookie name 266 | -- @tparam string key session key 267 | -- @tparam[opt] table metadata session meta data 268 | -- @treturn boolean|nil session data 269 | -- @treturn string error message 270 | function metatable:delete(...) 271 | return exec(self, DELETE, ...) 272 | end 273 | 274 | 275 | --- 276 | -- Read session metadata. 277 | -- 278 | -- @function instance:read_metadata 279 | -- @tparam string name cookie name 280 | -- @tparam string audience session key 281 | -- @tparam string subject session key 282 | -- @tparam number current_time current time 283 | -- @treturn table|nil session metadata 284 | -- @treturn string error message 285 | function metatable:read_metadata(...) 286 | return exec(self, read_metadata, ...) 287 | end 288 | 289 | 290 | local storage = {} 291 | 292 | 293 | --- 294 | -- Configuration 295 | -- @section configuration 296 | 297 | 298 | --- 299 | -- Distributed shared memory storage backend configuration 300 | -- @field prefix Prefix for the keys stored in memcached. 301 | -- @field suffix Suffix for the keys stored in memcached. 302 | -- @field host The host to connect (defaults to `"127.0.0.1"`). 303 | -- @field port The port to connect (defaults to `11211`). 304 | -- @field socket The socket file to connect to (defaults to `nil`). 305 | -- @field connect_timeout Controls the default timeout value used in TCP/unix-domain socket object's `connect` method. 306 | -- @field send_timeout Controls the default timeout value used in TCP/unix-domain socket object's `send` method. 307 | -- @field read_timeout Controls the default timeout value used in TCP/unix-domain socket object's `receive` method. 308 | -- @field keepalive_timeout Controls the default maximal idle time of the connections in the connection pool. 309 | -- @field pool A custom name for the connection pool being used. 310 | -- @field pool_size The size of the connection pool. 311 | -- @field backlog A queue size to use when the connection pool is full (configured with @pool_size). 312 | -- @field ssl Enable SSL (defaults to `false`). 313 | -- @field ssl_verify Verify server certificate (defaults to `nil`). 314 | -- @field server_name The server name for the new TLS extension Server Name Indication (SNI). 315 | -- @table configuration 316 | 317 | 318 | --- 319 | -- Constructors 320 | -- @section constructors 321 | 322 | 323 | --- 324 | -- Create a memcached storage. 325 | -- 326 | -- This creates a new memcached storage instance. 327 | -- 328 | -- @function module.new 329 | -- @tparam[opt] table configuration memcached storage @{configuration} 330 | -- @treturn table memcached storage instance 331 | function storage.new(configuration) 332 | local prefix = configuration and configuration.prefix 333 | local suffix = configuration and configuration.suffix 334 | 335 | local host = configuration and configuration.host or DEFAULT_HOST 336 | local port = configuration and configuration.port or DEFAULT_PORT 337 | local socket = configuration and configuration.socket 338 | 339 | local connect_timeout = configuration and configuration.connect_timeout 340 | local send_timeout = configuration and configuration.send_timeout 341 | local read_timeout = configuration and configuration.read_timeout 342 | local keepalive_timeout = configuration and configuration.keepalive_timeout 343 | 344 | local pool = configuration and configuration.pool 345 | local pool_size = configuration and configuration.pool_size 346 | local backlog = configuration and configuration.backlog 347 | local ssl = configuration and configuration.ssl 348 | local ssl_verify = configuration and configuration.ssl_verify 349 | local server_name = configuration and configuration.server_name 350 | 351 | if pool or pool_size or backlog then 352 | return setmetatable({ 353 | prefix = prefix, 354 | suffix = suffix, 355 | host = host, 356 | port = port, 357 | socket = socket, 358 | connect_timeout = connect_timeout, 359 | send_timeout = send_timeout, 360 | read_timeout = read_timeout, 361 | keepalive_timeout = keepalive_timeout, 362 | ssl = ssl, 363 | ssl_verify = ssl_verify, 364 | server_name = server_name, 365 | options = { 366 | pool = pool, 367 | pool_size = pool_size, 368 | backlog = backlog, 369 | } 370 | }, metatable) 371 | end 372 | 373 | return setmetatable({ 374 | prefix = prefix, 375 | suffix = suffix, 376 | host = host, 377 | port = port, 378 | socket = socket, 379 | connect_timeout = connect_timeout, 380 | send_timeout = send_timeout, 381 | read_timeout = read_timeout, 382 | keepalive_timeout = keepalive_timeout, 383 | ssl = ssl, 384 | ssl_verify = ssl_verify, 385 | server_name = server_name, 386 | }, metatable) 387 | end 388 | 389 | 390 | return storage 391 | -------------------------------------------------------------------------------- /docs/modules/resty.session.redis.common.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | Session Library for OpenResty Documentation 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 59 | 60 |
61 | 62 |

Module resty.session.redis.common

63 |

Common Redis functions shared between Redis, 64 | Redis Cluster and Redis Sentinel implementations.

65 |

66 | 67 |

68 | 69 | 70 |

Functions

71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 |
module.SET (storage, red, name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata])Store session data.
module.GET (storage, red, name, key)Retrieve session data.
module.UNLINK (storage, red, name, key, current_time[, metadata])Delete session data.
module.READ_METADATA (storage, red, name, audience, subject, current_time)Read session metadata.
89 | 90 |
91 |
92 | 93 | 94 |

Functions

95 | 96 |
97 |
98 | 99 | module.SET (storage, red, name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata]) 100 |
101 |
102 | Store session data. 103 | 104 | 105 |

Parameters:

106 |
    107 |
  • storage 108 | table 109 | the storage 110 |
  • 111 |
  • red 112 | table 113 | the redis instance 114 |
  • 115 |
  • name 116 | string 117 | the cookie name 118 |
  • 119 |
  • key 120 | string 121 | session key 122 |
  • 123 |
  • value 124 | string 125 | session value 126 |
  • 127 |
  • ttl 128 | number 129 | session ttl 130 |
  • 131 |
  • current_time 132 | number 133 | current time 134 |
  • 135 |
  • old_key 136 | string 137 | old session id 138 | (optional) 139 |
  • 140 |
  • stale_ttl 141 | string 142 | stale ttl 143 |
  • 144 |
  • metadata 145 | table 146 | table of metadata 147 | (optional) 148 |
  • 149 |
150 | 151 |

Returns:

152 |
    153 |
  1. 154 | true or nil 155 | ok
  2. 156 |
  3. 157 | string 158 | error message
  4. 159 |
160 | 161 | 162 | 163 | 164 |
165 |
166 | 167 | module.GET (storage, red, name, key) 168 |
169 |
170 | Retrieve session data. 171 | 172 | 173 |

Parameters:

174 |
    175 |
  • storage 176 | table 177 | the storage 178 |
  • 179 |
  • red 180 | table 181 | the redis instance 182 |
  • 183 |
  • name 184 | string 185 | cookie name 186 |
  • 187 |
  • key 188 | string 189 | session key 190 |
  • 191 |
192 | 193 |

Returns:

194 |
    195 |
  1. 196 | string or nil 197 | session data
  2. 198 |
  3. 199 | string 200 | error message
  4. 201 |
202 | 203 | 204 | 205 | 206 |
207 |
208 | 209 | module.UNLINK (storage, red, name, key, current_time[, metadata]) 210 |
211 |
212 | Delete session data. 213 | 214 | 215 |

Parameters:

216 |
    217 |
  • storage 218 | table 219 | the storage 220 |
  • 221 |
  • red 222 | table 223 | the redis instance 224 |
  • 225 |
  • name 226 | string 227 | cookie name 228 |
  • 229 |
  • key 230 | string 231 | session key 232 |
  • 233 |
  • current_time 234 | number 235 | current time 236 |
  • 237 |
  • metadata 238 | table 239 | session meta data 240 | (optional) 241 |
  • 242 |
243 | 244 |

Returns:

245 |
    246 |
  1. 247 | boolean or nil 248 | session data
  2. 249 |
  3. 250 | string 251 | error message
  4. 252 |
253 | 254 | 255 | 256 | 257 |
258 |
259 | 260 | module.READ_METADATA (storage, red, name, audience, subject, current_time) 261 |
262 |
263 | Read session metadata. 264 | 265 | 266 |

Parameters:

267 |
    268 |
  • storage 269 | table 270 | the storage 271 |
  • 272 |
  • red 273 | table 274 | the redis instance 275 |
  • 276 |
  • name 277 | string 278 | cookie name 279 |
  • 280 |
  • audience 281 | string 282 | session key 283 |
  • 284 |
  • subject 285 | string 286 | session key 287 |
  • 288 |
  • current_time 289 | number 290 | current time 291 |
  • 292 |
293 | 294 |

Returns:

295 |
    296 |
  1. 297 | table or nil 298 | session metadata
  2. 299 |
  3. 300 | string 301 | error message
  4. 302 |
303 | 304 | 305 | 306 | 307 |
308 |
309 | 310 | 311 |
312 |
313 |
314 | generated by LDoc 1.5.0 315 | Last updated 2025-11-24 15:25:35 316 |
317 |
318 | 319 | 320 | -------------------------------------------------------------------------------- /lib/resty/session/mysql.lua: -------------------------------------------------------------------------------- 1 | --- 2 | -- MySQL / MariaDB backend for session library 3 | -- 4 | -- @module resty.session.mysql 5 | 6 | 7 | --- 8 | -- Database 9 | -- @section database 10 | 11 | 12 | --- 13 | -- Sessions table. 14 | -- 15 | -- Database table that stores session data. 16 | -- 17 | -- @usage 18 | -- CREATE TABLE IF NOT EXISTS sessions ( 19 | -- sid CHAR(43) PRIMARY KEY, 20 | -- name VARCHAR(255), 21 | -- data MEDIUMTEXT, 22 | -- exp DATETIME, 23 | -- INDEX (exp) 24 | -- ) CHARACTER SET ascii; 25 | -- @table sessions 26 | 27 | 28 | --- 29 | -- Sessions metadata table. 30 | -- 31 | -- This is only needed if you want to store session metadata. 32 | -- 33 | -- @usage 34 | -- CREATE TABLE IF NOT EXISTS sessions_meta ( 35 | -- aud VARCHAR(255), 36 | -- sub VARCHAR(255), 37 | -- sid CHAR(43), 38 | -- PRIMARY KEY (aud, sub, sid), 39 | -- CONSTRAINT FOREIGN KEY (sid) REFERENCES sessions(sid) ON DELETE CASCADE ON UPDATE CASCADE 40 | -- ) CHARACTER SET ascii; 41 | -- @table metadata 42 | 43 | 44 | local buffer = require "string.buffer" 45 | local mysql = require "resty.mysql" 46 | 47 | 48 | local setmetatable = setmetatable 49 | local random = math.random 50 | local ipairs = ipairs 51 | local error = error 52 | local fmt = string.format 53 | 54 | 55 | local DEFAULT_HOST = "127.0.0.1" 56 | local DEFAULT_PORT = 3306 57 | local DEFAULT_TABLE = "sessions" 58 | local DEFAULT_CHARSET = "ascii" 59 | 60 | 61 | local SET = "INSERT INTO %s (sid, name, data, exp) VALUES ('%s', '%s', '%s', FROM_UNIXTIME(%d)) AS new ON DUPLICATE KEY UPDATE data = new.data" 62 | local SET_META_PREFIX = "INSERT INTO %s (aud, sub, sid) VALUES " 63 | local SET_META_VALUES = "('%s', '%s', '%s')" 64 | local SET_META_SUFFIX = " ON DUPLICATE KEY UPDATE sid = sid" 65 | local GET_META = "SELECT sid, exp FROM %s JOIN %s USING (sid) WHERE aud = '%s' AND sub = '%s' AND exp >= FROM_UNIXTIME(%d)" 66 | local GET = "SELECT data FROM %s WHERE sid = '%s' AND exp >= FROM_UNIXTIME(%d)" 67 | local EXPIRE = "UPDATE %s SET exp = FROM_UNIXTIME(%d) WHERE sid = '%s' AND exp > FROM_UNIXTIME(%d)" 68 | local DELETE = "DELETE FROM %s WHERE sid = '%s'" 69 | local CLEANUP = "DELETE FROM %s WHERE exp < FROM_UNIXTIME(%d)" 70 | 71 | 72 | local SQL = buffer.new() 73 | local STM_DELIM = ";\n" 74 | local VAL_DELIM = ", " 75 | 76 | 77 | local CLEANUP_PROBABILITY = 0.001 -- 1 / 1000 78 | 79 | 80 | local function exec(self, query) 81 | local my = mysql:new() 82 | 83 | local connect_timeout = self.connect_timeout 84 | local send_timeout = self.send_timeout 85 | local read_timeout = self.read_timeout 86 | if connect_timeout or send_timeout or read_timeout then 87 | if my.sock and my.sock.settimeouts then 88 | my.sock:settimeouts(connect_timeout, send_timeout, read_timeout) 89 | else 90 | my:set_timeout(connect_timeout) 91 | end 92 | end 93 | 94 | local ok, err = my:connect(self.options) 95 | if not ok then 96 | return nil, err 97 | end 98 | 99 | ok, err = my:query(query) 100 | 101 | if not my:set_keepalive(self.keepalive_timeout) then 102 | my:close() 103 | end 104 | 105 | return ok, err 106 | end 107 | 108 | 109 | --- 110 | -- Storage 111 | -- @section instance 112 | 113 | 114 | local metatable = {} 115 | 116 | 117 | metatable.__index = metatable 118 | 119 | 120 | function metatable.__newindex() 121 | error("attempt to update a read-only table", 2) 122 | end 123 | 124 | 125 | --- 126 | -- Store session data. 127 | -- 128 | -- @function instance:set 129 | -- @tparam string name cookie name 130 | -- @tparam string key session key 131 | -- @tparam string value session value 132 | -- @tparam number ttl session ttl 133 | -- @tparam number current_time current time 134 | -- @tparam[opt] string old_key old session id 135 | -- @tparam string stale_ttl stale ttl 136 | -- @tparam[opt] table metadata table of metadata 137 | -- @treturn true|nil ok 138 | -- @treturn string error message 139 | function metatable:set(name, key, value, ttl, current_time, old_key, stale_ttl, metadata) 140 | local table = self.table 141 | local exp = ttl + current_time 142 | 143 | if not metadata and not old_key then 144 | return exec(self, fmt(SET, table, key, name, value, exp)) 145 | end 146 | 147 | SQL:reset():putf(SET, table, key, name, value, exp) 148 | 149 | if old_key then 150 | local stale_exp = stale_ttl + current_time 151 | SQL:put(STM_DELIM):putf(EXPIRE, table, stale_exp, old_key, stale_exp) 152 | end 153 | 154 | local table_meta = self.table_meta 155 | if metadata then 156 | local audiences = metadata.audiences 157 | local subjects = metadata.subjects 158 | local count = #audiences 159 | 160 | SQL:put(STM_DELIM):putf(SET_META_PREFIX, table_meta) 161 | 162 | for i = 1, count do 163 | if i > 1 then 164 | SQL:put(VAL_DELIM) 165 | end 166 | SQL:putf(SET_META_VALUES, audiences[i], subjects[i], key) 167 | end 168 | 169 | SQL:putf(SET_META_SUFFIX) 170 | end 171 | 172 | if random() < CLEANUP_PROBABILITY then 173 | SQL:put(STM_DELIM):putf(CLEANUP, self.table, current_time) 174 | end 175 | 176 | return exec(self, SQL:get()) 177 | end 178 | 179 | 180 | --- 181 | -- Retrieve session data. 182 | -- 183 | -- @function instance:get 184 | -- @tparam string name cookie name 185 | -- @tparam string key session key 186 | -- @treturn string|nil session data 187 | -- @treturn string error message 188 | function metatable:get(name, key, current_time) -- luacheck: ignore 189 | local res, err = exec(self, fmt(GET, self.table, key, current_time)) 190 | if not res then 191 | return nil, err 192 | end 193 | 194 | local row = res[1] 195 | if not row then 196 | return nil, "session not found" 197 | end 198 | 199 | local data = row.data 200 | if not row.data then 201 | return nil, "session not found" 202 | end 203 | 204 | return data 205 | end 206 | 207 | 208 | --- 209 | -- Delete session data. 210 | -- 211 | -- @function instance:delete 212 | -- @tparam string name cookie name 213 | -- @tparam string key session key 214 | -- @tparam[opt] table metadata session meta data 215 | -- @treturn boolean|nil session data 216 | -- @treturn string error message 217 | function metatable:delete(name, key, current_time, metadata) -- luacheck: ignore 218 | SQL:reset():putf(DELETE, self.table, key) 219 | 220 | if random() < CLEANUP_PROBABILITY then 221 | SQL:put(STM_DELIM):putf(CLEANUP, self.table, current_time) 222 | end 223 | 224 | return exec(self, SQL:get()) 225 | end 226 | 227 | 228 | --- 229 | -- Read session metadata. 230 | -- 231 | -- @function instance:read_metadata 232 | -- @tparam string name cookie name 233 | -- @tparam string audience session key 234 | -- @tparam string subject session key 235 | -- @tparam number current_time current time 236 | -- @treturn table|nil session metadata 237 | -- @treturn string error message 238 | function metatable:read_metadata(name, audience, subject, current_time) -- luacheck: ignore 239 | local res = {} 240 | local t = exec(self, fmt(GET_META, self.table_meta, self.table, audience, subject, current_time)) 241 | if not t then 242 | return nil, "not found" 243 | end 244 | 245 | for _, v in ipairs(t) do 246 | local key = v.sid 247 | if key then 248 | res[key] = v.exp 249 | end 250 | end 251 | 252 | return res 253 | end 254 | 255 | local storage = {} 256 | 257 | 258 | --- 259 | -- Configuration 260 | -- @section configuration 261 | 262 | 263 | --- 264 | -- Postgres storage backend configuration 265 | -- @field host The host to connect (defaults to `"127.0.0.1"`). 266 | -- @field port The port to connect (defaults to `3306`). 267 | -- @field socket The socket file to connect to (defaults to `nil`). 268 | -- @field username The database username to authenticate (defaults to `nil`). 269 | -- @field password Password for authentication, may be required depending on server configuration. 270 | -- @field charset The character set used on the MySQL connection (defaults to `"ascii"`). 271 | -- @field database The database name to connect. 272 | -- @field table_name Name of database table to which to store session data (defaults to `"sessions"`). 273 | -- @field table_name_meta Name of database meta data table to which to store session meta data (defaults to `"sessions_meta"`). 274 | -- @field max_packet_size The upper limit for the reply packets sent from the MySQL server (defaults to 1 MB). 275 | -- @field connect_timeout Controls the default timeout value used in TCP/unix-domain socket object's `connect` method. 276 | -- @field send_timeout Controls the default timeout value used in TCP/unix-domain socket object's `send` method. 277 | -- @field read_timeout Controls the default timeout value used in TCP/unix-domain socket object's `receive` method. 278 | -- @field keepalive_timeout Controls the default maximal idle time of the connections in the connection pool. 279 | -- @field pool A custom name for the connection pool being used. 280 | -- @field pool_size The size of the connection pool. 281 | -- @field backlog A queue size to use when the connection pool is full (configured with @pool_size). 282 | -- @field ssl Enable SSL (defaults to `false`). 283 | -- @field ssl_verify Verify server certificate (defaults to `nil`). 284 | -- @table configuration 285 | 286 | 287 | --- 288 | -- Constructors 289 | -- @section constructors 290 | 291 | 292 | --- 293 | -- Create a MySQL / MariaDB storage. 294 | -- 295 | -- This creates a new MySQL / MariaDB storage instance. 296 | -- 297 | -- @function module.new 298 | -- @tparam[opt] table configuration mysql/mariadb storage @{configuration} 299 | -- @treturn table mysql/mariadb storage instance 300 | function storage.new(configuration) 301 | local host = configuration and configuration.host or DEFAULT_HOST 302 | local port = configuration and configuration.port or DEFAULT_PORT 303 | local socket = configuration and configuration.socket 304 | 305 | local username = configuration and configuration.username 306 | local password = configuration and configuration.password 307 | local charset = configuration and configuration.charset or DEFAULT_CHARSET 308 | local database = configuration and configuration.database 309 | local max_packet_size = configuration and configuration.max_packet_size 310 | 311 | local table_name = configuration and configuration.table or DEFAULT_TABLE 312 | local table_name_meta = configuration and configuration.table_meta 313 | 314 | local connect_timeout = configuration and configuration.connect_timeout 315 | local send_timeout = configuration and configuration.send_timeout 316 | local read_timeout = configuration and configuration.read_timeout 317 | local keepalive_timeout = configuration and configuration.keepalive_timeout 318 | 319 | local pool = configuration and configuration.pool 320 | local pool_size = configuration and configuration.pool_size 321 | local backlog = configuration and configuration.backlog 322 | local ssl = configuration and configuration.ssl 323 | local ssl_verify = configuration and configuration.ssl_verify 324 | 325 | if socket then 326 | return setmetatable({ 327 | table = table_name, 328 | table_meta = table_name_meta or (table_name .. "_meta"), 329 | connect_timeout = connect_timeout, 330 | send_timeout = send_timeout, 331 | read_timeout = read_timeout, 332 | keepalive_timeout = keepalive_timeout, 333 | options = { 334 | path = socket, 335 | user = username, 336 | password = password, 337 | charset = charset, 338 | database = database, 339 | max_packet_size = max_packet_size, 340 | pool = pool, 341 | pool_size = pool_size, 342 | backlog = backlog, 343 | ssl = ssl, 344 | ssl_verify = ssl_verify, 345 | } 346 | }, metatable) 347 | end 348 | 349 | return setmetatable({ 350 | table = table_name, 351 | table_meta = table_name_meta or (table_name .. "_meta"), 352 | connect_timeout = connect_timeout, 353 | send_timeout = send_timeout, 354 | read_timeout = read_timeout, 355 | keepalive_timeout = keepalive_timeout, 356 | options = { 357 | host = host, 358 | port = port, 359 | user = username, 360 | password = password, 361 | charset = charset, 362 | database = database, 363 | max_packet_size = max_packet_size, 364 | pool = pool, 365 | pool_size = pool_size, 366 | backlog = backlog, 367 | ssl = ssl, 368 | ssl_verify = ssl_verify, 369 | } 370 | }, metatable) 371 | end 372 | 373 | 374 | return storage 375 | -------------------------------------------------------------------------------- /docs/modules/resty.session.shm.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | Session Library for OpenResty Documentation 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 61 | 62 |
63 | 64 |

Module resty.session.shm

65 |

Shared Memory (SHM) backend for session library

66 |

67 | 68 |

69 | 70 | 71 |

Configuration

72 | 73 | 74 | 75 | 76 | 77 |
configurationShared memory storage backend configuration
78 |

Constructors

79 | 80 | 81 | 82 | 83 | 84 |
module.new ([configuration])Create a SHM storage.
85 |

Storage

86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 |
instance:set (name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata])Store session data.
instance:get (name, key)Retrieve session data.
instance:delete (name, key[, metadata])Delete session data.
instance:read_metadata (name, audience, subject, current_time)Read session metadata.
104 | 105 |
106 |
107 | 108 | 109 |

Configuration

110 | 111 |
112 |
113 | 114 | configuration 115 |
116 |
117 | Shared memory storage backend configuration 118 | 119 | 120 |

Fields:

121 |
    122 |
  • prefix 123 | Prefix for the keys stored in SHM. 124 |
  • 125 |
  • suffix 126 | Suffix for the keys stored in SHM. 127 |
  • 128 |
  • zone 129 | A name of shared memory zone (defaults to sessions). 130 |
  • 131 |
132 | 133 | 134 | 135 | 136 | 137 |
138 |
139 |

Constructors

140 | 141 |
142 |
143 | 144 | module.new ([configuration]) 145 |
146 |
147 | Create a SHM storage.

148 | 149 |

This creates a new shared memory storage instance. 150 | 151 | 152 |

Parameters:

153 |
    154 |
  • configuration 155 | table 156 | shm storage configuration 157 | (optional) 158 |
  • 159 |
160 | 161 |

Returns:

162 |
    163 | 164 | table 165 | shm storage instance 166 |
167 | 168 | 169 | 170 | 171 |
172 |
173 |

Storage

174 | 175 |
176 |
177 | 178 | instance:set (name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata]) 179 |
180 |
181 | Store session data. 182 | 183 | 184 |

Parameters:

185 |
    186 |
  • name 187 | string 188 | cookie name 189 |
  • 190 |
  • key 191 | string 192 | session key 193 |
  • 194 |
  • value 195 | string 196 | session value 197 |
  • 198 |
  • ttl 199 | number 200 | session ttl 201 |
  • 202 |
  • current_time 203 | number 204 | current time 205 |
  • 206 |
  • old_key 207 | string 208 | old session id 209 | (optional) 210 |
  • 211 |
  • stale_ttl 212 | string 213 | stale ttl 214 |
  • 215 |
  • metadata 216 | table 217 | table of metadata 218 | (optional) 219 |
  • 220 |
221 | 222 |

Returns:

223 |
    224 |
  1. 225 | true or nil 226 | ok
  2. 227 |
  3. 228 | string 229 | error message
  4. 230 |
231 | 232 | 233 | 234 | 235 |
236 |
237 | 238 | instance:get (name, key) 239 |
240 |
241 | Retrieve session data. 242 | 243 | 244 |

Parameters:

245 |
    246 |
  • name 247 | string 248 | cookie name 249 |
  • 250 |
  • key 251 | string 252 | session key 253 |
  • 254 |
255 | 256 |

Returns:

257 |
    258 |
  1. 259 | string or nil 260 | session data
  2. 261 |
  3. 262 | string 263 | error message
  4. 264 |
265 | 266 | 267 | 268 | 269 |
270 |
271 | 272 | instance:delete (name, key[, metadata]) 273 |
274 |
275 | Delete session data. 276 | 277 | 278 |

Parameters:

279 |
    280 |
  • name 281 | string 282 | cookie name 283 |
  • 284 |
  • key 285 | string 286 | session key 287 |
  • 288 |
  • metadata 289 | table 290 | session meta data 291 | (optional) 292 |
  • 293 |
294 | 295 |

Returns:

296 |
    297 |
  1. 298 | boolean or nil 299 | session data
  2. 300 |
  3. 301 | string 302 | error message
  4. 303 |
304 | 305 | 306 | 307 | 308 |
309 |
310 | 311 | instance:read_metadata (name, audience, subject, current_time) 312 |
313 |
314 | Read session metadata. 315 | 316 | 317 |

Parameters:

318 |
    319 |
  • name 320 | string 321 | cookie name 322 |
  • 323 |
  • audience 324 | string 325 | session key 326 |
  • 327 |
  • subject 328 | string 329 | session key 330 |
  • 331 |
  • current_time 332 | number 333 | current time 334 |
  • 335 |
336 | 337 |

Returns:

338 |
    339 |
  1. 340 | table or nil 341 | session metadata
  2. 342 |
  3. 343 | string 344 | error message
  4. 345 |
346 | 347 | 348 | 349 | 350 |
351 |
352 | 353 | 354 |
355 |
356 |
357 | generated by LDoc 1.5.0 358 | Last updated 2025-11-24 15:25:35 359 |
360 |
361 | 362 | 363 | -------------------------------------------------------------------------------- /docs/modules/resty.session.file.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | Session Library for OpenResty Documentation 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 61 | 62 |
63 | 64 |

Module resty.session.file

65 |

File storage backend for session library.

66 |

67 | 68 |

69 | 70 | 71 |

Configuration

72 | 73 | 74 | 75 | 76 | 77 |
configurationFile storage backend configuration
78 |

Constructors

79 | 80 | 81 | 82 | 83 | 84 |
module.new ([configuration])Create a file storage.
85 |

Storage

86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 |
instance:set (name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata])Store session data.
instance:get (name, key)Retrieve session data.
instance:delete (name, key[, metadata])Delete session data.
instance:read_metadata (name, audience, subject, current_time)Read session metadata.
104 | 105 |
106 |
107 | 108 | 109 |

Configuration

110 | 111 |
112 |
113 | 114 | configuration 115 |
116 |
117 | File storage backend configuration 118 | 119 | 120 |

Fields:

121 |
    122 |
  • prefix 123 | File prefix for session file. 124 |
  • 125 |
  • suffix 126 | File suffix (or extension without .) for session file. 127 |
  • 128 |
  • pool 129 | Name of the thread pool under which file writing happens (available on Linux only). 130 |
  • 131 |
  • path 132 | Path (or directory) under which session files are created. 133 |
  • 134 |
135 | 136 | 137 | 138 | 139 | 140 |
141 |
142 |

Constructors

143 | 144 |
145 |
146 | 147 | module.new ([configuration]) 148 |
149 |
150 | Create a file storage.

151 | 152 |

This creates a new file storage instance. 153 | 154 | 155 |

Parameters:

156 |
    157 |
  • configuration 158 | table 159 | file storage configuration 160 | (optional) 161 |
  • 162 |
163 | 164 |

Returns:

165 |
    166 | 167 | table 168 | file storage instance 169 |
170 | 171 | 172 | 173 | 174 |
175 |
176 |

Storage

177 | 178 |
179 |
180 | 181 | instance:set (name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata]) 182 |
183 |
184 | Store session data. 185 | 186 | 187 |

Parameters:

188 |
    189 |
  • name 190 | string 191 | cookie name 192 |
  • 193 |
  • key 194 | string 195 | session key 196 |
  • 197 |
  • value 198 | string 199 | session value 200 |
  • 201 |
  • ttl 202 | number 203 | session ttl 204 |
  • 205 |
  • current_time 206 | number 207 | current time 208 |
  • 209 |
  • old_key 210 | string 211 | old session id 212 | (optional) 213 |
  • 214 |
  • stale_ttl 215 | string 216 | stale ttl 217 |
  • 218 |
  • metadata 219 | table 220 | table of metadata 221 | (optional) 222 |
  • 223 |
224 | 225 |

Returns:

226 |
    227 |
  1. 228 | true or nil 229 | ok
  2. 230 |
  3. 231 | string 232 | error message
  4. 233 |
234 | 235 | 236 | 237 | 238 |
239 |
240 | 241 | instance:get (name, key) 242 |
243 |
244 | Retrieve session data. 245 | 246 | 247 |

Parameters:

248 |
    249 |
  • name 250 | string 251 | cookie name 252 |
  • 253 |
  • key 254 | string 255 | session key 256 |
  • 257 |
258 | 259 |

Returns:

260 |
    261 |
  1. 262 | string or nil 263 | session data
  2. 264 |
  3. 265 | string 266 | error message
  4. 267 |
268 | 269 | 270 | 271 | 272 |
273 |
274 | 275 | instance:delete (name, key[, metadata]) 276 |
277 |
278 | Delete session data. 279 | 280 | 281 |

Parameters:

282 |
    283 |
  • name 284 | string 285 | cookie name 286 |
  • 287 |
  • key 288 | string 289 | session key 290 |
  • 291 |
  • metadata 292 | table 293 | session meta data 294 | (optional) 295 |
  • 296 |
297 | 298 |

Returns:

299 |
    300 |
  1. 301 | boolean or nil 302 | session data
  2. 303 |
  3. 304 | string 305 | error message
  4. 306 |
307 | 308 | 309 | 310 | 311 |
312 |
313 | 314 | instance:read_metadata (name, audience, subject, current_time) 315 |
316 |
317 | Read session metadata. 318 | 319 | 320 |

Parameters:

321 |
    322 |
  • name 323 | string 324 | cookie name 325 |
  • 326 |
  • audience 327 | string 328 | session key 329 |
  • 330 |
  • subject 331 | string 332 | session key 333 |
  • 334 |
  • current_time 335 | number 336 | current time 337 |
  • 338 |
339 | 340 |

Returns:

341 |
    342 |
  1. 343 | table or nil 344 | session metadata
  2. 345 |
  3. 346 | string 347 | error message
  4. 348 |
349 | 350 | 351 | 352 | 353 |
354 |
355 | 356 | 357 |
358 |
359 |
360 | generated by LDoc 1.5.0 361 | Last updated 2025-11-24 15:25:35 362 |
363 |
364 | 365 | 366 | -------------------------------------------------------------------------------- /docs/modules/resty.session.file.thread.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | Session Library for OpenResty Documentation 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 59 | 60 |
61 | 62 |

Module resty.session.file.thread

63 |

File storage backend worker thread module

64 |

65 | 66 |

67 | 68 | 69 |

Functions

70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 |
module.set (path, prefix, suffix, name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata])Store session data.
module.GET (path, prefix, suffix, name, key)Retrieve session data.
module.delete (path, prefix, suffix, name, key, current_time)Delete session data.
module.read_metadata (path, prefix, suffix, name, audience, subject, current_time)Read session metadata.
88 | 89 |
90 |
91 | 92 | 93 |

Functions

94 | 95 |
96 |
97 | 98 | module.set (path, prefix, suffix, name, key, value, ttl, current_time[, old_key], stale_ttl[, metadata]) 99 |
100 |
101 | Store session data. 102 | 103 | 104 |

Parameters:

105 |
    106 |
  • path 107 | string 108 | the path where sessions are stored 109 |
  • 110 |
  • prefix 111 | string 112 | the prefix for session files 113 |
  • 114 |
  • suffix 115 | string 116 | the suffix for session files 117 |
  • 118 |
  • name 119 | string 120 | the cookie name 121 |
  • 122 |
  • key 123 | string 124 | session key 125 |
  • 126 |
  • value 127 | string 128 | session value 129 |
  • 130 |
  • ttl 131 | number 132 | session ttl 133 |
  • 134 |
  • current_time 135 | number 136 | current time 137 |
  • 138 |
  • old_key 139 | string 140 | old session id 141 | (optional) 142 |
  • 143 |
  • stale_ttl 144 | string 145 | stale ttl 146 |
  • 147 |
  • metadata 148 | table 149 | table of metadata 150 | (optional) 151 |
  • 152 |
153 | 154 |

Returns:

155 |
    156 |
  1. 157 | table or nil 158 | session metadata
  2. 159 |
  3. 160 | string 161 | error message
  4. 162 |
163 | 164 | 165 | 166 | 167 |
168 |
169 | 170 | module.GET (path, prefix, suffix, name, key) 171 |
172 |
173 | Retrieve session data. 174 | 175 | 176 |

Parameters:

177 |
    178 |
  • path 179 | string 180 | the path where sessions are stored 181 |
  • 182 |
  • prefix 183 | string 184 | the prefix for session files 185 |
  • 186 |
  • suffix 187 | string 188 | the suffix for session files 189 |
  • 190 |
  • name 191 | string 192 | cookie name 193 |
  • 194 |
  • key 195 | string 196 | session key 197 |
  • 198 |
199 | 200 |

Returns:

201 |
    202 |
  1. 203 | string or nil 204 | session data
  2. 205 |
  3. 206 | string 207 | error message
  4. 208 |
209 | 210 | 211 | 212 | 213 |
214 |
215 | 216 | module.delete (path, prefix, suffix, name, key, current_time) 217 |
218 |
219 | Delete session data. 220 | 221 | 222 |

Parameters:

223 |
    224 |
  • path 225 | string 226 | the path where sessions are stored 227 |
  • 228 |
  • prefix 229 | string 230 | the prefix for session files 231 |
  • 232 |
  • suffix 233 | string 234 | the suffix for session files 235 |
  • 236 |
  • name 237 | string 238 | the cookie name 239 |
  • 240 |
  • key 241 | string 242 | session key 243 |
  • 244 |
  • current_time 245 | number 246 | current time 247 |
  • 248 |
249 | 250 |

Returns:

251 |
    252 |
  1. 253 | table or nil 254 | session metadata
  2. 255 |
  3. 256 | string 257 | error message
  4. 258 |
259 | 260 | 261 | 262 | 263 |
264 |
265 | 266 | module.read_metadata (path, prefix, suffix, name, audience, subject, current_time) 267 |
268 |
269 | Read session metadata. 270 | 271 | 272 |

Parameters:

273 |
    274 |
  • path 275 | string 276 | the path where sessions are stored 277 |
  • 278 |
  • prefix 279 | string 280 | the prefix for session files 281 |
  • 282 |
  • suffix 283 | string 284 | the suffix for session files 285 |
  • 286 |
  • name 287 | string 288 | the cookie name 289 |
  • 290 |
  • audience 291 | string 292 | session audience 293 |
  • 294 |
  • subject 295 | string 296 | session subject 297 |
  • 298 |
  • current_time 299 | number 300 | current time 301 |
  • 302 |
303 | 304 |

Returns:

305 |
    306 |
  1. 307 | table or nil 308 | session metadata
  2. 309 |
  3. 310 | string 311 | error message
  4. 312 |
313 | 314 | 315 | 316 | 317 |
318 |
319 | 320 | 321 |
322 |
323 |
324 | generated by LDoc 1.5.0 325 | Last updated 2025-11-24 15:25:35 326 |
327 |
328 | 329 | 330 | --------------------------------------------------------------------------------