├── .gitignore ├── .travis.yml ├── Makefile ├── README.md ├── dist.ini ├── lib └── resty │ └── shdict │ ├── redis-commands.lua │ └── server.lua ├── patches ├── openresty-1.13.6.1-cross_module_shdict.patch ├── openresty-1.15.8.2-cross_module_shdict.patch ├── openresty-1.17.8.2-cross_module_shdict.patch └── openresty-1.19.3.1-cross_module_shdict.patch └── t ├── http.t ├── redis-commands.t ├── redis-inline.t └── redis-resp.t /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | *~ 4 | t/servroot/ 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | 4 | os: linux 5 | 6 | language: c 7 | 8 | compiler: gcc 9 | 10 | addons: 11 | apt: 12 | packages: 13 | - cpanminus 14 | - axel 15 | - luarocks 16 | 17 | cache: 18 | directories: 19 | - download-cache 20 | 21 | env: 22 | global: 23 | - JOBS=2 24 | - NGX_BUILD_JOBS=$JOBS 25 | - LUAJIT_PREFIX=/opt/luajit21 26 | - LUAJIT_LIB=$LUAJIT_PREFIX/lib 27 | - LUAJIT_INC=$LUAJIT_PREFIX/include/luajit-2.1 28 | - LUA_INCLUDE_DIR=$LUAJIT_INC 29 | - LUA_CMODULE_DIR=/lib 30 | - LD_LIBRARY_PATH=$LUAJIT_LIB:$LD_LIBRARY_PATH 31 | - TEST_NGINX_SLEEP=0.005 32 | - TEST_NGINX_RANDOMIZE=1 33 | - LUACHECK_VER=0.21.1 34 | matrix: 35 | - NGINX_VERSION=1.15.8 STREAM_LUA_MODULE_VERSION=v0.0.7 LUA_NGINX_MODULE_VERSION=v0.10.15 LUA_RESTY_CORE_VERSION=v0.1.17 36 | 37 | services: 38 | - memcache 39 | 40 | before_install: 41 | - sudo luarocks install luacheck $LUACHECK_VER 42 | # - luacheck -q . 43 | # - '! grep -n -P ''(?<=.{80}).+'' --color `find . -name ''*.lua''` || (echo "ERROR: Found Lua source lines exceeding 80 columns." > /dev/stderr; exit 1)' 44 | # - '! grep -n -P ''\t+'' --color `find . -name ''*.lua''` || (echo "ERROR: Cannot use tabs." > /dev/stderr; exit 1)' 45 | - sudo cpanm --notest Test::Nginx IPC::Run > build.log 2>&1 || (cat build.log && exit 1) 46 | 47 | install: 48 | - if [ ! -d download-cache ]; then mkdir download-cache; fi 49 | - git clone https://github.com/openresty/openresty.git ../openresty 50 | - git clone https://github.com/openresty/openresty-devel-utils.git 51 | - git clone https://github.com/simpl/ngx_devel_kit.git ../ndk-nginx-module 52 | - git clone -b $LUA_NGINX_MODULE_VERSION https://github.com/openresty/lua-nginx-module.git ../lua-nginx-module 53 | - git clone https://github.com/openresty/no-pool-nginx.git ../no-pool-nginx 54 | - git clone https://github.com/openresty/echo-nginx-module.git ../echo-nginx-module 55 | - git clone https://github.com/openresty/lua-resty-lrucache.git ../lua-resty-lrucache 56 | - git clone -b $LUA_RESTY_CORE_VERSION https://github.com/openresty/lua-resty-core.git ../lua-resty-core 57 | - git clone https://github.com/openresty/headers-more-nginx-module.git ../headers-more-nginx-module 58 | - git clone -b v2.1-agentzh https://github.com/openresty/luajit2.git luajit2 59 | - git clone https://github.com/openresty/set-misc-nginx-module.git ../set-misc-nginx-module 60 | - git clone https://github.com/openresty/test-nginx.git 61 | - git clone -b $STREAM_LUA_MODULE_VERSION https://github.com/openresty/stream-lua-nginx-module.git ../stream-lua-nginx-module 62 | 63 | script: 64 | - cd luajit2/ 65 | - make -j$JOBS CCDEBUG=-g Q= PREFIX=$LUAJIT_PREFIX CC=$CC XCFLAGS='-DLUA_USE_APICHECK -DLUA_USE_ASSERT -msse4.2' > build.log 2>&1 || (cat build.log && exit 1) 66 | - sudo make install PREFIX=$LUAJIT_PREFIX > build.log 2>&1 || (cat build.log && exit 1) 67 | - cd .. 68 | - export LD_LIBRARY_PATH=$PWD/mockeagain:$LD_LIBRARY_PATH 69 | - export TEST_NGINX_RESOLVER=8.8.4.4 70 | - export NGX_BUILD_CC=$CC 71 | - export PATH=$PWD/work/nginx/sbin:$PWD/openresty-devel-utils:$PATH 72 | - ngx-build $NGINX_VERSION --with-ipv6 --with-http_realip_module --add-module=../ndk-nginx-module --add-module=../echo-nginx-module --add-module=../set-misc-nginx-module --add-module=../headers-more-nginx-module --add-module=../lua-nginx-module --with-debug --with-stream --with-ipv6 --add-module=../stream-lua-nginx-module > build.log 2>&1 || (cat build.log && exit 1) 73 | - nginx -V 74 | - ldd `which nginx`|grep -E 'luajit|ssl|pcre' 75 | - prove -Itest-nginx/lib -j$JOBS -r t 76 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | OPENRESTY_PREFIX=/usr/local/openresty 2 | 3 | PREFIX ?= /usr/local 4 | LUA_LIB_DIR ?= $(PREFIX)/lib/lua/$(LUA_VERSION) 5 | INSTALL ?= install 6 | 7 | .PHONY: all test install 8 | 9 | all: ; 10 | 11 | install: all 12 | $(INSTALL) -d $(DESTDIR)$(LUA_LIB_DIR)/resty/shdict/ 13 | $(INSTALL) lib/resty/shdict/*.lua $(DESTDIR)$(LUA_LIB_DIR)/resty/shdict/ 14 | 15 | test: all 16 | PATH=$(OPENRESTY_PREFIX)/nginx/sbin:$$PATH prove -I../test-nginx/lib -r t 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Name 2 | ==== 3 | 4 | lua-resty-shdict-server - A HTTP and Redis protocol compatible interface for debugging ngx.shared API 5 | 6 | ![Build status](https://travis-ci.com/fffonion/lua-resty-shdict-server.svg?branch=master) 7 | 8 | Table of Contents 9 | ================= 10 | 11 | - [Description](#description) 12 | - [Status](#status) 13 | - [Synopsis](#synopsis) 14 | - [API](#api) 15 | - [Commands](#commands) 16 | * [Basic commands](#basic-commands) 17 | * [AUTH](#auth) 18 | * [SELECT](#select) 19 | * [PING](#ping) 20 | * [KEYS](#keys) 21 | * [EVAL](#eval) 22 | * [DEL](#del) 23 | * [GETFLAG](#getflag) 24 | - [Known Issues](#known-issues) 25 | - [TODO](#todo) 26 | - [Copyright and License](#copyright-and-license) 27 | - [See Also](#see-also) 28 | 29 | 30 | Description 31 | =========== 32 | 33 | This is a library that provides a HTTP and a Redis protocol compatible interface to debug [ngx.shared.DICT](https://github.com/openresty/lua-nginx-module#ngxshareddict) API. 34 | 35 | It can also be used as a mocking Redis server (WIP). 36 | 37 | [Back to TOC](#table-of-contents) 38 | 39 | 40 | Status 41 | ======== 42 | 43 | Production. 44 | 45 | Synopsis 46 | ======== 47 | 48 | Shared dictionaries defined in `http` and `stream` subsystems are not shared to each other *currently*, thus we need two sets of configurations to debug with either subsystem. 49 | 50 | However there're [patches](patches) if you want to share shdict between `http` and `stream` subsystems. After apply the patch, you will need to use two `lua_shared_dict` directives to define shared dict twice with same name and same size. e.g.: 51 | 52 | ```lua 53 | http { 54 | lua_shared_dict dogs 10m; 55 | } 56 | 57 | stream { 58 | lua_shared_dict dogs 10m; 59 | } 60 | ``` 61 | 62 | This module provide a similar API to use in both subsystems. 63 | 64 | http 65 | ---- 66 | 67 | ```lua 68 | lua_shared_dict dogs 10m; 69 | 70 | server { 71 | listen 80; 72 | 73 | location =/cli { 74 | content_by_lua_block { 75 | require "resty.core" 76 | local srv = require("resty.shdict.server") 77 | local s = srv:new("foobar", nil) 78 | s:serve() 79 | } 80 | } 81 | } 82 | ``` 83 | 84 | This sets up a simple HTTP server. Use any http client like `curl` to issue a http request to the location we configured. For example: 85 | 86 | ```shell 87 | $ curl "http://host/cli?dict=dogs&cmd=set%20dog%20wow&password=foobar" 88 | OK 89 | 90 | $ curl "http://host/cli?dict=dogs&cmd=get%20dog&password=foobar" 91 | "wow" 92 | ``` 93 | 94 | stream 95 | ---- 96 | 97 | ```lua 98 | lua_shared_dict dogs 10m; 99 | 100 | server { 101 | listen 127.0.0.1:18001; 102 | content_by_lua_block { 103 | require "resty.core.shdict" 104 | require "resty.shdict.redis-commands" 105 | local srv = require("resty.shdict.server") 106 | local s = srv:new("foobar", nil) 107 | s:serve() 108 | } 109 | } 110 | ``` 111 | 112 | This sets up a simple TCP server. Use telnet or equivalent tools to connect interactively. For example: 113 | 114 | ```shell 115 | $ telnet 127.0.0.1 18001 116 | Trying 127.0.0.1... 117 | Connected to 127.0.0.1. 118 | Escape character is '^]'. 119 | SELECT dogs 120 | -ERR authentication required 121 | AUTH foobar 122 | +OK 123 | SELECT dogs 124 | +OK 125 | SET dog wow 126 | +OK 127 | GET dog 128 | +wow 129 | ``` 130 | 131 | Also it supports [Redis RESP protocol](https://redis.io/topics/protocol). 132 | 133 | ```shell 134 | $ redis-cli -h 127.0.0.1 -p 18001 135 | 127.0.0.1:18001> get dogs 136 | (error) ERR authentication required 137 | 127.0.0.1:18001> auth foobar 138 | OK 139 | 127.0.0.1:18001> set dog wow 140 | (error) ERR no shdict selected 141 | 127.0.0.1:18001> select dogs 142 | OK 143 | 127.0.0.1:18001> set dog wow 144 | OK 145 | 127.0.0.1:18001> get dog 146 | wow 147 | ``` 148 | 149 | 150 | [Back to TOC](#table-of-contents) 151 | 152 | 153 | API 154 | ======= 155 | 156 | shdict.server:new(password, shdict) 157 | --------------------------------- 158 | 159 | Initialize the server instance with *password* and pre-selected shared dictionary *shdict*. 160 | 161 | If *password* is not set, the server is public. If *password* is set, client must call [auth](#auth) command to authenticate before running other commands. Please take proper security measurements if you're listening to non-local interfaces. 162 | 163 | If *shdict* is not set, client must call [select](#select) command to select a shared dictionary. 164 | 165 | shdict.server:serve(mode) 166 | --------------------- 167 | 168 | Start the server with handler named *mode`. To run handler `serve_stream_redis`, use: 169 | 170 | ```Lua 171 | shdict.server:serve("stream_redis") 172 | ``` 173 | 174 | If *mode* is not defined, default handler for each subsystem is used. For `http` the default handler is `serve_http_plain`. For `stream` the default handler is `serve_stream_redis`. 175 | 176 | shdict.server:serve_http_plain() 177 | -------------------------------- 178 | 179 | This handler accept a single HTTP request from client and send the response back in plain text. 180 | 181 | shdict.server:serve_http_json() 182 | ------------------------------- 183 | 184 | This handler accept a single HTTP request from client and send the response back in json encoded text. 185 | 186 | shdict.server:serve_http(output_filter) 187 | --------------------------------------- 188 | 189 | This handler accept a single HTTP request from client and send the response formatted by `output_filter`. 190 | 191 | If `output_filter` is not defined, **output_plain** is used and this handler is equivalent to `serve_http_plain`. 192 | 193 | `output_filter` is a function that takes a table as argument and returns a string. User can define their own filter and genereate desirable output. 194 | 195 | shdict.server:serve_stream_redis() 196 | ---------------------------------- 197 | 198 | This handler accept TCP connection in inline or Redis protocol. A plain text TCP client like `telnet` or `nc` or a Redis-compatible client or library can be used to connect to the server. 199 | 200 | [Back to TOC](#table-of-contents) 201 | 202 | 203 | Commands 204 | ======= 205 | 206 | Basic commands 207 | ------------- 208 | 209 | Methods from `ngx.shared.DICT` API are supported. 210 | 211 | * [ngx.shared.DICT](https://github.com/openresty/lua-nginx-module#ngxshareddict) 212 | * [ngx.shared.DICT.get](https://github.com/openresty/lua-nginx-module#ngxshareddictget) 213 | * [ngx.shared.DICT.get_stale](https://github.com/openresty/lua-nginx-module#ngxshareddictget_stale) 214 | * [ngx.shared.DICT.set](https://github.com/openresty/lua-nginx-module#ngxshareddictset) 215 | * [ngx.shared.DICT.safe_set](https://github.com/openresty/lua-nginx-module#ngxshareddictsafe_set) 216 | * [ngx.shared.DICT.add](https://github.com/openresty/lua-nginx-module#ngxshareddictadd) 217 | * [ngx.shared.DICT.safe_add](https://github.com/openresty/lua-nginx-module#ngxshareddictsafe_add) 218 | * [ngx.shared.DICT.replace](https://github.com/openresty/lua-nginx-module#ngxshareddictreplace) 219 | * [ngx.shared.DICT.delete](https://github.com/openresty/lua-nginx-module#ngxshareddictdelete) 220 | * [ngx.shared.DICT.incr](https://github.com/openresty/lua-nginx-module#ngxshareddictincr) 221 | * [ngx.shared.DICT.lpush](https://github.com/openresty/lua-nginx-module#ngxshareddictlpush) 222 | * [ngx.shared.DICT.rpush](https://github.com/openresty/lua-nginx-module#ngxshareddictrpush) 223 | * [ngx.shared.DICT.lpop](https://github.com/openresty/lua-nginx-module#ngxshareddictlpop) 224 | * [ngx.shared.DICT.rpop](https://github.com/openresty/lua-nginx-module#ngxshareddictrpop) 225 | * [ngx.shared.DICT.llen](https://github.com/openresty/lua-nginx-module#ngxshareddictllen) 226 | * [ngx.shared.DICT.ttl](https://github.com/openresty/lua-nginx-module#ngxshareddictttl) 227 | * [ngx.shared.DICT.expire](https://github.com/openresty/lua-nginx-module#ngxshareddictexpire) 228 | * [ngx.shared.DICT.flush_all](https://github.com/openresty/lua-nginx-module#ngxshareddictflush_all) 229 | * [ngx.shared.DICT.flush_expired](https://github.com/openresty/lua-nginx-module#ngxshareddictflush_expired) 230 | * [ngx.shared.DICT.get_keys](https://github.com/openresty/lua-nginx-module#ngxshareddictget_keys) 231 | * [ngx.shared.DICT.capacity](https://github.com/openresty/lua-nginx-module#ngxshareddictcapacity) 232 | * [ngx.shared.DICT.free_space](https://github.com/openresty/lua-nginx-module#ngxshareddictfree_space) 233 | 234 | Some of the commands like `ttl` and `capacity` require the `resty.core` being installed. To use these commands, put `require('resty.core')` for http subsystem and `require('resty.core.shdict')` for stream subsystem. 235 | 236 | Methods names are **case-insensitive**. Arguments are seperated by spaces. 237 | 238 | For example: 239 | 240 | - To set a value **wow** with key **dog**, use `SET dog wow` or `sEt dog wow`. 241 | - To set a value **wow !** with key **dog**, use `SET dog "wow !"`. 242 | - To set a value **"wow" !** with key **dog**, use `SET dog "\"wow\" !"`. 243 | 244 | Some commands are mapped to redis-flavoured commands if `resty.shdict.redis-commands` is included. 245 | 246 | - `setnx` as an alias of `add` 247 | - `setex` as an alias of `replace` 248 | - `flushall` as an alias of `flush_all` 249 | 250 | AUTH 251 | ---- 252 | 253 | Authenticate to the server. 254 | 255 | Returns **OK** if *password* is valid. 256 | 257 | ``` 258 | > AUTH password 259 | ``` 260 | 261 | SELECT 262 | ------ 263 | 264 | Select a shared dictionary. 265 | 266 | Returns **OK** if *shdict* is found. 267 | 268 | ``` 269 | > SELECT shdict 270 | ``` 271 | 272 | PING 273 | ---- 274 | 275 | Test connection to the server. 276 | 277 | Returns **PONG**. 278 | 279 | ``` 280 | > PING 281 | ``` 282 | 283 | KEYS 284 | ---- 285 | 286 | This command requires the `resty.shdict.redis-commands` module. 287 | 288 | Return all keys matching *pattern* in a list. The *pattern* is a glob-style pattern. 289 | 290 | The time complexity is **O(3n)**. This command is for debug only, please do not use in production code to search for keys. 291 | 292 | ``` 293 | > KEYS pattern 294 | > KEYS do? 295 | > KEYS do* 296 | > KEYS do[a-z] 297 | ``` 298 | 299 | EVAL 300 | ---- 301 | 302 | This command requires the `resty.shdict.redis-commands` module. 303 | 304 | Run a Lua script on the server. The syntax is same as Redis [EVAL](https://redis.io/commands/eval). 305 | 306 | ``` 307 | > EVAL script numkeys key [key ...] arg [arg ...] 308 | > EVAL "shdict.call('set', 'dog', 'wow') 0 309 | (nil) 310 | > EVAL "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second 311 | 1) "key1" 312 | 2) "key2" 313 | 3) "first" 314 | 4) "second" 315 | ``` 316 | 317 | For security reasons, only the following APIs are available: 318 | 319 | - Functions: `assert`, `error`, `getmetatable`, `ipairs`, `next`, `pairs`, `pcall`, `select`, `setmetatable`, `tonumber`, `tostring`, `unpack` 320 | - Modules: `bit`, `math`, `string`, `table` 321 | - `ngx.shared` and `ngx.re` 322 | - `shdict.call` and `shdict.pcall` for invoking shdict APIs 323 | - `zone` as the current shdict instance 324 | 325 | Also an alias from `redis.call` to `shdict.call` is created for convenience. 326 | 327 | 328 | GETFLAG 329 | ------- 330 | 331 | This command requires the `resty.shdict.redis-commands` module. 332 | 333 | Get the user flag for a key. 334 | 335 | Return `nil` if the user flag is not set or key is not found. 336 | 337 | ``` 338 | GETFLAG key 339 | ``` 340 | 341 | DEL 342 | --- 343 | 344 | This command requires the `resty.shdict.redis-commands` module. 345 | 346 | Delete one or more keys from shdict. 347 | 348 | Returns **OK**. 349 | 350 | ``` 351 | DEL key [key ...] 352 | DELETE key [key ...] 353 | ``` 354 | 355 | 356 | [Back to TOC](#table-of-contents) 357 | 358 | 359 | Known Issues 360 | ==== 361 | 362 | - The library will use `resty.core` if it's installed, the behaviour will be slightly different from the C implementation. For example, missing arguments will be filled by `nil` when using `resty.core`, issuing `SET a` is equivalent to `SET a nil` in this situation. 363 | - For performance issues, inline protocol (HTTP inline or Redis inline) only accept four arguments at most. `EVAL` may fail with **Invalid argument(s)** for this reason. To solve this, always use other protocols (like Redis RESP protocol) to call these commands. 364 | - `EVAL` command is **not atomic** when running multiple `shdict.call` altough the script is run on server. 365 | 366 | [Back to TOC](#table-of-contents) 367 | 368 | 369 | TODO 370 | ==== 371 | 372 | - Add more ngx.* API to EVAL command. 373 | - Add atomic SETFLAG command. 374 | - Return affected keys count for DEL command. 375 | - Add INFO command. 376 | 377 | [Back to TOC](#table-of-contents) 378 | 379 | 380 | Copyright and License 381 | ===================== 382 | 383 | This module is licensed under the BSD license. 384 | 385 | Copyright (C) 2018, by fffonion . 386 | 387 | All rights reserved. 388 | 389 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 390 | 391 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 392 | 393 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 394 | 395 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 396 | 397 | [Back to TOC](#table-of-contents) 398 | 399 | 400 | See Also 401 | ======== 402 | * [openresty/lua-nginx-module](https://github.com/openresty/lua-nginx-module) 403 | 404 | [Back to TOC](#table-of-contents) 405 | -------------------------------------------------------------------------------- /dist.ini: -------------------------------------------------------------------------------- 1 | name = lua-resty-shdict-server 2 | abstract = A HTTP and Redis protocol compatible interface for debugging ngx.shared API 3 | author = fffonion 4 | is_original = yes 5 | license = 3bsd 6 | lib_dir = lib 7 | doc_dir = lib 8 | repo_link = https://github.com/fffonion/lua-resty-shdict-server 9 | main_module = lib/resty/shdict/server.lua 10 | requires = luajit 11 | -------------------------------------------------------------------------------- /lib/resty/shdict/redis-commands.lua: -------------------------------------------------------------------------------- 1 | 2 | local ffi = require 'ffi' 3 | 4 | local ffi_new = ffi.new 5 | local ffi_str = ffi.string 6 | local C = ffi.C 7 | local next = next 8 | local type = type 9 | local error = error 10 | local table_remove = table.remove 11 | local ngx_re = ngx.re 12 | local ngx_shared = ngx.shared 13 | local getmetatable = getmetatable 14 | local setmetatable = setmetatable 15 | 16 | local ok, new_tab = pcall(require, "table.new") 17 | if not ok or type(new_tab) ~= "function" then 18 | new_tab = function (narr, nrec) return {} end 19 | end 20 | 21 | -- resty.core.shdict 22 | local function check_zone(zone) 23 | if not zone or type(zone) ~= "table" then 24 | return error("bad \"zone\" argument") 25 | end 26 | 27 | zone = zone[1] 28 | if type(zone) ~= "userdata" then 29 | return error("bad \"zone\" argument") 30 | end 31 | 32 | return zone 33 | end 34 | 35 | 36 | if ngx_shared then 37 | local _, dict = next(ngx_shared, nil) 38 | if dict then 39 | local mt = getmetatable(dict) 40 | if mt then 41 | mt = mt.__index 42 | if mt then 43 | mt.delete = function(zone, ...) 44 | for i, key in ipairs({...}) do 45 | mt.set(zone, key, nil) 46 | end 47 | return true 48 | end 49 | 50 | mt.keys = function(zone, pattern) 51 | if pattern == nil then 52 | return nil, "expecting exactly two arguments, but only seen 1" 53 | end 54 | -- return a list of keys that matches given pattern 55 | -- complexity is O(3n) 56 | -- For debug only, do not use in production code 57 | local keys = mt.get_keys(zone, 0) 58 | -- Let's convert glob pattern to regexp pattern 59 | -- a*b? => a.*b. 60 | pattern = ngx_re.gsub(pattern, [=[[\*\?]{2,}]=], "*", "jo") -- remove continous * or ? 61 | pattern = ngx_re.gsub(pattern, [=[[\.\(\)]+]=], [[\$0]], "jo") -- add \ before . ( ) 62 | pattern = ngx_re.gsub(pattern, [=[[\*\?]]=], ".$0", "jo") -- convert * to .*, ? to .? 63 | pattern = "^" .. pattern .. "$" -- match the whole word 64 | for i=#keys, 1, -1 do 65 | if not ngx_re.match(keys[i], pattern, "jo") then 66 | table_remove(keys, i) 67 | end 68 | end 69 | return keys 70 | end 71 | 72 | mt.eval = function(zone, code, numkeys, ...) 73 | local arg = {...} 74 | if numkeys ~= nil then 75 | numkeys = tonumber(numkeys) 76 | if numkeys == nil then 77 | return nil, "value is not an integer or out of range" 78 | elseif numkeys < 0 then 79 | return nil, "Number of keys can't be negative" 80 | elseif #arg < numkeys then 81 | return nil, "Number of keys can't be greater than number of arg" 82 | end 83 | else 84 | return nil, "wrong number of arguments for 'eval' command" 85 | end 86 | 87 | local injected = [[ 88 | shdict.call = function(cmd, ...) 89 | assert(cmd and zone[cmd:lower()], "Unknown ngx.shared command called from Lua script") 90 | return zone[cmd:lower()](zone, ...) 91 | end 92 | shdict.pcall = function(...) 93 | local ok, result, err = pcall(shdict.call, ...) 94 | if not ok then 95 | return nil, result 96 | end 97 | return result, err 98 | end 99 | ]] 100 | -- provide a jailed environment 101 | -- optionally: http://metalua.luaforge.net/src/lib/strict.lua.html 102 | local env = { 103 | ngx = { shared = ngx_shared, re = ngx_re }, 104 | shdict = new_tab(2, 0), 105 | zone = zone, 106 | KEYS = arg, 107 | ARGV = setmetatable({}, { __index = function(_, i) return arg[i + numkeys] end }), 108 | assert = assert, 109 | error = error, 110 | getmetatable = getmetatable, 111 | ipairs = ipairs, 112 | next = next, 113 | pairs = pairs, 114 | pcall = pcall, 115 | select = select, 116 | setmetatable = setmetatable, 117 | tonumber = tonumber, 118 | tostring = tostring, 119 | unpack = unpack, 120 | 121 | bit = bit, 122 | math = math, 123 | string = string, 124 | table = table, 125 | } 126 | 127 | env['redis'] = env['shdict'] 128 | 129 | local f, err = load(injected .. code, "=(user_script)", "t", env) 130 | if not f then 131 | return nil, "Error compiling script " .. err 132 | end 133 | 134 | local ok, result, err = pcall(f) 135 | if not ok then 136 | -- TODO: fix the wrong line number becuase we injected some code 137 | return nil, "Error running script ".. result 138 | end 139 | return result, err 140 | 141 | end 142 | 143 | mt.getflag = function(zone, key) 144 | local value, flag = mt.get(zone, key) 145 | if value == nil then 146 | return value, flag 147 | else 148 | return flag 149 | end 150 | end 151 | 152 | -- alias 153 | mt.del = mt.delete 154 | mt.flushall = mt.flush_all 155 | mt.setnx = mt.add 156 | mt.setex = mt.replace 157 | 158 | end 159 | end 160 | end 161 | end 162 | 163 | 164 | return { 165 | version = 0.01 166 | } 167 | 168 | -------------------------------------------------------------------------------- /lib/resty/shdict/server.lua: -------------------------------------------------------------------------------- 1 | 2 | 3 | local tonumber = tonumber 4 | local pairs = pairs 5 | local type = type 6 | local sub = string.sub 7 | local byte = string.byte 8 | local table_concat = table.concat 9 | local ngx_log = ngx.log 10 | local ngx_re = ngx.re 11 | local ngx_print = ngx.print 12 | local ngx_shared = ngx.shared 13 | 14 | local ok, new_tab = pcall(require, "table.new") 15 | if not ok or type(new_tab) ~= "function" then 16 | new_tab = function (narr, nrec) return {} end 17 | end 18 | 19 | local _M = new_tab(0, 54) 20 | 21 | _M._VERSION = '0.02' 22 | 23 | -- shdict API has 4 arguments at most 24 | local SHDICT_MAX_ARGUMENTS = 4 25 | local CRLF = "\r\n" 26 | 27 | local mt = { __index = _M } 28 | 29 | -- put password before shdict, if someone accidently use module.new to initialize, 30 | -- we will not expose password as shdict name 31 | function _M.new(self, password, shdict) 32 | return setmetatable({ shdict = shdict, password = password, authenticated = false}, mt) 33 | end 34 | 35 | local function _do_cmd(self, cmd, args) 36 | local ret = new_tab(0, 3) 37 | cmd = cmd:lower() 38 | 39 | while true do 40 | -- authentication 41 | if cmd == "auth" then 42 | if not self.password then 43 | ret.err = "Client sent AUTH, but no password is set" 44 | elseif args[1] ~= self.password then 45 | ret.err = "invalid password" 46 | self.authenticated = false 47 | else 48 | self.authenticated = true 49 | end 50 | break 51 | elseif self.password and not self.authenticated then 52 | ret.err = "authentication required" 53 | break 54 | end 55 | 56 | if cmd == "ping" then 57 | ret.has_msg = true 58 | ret.msg = "PONG" 59 | break 60 | elseif cmd == "select" then 61 | self.shdict = args[1] 62 | elseif not self.shdict then 63 | ret.err = "no shdict selected" 64 | break 65 | end 66 | 67 | local dict = ngx_shared[self.shdict] 68 | if not dict then 69 | ret.err = "shdict '".. self.shdict .."' not defined" 70 | if cmd == "select" then 71 | self.shdict = nil 72 | end 73 | break 74 | end 75 | if cmd == "select" then 76 | break 77 | end 78 | 79 | local _func = dict[cmd] 80 | if not _func then 81 | ret.err = "unknown command '" .. cmd .. "'" 82 | break 83 | end 84 | 85 | local ok, result, err = pcall(_func, dict, unpack(args)) 86 | -- lua level error 87 | if not ok then 88 | ret.err = result 89 | break 90 | end 91 | if not result and err then 92 | -- shdict API error 93 | ret.err = err 94 | elseif result ~= true then 95 | -- shdict always return true as first return value if there's 96 | -- no "actual" return values 97 | -- we only put it into ret.msg if shdict actually return something 98 | ret.msg = result 99 | -- in some cases, result is nil 100 | -- ret.has_msg is used to indicate that we has set a value to ret.msg 101 | ret.has_msg = true 102 | end 103 | 104 | break 105 | end 106 | return ret 107 | end 108 | 109 | local function _parse_line(line) 110 | local cmd 111 | local args = new_tab(SHDICT_MAX_ARGUMENTS, 0) 112 | 113 | local s_quote_level = 0 -- single quote level 114 | local d_quote_level = 0 -- double quote level 115 | local buf = {} 116 | local flush_buf = false 117 | local unquoted = false 118 | local argc = 0 119 | local it, err = ngx_re.gmatch(line, "(.)", "jo") 120 | if not it then 121 | ngx_log(ngx.DEBUG, "error splitting arguments ", err, ", line: ", line) 122 | return nil, nil 123 | end 124 | while true do 125 | local v, err = it() 126 | if err then 127 | ngx_log(ngx.DEBUG, "error iterating arguments ", err, ", line: ", line) 128 | return nil, nil 129 | end 130 | if not v then 131 | break 132 | end 133 | 134 | v = v[0] 135 | if v == '"' then 136 | if unquoted then 137 | buf[#buf + 1] = '"' 138 | unquoted = false 139 | elseif d_quote_level > 0 then 140 | flush_buf = true 141 | elseif s_quote_level > 0 then 142 | buf[#buf + 1] = '"' 143 | else 144 | d_quote_level = d_quote_level + 1 145 | end 146 | elseif v == "'" then 147 | if unquoted then 148 | buf[#buf + 1] = "'" 149 | unquoted = false 150 | elseif s_quote_level > 0 then 151 | flush_buf = true 152 | elseif d_quote_level > 0 then 153 | buf[#buf + 1] = "'" 154 | else 155 | s_quote_level = s_quote_level + 1 156 | end 157 | elseif v == " " then 158 | if s_quote_level + d_quote_level == 0 then 159 | if #buf > 0 then 160 | flush_buf = true 161 | end 162 | else 163 | buf[#buf + 1] = v 164 | end 165 | elseif v == [[\]] then 166 | if unquoted then 167 | -- 12\\3 -> "12\\\\3" 168 | -- "12\\3" -> "12\\3" 169 | buf[#buf + 1] = [[\]] 170 | if d_quote_level + s_quote_level == 0 then 171 | buf[#buf + 1] = [[\]] 172 | end 173 | unquoted = false 174 | else 175 | unquoted = true 176 | end 177 | else 178 | buf[#buf + 1] = v 179 | end 180 | 181 | if flush_buf then 182 | -- move buf to cmd or args 183 | if not cmd then 184 | cmd = table_concat(buf, "") 185 | else 186 | -- exceeds max argument count 187 | if argc >= SHDICT_MAX_ARGUMENTS then 188 | return nil, nil 189 | end 190 | args[argc + 1] = table_concat(buf, "") 191 | argc = argc + 1 192 | end 193 | buf = {} 194 | flush_buf = false 195 | d_quote_level = 0 196 | s_quote_level = 0 197 | end 198 | end 199 | 200 | -- has unmatched quotes 201 | if s_quote_level + d_quote_level > 0 then 202 | return nil, nil 203 | end 204 | 205 | -- flush rest buffer 206 | if #buf > 0 then 207 | if not cmd then 208 | cmd = table_concat(buf, "") 209 | else 210 | -- exceeds max argument count 211 | if argc >= SHDICT_MAX_ARGUMENTS then 212 | return nil, nil 213 | end 214 | args[argc + 1] = table_concat(buf, "") 215 | argc = argc + 1 216 | end 217 | end 218 | 219 | return cmd, args 220 | end 221 | 222 | 223 | local function output_plain(ret) 224 | local output 225 | 226 | if ret.err then 227 | output = "ERR " .. ret.err 228 | else 229 | if ret.has_msg then 230 | if ret.msg == nil then 231 | output = "(nil)" 232 | elseif type(ret.msg) == 'string' then 233 | output = "\"" .. ret.msg .. "\"" 234 | elseif type(ret.msg) == 'table' then 235 | local r = {} 236 | for i, v in pairs(ret.msg) do 237 | r[#r + 1] = i .. ") " .. v 238 | end 239 | output = table_concat(r, "\n") 240 | else 241 | output = ret.msg 242 | end 243 | else 244 | output = "OK" 245 | end 246 | end 247 | 248 | return output 249 | end 250 | 251 | local function output_json(ret) 252 | local output = {ok = true, response = nil, error = nil} 253 | local json = require("cjson") 254 | if not json then 255 | ngx_log(ngx.WARN, "cjson is not found while output_json handler is selected") 256 | ngx.exit(500) 257 | end 258 | 259 | if ret.err then 260 | output.ok = false 261 | output.error = ret.err 262 | else 263 | if ret.has_msg then 264 | output.response = ret.msg 265 | end 266 | end 267 | return json.encode(output) 268 | end 269 | 270 | function _M.serve_http(self, output_filter) 271 | -- HTTP subsystem server returning a single line response at a time 272 | self.shdict = ngx.var.arg_dict or self.shdict 273 | -- silently ignore password if there's no password set 274 | if self.password and ngx.var.arg_password then 275 | local ret = _do_cmd(self, "auth", { ngx.var.arg_password }) 276 | if ret.err ~= nil then 277 | ngx_print(output_plain(ret)) 278 | return 279 | end 280 | end 281 | 282 | local cmd, args = _parse_line(ngx.unescape_uri(ngx.var.arg_cmd)) 283 | 284 | if not cmd then 285 | ngx_print("Invalid argument(s)") 286 | return 287 | end 288 | 289 | local ret = _do_cmd(self, cmd, args) 290 | 291 | output_filter = output_filter or output_plain 292 | ngx_print(output_filter(ret)) 293 | 294 | end 295 | 296 | function _M.serve_http_plain(self) 297 | ngx.header.content_type = 'text/plain'; 298 | return _M.serve_http(self, output_plain) 299 | end 300 | 301 | function _M.serve_http_json(self) 302 | ngx.header.content_type = 'application/json'; 303 | return _M.serve_http(self, output_json) 304 | end 305 | 306 | 307 | 308 | local function _parse_redis_req(line, sock) 309 | -- parse RESP protocol request with a preread line and the request socket 310 | local err 311 | if not line then 312 | line, err = sock:receive() 313 | if not line then 314 | return nil, err 315 | end 316 | end 317 | 318 | -- taken from lua-resty-redis._read_reply 319 | local prefix = byte(line) 320 | 321 | if prefix == 36 then -- char '$' 322 | -- print("bulk reply") 323 | 324 | local size = tonumber(sub(line, 2)) 325 | if size < 0 then 326 | return nil 327 | end 328 | 329 | local data, err = sock:receive(size) 330 | if not data then 331 | if err == "timeout" then 332 | sock:close() 333 | end 334 | return nil, err 335 | end 336 | 337 | local dummy, err = sock:receive(2) -- ignore CRLF 338 | if not dummy then 339 | return nil, err 340 | end 341 | 342 | return data 343 | 344 | elseif prefix == 43 then -- char '+' 345 | -- print("status reply") 346 | 347 | return sub(line, 2) 348 | 349 | elseif prefix == 42 then -- char '*' 350 | local n = tonumber(sub(line, 2)) 351 | 352 | -- print("multi-bulk reply: ", n) 353 | if n < 0 then 354 | return null 355 | end 356 | 357 | local cmd 358 | local vals = new_tab(n - 1, 0) 359 | local nvals = 0 360 | for i = 1, n do 361 | local res, err = _parse_redis_req(nil, sock) 362 | if res then 363 | if cmd == nil then 364 | cmd = res 365 | else 366 | nvals = nvals + 1 367 | vals[nvals] = res 368 | end 369 | 370 | elseif res == nil then 371 | return nil, err 372 | 373 | else 374 | -- be a valid redis error value 375 | if cmd == nil then 376 | cmd = res 377 | else 378 | nvals = nvals + 1 379 | vals[nvals] = {false, err} 380 | end 381 | end 382 | end 383 | 384 | return cmd, vals 385 | 386 | elseif prefix == 58 then -- char ':' 387 | -- print("integer reply") 388 | return tonumber(sub(line, 2)) 389 | 390 | elseif prefix == 45 then -- char '-' 391 | -- print("error reply: ", n) 392 | 393 | return false, sub(line, 2) 394 | 395 | else 396 | -- when `line` is an empty string, `prefix` will be equal to nil. 397 | return nil, "unknown prefix: \"" .. tostring(prefix) .. "\"" 398 | end 399 | end 400 | 401 | local function _serialize_redis(data) 402 | if data == nil then 403 | return "$-1" .. CRLF 404 | elseif type(data) == 'string' then 405 | return "+" .. data .. CRLF 406 | elseif type(data) == 'number' then 407 | return ":" .. data .. CRLF 408 | elseif type(data) == 'table' then 409 | local r = {"*" .. #data} 410 | -- only iterate the array part 411 | for i, v in ipairs(data) do 412 | if type(v) == 'string' then 413 | r[#r + 1] = "$" .. #v 414 | r[#r + 1] = v 415 | elseif type(v) == 'number' then 416 | r[#r + 1] = ":" .. v 417 | elseif type(v) == 'table' then 418 | r[#r + 1] = _serialize_redis(v) 419 | elseif v == nil then 420 | r[#r + 1] = "$-1" 421 | else 422 | ngx_log(ngx.DEBUG, "value ", v, " (type: ", type(v), ") can't be serialized in an array") 423 | end 424 | end 425 | -- add trailling CRLF 426 | r[#r + 1] = "" 427 | return table_concat(r, CRLF) 428 | else 429 | return "-ERR Type '" .. type(data) .. "' can't be serialized using RESP" .. CRLF 430 | end 431 | end 432 | 433 | local function output_redis(ret) 434 | if ret.err then 435 | return "-ERR " .. ret.err .. CRLF 436 | end 437 | local output 438 | if ret.has_msg then 439 | return _serialize_redis(ret.msg) 440 | else 441 | return _serialize_redis("OK") 442 | end 443 | 444 | end 445 | 446 | 447 | function _M.serve_stream_redis(self) 448 | -- stream subsystem server that is Redis-compatible and supports RESP and inline protocol 449 | local sock = assert(ngx.req.socket(true)) 450 | local shdict = self.shdict 451 | local line, prefix, err 452 | local cmd, args 453 | while true do 454 | -- read a line 455 | line, err = sock:receive() 456 | if not line then 457 | if err == "timeout" then 458 | sock:close() 459 | end 460 | return 461 | elseif #line == 0 then 462 | -- ignore empty line 463 | goto redis_nextline 464 | end 465 | prefix = byte(line) 466 | if prefix == 42 then -- char '*' 467 | cmd, args = _parse_redis_req(line, sock) 468 | if cmd == nil then 469 | ngx_print(output_redis({["err"] = args})) 470 | end 471 | else 472 | cmd, args = _parse_line(line) 473 | end 474 | 475 | if not cmd then 476 | ngx_print("Invalid argument(s)", CRLF) 477 | elseif cmd == "quit" then 478 | return 479 | else 480 | local ret = _do_cmd(self, cmd, args) 481 | ngx_print(output_redis(ret)) 482 | end 483 | ::redis_nextline:: 484 | end 485 | 486 | 487 | 488 | end 489 | 490 | 491 | function _M.serve(self, mode) 492 | if not mode then 493 | if ngx.config.subsystem == "http" then 494 | mode = "http_plain" 495 | elseif ngx.config.subsystem == "stream" then 496 | mode = "stream_redis" 497 | else 498 | ngx_log(ngx.ERR, "subsystem ", ngx.config.subsystem, " is not supported") 499 | ngx.exit(500) 500 | end 501 | end 502 | local handler = _M["serve_" .. mode] 503 | if not handler then 504 | ngx_log(ngx.ERR, "handler ", mode, " not found") 505 | ngx.exit(500) 506 | end 507 | 508 | return handler(self) 509 | end 510 | 511 | return _M 512 | -------------------------------------------------------------------------------- /patches/openresty-1.13.6.1-cross_module_shdict.patch: -------------------------------------------------------------------------------- 1 | diff --git a/ngx_lua-0.10.11/src/ngx_http_lua_directive.c b/ngx_lua-0.10.11/src/ngx_http_lua_directive.c 2 | index 014a472..60493b2 100644 3 | --- a/ngx_lua-0.10.11/src/ngx_http_lua_directive.c 4 | +++ b/ngx_lua-0.10.11/src/ngx_http_lua_directive.c 5 | @@ -125,7 +125,7 @@ ngx_http_lua_shared_dict(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 6 | ctx->log = &cf->cycle->new_log; 7 | 8 | zone = ngx_http_lua_shared_memory_add(cf, &name, (size_t) size, 9 | - &ngx_http_lua_module); 10 | + &ngx_shared_memory_add); 11 | if (zone == NULL) { 12 | return NGX_CONF_ERROR; 13 | } 14 | @@ -133,10 +133,16 @@ ngx_http_lua_shared_dict(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 15 | if (zone->data) { 16 | ctx = zone->data; 17 | 18 | - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 19 | - "lua_shared_dict \"%V\" is already defined as " 20 | - "\"%V\"", &name, &ctx->name); 21 | - return NGX_CONF_ERROR; 22 | + if((&name)->len == (&ctx->name)->len && 23 | + ! ngx_strcmp((&name)->data, (&ctx->name)->data) && 24 | + zone->tag == &ngx_shared_memory_add) { 25 | + dd("same zone in other subsystem"); 26 | + } else { 27 | + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 28 | + "http lua_shared_dict \"%V\" is already defined as " 29 | + "\"%V\"", &name, &ctx->name); 30 | + return NGX_CONF_ERROR; 31 | + } 32 | } 33 | 34 | zone->init = ngx_http_lua_shdict_init_zone; 35 | diff --git a/ngx_stream_lua-0.0.3/src/ngx_stream_lua_directive.c b/ngx_stream_lua-0.0.3/src/ngx_stream_lua_directive.c 36 | index dbf491c..e59f019 100644 37 | --- a/ngx_stream_lua-0.0.3/src/ngx_stream_lua_directive.c 38 | +++ b/ngx_stream_lua-0.0.3/src/ngx_stream_lua_directive.c 39 | @@ -115,7 +115,7 @@ ngx_stream_lua_shared_dict(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 40 | ctx->log = &cf->cycle->new_log; 41 | 42 | zone = ngx_stream_lua_shared_memory_add(cf, &name, (size_t) size, 43 | - &ngx_stream_lua_module); 44 | + &ngx_shared_memory_add); 45 | if (zone == NULL) { 46 | return NGX_CONF_ERROR; 47 | } 48 | @@ -123,10 +123,16 @@ ngx_stream_lua_shared_dict(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 49 | if (zone->data) { 50 | ctx = zone->data; 51 | 52 | - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 53 | - "lua_shared_dict \"%V\" is already defined as " 54 | - "\"%V\"", &name, &ctx->name); 55 | - return NGX_CONF_ERROR; 56 | + if((&name)->len == (&ctx->name)->len && 57 | + ! ngx_strcmp((&name)->data, (&ctx->name)->data) && 58 | + zone->tag == &ngx_shared_memory_add) { 59 | + dd("same zone in other subsystem"); 60 | + } else { 61 | + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 62 | + "stream lua_shared_dict \"%V\" is already defined as " 63 | + "\"%V\"", &name, &ctx->name); 64 | + return NGX_CONF_ERROR; 65 | + } 66 | } 67 | 68 | zone->init = ngx_stream_lua_shdict_init_zone; -------------------------------------------------------------------------------- /patches/openresty-1.15.8.2-cross_module_shdict.patch: -------------------------------------------------------------------------------- 1 | diff --git a/bundle/ngx_lua-0.10.15/src/ngx_http_lua_directive.c b/bundle/ngx_lua-0.10.15/src/ngx_http_lua_directive.c 2 | index a989c26..b5bc8af 100644 3 | --- a/bundle/ngx_lua-0.10.15/src/ngx_http_lua_directive.c 4 | +++ b/bundle/ngx_lua-0.10.15/src/ngx_http_lua_directive.c 5 | @@ -125,7 +125,7 @@ ngx_http_lua_shared_dict(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 6 | ctx->log = &cf->cycle->new_log; 7 | 8 | zone = ngx_http_lua_shared_memory_add(cf, &name, (size_t) size, 9 | - &ngx_http_lua_module); 10 | + &ngx_shared_memory_add); 11 | if (zone == NULL) { 12 | return NGX_CONF_ERROR; 13 | } 14 | @@ -133,10 +133,16 @@ ngx_http_lua_shared_dict(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 15 | if (zone->data) { 16 | ctx = zone->data; 17 | 18 | - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 19 | - "lua_shared_dict \"%V\" is already defined as " 20 | - "\"%V\"", &name, &ctx->name); 21 | - return NGX_CONF_ERROR; 22 | + if((&name)->len == (&ctx->name)->len && 23 | + ! ngx_strcmp((&name)->data, (&ctx->name)->data) && 24 | + zone->tag == &ngx_shared_memory_add) { 25 | + dd("same zone in other subsystem"); 26 | + } else { 27 | + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 28 | + "http lua_shared_dict \"%V\" is already defined as " 29 | + "\"%V\"", &name, &ctx->name); 30 | + return NGX_CONF_ERROR; 31 | + } 32 | } 33 | 34 | zone->init = ngx_http_lua_shdict_init_zone; 35 | diff --git a/bundle/ngx_stream_lua-0.0.7/src/ngx_stream_lua_directive.c b/bundle/ngx_stream_lua-0.0.7/src/ngx_stream_lua_directive.c 36 | index d32edea..248f961 100644 37 | --- a/bundle/ngx_stream_lua-0.0.7/src/ngx_stream_lua_directive.c 38 | +++ b/bundle/ngx_stream_lua-0.0.7/src/ngx_stream_lua_directive.c 39 | @@ -123,7 +123,7 @@ ngx_stream_lua_shared_dict(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 40 | ctx->log = &cf->cycle->new_log; 41 | 42 | zone = ngx_stream_lua_shared_memory_add(cf, &name, (size_t) size, 43 | - &ngx_stream_lua_module); 44 | + &ngx_shared_memory_add); 45 | if (zone == NULL) { 46 | return NGX_CONF_ERROR; 47 | } 48 | @@ -131,10 +131,16 @@ ngx_stream_lua_shared_dict(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 49 | if (zone->data) { 50 | ctx = zone->data; 51 | 52 | - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 53 | - "lua_shared_dict \"%V\" is already defined as " 54 | - "\"%V\"", &name, &ctx->name); 55 | - return NGX_CONF_ERROR; 56 | + if((&name)->len == (&ctx->name)->len && 57 | + ! ngx_strcmp((&name)->data, (&ctx->name)->data) && 58 | + zone->tag == &ngx_shared_memory_add) { 59 | + dd("same zone in other subsystem"); 60 | + } else { 61 | + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 62 | + "stream lua_shared_dict \"%V\" is already defined as " 63 | + "\"%V\"", &name, &ctx->name); 64 | + return NGX_CONF_ERROR; 65 | + } 66 | } 67 | 68 | zone->init = ngx_stream_lua_shdict_init_zone; 69 | -------------------------------------------------------------------------------- /patches/openresty-1.17.8.2-cross_module_shdict.patch: -------------------------------------------------------------------------------- 1 | diff --git a/bundle/ngx_lua-0.10.17/src/ngx_http_lua_directive.c b/bundle/ngx_lua-0.10.17/src/ngx_http_lua_directive.c 2 | index a989c26..b5bc8af 100644 3 | --- a/bundle/ngx_lua-0.10.17/src/ngx_http_lua_directive.c 4 | +++ b/bundle/ngx_lua-0.10.17/src/ngx_http_lua_directive.c 5 | @@ -125,7 +125,7 @@ ngx_http_lua_shared_dict(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 6 | ctx->log = &cf->cycle->new_log; 7 | 8 | zone = ngx_http_lua_shared_memory_add(cf, &name, (size_t) size, 9 | - &ngx_http_lua_module); 10 | + &ngx_shared_memory_add); 11 | if (zone == NULL) { 12 | return NGX_CONF_ERROR; 13 | } 14 | @@ -133,10 +133,16 @@ ngx_http_lua_shared_dict(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 15 | if (zone->data) { 16 | ctx = zone->data; 17 | 18 | - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 19 | - "lua_shared_dict \"%V\" is already defined as " 20 | - "\"%V\"", &name, &ctx->name); 21 | - return NGX_CONF_ERROR; 22 | + if((&name)->len == (&ctx->name)->len && 23 | + ! ngx_strcmp((&name)->data, (&ctx->name)->data) && 24 | + zone->tag == &ngx_shared_memory_add) { 25 | + dd("same zone in other subsystem"); 26 | + } else { 27 | + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 28 | + "http lua_shared_dict \"%V\" is already defined as " 29 | + "\"%V\"", &name, &ctx->name); 30 | + return NGX_CONF_ERROR; 31 | + } 32 | } 33 | 34 | zone->init = ngx_http_lua_shdict_init_zone; 35 | diff --git a/bundle/ngx_stream_lua-0.0.8/src/ngx_stream_lua_directive.c b/bundle/ngx_stream_lua-0.0.8/src/ngx_stream_lua_directive.c 36 | index d32edea..248f961 100644 37 | --- a/bundle/ngx_stream_lua-0.0.8/src/ngx_stream_lua_directive.c 38 | +++ b/bundle/ngx_stream_lua-0.0.8/src/ngx_stream_lua_directive.c 39 | @@ -123,7 +123,7 @@ ngx_stream_lua_shared_dict(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 40 | ctx->log = &cf->cycle->new_log; 41 | 42 | zone = ngx_stream_lua_shared_memory_add(cf, &name, (size_t) size, 43 | - &ngx_stream_lua_module); 44 | + &ngx_shared_memory_add); 45 | if (zone == NULL) { 46 | return NGX_CONF_ERROR; 47 | } 48 | @@ -131,10 +131,16 @@ ngx_stream_lua_shared_dict(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 49 | if (zone->data) { 50 | ctx = zone->data; 51 | 52 | - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 53 | - "lua_shared_dict \"%V\" is already defined as " 54 | - "\"%V\"", &name, &ctx->name); 55 | - return NGX_CONF_ERROR; 56 | + if((&name)->len == (&ctx->name)->len && 57 | + ! ngx_strcmp((&name)->data, (&ctx->name)->data) && 58 | + zone->tag == &ngx_shared_memory_add) { 59 | + dd("same zone in other subsystem"); 60 | + } else { 61 | + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 62 | + "stream lua_shared_dict \"%V\" is already defined as " 63 | + "\"%V\"", &name, &ctx->name); 64 | + return NGX_CONF_ERROR; 65 | + } 66 | } 67 | 68 | zone->init = ngx_stream_lua_shdict_init_zone; 69 | -------------------------------------------------------------------------------- /patches/openresty-1.19.3.1-cross_module_shdict.patch: -------------------------------------------------------------------------------- 1 | diff --git a/bundle/ngx_lua-0.10.19/src/ngx_http_lua_directive.c b/bundle/ngx_lua-0.10.19/src/ngx_http_lua_directive.c 2 | index a989c26..b5bc8af 100644 3 | --- a/bundle/ngx_lua-0.10.19/src/ngx_http_lua_directive.c 4 | +++ b/bundle/ngx_lua-0.10.19/src/ngx_http_lua_directive.c 5 | @@ -125,7 +125,7 @@ ngx_http_lua_shared_dict(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 6 | ctx->log = &cf->cycle->new_log; 7 | 8 | zone = ngx_http_lua_shared_memory_add(cf, &name, (size_t) size, 9 | - &ngx_http_lua_module); 10 | + &ngx_shared_memory_add); 11 | if (zone == NULL) { 12 | return NGX_CONF_ERROR; 13 | } 14 | @@ -133,10 +133,16 @@ ngx_http_lua_shared_dict(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 15 | if (zone->data) { 16 | ctx = zone->data; 17 | 18 | - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 19 | - "lua_shared_dict \"%V\" is already defined as " 20 | - "\"%V\"", &name, &ctx->name); 21 | - return NGX_CONF_ERROR; 22 | + if((&name)->len == (&ctx->name)->len && 23 | + ! ngx_strcmp((&name)->data, (&ctx->name)->data) && 24 | + zone->tag == &ngx_shared_memory_add) { 25 | + dd("same zone in other subsystem"); 26 | + } else { 27 | + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 28 | + "http lua_shared_dict \"%V\" is already defined as " 29 | + "\"%V\"", &name, &ctx->name); 30 | + return NGX_CONF_ERROR; 31 | + } 32 | } 33 | 34 | zone->init = ngx_http_lua_shdict_init_zone; 35 | diff --git a/bundle/ngx_stream_lua-0.0.9/src/ngx_stream_lua_directive.c b/bundle/ngx_stream_lua-0.0.9/src/ngx_stream_lua_directive.c 36 | index d32edea..248f961 100644 37 | --- a/bundle/ngx_stream_lua-0.0.9/src/ngx_stream_lua_directive.c 38 | +++ b/bundle/ngx_stream_lua-0.0.9/src/ngx_stream_lua_directive.c 39 | @@ -123,7 +123,7 @@ ngx_stream_lua_shared_dict(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 40 | ctx->log = &cf->cycle->new_log; 41 | 42 | zone = ngx_stream_lua_shared_memory_add(cf, &name, (size_t) size, 43 | - &ngx_stream_lua_module); 44 | + &ngx_shared_memory_add); 45 | if (zone == NULL) { 46 | return NGX_CONF_ERROR; 47 | } 48 | @@ -131,10 +131,16 @@ ngx_stream_lua_shared_dict(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 49 | if (zone->data) { 50 | ctx = zone->data; 51 | 52 | - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 53 | - "lua_shared_dict \"%V\" is already defined as " 54 | - "\"%V\"", &name, &ctx->name); 55 | - return NGX_CONF_ERROR; 56 | + if((&name)->len == (&ctx->name)->len && 57 | + ! ngx_strcmp((&name)->data, (&ctx->name)->data) && 58 | + zone->tag == &ngx_shared_memory_add) { 59 | + dd("same zone in other subsystem"); 60 | + } else { 61 | + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 62 | + "stream lua_shared_dict \"%V\" is already defined as " 63 | + "\"%V\"", &name, &ctx->name); 64 | + return NGX_CONF_ERROR; 65 | + } 66 | } 67 | 68 | zone->init = ngx_stream_lua_shdict_init_zone; 69 | -------------------------------------------------------------------------------- /t/http.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | 3 | use Test::Nginx::Socket::Lua 'no_plan'; 4 | use Cwd qw(cwd); 5 | 6 | 7 | my $pwd = cwd(); 8 | 9 | our $HttpConfig = qq{ 10 | lua_package_path "$pwd/lib/?.lua;$pwd/lib/?/init.lua;;"; 11 | lua_shared_dict dogs 1m; 12 | }; 13 | 14 | run_tests(); 15 | 16 | __DATA__ 17 | === TEST 1: HTTP with get. 18 | --- http_config eval: $::HttpConfig 19 | --- config 20 | location =/t { 21 | content_by_lua_block { 22 | ngx.shared.dogs:set("doge", "wow") 23 | local srv = require("resty.shdict.server") 24 | local s = srv:new(nil, "dogs") 25 | s:serve() 26 | } 27 | } 28 | --- request 29 | GET /t?cmd=get%20doge 30 | --- response_body eval 31 | "\"wow\"" 32 | --- no_error_log 33 | [error] 34 | 35 | 36 | === TEST 2: HTTP initialized without shdict, no dict arg. 37 | --- http_config eval: $::HttpConfig 38 | --- config 39 | location =/t { 40 | content_by_lua_block { 41 | ngx.shared.dogs:set("doge", "wow") 42 | local srv = require("resty.shdict.server") 43 | local s = srv:new(nil, nil) 44 | s:serve() 45 | } 46 | } 47 | --- request 48 | GET /t?cmd=get%20doge 49 | --- response_body eval 50 | "ERR no shdict selected" 51 | --- no_error_log 52 | [error] 53 | 54 | 55 | === TEST 3: HTTP initialized without shdict, has dict arg. 56 | --- http_config eval: $::HttpConfig 57 | --- config 58 | location =/t { 59 | content_by_lua_block { 60 | ngx.shared.dogs:set("doge", "wow") 61 | local srv = require("resty.shdict.server") 62 | local s = srv:new(nil, nil) 63 | s:serve() 64 | } 65 | } 66 | --- request 67 | GET /t?dict=dogs&cmd=get%20doge 68 | --- response_body eval 69 | "\"wow\"" 70 | --- no_error_log 71 | [error] 72 | 73 | 74 | === TEST 4: HTTP initialized without shdict, has wrong dict arg. 75 | --- http_config eval: $::HttpConfig 76 | --- config 77 | location =/t { 78 | content_by_lua_block { 79 | ngx.shared.dogs:set("doge", "wow") 80 | local srv = require("resty.shdict.server") 81 | local s = srv:new(nil, nil) 82 | s:serve() 83 | } 84 | } 85 | --- request 86 | GET /t?dict=cats&cmd=get%20dog 87 | --- response_body eval 88 | "ERR shdict 'cats' not defined" 89 | --- no_error_log 90 | [error] 91 | 92 | 93 | === TEST 5: HTTP initialized with password, no password arg. 94 | --- http_config eval: $::HttpConfig 95 | --- config 96 | location =/t { 97 | content_by_lua_block { 98 | ngx.shared.dogs:set("doge", "wow") 99 | local srv = require("resty.shdict.server") 100 | local s = srv:new("foobar", nil) 101 | s:serve() 102 | } 103 | } 104 | --- request 105 | GET /t?dict=dogs&cmd=get%20dog 106 | --- response_body eval 107 | "ERR authentication required" 108 | --- no_error_log 109 | [error] 110 | 111 | 112 | === TEST 6: HTTP initialized with password, has password arg. 113 | --- http_config eval: $::HttpConfig 114 | --- config 115 | location =/t { 116 | content_by_lua_block { 117 | ngx.shared.dogs:set("doge", "wow") 118 | local srv = require("resty.shdict.server") 119 | local s = srv:new("foobar", nil) 120 | s:serve() 121 | } 122 | } 123 | --- request 124 | GET /t?dict=dogs&cmd=get%20doge&password=foobar 125 | --- response_body eval 126 | "\"wow\"" 127 | --- no_error_log 128 | [error] 129 | 130 | 131 | === TEST 7: HTTP initialized with password, wrong password arg. 132 | --- http_config eval: $::HttpConfig 133 | --- config 134 | location =/t { 135 | content_by_lua_block { 136 | ngx.shared.dogs:set("doge", "wow") 137 | local srv = require("resty.shdict.server") 138 | local s = srv:new("foobar", nil) 139 | s:serve() 140 | } 141 | } 142 | --- request 143 | GET /t?dict=dogs&cmd=get%20doge&password=foobarbar 144 | --- response_body eval 145 | "ERR invalid password" 146 | --- no_error_log 147 | [error] 148 | 149 | -------------------------------------------------------------------------------- /t/redis-commands.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | 3 | use Test::Nginx::Socket::Lua::Stream 'no_plan'; 4 | 5 | use Cwd qw(cwd); 6 | 7 | 8 | my $pwd = cwd(); 9 | 10 | our $StreamConfig = qq{ 11 | lua_package_path "$pwd/lib/?.lua;$pwd/lib/?/init.lua;;"; 12 | lua_shared_dict dogs 1m; 13 | }; 14 | 15 | no_shuffle(); 16 | run_tests(); 17 | 18 | __DATA__ 19 | === TEST 1: Redis commands KEYS 20 | --- stream_config eval: $::StreamConfig 21 | --- stream_server_config 22 | content_by_lua_block { 23 | ngx.shared.dogs:set("doge1", "wow") 24 | ngx.shared.dogs:set("doge2", "such") 25 | ngx.shared.dogs:set("doge3", "doge") 26 | require("resty.shdict.redis-commands") 27 | local srv = require("resty.shdict.server") 28 | local s = srv:new(nil, "dogs") 29 | s:serve() 30 | } 31 | --- stream_request eval 32 | "*2\r 33 | \$4\r 34 | keys\r 35 | \$5\r 36 | doge*\r 37 | *2\r 38 | \$4\r 39 | keys\r 40 | \$5\r 41 | do?e1\r 42 | 43 | quit\r 44 | " 45 | 46 | --- stream_response eval 47 | "*3\r 48 | \$5\r 49 | doge1\r 50 | \$5\r 51 | doge2\r 52 | \$5\r 53 | doge3\r 54 | *1\r 55 | \$5\r 56 | doge1\r 57 | " 58 | 59 | --- no_error_log 60 | [error] 61 | 62 | === TEST 2: Redis commands EVAL keys and argv 63 | --- stream_config eval: $::StreamConfig 64 | --- stream_server_config 65 | content_by_lua_block { 66 | ngx.shared.dogs:set("doge", "wow") 67 | require("resty.shdict.redis-commands") 68 | local srv = require("resty.shdict.server") 69 | local s = srv:new(nil, "dogs") 70 | s:serve() 71 | } 72 | --- stream_request eval 73 | "*7\r 74 | \$4\r 75 | eval\r 76 | \$25\r 77 | return KEYS[2] .. ARGV[1]\r 78 | :2\r 79 | \$1\r 80 | 1\r 81 | \$1\r 82 | 2\r 83 | \$1\r 84 | 3\r 85 | \$1\r 86 | 4\r 87 | 88 | *3\r 89 | \$4\r 90 | eval\r 91 | \$8\r 92 | return 1\r 93 | \$2\r 94 | aa\r 95 | 96 | *3\r 97 | \$4\r 98 | eval\r 99 | \$8\r 100 | return 1\r 101 | :-1\r 102 | 103 | *3\r 104 | \$4\r 105 | eval\r 106 | \$8\r 107 | return 1\r 108 | :1\r 109 | 110 | *2\r 111 | \$4\r 112 | eval\r 113 | \$8\r 114 | return 1\r 115 | 116 | quit\r 117 | " 118 | 119 | --- stream_response eval 120 | "+23\r 121 | -ERR value is not an integer or out of range\r 122 | -ERR Number of keys can't be negative\r 123 | -ERR Number of keys can't be greater than number of arg\r 124 | -ERR wrong number of arguments for 'eval' command\r 125 | " 126 | 127 | --- no_error_log 128 | [error] 129 | 130 | 131 | === TEST 3: Redis commands EVAL shdict.call no error 132 | --- stream_config eval: $::StreamConfig 133 | --- stream_server_config 134 | content_by_lua_block { 135 | ngx.shared.dogs:set("doge", "wow") 136 | require("resty.shdict.redis-commands") 137 | local srv = require("resty.shdict.server") 138 | local s = srv:new(nil, "dogs") 139 | s:serve() 140 | } 141 | --- stream_request eval 142 | "*3\r 143 | \$4\r 144 | eval\r 145 | \$33\r 146 | return shdict.call('get', 'doge')\r 147 | :0\r 148 | 149 | *3\r 150 | \$4\r 151 | eval\r 152 | \$35\r 153 | return shdict.call('set', 'cat', 1)\r 154 | :0\r 155 | 156 | *3\r 157 | \$4\r 158 | eval\r 159 | \$32\r 160 | return shdict.call('get', 'cat')\r 161 | :0\r 162 | 163 | *3\r 164 | \$4\r 165 | eval\r 166 | \$33\r 167 | return shdict.pcall('get', 'cat')\r 168 | :0\r 169 | 170 | quit\r 171 | " 172 | --- stream_response eval 173 | "+wow\r 174 | +OK\r 175 | :1\r 176 | :1\r 177 | " 178 | 179 | --- no_error_log 180 | [error] 181 | 182 | 183 | === TEST 4: Redis commands EVAL shdict.call and shdict.pcall error handling 184 | --- stream_config eval: $::StreamConfig 185 | --- stream_server_config 186 | content_by_lua_block { 187 | ngx.shared.dogs:set("doge", "wow") 188 | require("resty.shdict.redis-commands") 189 | local srv = require("resty.shdict.server") 190 | local s = srv:new(nil, "dogs") 191 | s:serve() 192 | } 193 | --- stream_request eval 194 | "*3\r 195 | \$4\r 196 | eval\r 197 | \$34\r 198 | return shdict.call('gget', 'doge')\r 199 | :0\r 200 | 201 | *3\r 202 | \$4\r 203 | eval\r 204 | \$28\r 205 | shdict.pcall('gget', 'doge')\r 206 | :0\r 207 | 208 | *3\r 209 | \$4\r 210 | eval\r 211 | \$35\r 212 | return shdict.pcall('gget', 'doge')\r 213 | :0\r 214 | 215 | quit\r 216 | " 217 | --- stream_response eval 218 | "-ERR Error running script (user_script):2: Unknown ngx.shared command called from Lua script\r 219 | \$-1\r 220 | -ERR (user_script):2: Unknown ngx.shared command called from Lua script\r 221 | " 222 | 223 | --- no_error_log 224 | [error] 225 | -------------------------------------------------------------------------------- /t/redis-inline.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | 3 | use Test::Nginx::Socket::Lua::Stream 'no_plan'; 4 | 5 | use Cwd qw(cwd); 6 | 7 | 8 | my $pwd = cwd(); 9 | 10 | our $StreamConfig = qq{ 11 | lua_package_path "$pwd/lib/?.lua;$pwd/lib/?/init.lua;;"; 12 | lua_shared_dict dogs 1m; 13 | }; 14 | 15 | #no_shuffle(); 16 | run_tests(); 17 | 18 | __DATA__ 19 | === TEST 1: Redis inline protocol simple get. 20 | --- stream_config eval: $::StreamConfig 21 | --- stream_server_config 22 | content_by_lua_block { 23 | ngx.shared.dogs:set("doge", "wow") 24 | local srv = require("resty.shdict.server") 25 | local s = srv:new(nil, "dogs") 26 | s:serve_stream_redis() 27 | } 28 | --- stream_request eval 29 | "get doge\r 30 | quit\r 31 | " 32 | 33 | --- stream_response eval 34 | "+wow\r 35 | " 36 | 37 | --- no_error_log 38 | [error] 39 | 40 | 41 | === TEST 2: Redis inline protocol initialized without shdict, no dict arg. 42 | --- stream_config eval: $::StreamConfig 43 | --- stream_server_config 44 | content_by_lua_block { 45 | ngx.shared.dogs:set("doge", "wow") 46 | local srv = require("resty.shdict.server") 47 | local s = srv:new(nil, nil) 48 | s:serve_stream_redis() 49 | } 50 | --- stream_request eval 51 | "get doge\r 52 | quit\r 53 | " 54 | 55 | --- stream_response eval 56 | "-ERR no shdict selected\r 57 | " 58 | 59 | --- no_error_log 60 | [error] 61 | 62 | 63 | === TEST 3: Redis inline protocol initialized without shdict, has dict arg. 64 | --- stream_config eval: $::StreamConfig 65 | --- stream_server_config 66 | content_by_lua_block { 67 | ngx.shared.dogs:set("doge", "wow") 68 | local srv = require("resty.shdict.server") 69 | local s = srv:new(nil, nil) 70 | s:serve_stream_redis() 71 | } 72 | --- stream_request eval 73 | "select dogs\r 74 | get doge\r 75 | quit\r 76 | " 77 | 78 | --- stream_response eval 79 | "+OK\r 80 | +wow\r 81 | " 82 | 83 | --- no_error_log 84 | [error] 85 | 86 | 87 | === TEST 4: Redis inline protocol initialized without shdict, has wrong dict arg. 88 | --- stream_config eval: $::StreamConfig 89 | --- stream_server_config 90 | content_by_lua_block { 91 | ngx.shared.dogs:set("doge", "wow") 92 | local srv = require("resty.shdict.server") 93 | local s = srv:new(nil, nil) 94 | s:serve_stream_redis() 95 | } 96 | --- stream_request eval 97 | "select cats\r 98 | quit\r 99 | " 100 | 101 | --- stream_response eval 102 | "-ERR shdict 'cats' not defined\r 103 | " 104 | 105 | --- no_error_log 106 | [error] 107 | 108 | 109 | === TEST 5: Redis inline protocol initialized with password, no password arg. 110 | --- stream_config eval: $::StreamConfig 111 | --- stream_server_config 112 | content_by_lua_block { 113 | ngx.shared.dogs:set("doge", "wow") 114 | local srv = require("resty.shdict.server") 115 | local s = srv:new("foobar", nil) 116 | s:serve_stream_redis() 117 | } 118 | --- stream_request eval 119 | "select dogs\r 120 | get doge\r 121 | quit\r 122 | " 123 | 124 | --- stream_response eval 125 | "-ERR authentication required\r 126 | -ERR authentication required\r 127 | " 128 | 129 | --- no_error_log 130 | [error] 131 | 132 | 133 | === TEST 6: Redis inline protocol initialized with password, has password arg. 134 | --- stream_config eval: $::StreamConfig 135 | --- stream_server_config 136 | content_by_lua_block { 137 | ngx.shared.dogs:set("doge", "wow") 138 | local srv = require("resty.shdict.server") 139 | local s = srv:new("foobar", nil) 140 | s:serve_stream_redis() 141 | } 142 | --- stream_request eval 143 | "auth foobar\r 144 | select dogs\r 145 | get doge\r 146 | quit\r 147 | " 148 | 149 | --- stream_response eval 150 | "+OK\r 151 | +OK\r 152 | +wow\r 153 | " 154 | 155 | --- no_error_log 156 | [error] 157 | 158 | 159 | === TEST 7: Redis inline protocol initialized with password, wrong password arg. 160 | --- stream_config eval: $::StreamConfig 161 | --- stream_server_config 162 | content_by_lua_block { 163 | ngx.shared.dogs:set("doge", "wow") 164 | local srv = require("resty.shdict.server") 165 | local s = srv:new("foobar", nil) 166 | s:serve_stream_redis() 167 | } 168 | --- stream_request eval 169 | "auth foobarbar\r 170 | select dogs\r 171 | get doge\r 172 | quit\r 173 | " 174 | 175 | --- stream_response eval 176 | "-ERR invalid password\r 177 | -ERR authentication required\r 178 | -ERR authentication required\r 179 | " 180 | 181 | --- no_error_log 182 | [error] 183 | 184 | 185 | === TEST 8: Redis inline protocol ping 186 | --- stream_config eval: $::StreamConfig 187 | --- stream_server_config 188 | content_by_lua_block { 189 | local srv = require("resty.shdict.server") 190 | local s = srv:new(nil, nil) 191 | s:serve_stream_redis() 192 | } 193 | --- stream_request eval 194 | "ping\r 195 | quit\r 196 | " 197 | 198 | --- stream_response eval 199 | "+PONG\r 200 | " 201 | 202 | --- no_error_log 203 | [error] 204 | 205 | 206 | === TEST 9: Redis inline protocol parse line 207 | --- stream_config eval: $::StreamConfig 208 | --- stream_server_config 209 | content_by_lua_block { 210 | ngx.shared.dogs:set("d'o'ge", "wow") 211 | ngx.shared.dogs:set("do ge", "wo w") 212 | ngx.shared.dogs:set("do", "wo") 213 | ngx.shared.dogs:set([[do"ge]], [[wo"w]]) 214 | local srv = require("resty.shdict.server") 215 | local s = srv:new(nil, "dogs") 216 | s:serve_stream_redis() 217 | } 218 | --- stream_request eval 219 | "get \"\r 220 | get \"doge\" \"\r 221 | get \"d'o'ge\"\r 222 | get \"d'o\"ge'\r 223 | get \"do ge\"\r 224 | get \"do\\\"ge\"\r 225 | quit\r 226 | " 227 | 228 | --- stream_response eval 229 | "Invalid argument(s)\r 230 | Invalid argument(s)\r 231 | +wow\r 232 | Invalid argument(s)\r 233 | +wo w\r 234 | +wo\"w\r 235 | " 236 | 237 | --- no_error_log 238 | [error] 239 | 240 | -------------------------------------------------------------------------------- /t/redis-resp.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | 3 | 4 | use Test::Nginx::Socket::Lua::Stream 'no_plan'; 5 | 6 | use Cwd qw(cwd); 7 | 8 | 9 | my $pwd = cwd(); 10 | 11 | our $StreamConfig = qq{ 12 | lua_package_path "$pwd/lib/?.lua;$pwd/lib/?/init.lua;;"; 13 | lua_shared_dict dogs 1m; 14 | }; 15 | 16 | no_shuffle(); 17 | run_tests(); 18 | 19 | __DATA__ 20 | === TEST 1: Redis RESP protocol simple get. 21 | --- stream_config eval: $::StreamConfig 22 | --- stream_server_config 23 | content_by_lua_block { 24 | ngx.shared.dogs:set("doge", "wow") 25 | local srv = require("resty.shdict.server") 26 | local s = srv:new(nil, "dogs") 27 | s:serve_stream_redis() 28 | } 29 | --- stream_request eval 30 | "*2\r 31 | \$3\r 32 | get\r 33 | \$4\r 34 | doge\r 35 | 36 | quit\r 37 | " 38 | 39 | --- stream_response eval 40 | "+wow\r 41 | " 42 | 43 | --- no_error_log 44 | [error] 45 | 46 | 47 | === TEST 2: Redis RESP protocol initialized without shdict, no dict arg. 48 | --- stream_config eval: $::StreamConfig 49 | --- stream_server_config 50 | content_by_lua_block { 51 | ngx.shared.dogs:set("doge", "wow") 52 | local srv = require("resty.shdict.server") 53 | local s = srv:new(nil, nil) 54 | s:serve() 55 | } 56 | --- stream_request eval 57 | "*2\r 58 | \$3\r 59 | get\r 60 | \$4\r 61 | doge\r 62 | 63 | quit\r 64 | " 65 | 66 | --- stream_response eval 67 | "-ERR no shdict selected\r 68 | " 69 | 70 | --- no_error_log 71 | [error] 72 | 73 | 74 | === TEST 3: Redis RESP protocol initialized without shdict, has dict arg. 75 | --- stream_config eval: $::StreamConfig 76 | --- stream_server_config 77 | content_by_lua_block { 78 | ngx.shared.dogs:set("doge", "wow") 79 | local srv = require("resty.shdict.server") 80 | local s = srv:new(nil, nil) 81 | s:serve() 82 | } 83 | --- stream_request eval 84 | "*2\r 85 | \$6\r 86 | select\r 87 | \$4\r 88 | dogs\r 89 | 90 | *2\r 91 | \$3\r 92 | get\r 93 | \$4\r 94 | doge\r 95 | 96 | quit\r 97 | " 98 | 99 | --- stream_response eval 100 | "+OK\r 101 | +wow\r 102 | " 103 | 104 | --- no_error_log 105 | [error] 106 | 107 | 108 | === TEST 4: Redis RESP protocol initialized without shdict, has wrong dict arg. 109 | --- stream_config eval: $::StreamConfig 110 | --- stream_server_config 111 | content_by_lua_block { 112 | ngx.shared.dogs:set("doge", "wow") 113 | local srv = require("resty.shdict.server") 114 | local s = srv:new(nil, nil) 115 | s:serve() 116 | } 117 | --- stream_request eval 118 | "*2\r 119 | \$6\r 120 | select\r 121 | \$4\r 122 | cats\r 123 | 124 | quit\r 125 | " 126 | 127 | --- stream_response eval 128 | "-ERR shdict 'cats' not defined\r 129 | " 130 | 131 | --- no_error_log 132 | [error] 133 | 134 | 135 | === TEST 5: Redis RESP protocol initialized with password, no password arg. 136 | --- stream_config eval: $::StreamConfig 137 | --- stream_server_config 138 | content_by_lua_block { 139 | ngx.shared.dogs:set("doge", "wow") 140 | local srv = require("resty.shdict.server") 141 | local s = srv:new("foobar", nil) 142 | s:serve() 143 | } 144 | --- stream_request eval 145 | "*2\r 146 | \$6\r 147 | select\r 148 | \$4\r 149 | dogs\r 150 | 151 | *2\r 152 | \$3\r 153 | get\r 154 | \$4\r 155 | doge\r 156 | 157 | quit\r 158 | " 159 | 160 | --- stream_response eval 161 | "-ERR authentication required\r 162 | -ERR authentication required\r 163 | " 164 | 165 | --- no_error_log 166 | [error] 167 | 168 | 169 | === TEST 6: Redis RESP protocol initialized with password, has password arg. 170 | --- stream_config eval: $::StreamConfig 171 | --- stream_server_config 172 | content_by_lua_block { 173 | ngx.shared.dogs:set("doge", "wow") 174 | local srv = require("resty.shdict.server") 175 | local s = srv:new("foobar", nil) 176 | s:serve() 177 | } 178 | --- stream_request eval 179 | "*2\r 180 | \$4\r 181 | auth\r 182 | \$6\r 183 | foobar\r 184 | 185 | *2\r 186 | \$6\r 187 | select\r 188 | \$4\r 189 | dogs\r 190 | 191 | *2\r 192 | \$3\r 193 | get\r 194 | \$4\r 195 | doge\r 196 | 197 | quit\r 198 | " 199 | 200 | --- stream_response eval 201 | "+OK\r 202 | +OK\r 203 | +wow\r 204 | " 205 | 206 | --- no_error_log 207 | [error] 208 | 209 | 210 | === TEST 7: Redis RESP protocol initialized with password, wrong password arg. 211 | --- stream_config eval: $::StreamConfig 212 | --- stream_server_config 213 | content_by_lua_block { 214 | ngx.shared.dogs:set("doge", "wow") 215 | local srv = require("resty.shdict.server") 216 | local s = srv:new("foobar", nil) 217 | s:serve() 218 | } 219 | --- stream_request eval 220 | "*2\r 221 | \$4\r 222 | auth\r 223 | \$9\r 224 | foobarbar\r 225 | 226 | *2\r 227 | \$6\r 228 | select\r 229 | \$4\r 230 | dogs\r 231 | 232 | *2\r 233 | \$3\r 234 | get\r 235 | \$4\r 236 | doge\r 237 | 238 | quit\r 239 | " 240 | 241 | --- stream_response eval 242 | "-ERR invalid password\r 243 | -ERR authentication required\r 244 | -ERR authentication required\r 245 | " 246 | 247 | --- no_error_log 248 | [error] 249 | 250 | --------------------------------------------------------------------------------