├── .gitattributes ├── .gitignore ├── .luacheckrc ├── .travis.yml ├── Makefile ├── README.markdown ├── dist.ini ├── lib └── resty │ └── redis.lua ├── t ├── Test.pm ├── bugs.t ├── cert │ ├── test.crt │ └── test.key ├── count.t ├── hmset.t ├── mock.t ├── module.t ├── pipeline.t ├── pubsub.t ├── sanity.t ├── ssl.t ├── transaction.t ├── user-cmds.t └── version.t └── valgrind.suppress /.gitattributes: -------------------------------------------------------------------------------- 1 | *.t linguist-language=Text 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | *~ 4 | go 5 | t/servroot/ 6 | reindex 7 | *.t_ 8 | tags 9 | -------------------------------------------------------------------------------- /.luacheckrc: -------------------------------------------------------------------------------- 1 | std = 'ngx_lua' 2 | redefined = false 3 | unused_args = false 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: focal 3 | 4 | branches: 5 | only: 6 | - "master" 7 | 8 | os: linux 9 | 10 | language: c 11 | 12 | compiler: gcc 13 | 14 | addons: 15 | apt: 16 | packages: 17 | - luarocks 18 | 19 | services: 20 | - docker 21 | 22 | cache: 23 | directories: 24 | - download-cache 25 | 26 | env: 27 | global: 28 | - JOBS=3 29 | - NGX_BUILD_JOBS=$JOBS 30 | - LUAJIT_PREFIX=/opt/luajit21 31 | - LUAJIT_LIB=$LUAJIT_PREFIX/lib 32 | - LUAJIT_INC=$LUAJIT_PREFIX/include/luajit-2.1 33 | - LUA_INCLUDE_DIR=$LUAJIT_INC 34 | - OPENSSL_PREFIX=/opt/ssl 35 | - OPENSSL_LIB=$OPENSSL_PREFIX/lib 36 | - OPENSSL_INC=$OPENSSL_PREFIX/include 37 | - OPENSSL_VER=1.1.1l 38 | - LD_LIBRARY_PATH=$LUAJIT_LIB:$LD_LIBRARY_PATH 39 | - TEST_NGINX_SLEEP=0.006 40 | - LUACHECK_VER=0.21.1 41 | jobs: 42 | - NGINX_VERSION=1.27.1 43 | 44 | before_install: 45 | - sudo luarocks install luacheck $LUACHECK_VER 46 | - luacheck -q . 47 | 48 | install: 49 | - if [ ! -d download-cache ]; then mkdir download-cache; fi 50 | - if [ ! -f download-cache/openssl-$OPENSSL_VER.tar.gz ]; then wget -O download-cache/openssl-$OPENSSL_VER.tar.gz https://www.openssl.org/source/openssl-$OPENSSL_VER.tar.gz; fi 51 | - sudo apt-get install -qq -y cpanminus axel 52 | - sudo cpanm --notest Test::Nginx > build.log 2>&1 || (cat build.log && exit 1) 53 | - git clone https://github.com/openresty/openresty.git ../openresty 54 | - git clone https://github.com/openresty/nginx-devel-utils.git 55 | - git clone https://github.com/openresty/lua-cjson.git 56 | - git clone https://github.com/openresty/lua-nginx-module.git ../lua-nginx-module 57 | - git clone https://github.com/openresty/stream-lua-nginx-module.git ../stream-lua-nginx-module 58 | - git clone https://github.com/openresty/lua-resty-core.git ../lua-resty-core 59 | - git clone https://github.com/openresty/lua-resty-lrucache.git ../lua-resty-lrucache 60 | - git clone https://github.com/openresty/echo-nginx-module.git ../echo-nginx-module 61 | - git clone https://github.com/openresty/no-pool-nginx.git ../no-pool-nginx 62 | - git clone -b v2.1-agentzh https://github.com/openresty/luajit2.git 63 | 64 | script: 65 | - sudo iptables -A OUTPUT -p tcp --dst 127.0.0.2 --dport 12345 -j DROP 66 | - cd luajit2/ 67 | - make -j$JOBS CCDEBUG=-g Q= PREFIX=$LUAJIT_PREFIX CC=$CC XCFLAGS='-DLUA_USE_APICHECK -DLUA_USE_ASSERT' > build.log 2>&1 || (cat build.log && exit 1) 68 | - sudo make install PREFIX=$LUAJIT_PREFIX > build.log 2>&1 || (cat build.log && exit 1) 69 | - cd ../lua-cjson && make && sudo PATH=$PATH make install && cd .. 70 | - tar zxf download-cache/openssl-$OPENSSL_VER.tar.gz 71 | - cd openssl-$OPENSSL_VER/ 72 | - ./config shared --prefix=$OPENSSL_PREFIX -DPURIFY > build.log 2>&1 || (cat build.log && exit 1) 73 | - make -j$JOBS > build.log 2>&1 || (cat build.log && exit 1) 74 | - sudo make PATH=$PATH install_sw > build.log 2>&1 || (cat build.log && exit 1) 75 | - cd .. 76 | - export PATH=$PWD/work/nginx/sbin:$PWD/nginx-devel-utils:$PATH 77 | - export NGX_BUILD_CC=$CC 78 | - ngx-build $NGINX_VERSION --without-pcre2 --with-ipv6 --with-http_realip_module --with-http_ssl_module --with-cc-opt="-I$OPENSSL_INC" --with-ld-opt="-L$OPENSSL_LIB -Wl,-rpath,$OPENSSL_LIB" --add-module=../echo-nginx-module --add-module=../lua-nginx-module --add-module=../stream-lua-nginx-module --with-stream --with-stream_ssl_module --with-debug > build.log 2>&1 || (cat build.log && exit 1) 79 | - nginx -V 80 | - ldd `which nginx`|grep -E 'luajit|ssl|pcre' 81 | - docker run -d -p 6379:6379 --rm --name redis-redisbloom redislabs/rebloom:latest 82 | - TEST_SUBSYSTEM=http prove -I. -r t 83 | - TEST_SUBSYSTEM=stream prove -I. -r t 84 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | OPENRESTY_PREFIX=/usr/local/openresty-debug 2 | 3 | PREFIX ?= /usr/local 4 | LUA_INCLUDE_DIR ?= $(PREFIX)/include 5 | LUA_LIB_DIR ?= $(PREFIX)/lib/lua/$(LUA_VERSION) 6 | INSTALL ?= install 7 | 8 | .PHONY: all test install 9 | 10 | all: ; 11 | 12 | install: all 13 | $(INSTALL) -d $(DESTDIR)$(LUA_LIB_DIR)/resty 14 | $(INSTALL) lib/resty/*.lua $(DESTDIR)$(LUA_LIB_DIR)/resty 15 | 16 | test: all 17 | PATH=$(OPENRESTY_PREFIX)/nginx/sbin:$$PATH prove -I../test-nginx/lib -r t 18 | 19 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | Name 2 | ==== 3 | 4 | lua-resty-redis - Lua redis client driver for the ngx_lua based on the cosocket API 5 | 6 | Table of Contents 7 | ================= 8 | 9 | * [Name](#name) 10 | * [Status](#status) 11 | * [Description](#description) 12 | * [Synopsis](#synopsis) 13 | * [Methods](#methods) 14 | * [new](#new) 15 | * [connect](#connect) 16 | * [set_timeout](#set_timeout) 17 | * [set_timeouts](#set_timeouts) 18 | * [set_keepalive](#set_keepalive) 19 | * [get_reused_times](#get_reused_times) 20 | * [close](#close) 21 | * [init_pipeline](#init_pipeline) 22 | * [commit_pipeline](#commit_pipeline) 23 | * [cancel_pipeline](#cancel_pipeline) 24 | * [hmset](#hmset) 25 | * [array_to_hash](#array_to_hash) 26 | * [read_reply](#read_reply) 27 | * [add_commands](#add_commands) 28 | * [Redis Authentication](#redis-authentication) 29 | * [Redis Transactions](#redis-transactions) 30 | * [Load Balancing and Failover](#load-balancing-and-failover) 31 | * [Debugging](#debugging) 32 | * [Automatic Error Logging](#automatic-error-logging) 33 | * [Check List for Issues](#check-list-for-issues) 34 | * [Limitations](#limitations) 35 | * [Installation](#installation) 36 | * [TODO](#todo) 37 | * [Community](#community) 38 | * [English Mailing List](#english-mailing-list) 39 | * [Chinese Mailing List](#chinese-mailing-list) 40 | * [Bugs and Patches](#bugs-and-patches) 41 | * [Author](#author) 42 | * [Copyright and License](#copyright-and-license) 43 | * [See Also](#see-also) 44 | 45 | Status 46 | ====== 47 | 48 | This library is considered production ready. 49 | 50 | Description 51 | =========== 52 | 53 | This Lua library is a Redis client driver for the ngx_lua nginx module: 54 | 55 | https://github.com/openresty/lua-nginx-module/#readme 56 | 57 | This Lua library takes advantage of ngx_lua's cosocket API, which ensures 58 | 100% nonblocking behavior. 59 | 60 | Note that at least [ngx_lua 0.5.14](https://github.com/chaoslawful/lua-nginx-module/tags) or [OpenResty 1.2.1.14](http://openresty.org/#Download) is required. 61 | 62 | Synopsis 63 | ======== 64 | 65 | ```lua 66 | # you do not need the following line if you are using 67 | # the OpenResty bundle: 68 | lua_package_path "/path/to/lua-resty-redis/lib/?.lua;;"; 69 | 70 | server { 71 | location /test { 72 | # need to specify the resolver to resolve the hostname 73 | resolver 8.8.8.8; 74 | 75 | content_by_lua_block { 76 | local redis = require "resty.redis" 77 | local red = redis:new() 78 | 79 | red:set_timeouts(1000, 1000, 1000) -- 1 sec 80 | 81 | -- or connect to a unix domain socket file listened 82 | -- by a redis server: 83 | -- local ok, err = red:connect("unix:/path/to/redis.sock") 84 | 85 | -- connect via ip address directly 86 | local ok, err = red:connect("127.0.0.1", 6379) 87 | 88 | -- or connect via hostname, need to specify resolver just like above 89 | local ok, err = red:connect("redis.openresty.com", 6379) 90 | 91 | if not ok then 92 | ngx.say("failed to connect: ", err) 93 | return 94 | end 95 | 96 | ok, err = red:set("dog", "an animal") 97 | if not ok then 98 | ngx.say("failed to set dog: ", err) 99 | return 100 | end 101 | 102 | ngx.say("set result: ", ok) 103 | 104 | local res, err = red:get("dog") 105 | if not res then 106 | ngx.say("failed to get dog: ", err) 107 | return 108 | end 109 | 110 | if res == ngx.null then 111 | ngx.say("dog not found.") 112 | return 113 | end 114 | 115 | ngx.say("dog: ", res) 116 | 117 | red:init_pipeline() 118 | red:set("cat", "Marry") 119 | red:set("horse", "Bob") 120 | red:get("cat") 121 | red:get("horse") 122 | local results, err = red:commit_pipeline() 123 | if not results then 124 | ngx.say("failed to commit the pipelined requests: ", err) 125 | return 126 | end 127 | 128 | for i, res in ipairs(results) do 129 | if type(res) == "table" then 130 | if res[1] == false then 131 | ngx.say("failed to run command ", i, ": ", res[2]) 132 | else 133 | -- process the table value 134 | end 135 | else 136 | -- process the scalar value 137 | end 138 | end 139 | 140 | -- put it into the connection pool of size 100, 141 | -- with 10 seconds max idle time 142 | local ok, err = red:set_keepalive(10000, 100) 143 | if not ok then 144 | ngx.say("failed to set keepalive: ", err) 145 | return 146 | end 147 | 148 | -- or just close the connection right away: 149 | -- local ok, err = red:close() 150 | -- if not ok then 151 | -- ngx.say("failed to close: ", err) 152 | -- return 153 | -- end 154 | } 155 | } 156 | } 157 | ``` 158 | 159 | [Back to TOC](#table-of-contents) 160 | 161 | Methods 162 | ======= 163 | 164 | All of the Redis commands have their own methods with the same name except all in lower case. 165 | 166 | You can find the complete list of Redis commands here: 167 | 168 | http://redis.io/commands 169 | 170 | You need to check out this Redis command reference to see what Redis command accepts what arguments. 171 | 172 | The Redis command arguments can be directly fed into the corresponding method call. For example, the "GET" redis command accepts a single key argument, then you can just call the "get" method like this: 173 | 174 | ```lua 175 | local res, err = red:get("key") 176 | ``` 177 | 178 | Similarly, the "LRANGE" redis command accepts three arguments, then you should call the "lrange" method like this: 179 | 180 | ```lua 181 | local res, err = red:lrange("nokey", 0, 1) 182 | ``` 183 | 184 | For example, "SET", "GET", "LRANGE", and "BLPOP" commands correspond to the methods "set", "get", "lrange", and "blpop". 185 | 186 | Here are some more examples: 187 | 188 | ```lua 189 | -- HMGET myhash field1 field2 nofield 190 | local res, err = red:hmget("myhash", "field1", "field2", "nofield") 191 | ``` 192 | 193 | ```lua 194 | -- HMSET myhash field1 "Hello" field2 "World" 195 | local res, err = red:hmset("myhash", "field1", "Hello", "field2", "World") 196 | ``` 197 | 198 | All these command methods returns a single result in success and `nil` otherwise. In case of errors or failures, it will also return a second value which is a string describing the error. 199 | 200 | A Redis "status reply" results in a string typed return value with the "+" prefix stripped. 201 | 202 | A Redis "integer reply" results in a Lua number typed return value. 203 | 204 | A Redis "error reply" results in a `false` value *and* a string describing the error. 205 | 206 | A non-nil Redis "bulk reply" results in a Lua string as the return value. A nil bulk reply results in a `ngx.null` return value. 207 | 208 | A non-nil Redis "multi-bulk reply" results in a Lua table holding all the composing values (if any). If any of the composing value is a valid redis error value, then it will be a two element table `{false, err}`. 209 | 210 | A nil multi-bulk reply returns in a `ngx.null` value. 211 | 212 | See http://redis.io/topics/protocol for details regarding various Redis reply types. 213 | 214 | In addition to all those redis command methods, the following methods are also provided: 215 | 216 | [Back to TOC](#table-of-contents) 217 | 218 | new 219 | --- 220 | `syntax: red, err = redis:new()` 221 | 222 | Creates a redis object. In case of failures, returns `nil` and a string describing the error. 223 | 224 | [Back to TOC](#table-of-contents) 225 | 226 | connect 227 | ------- 228 | `syntax: ok, err = red:connect(host, port, options_table?)` 229 | 230 | `syntax: ok, err = red:connect("unix:/path/to/unix.sock", options_table?)` 231 | 232 | Attempts to connect to the remote host and port that the redis server is listening to or a local unix domain socket file listened by the redis server. 233 | 234 | Before actually resolving the host name and connecting to the remote backend, this method will always look up the connection pool for matched idle connections created by previous calls of this method. 235 | 236 | The optional `options_table` argument is a Lua table holding the following keys: 237 | 238 | * `ssl` 239 | 240 | If set to true, then uses SSL to connect to redis (defaults to false). 241 | 242 | * `ssl_verify` 243 | 244 | If set to true, then verifies the validity of the server SSL certificate (defaults to false). Note that you need to configure the lua_ssl_trusted_certificate to specify the CA (or server) certificate used by your redis server. You may also need to configure lua_ssl_verify_depth accordingly. 245 | 246 | * `server_name` 247 | 248 | Specifies the server name for the new TLS extension Server Name Indication (SNI) when connecting over SSL. 249 | 250 | * `pool` 251 | 252 | Specifies a custom name for the connection pool being used. If omitted, then the connection pool name will be generated from the string template `:` or ``. 253 | 254 | * `pool_size` 255 | 256 | Specifies the size of the connection pool. If omitted and no `backlog` option was provided, no pool will be created. If omitted but `backlog` was provided, the pool will be created with a default size equal to the value of the [lua_socket_pool_size](https://github.com/openresty/lua-nginx-module#lua_socket_pool_size) directive. The connection pool holds up to `pool_size` alive connections ready to be reused by subsequent calls to [connect](#connect), but note that there is no upper limit to the total number of opened connections outside of the pool. If you need to restrict the total number of opened connections, specify the `backlog` option. When the connection pool would exceed its size limit, the least recently used (kept-alive) connection already in the pool will be closed to make room for the current connection. Note that the cosocket connection pool is per Nginx worker process rather than per Nginx server instance, so the size limit specified here also applies to every single Nginx worker process. Also note that the size of the connection pool cannot be changed once it has been created. Note that at least [ngx_lua 0.10.14](https://github.com/openresty/lua-nginx-module/tags) is required to use this options. 257 | 258 | * `backlog` 259 | 260 | If specified, this module will limit the total number of opened connections for this pool. No more connections than `pool_size` can be opened for this pool at any time. If the connection pool is full, subsequent connect operations will be queued into a queue equal to this option's value (the "backlog" queue). If the number of queued connect operations is equal to `backlog`, subsequent connect operations will fail and return nil plus the error string `"too many waiting connect operations"`. The queued connect operations will be resumed once the number of connections in the pool is less than `pool_size`. The queued connect operation will abort once they have been queued for more than `connect_timeout`, controlled by [set_timeout](#set_timeout), and will return nil plus the error string "timeout". Note that at least [ngx_lua 0.10.14](https://github.com/openresty/lua-nginx-module/tags) is required to use this options. 261 | 262 | [Back to TOC](#table-of-contents) 263 | 264 | set_timeout 265 | ----------- 266 | `syntax: red:set_timeout(time)` 267 | 268 | Sets the timeout (in ms) protection for subsequent operations, including the `connect` method. 269 | 270 | Since version `v0.28` of this module, it is advised that 271 | [set_timeouts](#set_timeouts) be used in favor of this method. 272 | 273 | [Back to TOC](#table-of-contents) 274 | 275 | set_timeouts 276 | ------------ 277 | `syntax: red:set_timeouts(connect_timeout, send_timeout, read_timeout)` 278 | 279 | Respectively sets the connect, send, and read timeout thresholds (in ms), for 280 | subsequent socket operations. Setting timeout thresholds with this method 281 | offers more granularity than [set_timeout](#set_timeout). As such, it is 282 | preferred to use [set_timeouts](#set_timeouts) over 283 | [set_timeout](#set_timeout). 284 | 285 | This method was added in the `v0.28` release. 286 | 287 | [Back to TOC](#table-of-contents) 288 | 289 | set_keepalive 290 | ------------- 291 | `syntax: ok, err = red:set_keepalive(max_idle_timeout, pool_size)` 292 | 293 | Puts the current Redis connection immediately into the ngx_lua cosocket connection pool. 294 | 295 | You can specify the max idle timeout (in ms) when the connection is in the pool and the maximal size of the pool every nginx worker process. 296 | 297 | In case of success, returns `1`. In case of errors, returns `nil` with a string describing the error. 298 | 299 | Only call this method in the place you would have called the `close` method instead. Calling this method will immediately turn the current redis object into the `closed` state. Any subsequent operations other than `connect()` on the current object will return the `closed` error. 300 | 301 | [Back to TOC](#table-of-contents) 302 | 303 | get_reused_times 304 | ---------------- 305 | `syntax: times, err = red:get_reused_times()` 306 | 307 | This method returns the (successfully) reused times for the current connection. In case of error, it returns `nil` and a string describing the error. 308 | 309 | If the current connection does not come from the built-in connection pool, then this method always returns `0`, that is, the connection has never been reused (yet). If the connection comes from the connection pool, then the return value is always non-zero. So this method can also be used to determine if the current connection comes from the pool. 310 | 311 | [Back to TOC](#table-of-contents) 312 | 313 | close 314 | ----- 315 | `syntax: ok, err = red:close()` 316 | 317 | Closes the current redis connection and returns the status. 318 | 319 | In case of success, returns `1`. In case of errors, returns `nil` with a string describing the error. 320 | 321 | [Back to TOC](#table-of-contents) 322 | 323 | init_pipeline 324 | ------------- 325 | `syntax: red:init_pipeline()` 326 | 327 | `syntax: red:init_pipeline(n)` 328 | 329 | Enable the redis pipelining mode. All subsequent calls to Redis command methods will automatically get cached and will send to the server in one run when the `commit_pipeline` method is called or get cancelled by calling the `cancel_pipeline` method. 330 | 331 | This method always succeeds. 332 | 333 | If the redis object is already in the Redis pipelining mode, then calling this method will discard existing cached Redis queries. 334 | 335 | The optional `n` argument specifies the (approximate) number of commands that are going to add to this pipeline, which can make things a little faster. 336 | 337 | [Back to TOC](#table-of-contents) 338 | 339 | commit_pipeline 340 | --------------- 341 | `syntax: results, err = red:commit_pipeline()` 342 | 343 | Quits the pipelining mode by committing all the cached Redis queries to the remote server in a single run. All the replies for these queries will be collected automatically and are returned as if a big multi-bulk reply at the highest level. 344 | 345 | This method returns `nil` and a Lua string describing the error upon failures. 346 | 347 | [Back to TOC](#table-of-contents) 348 | 349 | cancel_pipeline 350 | --------------- 351 | `syntax: red:cancel_pipeline()` 352 | 353 | Quits the pipelining mode by discarding all existing cached Redis commands since the last call to the `init_pipeline` method. 354 | 355 | This method always succeeds. 356 | 357 | If the redis object is not in the Redis pipelining mode, then this method is a no-op. 358 | 359 | [Back to TOC](#table-of-contents) 360 | 361 | hmset 362 | ----- 363 | `syntax: res, err = red:hmset(myhash, field1, value1, field2, value2, ...)` 364 | 365 | `syntax: res, err = red:hmset(myhash, { field1 = value1, field2 = value2, ... })` 366 | 367 | Special wrapper for the Redis "hmset" command. 368 | 369 | When there are only three arguments (including the "red" object 370 | itself), then the last argument must be a Lua table holding all the field/value pairs. 371 | 372 | [Back to TOC](#table-of-contents) 373 | 374 | array_to_hash 375 | ------------- 376 | `syntax: hash = red:array_to_hash(array)` 377 | 378 | Auxiliary function that converts an array-like Lua table into a hash-like table. 379 | 380 | This method was first introduced in the `v0.11` release. 381 | 382 | [Back to TOC](#table-of-contents) 383 | 384 | read_reply 385 | ---------- 386 | `syntax: res, err = red:read_reply()` 387 | 388 | Reading a reply from the redis server. This method is mostly useful for the [Redis Pub/Sub API](http://redis.io/topics/pubsub/), for example, 389 | 390 | ```lua 391 | local cjson = require "cjson" 392 | local redis = require "resty.redis" 393 | 394 | local red = redis:new() 395 | local red2 = redis:new() 396 | 397 | red:set_timeouts(1000, 1000, 1000) -- 1 sec 398 | red2:set_timeouts(1000, 1000, 1000) -- 1 sec 399 | 400 | local ok, err = red:connect("127.0.0.1", 6379) 401 | if not ok then 402 | ngx.say("1: failed to connect: ", err) 403 | return 404 | end 405 | 406 | ok, err = red2:connect("127.0.0.1", 6379) 407 | if not ok then 408 | ngx.say("2: failed to connect: ", err) 409 | return 410 | end 411 | 412 | local res, err = red:subscribe("dog") 413 | if not res then 414 | ngx.say("1: failed to subscribe: ", err) 415 | return 416 | end 417 | 418 | ngx.say("1: subscribe: ", cjson.encode(res)) 419 | 420 | res, err = red2:publish("dog", "Hello") 421 | if not res then 422 | ngx.say("2: failed to publish: ", err) 423 | return 424 | end 425 | 426 | ngx.say("2: publish: ", cjson.encode(res)) 427 | 428 | res, err = red:read_reply() 429 | if not res then 430 | ngx.say("1: failed to read reply: ", err) 431 | return 432 | end 433 | 434 | ngx.say("1: receive: ", cjson.encode(res)) 435 | 436 | red:close() 437 | red2:close() 438 | ``` 439 | 440 | Running this example gives the output like this: 441 | 442 | 1: subscribe: ["subscribe","dog",1] 443 | 2: publish: 1 444 | 1: receive: ["message","dog","Hello"] 445 | 446 | The following class methods are provided: 447 | 448 | [Back to TOC](#table-of-contents) 449 | 450 | add_commands 451 | ------------ 452 | `syntax: hash = redis.add_commands(cmd_name1, cmd_name2, ...)` 453 | 454 | *WARNING* this method is now deprecated since we already do automatic Lua method generation 455 | for any redis commands the user attempts to use and thus we no longer need this. 456 | 457 | Adds new redis commands to the `resty.redis` class. Here is an example: 458 | 459 | ```lua 460 | local redis = require "resty.redis" 461 | 462 | redis.add_commands("foo", "bar") 463 | 464 | local red = redis:new() 465 | 466 | red:set_timeouts(1000, 1000, 1000) -- 1 sec 467 | 468 | local ok, err = red:connect("127.0.0.1", 6379) 469 | if not ok then 470 | ngx.say("failed to connect: ", err) 471 | return 472 | end 473 | 474 | local res, err = red:foo("a") 475 | if not res then 476 | ngx.say("failed to foo: ", err) 477 | end 478 | 479 | res, err = red:bar() 480 | if not res then 481 | ngx.say("failed to bar: ", err) 482 | end 483 | ``` 484 | 485 | [Back to TOC](#table-of-contents) 486 | 487 | Redis Authentication 488 | ==================== 489 | 490 | Redis uses the `AUTH` command to do authentication: http://redis.io/commands/auth 491 | 492 | There is nothing special for this command as compared to other Redis 493 | commands like `GET` and `SET`. So one can just invoke the `auth` method on your `resty.redis` instance. Here is an example: 494 | 495 | ```lua 496 | local redis = require "resty.redis" 497 | local red = redis:new() 498 | 499 | red:set_timeouts(1000, 1000, 1000) -- 1 sec 500 | 501 | local ok, err = red:connect("127.0.0.1", 6379) 502 | if not ok then 503 | ngx.say("failed to connect: ", err) 504 | return 505 | end 506 | 507 | local res, err = red:auth("foobared") 508 | if not res then 509 | ngx.say("failed to authenticate: ", err) 510 | return 511 | end 512 | ``` 513 | 514 | where we assume that the Redis server is configured with the 515 | password `foobared` in the `redis.conf` file: 516 | 517 | requirepass foobared 518 | 519 | If the password specified is wrong, then the sample above will output the 520 | following to the HTTP client: 521 | 522 | failed to authenticate: ERR invalid password 523 | 524 | [Back to TOC](#table-of-contents) 525 | 526 | Redis Transactions 527 | ================== 528 | 529 | This library supports the [Redis transactions](http://redis.io/topics/transactions/). Here is an example: 530 | 531 | ```lua 532 | local cjson = require "cjson" 533 | local redis = require "resty.redis" 534 | local red = redis:new() 535 | 536 | red:set_timeouts(1000, 1000, 1000) -- 1 sec 537 | 538 | local ok, err = red:connect("127.0.0.1", 6379) 539 | if not ok then 540 | ngx.say("failed to connect: ", err) 541 | return 542 | end 543 | 544 | local ok, err = red:multi() 545 | if not ok then 546 | ngx.say("failed to run multi: ", err) 547 | return 548 | end 549 | ngx.say("multi ans: ", cjson.encode(ok)) 550 | 551 | local ans, err = red:set("a", "abc") 552 | if not ans then 553 | ngx.say("failed to run sort: ", err) 554 | return 555 | end 556 | ngx.say("set ans: ", cjson.encode(ans)) 557 | 558 | local ans, err = red:lpop("a") 559 | if not ans then 560 | ngx.say("failed to run sort: ", err) 561 | return 562 | end 563 | ngx.say("set ans: ", cjson.encode(ans)) 564 | 565 | ans, err = red:exec() 566 | ngx.say("exec ans: ", cjson.encode(ans)) 567 | 568 | red:close() 569 | ``` 570 | 571 | Then the output will be 572 | 573 | multi ans: "OK" 574 | set ans: "QUEUED" 575 | set ans: "QUEUED" 576 | exec ans: ["OK",[false,"ERR Operation against a key holding the wrong kind of value"]] 577 | 578 | [Back to TOC](#table-of-contents) 579 | 580 | Redis Module 581 | ================== 582 | 583 | This library supports the Redis module. Here is an example with RedisBloom module: 584 | 585 | ```lua 586 | local cjson = require "cjson" 587 | local redis = require "resty.redis" 588 | -- register the module prefix "bf" for RedisBloom 589 | redis.register_module_prefix("bf") 590 | 591 | local red = redis:new() 592 | 593 | local ok, err = red:connect("127.0.0.1", 6379) 594 | if not ok then 595 | ngx.say("failed to connect: ", err) 596 | return 597 | end 598 | 599 | -- call BF.ADD command with the prefix 'bf' 600 | res, err = red:bf():add("dog", 1) 601 | if not res then 602 | ngx.say(err) 603 | return 604 | end 605 | ngx.say("receive: ", cjson.encode(res)) 606 | 607 | -- call BF.EXISTS command 608 | res, err = red:bf():exists("dog") 609 | if not res then 610 | ngx.say(err) 611 | return 612 | end 613 | ngx.say("receive: ", cjson.encode(res)) 614 | ``` 615 | 616 | Load Balancing and Failover 617 | =========================== 618 | 619 | You can trivially implement your own Redis load balancing logic yourself in Lua. Just keep a Lua table of all available Redis backend information (like host name and port numbers) and pick one server according to some rule (like round-robin or key-based hashing) from the Lua table at every request. You can keep track of the current rule state in your own Lua module's data, see https://github.com/openresty/lua-nginx-module/#data-sharing-within-an-nginx-worker 620 | 621 | Similarly, you can implement automatic failover logic in Lua at great flexibility. 622 | 623 | [Back to TOC](#table-of-contents) 624 | 625 | Debugging 626 | ========= 627 | 628 | It is usually convenient to use the [lua-cjson](http://www.kyne.com.au/~mark/software/lua-cjson.php) library to encode the return values of the redis command methods to JSON. For example, 629 | 630 | ```lua 631 | local cjson = require "cjson" 632 | ... 633 | local res, err = red:mget("h1234", "h5678") 634 | if res then 635 | print("res: ", cjson.encode(res)) 636 | end 637 | ``` 638 | 639 | [Back to TOC](#table-of-contents) 640 | 641 | Automatic Error Logging 642 | ======================= 643 | 644 | By default the underlying [ngx_lua](https://github.com/openresty/lua-nginx-module/#readme) module 645 | does error logging when socket errors happen. If you are already doing proper error 646 | handling in your own Lua code, then you are recommended to disable this automatic error logging by turning off [ngx_lua](https://github.com/openresty/lua-nginx-module/#readme)'s [lua_socket_log_errors](https://github.com/openresty/lua-nginx-module/#lua_socket_log_errors) directive, that is, 647 | 648 | ```nginx 649 | lua_socket_log_errors off; 650 | ``` 651 | 652 | [Back to TOC](#table-of-contents) 653 | 654 | Check List for Issues 655 | ===================== 656 | 657 | 1. Ensure you configure the connection pool size properly in the [set_keepalive](#set_keepalive). Basically if your Redis can handle `n` concurrent connections and your NGINX has `m` workers, then the connection pool size should be configured as `n/m`. For example, if your Redis usually handles 1000 concurrent requests and you have 10 NGINX workers, then the connection pool size should be 100. Similarly if you have `p` different NGINX instances, then connection pool size should be `n/m/p`. 658 | 2. Ensure the backlog setting on the Redis side is large enough. For Redis 2.8+, you can directly tune the `tcp-backlog` parameter in the `redis.conf` file (and also tune the kernel parameter `SOMAXCONN` accordingly at least on Linux). You may also want to tune the `maxclients` parameter in `redis.conf`. 659 | 3. Ensure you are not using too short timeout setting in the [set_timeout](#set_timeout) or [set_timeouts](#set_timeouts) methods. If you have to, try redoing the operation upon timeout and turning off [automatic error logging](#automatic-error-logging) (because you are already doing proper error handling in your own Lua code). 660 | 4. If your NGINX worker processes' CPU usage is very high under load, then the NGINX event loop might be blocked by the CPU computation too much. Try sampling a [C-land on-CPU Flame Graph](https://github.com/agentzh/nginx-systemtap-toolkit#sample-bt) and [Lua-land on-CPU Flame Graph](https://github.com/agentzh/stapxx#ngx-lj-lua-stacks) for a typical NGINX worker process. You can optimize the CPU-bound things according to these Flame Graphs. 661 | 5. If your NGINX worker processes' CPU usage is very low under load, then the NGINX event loop might be blocked by some blocking system calls (like file IO system calls). You can confirm the issue by running the [epoll-loop-blocking-distr](https://github.com/agentzh/stapxx#epoll-loop-blocking-distr) tool against a typical NGINX worker process. If it is indeed the case, then you can further sample a [C-land off-CPU Flame Graph](https://github.com/agentzh/nginx-systemtap-toolkit#sample-bt-off-cpu) for a NGINX worker process to analyze the actual blockers. 662 | 6. If your `redis-server` process is running near 100% CPU usage, then you should consider scale your Redis backend by multiple nodes or use the [C-land on-CPU Flame Graph tool](https://github.com/agentzh/nginx-systemtap-toolkit#sample-bt) to analyze the internal bottlenecks within the Redis server process. 663 | 664 | [Back to TOC](#table-of-contents) 665 | 666 | Limitations 667 | =========== 668 | 669 | * This library cannot be used in code contexts like init_by_lua*, set_by_lua*, log_by_lua*, and 670 | header_filter_by_lua* where the ngx_lua cosocket API is not available. 671 | * The `resty.redis` object instance cannot be stored in a Lua variable at the Lua module level, 672 | because it will then be shared by all the concurrent requests handled by the same nginx 673 | worker process (see 674 | https://github.com/openresty/lua-nginx-module/#data-sharing-within-an-nginx-worker ) and 675 | result in bad race conditions when concurrent requests are trying to use the same `resty.redis` instance 676 | (you would see the "bad request" or "socket busy" error to be returned from the method calls). 677 | You should always initiate `resty.redis` objects in function local 678 | variables or in the `ngx.ctx` table. These places all have their own data copies for 679 | each request. 680 | 681 | [Back to TOC](#table-of-contents) 682 | 683 | Installation - Build from source 684 | ============ 685 | 686 | ```sh 687 | # Clone latest release , assuming v0.29 688 | wget https://github.com/openresty/lua-resty-redis/archive/refs/tags/v0.29.tar.gz 689 | 690 | # Extract 691 | tar -xvzf v0.29.tar.gz 692 | 693 | # go into directory 694 | cd lua-resty-redis-0.29 695 | 696 | export LUA_LIB_DIR=/usr/local/openresty/site/lualib 697 | 698 | # Compile and Install 699 | make install 700 | 701 | # Now compiled path will be outputted 702 | # /usr/local/lib/lua/resty = lua_package_path in nginx conf 703 | ``` 704 | 705 | Installation Notes 706 | ============ 707 | 708 | If you are using the OpenResty bundle (http://openresty.org ), then 709 | you do not need to do anything because it already includes and enables 710 | lua-resty-redis by default. And you can just use it in your Lua code, 711 | as in 712 | 713 | ```lua 714 | local redis = require "resty.redis" 715 | ... 716 | ``` 717 | 718 | If you are using your own nginx + ngx_lua build, then you need to configure 719 | the lua_package_path directive to add the path of your lua-resty-redis source 720 | tree to ngx_lua's LUA_PATH search path, as in 721 | 722 | ```nginx 723 | # nginx.conf 724 | http { 725 | lua_package_path "/path/to/lua-resty-redis/lib/?.lua;;"; 726 | ... 727 | } 728 | ``` 729 | 730 | Ensure that the system account running your Nginx ''worker'' proceses have 731 | enough permission to read the `.lua` file. 732 | 733 | [Back to TOC](#table-of-contents) 734 | 735 | TODO 736 | ==== 737 | 738 | [Back to TOC](#table-of-contents) 739 | 740 | Community 741 | ========= 742 | 743 | [Back to TOC](#table-of-contents) 744 | 745 | English Mailing List 746 | -------------------- 747 | 748 | The [openresty-en](https://groups.google.com/group/openresty-en) mailing list is for English speakers. 749 | 750 | [Back to TOC](#table-of-contents) 751 | 752 | Chinese Mailing List 753 | -------------------- 754 | 755 | The [openresty](https://groups.google.com/group/openresty) mailing list is for Chinese speakers. 756 | 757 | [Back to TOC](#table-of-contents) 758 | 759 | Bugs and Patches 760 | ================ 761 | 762 | Please report bugs or submit patches by 763 | 764 | 1. creating a ticket on the [GitHub Issue Tracker](http://github.com/agentzh/lua-resty-redis/issues), 765 | 1. or posting to the [OpenResty community](#community). 766 | 767 | [Back to TOC](#table-of-contents) 768 | 769 | Author 770 | ====== 771 | 772 | Yichun "agentzh" Zhang (章亦春) , OpenResty Inc. 773 | 774 | [Back to TOC](#table-of-contents) 775 | 776 | Copyright and License 777 | ===================== 778 | 779 | This module is licensed under the BSD license. 780 | 781 | Copyright (C) 2012-2017, by Yichun Zhang (agentzh) , OpenResty Inc. 782 | 783 | All rights reserved. 784 | 785 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 786 | 787 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 788 | 789 | * 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. 790 | 791 | 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. 792 | 793 | [Back to TOC](#table-of-contents) 794 | 795 | See Also 796 | ======== 797 | * the ngx_lua module: https://github.com/openresty/lua-nginx-module/#readme 798 | * the redis wired protocol specification: http://redis.io/topics/protocol 799 | * the [lua-resty-memcached](https://github.com/agentzh/lua-resty-memcached) library 800 | * the [lua-resty-mysql](https://github.com/agentzh/lua-resty-mysql) library 801 | 802 | [Back to TOC](#table-of-contents) 803 | 804 | -------------------------------------------------------------------------------- /dist.ini: -------------------------------------------------------------------------------- 1 | name=lua-resty-redis 2 | abstract=Lua redis client driver for the ngx_lua based on the cosocket API 3 | author=Yichun "agentzh" Zhang (agentzh) 4 | is_original=yes 5 | license=2bsd 6 | lib_dir=lib 7 | doc_dir=lib 8 | repo_link=https://github.com/openresty/lua-resty-redis 9 | main_module=lib/resty/redis.lua 10 | -------------------------------------------------------------------------------- /lib/resty/redis.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (C) Yichun Zhang (agentzh) 2 | 3 | 4 | local sub = string.sub 5 | local byte = string.byte 6 | local tab_insert = table.insert 7 | local tab_remove = table.remove 8 | local tcp = ngx.socket.tcp 9 | local null = ngx.null 10 | local ipairs = ipairs 11 | local type = type 12 | local pairs = pairs 13 | local unpack = unpack 14 | local setmetatable = setmetatable 15 | local tonumber = tonumber 16 | local tostring = tostring 17 | local rawget = rawget 18 | local select = select 19 | local tb_clear = require "table.clear" 20 | --local error = error 21 | 22 | 23 | local ok, new_tab = pcall(require, "table.new") 24 | if not ok or type(new_tab) ~= "function" then 25 | new_tab = function (narr, nrec) return {} end 26 | end 27 | 28 | local tab_pool_len = 0 29 | local tab_pool = new_tab(16, 0) 30 | local _M = new_tab(0, 55) 31 | 32 | _M._VERSION = '0.32' 33 | 34 | 35 | local common_cmds = { 36 | "get", "set", "mget", "mset", 37 | "del", "incr", "decr", -- Strings 38 | "llen", "lindex", "lpop", "lpush", 39 | "lrange", "linsert", -- Lists 40 | "hexists", "hget", "hset", "hmget", 41 | --[[ "hmset", ]] "hdel", -- Hashes 42 | "smembers", "sismember", "sadd", "srem", 43 | "sdiff", "sinter", "sunion", -- Sets 44 | "zrange", "zrangebyscore", "zrank", "zadd", 45 | "zrem", "zincrby", -- Sorted Sets 46 | "auth", "eval", "expire", "script", 47 | "sort" -- Others 48 | } 49 | 50 | 51 | local sub_commands = { 52 | "subscribe", "psubscribe" 53 | } 54 | 55 | 56 | local unsub_commands = { 57 | "unsubscribe", "punsubscribe" 58 | } 59 | 60 | 61 | local mt = { __index = _M } 62 | 63 | 64 | local function get_tab_from_pool() 65 | if tab_pool_len > 0 then 66 | tab_pool_len = tab_pool_len - 1 67 | return tab_pool[tab_pool_len + 1] 68 | end 69 | 70 | return new_tab(24, 0) -- one field takes 5 slots 71 | end 72 | 73 | 74 | local function put_tab_into_pool(tab) 75 | if tab_pool_len >= 32 then 76 | return 77 | end 78 | 79 | tb_clear(tab) 80 | tab_pool_len = tab_pool_len + 1 81 | tab_pool[tab_pool_len] = tab 82 | end 83 | 84 | 85 | function _M.new(self) 86 | local sock, err = tcp() 87 | if not sock then 88 | return nil, err 89 | end 90 | local redis = setmetatable({ _sock = sock, 91 | _subscribed = false, 92 | _n_channel = { 93 | unsubscribe = 0, 94 | punsubscribe = 0, 95 | }, 96 | }, mt) 97 | return redis 98 | end 99 | 100 | 101 | function _M.register_module_prefix(mod) 102 | _M[mod] = function(self) 103 | self._module_prefix = mod 104 | return self 105 | end 106 | end 107 | 108 | 109 | function _M.set_timeout(self, timeout) 110 | local sock = rawget(self, "_sock") 111 | if not sock then 112 | error("not initialized", 2) 113 | return 114 | end 115 | 116 | sock:settimeout(timeout) 117 | end 118 | 119 | 120 | function _M.set_timeouts(self, connect_timeout, send_timeout, read_timeout) 121 | local sock = rawget(self, "_sock") 122 | if not sock then 123 | error("not initialized", 2) 124 | return 125 | end 126 | 127 | sock:settimeouts(connect_timeout, send_timeout, read_timeout) 128 | end 129 | 130 | 131 | function _M.connect(self, host, port_or_opts, opts) 132 | local sock = rawget(self, "_sock") 133 | if not sock then 134 | return nil, "not initialized" 135 | end 136 | 137 | local unix 138 | 139 | do 140 | local typ = type(host) 141 | if typ ~= "string" then 142 | error("bad argument #1 host: string expected, got " .. typ, 2) 143 | end 144 | 145 | if sub(host, 1, 5) == "unix:" then 146 | unix = true 147 | end 148 | 149 | if unix then 150 | typ = type(port_or_opts) 151 | if port_or_opts ~= nil and typ ~= "table" then 152 | error("bad argument #2 opts: nil or table expected, got " .. 153 | typ, 2) 154 | end 155 | 156 | else 157 | typ = type(port_or_opts) 158 | if typ ~= "number" then 159 | port_or_opts = tonumber(port_or_opts) 160 | if port_or_opts == nil then 161 | error("bad argument #2 port: number expected, got " .. 162 | typ, 2) 163 | end 164 | end 165 | 166 | if opts ~= nil then 167 | typ = type(opts) 168 | if typ ~= "table" then 169 | error("bad argument #3 opts: nil or table expected, got " .. 170 | typ, 2) 171 | end 172 | end 173 | end 174 | 175 | end 176 | 177 | self._subscribed = false 178 | 179 | local ok, err 180 | 181 | if unix then 182 | -- second argument of sock:connect() cannot be nil 183 | if port_or_opts ~= nil then 184 | ok, err = sock:connect(host, port_or_opts) 185 | opts = port_or_opts 186 | else 187 | ok, err = sock:connect(host) 188 | end 189 | else 190 | ok, err = sock:connect(host, port_or_opts, opts) 191 | end 192 | 193 | if not ok then 194 | return ok, err 195 | end 196 | 197 | if opts and opts.ssl then 198 | ok, err = sock:sslhandshake(false, opts.server_name, opts.ssl_verify) 199 | if not ok then 200 | return ok, "failed to do ssl handshake: " .. err 201 | end 202 | end 203 | 204 | return ok, err 205 | end 206 | 207 | 208 | function _M.set_keepalive(self, ...) 209 | local sock = rawget(self, "_sock") 210 | if not sock then 211 | return nil, "not initialized" 212 | end 213 | 214 | if rawget(self, "_subscribed") then 215 | return nil, "subscribed state" 216 | end 217 | 218 | return sock:setkeepalive(...) 219 | end 220 | 221 | 222 | function _M.get_reused_times(self) 223 | local sock = rawget(self, "_sock") 224 | if not sock then 225 | return nil, "not initialized" 226 | end 227 | 228 | return sock:getreusedtimes() 229 | end 230 | 231 | 232 | local function close(self) 233 | local sock = rawget(self, "_sock") 234 | if not sock then 235 | return nil, "not initialized" 236 | end 237 | 238 | return sock:close() 239 | end 240 | _M.close = close 241 | 242 | 243 | local function _read_reply(self, sock) 244 | local line, err = sock:receive() 245 | if not line then 246 | if err == "timeout" and not rawget(self, "_subscribed") then 247 | sock:close() 248 | end 249 | return nil, err 250 | end 251 | 252 | local prefix = byte(line) 253 | 254 | if prefix == 36 then -- char '$' 255 | -- print("bulk reply") 256 | 257 | local size = tonumber(sub(line, 2)) 258 | if size < 0 then 259 | return null 260 | end 261 | 262 | local data, err = sock:receive(size) 263 | if not data then 264 | if err == "timeout" then 265 | sock:close() 266 | end 267 | return nil, err 268 | end 269 | 270 | local dummy, err = sock:receive(2) -- ignore CRLF 271 | if not dummy then 272 | if err == "timeout" then 273 | sock:close() 274 | end 275 | return nil, err 276 | end 277 | 278 | return data 279 | 280 | elseif prefix == 43 then -- char '+' 281 | -- print("status reply") 282 | 283 | return sub(line, 2) 284 | 285 | elseif prefix == 42 then -- char '*' 286 | local n = tonumber(sub(line, 2)) 287 | 288 | -- print("multi-bulk reply: ", n) 289 | if n < 0 then 290 | return null 291 | end 292 | 293 | local vals = new_tab(n, 0) 294 | local nvals = 0 295 | for i = 1, n do 296 | local res, err = _read_reply(self, sock) 297 | if res then 298 | nvals = nvals + 1 299 | vals[nvals] = res 300 | 301 | elseif res == nil then 302 | return nil, err 303 | 304 | else 305 | -- be a valid redis error value 306 | nvals = nvals + 1 307 | vals[nvals] = {false, err} 308 | end 309 | end 310 | 311 | return vals 312 | 313 | elseif prefix == 58 then -- char ':' 314 | -- print("integer reply") 315 | return tonumber(sub(line, 2)) 316 | 317 | elseif prefix == 45 then -- char '-' 318 | -- print("error reply: ", n) 319 | 320 | return false, sub(line, 2) 321 | 322 | else 323 | -- when `line` is an empty string, `prefix` will be equal to nil. 324 | return nil, "unknown prefix: \"" .. tostring(prefix) .. "\"" 325 | end 326 | end 327 | 328 | 329 | local function _gen_req(args) 330 | local nargs = #args 331 | 332 | local req = get_tab_from_pool() 333 | req[1] = "*" 334 | req[2] = nargs 335 | req[3] = "\r\n" 336 | local nbits = 4 337 | 338 | for i = 1, nargs do 339 | local arg = args[i] 340 | if type(arg) ~= "string" then 341 | arg = tostring(arg) 342 | end 343 | 344 | req[nbits] = "$" 345 | req[nbits + 1] = #arg 346 | req[nbits + 2] = "\r\n" 347 | req[nbits + 3] = arg 348 | req[nbits + 4] = "\r\n" 349 | 350 | nbits = nbits + 5 351 | end 352 | 353 | -- it is much faster to do string concatenation on the C land 354 | -- in real world (large number of strings in the Lua VM) 355 | return req 356 | end 357 | 358 | 359 | local function _check_msg(self, res) 360 | return rawget(self, "_subscribed") and 361 | type(res) == "table" and (res[1] == "message" or res[1] == "pmessage") 362 | end 363 | 364 | 365 | local function _do_cmd(self, ...) 366 | local args = {...} 367 | 368 | local sock = rawget(self, "_sock") 369 | if not sock then 370 | return nil, "not initialized" 371 | end 372 | 373 | local req = _gen_req(args) 374 | 375 | local reqs = rawget(self, "_reqs") 376 | if reqs then 377 | reqs[#reqs + 1] = req 378 | return 379 | end 380 | 381 | -- print("request: ", table.concat(req)) 382 | 383 | local bytes, err = sock:send(req) 384 | put_tab_into_pool(req) 385 | 386 | if not bytes then 387 | return nil, err 388 | end 389 | 390 | local res, err = _read_reply(self, sock) 391 | while _check_msg(self, res) do 392 | if rawget(self, "_buffered_msg") == nil then 393 | self._buffered_msg = new_tab(1, 0) 394 | end 395 | 396 | tab_insert(self._buffered_msg, res) 397 | res, err = _read_reply(self, sock) 398 | end 399 | 400 | return res, err 401 | end 402 | 403 | 404 | local function _check_unsubscribed(self, res) 405 | if type(res) == "table" 406 | and (res[1] == "unsubscribe" or res[1] == "punsubscribe") 407 | then 408 | self._n_channel[res[1]] = self._n_channel[res[1]] - 1 409 | 410 | local buffered_msg = rawget(self, "_buffered_msg") 411 | if buffered_msg then 412 | -- remove messages of unsubscribed channel 413 | local msg_type = 414 | (res[1] == "punsubscribe") and "pmessage" or "message" 415 | local j = 1 416 | for _, msg in ipairs(buffered_msg) do 417 | if msg[1] == msg_type and msg[2] ~= res[2] then 418 | -- move messages to overwrite the removed ones 419 | buffered_msg[j] = msg 420 | j = j + 1 421 | end 422 | end 423 | 424 | -- clear remain messages 425 | for i = j, #buffered_msg do 426 | buffered_msg[i] = nil 427 | end 428 | 429 | if #buffered_msg == 0 then 430 | self._buffered_msg = nil 431 | end 432 | end 433 | 434 | if res[3] == 0 then 435 | -- all channels are unsubscribed 436 | self._subscribed = false 437 | end 438 | end 439 | end 440 | 441 | 442 | local function _check_subscribed(self, res) 443 | if type(res) == "table" 444 | and (res[1] == "subscribe" or res[1] == "psubscribe") 445 | then 446 | if res[1] == "subscribe" then 447 | self._n_channel.unsubscribe = self._n_channel.unsubscribe + 1 448 | 449 | elseif res[1] == "psubscribe" then 450 | self._n_channel.punsubscribe = self._n_channel.punsubscribe + 1 451 | end 452 | end 453 | end 454 | 455 | 456 | function _M.read_reply(self) 457 | local sock = rawget(self, "_sock") 458 | if not sock then 459 | return nil, "not initialized" 460 | end 461 | 462 | if not rawget(self, "_subscribed") then 463 | return nil, "not subscribed" 464 | end 465 | 466 | local buffered_msg = rawget(self, "_buffered_msg") 467 | if buffered_msg then 468 | local msg = buffered_msg[1] 469 | tab_remove(buffered_msg, 1) 470 | 471 | if #buffered_msg == 0 then 472 | self._buffered_msg = nil 473 | end 474 | 475 | return msg 476 | end 477 | 478 | local res, err = _read_reply(self, sock) 479 | _check_unsubscribed(self, res) 480 | 481 | return res, err 482 | end 483 | 484 | 485 | local function do_cmd(self, cmd, ...) 486 | local module_prefix = rawget(self, "_module_prefix") 487 | if module_prefix then 488 | self._module_prefix = nil 489 | return _do_cmd(self, module_prefix .. "." .. cmd, ...) 490 | end 491 | 492 | return _do_cmd(self, cmd, ...) 493 | end 494 | 495 | 496 | for i = 1, #common_cmds do 497 | local cmd = common_cmds[i] 498 | 499 | _M[cmd] = 500 | function (self, ...) 501 | return do_cmd(self, cmd, ...) 502 | end 503 | end 504 | 505 | 506 | local function handle_subscribe_result(self, cmd, nargs, res) 507 | local err 508 | _check_subscribed(self, res) 509 | 510 | if nargs <= 1 then 511 | return res 512 | end 513 | 514 | local results = new_tab(nargs, 0) 515 | results[1] = res 516 | local sock = rawget(self, "_sock") 517 | 518 | for i = 2, nargs do 519 | res, err = _read_reply(self, sock) 520 | if not res then 521 | return nil, err 522 | end 523 | 524 | _check_subscribed(self, res) 525 | results[i] = res 526 | end 527 | 528 | return results 529 | end 530 | 531 | for i = 1, #sub_commands do 532 | local cmd = sub_commands[i] 533 | 534 | _M[cmd] = 535 | function (self, ...) 536 | if not rawget(self, "_subscribed") then 537 | self._subscribed = true 538 | end 539 | 540 | local nargs = select("#", ...) 541 | 542 | local res, err = _do_cmd(self, cmd, ...) 543 | if not res then 544 | return nil, err 545 | end 546 | 547 | return handle_subscribe_result(self, cmd, nargs, res) 548 | end 549 | end 550 | 551 | 552 | local function handle_unsubscribe_result(self, cmd, nargs, res) 553 | local err 554 | _check_unsubscribed(self, res) 555 | 556 | if self._n_channel[cmd] == 0 or nargs == 1 then 557 | return res 558 | end 559 | 560 | local results = new_tab(nargs, 0) 561 | results[1] = res 562 | local sock = rawget(self, "_sock") 563 | local i = 2 564 | 565 | while nargs == 0 or i <= nargs do 566 | res, err = _read_reply(self, sock) 567 | if not res then 568 | return nil, err 569 | end 570 | 571 | results[i] = res 572 | i = i + 1 573 | 574 | _check_unsubscribed(self, res) 575 | if self._n_channel[cmd] == 0 then 576 | -- exit the loop for unsubscribe() call 577 | break 578 | end 579 | end 580 | 581 | return results 582 | end 583 | 584 | for i = 1, #unsub_commands do 585 | local cmd = unsub_commands[i] 586 | 587 | _M[cmd] = 588 | function (self, ...) 589 | -- assume all channels are unsubscribed by only one time 590 | if not rawget(self, "_subscribed") then 591 | return nil, "not subscribed" 592 | end 593 | 594 | local nargs = select("#", ...) 595 | 596 | local res, err = _do_cmd(self, cmd, ...) 597 | if not res then 598 | return nil, err 599 | end 600 | 601 | return handle_unsubscribe_result(self, cmd, nargs, res) 602 | end 603 | end 604 | 605 | 606 | function _M.hmset(self, hashname, ...) 607 | if select('#', ...) == 1 then 608 | local t = select(1, ...) 609 | 610 | local n = 0 611 | for k, v in pairs(t) do 612 | n = n + 2 613 | end 614 | 615 | local array = new_tab(n, 0) 616 | 617 | local i = 0 618 | for k, v in pairs(t) do 619 | array[i + 1] = k 620 | array[i + 2] = v 621 | i = i + 2 622 | end 623 | -- print("key", hashname) 624 | return _do_cmd(self, "hmset", hashname, unpack(array)) 625 | end 626 | 627 | -- backwards compatibility 628 | return _do_cmd(self, "hmset", hashname, ...) 629 | end 630 | 631 | 632 | function _M.init_pipeline(self, n) 633 | self._reqs = new_tab(n or 4, 0) 634 | end 635 | 636 | 637 | function _M.cancel_pipeline(self) 638 | self._reqs = nil 639 | end 640 | 641 | 642 | function _M.commit_pipeline(self) 643 | local reqs = rawget(self, "_reqs") 644 | if not reqs then 645 | return nil, "no pipeline" 646 | end 647 | 648 | self._reqs = nil 649 | 650 | local sock = rawget(self, "_sock") 651 | if not sock then 652 | return nil, "not initialized" 653 | end 654 | 655 | local bytes, err = sock:send(reqs) 656 | for _, req in ipairs(reqs) do 657 | put_tab_into_pool(req) 658 | end 659 | 660 | if not bytes then 661 | return nil, err 662 | end 663 | 664 | local nvals = 0 665 | local nreqs = #reqs 666 | local vals = new_tab(nreqs, 0) 667 | for i = 1, nreqs do 668 | local res, err = _read_reply(self, sock) 669 | if res then 670 | nvals = nvals + 1 671 | vals[nvals] = res 672 | 673 | elseif res == nil then 674 | if err == "timeout" then 675 | close(self) 676 | end 677 | return nil, err 678 | 679 | else 680 | -- be a valid redis error value 681 | nvals = nvals + 1 682 | vals[nvals] = {false, err} 683 | end 684 | end 685 | 686 | return vals 687 | end 688 | 689 | 690 | function _M.array_to_hash(self, t) 691 | local n = #t 692 | -- print("n = ", n) 693 | local h = new_tab(0, n / 2) 694 | for i = 1, n, 2 do 695 | h[t[i]] = t[i + 1] 696 | end 697 | return h 698 | end 699 | 700 | 701 | -- this method is deperate since we already do lazy method generation. 702 | function _M.add_commands(...) 703 | local cmds = {...} 704 | for i = 1, #cmds do 705 | local cmd = cmds[i] 706 | _M[cmd] = 707 | function (self, ...) 708 | return _do_cmd(self, cmd, ...) 709 | end 710 | end 711 | end 712 | 713 | 714 | setmetatable(_M, {__index = function(self, cmd) 715 | local method = 716 | function (self, ...) 717 | return do_cmd(self, cmd, ...) 718 | end 719 | 720 | -- cache the lazily generated method in our 721 | -- module table 722 | _M[cmd] = method 723 | return method 724 | end}) 725 | 726 | 727 | return _M 728 | -------------------------------------------------------------------------------- /t/Test.pm: -------------------------------------------------------------------------------- 1 | package t::Test; 2 | 3 | use strict; 4 | use warnings; 5 | 6 | use Test::Nginx::Socket::Lua::Stream -Base; 7 | use Cwd qw(cwd); 8 | 9 | my $pwd = cwd(); 10 | my $HtmlDir = html_dir; 11 | 12 | our @EXPORT = qw($GlobalConfig); 13 | our $GlobalConfig = qq{ 14 | lua_package_path "$pwd/lib/?.lua;;"; 15 | lua_package_cpath "/usr/local/openresty-debug/lualib/?.so;/usr/local/openresty/lualib/?.so;;"; 16 | }; 17 | 18 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 19 | $ENV{TEST_NGINX_REDIS_PORT} ||= 6379; 20 | 21 | no_long_string(); 22 | 23 | add_block_preprocessor(sub { 24 | my $block = shift; 25 | 26 | if (!defined $block->http_only) { 27 | if (defined($ENV{TEST_SUBSYSTEM}) && $ENV{TEST_SUBSYSTEM} eq "stream") { 28 | if (!defined $block->stream_config) { 29 | $block->set_value("stream_config", $block->global_config); 30 | } 31 | if (!defined $block->stream_server_config) { 32 | $block->set_value("stream_server_config", $block->server_config); 33 | } 34 | if (defined $block->internal_server_error) { 35 | $block->set_value("stream_respons", ""); 36 | } 37 | } else { 38 | if (!defined $block->http_config) { 39 | $block->set_value("http_config", $block->global_config); 40 | } 41 | if (!defined $block->request) { 42 | $block->set_value("request", <<\_END_); 43 | GET /t 44 | _END_ 45 | } 46 | if (!defined $block->config) { 47 | $block->set_value("config", "location /t {\n" . $block->server_config . "\n}"); 48 | } 49 | if (defined $block->internal_server_error) { 50 | $block->set_value("error_code", 500); 51 | $block->set_value("ignore_response_body", ""); 52 | } 53 | } 54 | } 55 | }); 56 | 57 | 1; 58 | -------------------------------------------------------------------------------- /t/bugs.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use t::Test; 4 | 5 | repeat_each(2); 6 | 7 | plan tests => repeat_each() * (3 * blocks()); 8 | 9 | log_level 'warn'; 10 | 11 | run_tests(); 12 | 13 | __DATA__ 14 | 15 | === TEST 1: github issue #108: ngx.location.capture + redis.set_keepalive 16 | --- http_only 17 | --- http_config eval: $::GlobalConfig 18 | --- config 19 | location /r1 { 20 | default_type text/html; 21 | set $port $TEST_NGINX_REDIS_PORT; 22 | #lua_code_cache off; 23 | lua_need_request_body on; 24 | content_by_lua_file html/r1.lua; 25 | } 26 | 27 | location /r2 { 28 | default_type text/html; 29 | set $port $TEST_NGINX_REDIS_PORT; 30 | #lua_code_cache off; 31 | lua_need_request_body on; 32 | content_by_lua_file html/r2.lua; 33 | } 34 | 35 | location /anyurl { 36 | internal; 37 | proxy_pass http://127.0.0.1:$server_port/dummy; 38 | } 39 | 40 | location = /dummy { 41 | echo dummy; 42 | } 43 | --- user_files 44 | >>> r1.lua 45 | local redis = require "resty.redis" 46 | local red = redis:new() 47 | local ok, err = red:connect("127.0.0.1", ngx.var.port) 48 | if not ok then 49 | ngx.say("failed to connect: ", err) 50 | return 51 | end 52 | local ok, err = red:flushall() 53 | if not ok then 54 | ngx.say("failed to flushall: ", err) 55 | return 56 | end 57 | ok, err = red:set_keepalive() 58 | if not ok then 59 | ngx.say("failed to set keepalive: ", err) 60 | return 61 | end 62 | local http_ress = ngx.location.capture("/r2") -- 1 63 | ngx.say("ok") 64 | 65 | >>> r2.lua 66 | local redis = require "resty.redis" 67 | local red = redis:new() 68 | local ok, err = red:connect("127.0.0.1", ngx.var.port) --2 69 | if not ok then 70 | ngx.say("failed to connect: ", err) 71 | return 72 | end 73 | local res = ngx.location.capture("/anyurl") --3 74 | --- request 75 | GET /r1 76 | --- response_body 77 | ok 78 | --- no_error_log 79 | [error] 80 | 81 | 82 | 83 | === TEST 2: exit(404) after I/O (ngx_lua github issue #110 84 | https://github.com/chaoslawful/lua-nginx-module/issues/110 85 | --- http_only 86 | --- http_config eval: $::GlobalConfig 87 | --- config 88 | error_page 400 /400.html; 89 | error_page 404 /404.html; 90 | location /foo { 91 | access_by_lua ' 92 | local redis = require "resty.redis" 93 | local red = redis:new() 94 | 95 | red:set_timeout(2000) -- 2 sec 96 | 97 | -- ngx.log(ngx.ERR, "hello"); 98 | 99 | -- or connect to a unix domain socket file listened 100 | -- by a redis server: 101 | -- local ok, err = red:connect("unix:/path/to/redis.sock") 102 | 103 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 104 | if not ok then 105 | ngx.log(ngx.ERR, "failed to connect: ", err) 106 | return 107 | end 108 | 109 | res, err = red:set("dog", "an animal") 110 | if not res then 111 | ngx.log(ngx.ERR, "failed to set dog: ", err) 112 | return 113 | end 114 | 115 | -- ngx.say("set dog: ", res) 116 | 117 | local res, err = red:get("dog") 118 | if err then 119 | ngx.log(ngx.ERR, "failed to get dog: ", err) 120 | return 121 | end 122 | 123 | if not res then 124 | ngx.log(ngx.ERR, "dog not found.") 125 | return 126 | end 127 | 128 | -- ngx.say("dog: ", res) 129 | 130 | -- red:close() 131 | local ok, err = red:set_keepalive(0, 100) 132 | if not ok then 133 | ngx.log(ngx.ERR, "failed to set keepalive: ", err) 134 | return 135 | end 136 | 137 | ngx.exit(404) 138 | '; 139 | echo Hello; 140 | } 141 | --- user_files 142 | >>> 400.html 143 | Bad request, dear... 144 | >>> 404.html 145 | Not found, dear... 146 | --- request 147 | GET /foo 148 | --- response_body 149 | Not found, dear... 150 | --- error_code: 404 151 | --- no_error_log 152 | [error] 153 | 154 | 155 | 156 | === TEST 3: set and get an empty string 157 | --- global_config eval: $::GlobalConfig 158 | --- server_config 159 | content_by_lua ' 160 | local redis = require "resty.redis" 161 | local red = redis:new() 162 | 163 | red:set_timeout(1000) -- 1 sec 164 | 165 | -- or connect to a unix domain socket file listened 166 | -- by a redis server: 167 | -- local ok, err = red:connect("unix:/path/to/redis.sock") 168 | 169 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 170 | if not ok then 171 | ngx.say("failed to connect: ", err) 172 | return 173 | end 174 | 175 | res, err = red:set("dog", "") 176 | if not res then 177 | ngx.say("failed to set dog: ", err) 178 | return 179 | end 180 | 181 | ngx.say("set dog: ", res) 182 | 183 | for i = 1, 2 do 184 | local res, err = red:get("dog") 185 | if err then 186 | ngx.say("failed to get dog: ", err) 187 | return 188 | end 189 | 190 | if not res then 191 | ngx.say("dog not found.") 192 | return 193 | end 194 | 195 | ngx.say("dog: ", res) 196 | end 197 | 198 | red:close() 199 | '; 200 | --- response_body 201 | set dog: OK 202 | dog: 203 | dog: 204 | --- no_error_log 205 | [error] 206 | 207 | 208 | 209 | === TEST 4: ngx.exec() after red:get() 210 | --- http_only 211 | --- http_config eval: $::GlobalConfig 212 | --- config 213 | location /t { 214 | content_by_lua ' 215 | local redis = require "resty.redis" 216 | local red = redis:new() 217 | 218 | red:set_timeout(1000) -- 1 sec 219 | 220 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 221 | if not ok then 222 | ngx.say("failed to connect: ", err) 223 | return 224 | end 225 | 226 | local res, err = red:get("dog") 227 | if err then 228 | ngx.say("failed to get dog: ", err) 229 | return 230 | end 231 | 232 | ngx.exec("/hello") 233 | '; 234 | } 235 | 236 | location = /hello { 237 | echo hello world; 238 | } 239 | 240 | --- request 241 | GET /t 242 | --- response_body 243 | hello world 244 | --- no_error_log 245 | [error] 246 | -------------------------------------------------------------------------------- /t/cert/test.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICqTCCAhICCQClDm1WkreW4jANBgkqhkiG9w0BAQUFADCBlzELMAkGA1UEBhMC 3 | VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28x 4 | EjAQBgNVBAoMCU9wZW5SZXN0eTESMBAGA1UECwwJT3BlblJlc3R5MREwDwYDVQQD 5 | DAh0ZXN0LmNvbTEgMB4GCSqGSIb3DQEJARYRYWdlbnR6aEBnbWFpbC5jb20wIBcN 6 | MTQwNzIxMDMyMzQ3WhgPMjE1MTA2MTMwMzIzNDdaMIGXMQswCQYDVQQGEwJVUzET 7 | MBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzESMBAG 8 | A1UECgwJT3BlblJlc3R5MRIwEAYDVQQLDAlPcGVuUmVzdHkxETAPBgNVBAMMCHRl 9 | c3QuY29tMSAwHgYJKoZIhvcNAQkBFhFhZ2VudHpoQGdtYWlsLmNvbTCBnzANBgkq 10 | hkiG9w0BAQEFAAOBjQAwgYkCgYEA6P18zUvtmaKQK2xePy8ZbFwSyTLw+jW6t9eZ 11 | aiTec8X3ibN9WemrxHzkTRikxP3cAQoITRuZiQvF4Q7DO6wMkz/b0zwfgX5uedGq 12 | 047AJP6n/mwlDOjGSNomBLoXQzo7tVe60ikEm3ZyDUqnJPJMt3hImO5XSop4MPMu 13 | Za9WhFcCAwEAATANBgkqhkiG9w0BAQUFAAOBgQA4OBb9bOyWB1//93nSXX1mdENZ 14 | IQeyTK0Dd6My76lnZxnZ4hTWrvvd0b17KLDU6JnS2N5ee3ATVkojPidRLWLIhnh5 15 | 0eXrcKalbO2Ce6nShoFvQCQKXN2Txmq2vO/Mud2bHAWwJALg+qi1Iih/gVYB9sct 16 | FLg8zFOzRlYiU+6Mmw== 17 | -----END CERTIFICATE----- 18 | -------------------------------------------------------------------------------- /t/cert/test.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXgIBAAKBgQDo/XzNS+2ZopArbF4/LxlsXBLJMvD6Nbq315lqJN5zxfeJs31Z 3 | 6avEfORNGKTE/dwBCghNG5mJC8XhDsM7rAyTP9vTPB+Bfm550arTjsAk/qf+bCUM 4 | 6MZI2iYEuhdDOju1V7rSKQSbdnINSqck8ky3eEiY7ldKingw8y5lr1aEVwIDAQAB 5 | AoGBANgB66sKMga2SKN5nQdHS3LDCkevCutu1OWM5ZcbB4Kej5kC57xsf+tzPtab 6 | emeIVGhCPOAALqB4YcT+QtMX967oM1MjcFbtH7si5oq6UYyp3i0G9Si6jIoVHz3+ 7 | 8yOUaqwKbK+bRX8VS0YsHZmBsPK5ryN50iUwsU08nemoA94BAkEA9GS9Q5OPeFkM 8 | tFxsIQ1f2FSsZAuN/1cpZgJqY+YaAN7MSPGTWyfd7nWG/Zgk3GO9/2ihh4gww+7B 9 | To09GkmW4QJBAPQOHC2V+t2TA98+6Lj6+TYwcGEkhOENfVpH25mQ+kXgF/1Bd6rA 10 | nosT1bdAY+SnmWXbSw6Kv5C20Em+bEX8WjcCQCSRRjhsRdVODbaW9Z7kb2jhEoJN 11 | sEt6cTlQNzcHYPCsZYisjM3g4zYg47fiIfHQAsfKkhDDcfh/KvFj9LaQOEECQQCH 12 | eBWYEDpSJ7rsfqT7mQQgWj7nDThdG/nK1TxGP71McBmg0Gg2dfkLRhVJRQqt74Is 13 | kc9V4Rp4n6F6baL4Lh19AkEA6pZZer0kg3Kv9hjhaITIKUYdfIp9vYnDRWbQlBmR 14 | atV8V9u9q2ETZvqfHpN+9Lu6NYR4yXIEIRf1bnIZ/mr9eQ== 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /t/count.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use t::Test; 4 | 5 | repeat_each(2); 6 | 7 | plan tests => repeat_each() * (3 * blocks()); 8 | 9 | run_tests(); 10 | 11 | __DATA__ 12 | 13 | === TEST 1: module size of resty.redis 14 | --- global_config eval: $::GlobalConfig 15 | --- server_config 16 | content_by_lua ' 17 | local redis = require "resty.redis" 18 | n = 0 19 | for _, _ in pairs(redis) do 20 | n = n + 1 21 | end 22 | ngx.say("size: ", n) 23 | '; 24 | --- response_body 25 | size: 56 26 | --- no_error_log 27 | [error] 28 | -------------------------------------------------------------------------------- /t/hmset.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use t::Test; 4 | 5 | repeat_each(2); 6 | 7 | plan tests => repeat_each() * (3 * blocks()) - 2; 8 | 9 | run_tests(); 10 | 11 | __DATA__ 12 | 13 | === TEST 1: hmset key-pairs 14 | --- global_config eval: $::GlobalConfig 15 | --- server_config 16 | content_by_lua ' 17 | local redis = require "resty.redis" 18 | local red = redis:new() 19 | 20 | red:set_timeout(1000) -- 1 sec 21 | 22 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 23 | if not ok then 24 | ngx.say("failed to connect: ", err) 25 | return 26 | end 27 | 28 | local res, err = red:hmset("animals", "dog", "bark", "cat", "meow") 29 | if not res then 30 | ngx.say("failed to set animals: ", err) 31 | return 32 | end 33 | ngx.say("hmset animals: ", res) 34 | 35 | local res, err = red:hmget("animals", "dog", "cat") 36 | if not res then 37 | ngx.say("failed to get animals: ", err) 38 | return 39 | end 40 | 41 | ngx.say("hmget animals: ", res) 42 | 43 | red:close() 44 | '; 45 | --- response_body 46 | hmset animals: OK 47 | hmget animals: barkmeow 48 | --- no_error_log 49 | [error] 50 | 51 | 52 | 53 | === TEST 2: hmset lua tables 54 | --- global_config eval: $::GlobalConfig 55 | --- server_config 56 | content_by_lua ' 57 | local redis = require "resty.redis" 58 | local red = redis:new() 59 | 60 | red:set_timeout(1000) -- 1 sec 61 | 62 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 63 | if not ok then 64 | ngx.say("failed to connect: ", err) 65 | return 66 | end 67 | 68 | local t = { dog = "bark", cat = "meow", cow = "moo" } 69 | local res, err = red:hmset("animals", t) 70 | if not res then 71 | ngx.say("failed to set animals: ", err) 72 | return 73 | end 74 | ngx.say("hmset animals: ", res) 75 | 76 | local res, err = red:hmget("animals", "dog", "cat", "cow") 77 | if not res then 78 | ngx.say("failed to get animals: ", err) 79 | return 80 | end 81 | 82 | ngx.say("hmget animals: ", res) 83 | 84 | red:close() 85 | '; 86 | --- response_body 87 | hmset animals: OK 88 | hmget animals: barkmeowmoo 89 | --- no_error_log 90 | [error] 91 | 92 | 93 | 94 | === TEST 3: hmset a single scalar 95 | --- global_config eval: $::GlobalConfig 96 | --- server_config 97 | content_by_lua ' 98 | local redis = require "resty.redis" 99 | local red = redis:new() 100 | 101 | red:set_timeout(1000) -- 1 sec 102 | 103 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 104 | if not ok then 105 | ngx.say("failed to connect: ", err) 106 | return 107 | end 108 | 109 | local res, err = red:hmset("animals", "cat") 110 | if not res then 111 | ngx.say("failed to set animals: ", err) 112 | return 113 | end 114 | ngx.say("hmset animals: ", res) 115 | 116 | local res, err = red:hmget("animals", "cat") 117 | if not res then 118 | ngx.say("failed to get animals: ", err) 119 | return 120 | end 121 | 122 | ngx.say("hmget animals: ", res) 123 | 124 | red:close() 125 | '; 126 | --- internal_server_error 127 | --- error_log 128 | table expected, got string 129 | -------------------------------------------------------------------------------- /t/mock.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use t::Test; 4 | 5 | repeat_each(2); 6 | 7 | plan tests => repeat_each() * (4 * blocks()); 8 | 9 | run_tests(); 10 | 11 | __DATA__ 12 | 13 | === TEST 1: continue using the obj when read timeout happens 14 | --- global_config eval: $::GlobalConfig 15 | --- server_config 16 | content_by_lua ' 17 | local redis = require "resty.redis" 18 | local red = redis:new() 19 | 20 | local ok, err = red:connect("127.0.0.1", 1921); 21 | if not ok then 22 | ngx.say("failed to connect: ", err) 23 | return 24 | end 25 | 26 | red:set_timeout(100) -- 0.1 sec 27 | 28 | for i = 1, 2 do 29 | local data, err = red:get("foo") 30 | if not data then 31 | ngx.say("failed to get: ", err) 32 | else 33 | ngx.say("get: ", data); 34 | end 35 | ngx.sleep(0.1) 36 | end 37 | 38 | red:close() 39 | '; 40 | --- tcp_listen: 1921 41 | --- tcp_query eval 42 | "*2\r 43 | \$3\r 44 | get\r 45 | \$3\r 46 | foo\r 47 | " 48 | --- tcp_reply eval 49 | "\$5\r\nhello\r\n" 50 | --- tcp_reply_delay: 150ms 51 | --- response_body 52 | failed to get: timeout 53 | failed to get: closed 54 | --- error_log 55 | lua tcp socket read timed out 56 | -------------------------------------------------------------------------------- /t/module.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use t::Test; 4 | 5 | repeat_each(2); 6 | 7 | plan tests => repeat_each() * (3 * blocks()); 8 | 9 | run_tests(); 10 | 11 | __DATA__ 12 | 13 | === TEST 1: sanity 14 | --- global_config eval: $::GlobalConfig 15 | --- server_config 16 | content_by_lua_block { 17 | local cjson = require "cjson" 18 | local redis = require "resty.redis" 19 | redis.register_module_prefix("bf") 20 | redis.register_module_prefix("test") 21 | 22 | local red = redis:new() 23 | 24 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 25 | if not ok then 26 | ngx.say("failed to connect: ", err) 27 | return 28 | end 29 | 30 | local res, err = red:del("module_1") 31 | if not res then 32 | ngx.say(err) 33 | return 34 | end 35 | 36 | res, err = red:bf():add("module_1", 1) 37 | if not res then 38 | ngx.say(err) 39 | return 40 | end 41 | ngx.say("receive: ", cjson.encode(res)) 42 | 43 | res, err = red:bf():exists("module_1", 1) 44 | if not res then 45 | ngx.say(err) 46 | return 47 | end 48 | ngx.say("receive: ", cjson.encode(res)) 49 | 50 | -- call normal command 51 | res, err = red:del("module_1") 52 | if not res then 53 | ngx.say(err) 54 | return 55 | end 56 | ngx.say("receive: ", cjson.encode(res)) 57 | 58 | -- call cached 'exists' again 59 | res, err = red:exists("module_1") 60 | if not res then 61 | ngx.say(err) 62 | return 63 | end 64 | ngx.say("receive: ", cjson.encode(res)) 65 | 66 | -- call pre-created 'get' method 67 | res, err = red:test():get() 68 | if not res then 69 | ngx.say(err) 70 | end 71 | 72 | red:close() 73 | } 74 | --- response_body eval 75 | qr/receive: 1 76 | receive: 1 77 | receive: 1 78 | receive: 0 79 | ERR unknown command [`']test.get['`].+ 80 | /ms 81 | --- no_error_log 82 | [error] 83 | -------------------------------------------------------------------------------- /t/pipeline.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use t::Test; 4 | 5 | repeat_each(2); 6 | 7 | plan tests => repeat_each() * (3 * blocks()); 8 | 9 | run_tests(); 10 | 11 | __DATA__ 12 | 13 | === TEST 1: basic 14 | --- global_config eval: $::GlobalConfig 15 | --- server_config 16 | content_by_lua ' 17 | local redis = require "resty.redis" 18 | local red = redis:new() 19 | 20 | red:set_timeout(1000) -- 1 sec 21 | 22 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 23 | if not ok then 24 | ngx.say("failed to connect: ", err) 25 | return 26 | end 27 | 28 | for i = 1, 2 do 29 | red:init_pipeline() 30 | 31 | red:set("dog", "an animal") 32 | red:get("dog") 33 | red:set("dog", "hello") 34 | red:get("dog") 35 | 36 | local results = red:commit_pipeline() 37 | local cjson = require "cjson" 38 | ngx.say(cjson.encode(results)) 39 | end 40 | 41 | red:close() 42 | '; 43 | --- response_body 44 | ["OK","an animal","OK","hello"] 45 | ["OK","an animal","OK","hello"] 46 | --- no_error_log 47 | [error] 48 | 49 | 50 | 51 | === TEST 2: cancel automatically 52 | --- global_config eval: $::GlobalConfig 53 | --- server_config 54 | content_by_lua ' 55 | local redis = require "resty.redis" 56 | local red = redis:new() 57 | 58 | red:set_timeout(1000) -- 1 sec 59 | 60 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 61 | if not ok then 62 | ngx.say("failed to connect: ", err) 63 | return 64 | end 65 | 66 | red:init_pipeline() 67 | 68 | red:set("dog", "an animal") 69 | red:get("dog") 70 | 71 | for i = 1, 2 do 72 | red:init_pipeline() 73 | 74 | red:set("dog", "an animal") 75 | red:get("dog") 76 | red:set("dog", "hello") 77 | red:get("dog") 78 | 79 | local results = red:commit_pipeline() 80 | local cjson = require "cjson" 81 | ngx.say(cjson.encode(results)) 82 | end 83 | 84 | red:close() 85 | '; 86 | --- response_body 87 | ["OK","an animal","OK","hello"] 88 | ["OK","an animal","OK","hello"] 89 | --- no_error_log 90 | [error] 91 | 92 | 93 | 94 | === TEST 3: cancel explicitly 95 | --- global_config eval: $::GlobalConfig 96 | --- server_config 97 | content_by_lua ' 98 | local redis = require "resty.redis" 99 | local red = redis:new() 100 | 101 | red:set_timeout(1000) -- 1 sec 102 | 103 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 104 | if not ok then 105 | ngx.say("failed to connect: ", err) 106 | return 107 | end 108 | 109 | red:init_pipeline() 110 | 111 | red:set("dog", "an animal") 112 | red:get("dog") 113 | 114 | red:cancel_pipeline() 115 | 116 | local res, err = red:flushall() 117 | if not res then 118 | ngx.say("failed to flush all: ", err) 119 | return 120 | end 121 | 122 | ngx.say("flushall: ", res) 123 | 124 | for i = 1, 2 do 125 | red:init_pipeline() 126 | 127 | red:set("dog", "an animal") 128 | red:get("dog") 129 | red:set("dog", "hello") 130 | red:get("dog") 131 | 132 | local results = red:commit_pipeline() 133 | local cjson = require "cjson" 134 | ngx.say(cjson.encode(results)) 135 | end 136 | 137 | red:close() 138 | '; 139 | --- response_body 140 | flushall: OK 141 | ["OK","an animal","OK","hello"] 142 | ["OK","an animal","OK","hello"] 143 | --- no_error_log 144 | [error] 145 | 146 | 147 | 148 | === TEST 4: mixed 149 | --- global_config eval: $::GlobalConfig 150 | --- server_config 151 | content_by_lua ' 152 | local redis = require "resty.redis" 153 | local red = redis:new() 154 | 155 | red:set_timeout(1000) -- 1 sec 156 | 157 | local ok, err = red:connect("127.0.0.1", 6379) 158 | if not ok then 159 | ngx.say("failed to connect: ", err) 160 | return 161 | end 162 | 163 | ok, err = red:set("dog", "an aniaml") 164 | if not ok then 165 | ngx.say("failed to set dog: ", err) 166 | return 167 | end 168 | 169 | ngx.say("set result: ", ok) 170 | 171 | local res, err = red:get("dog") 172 | if not res then 173 | ngx.say("failed to get dog: ", err) 174 | return 175 | end 176 | 177 | if res == ngx.null then 178 | ngx.say("dog not found.") 179 | return 180 | end 181 | 182 | ngx.say("dog: ", res) 183 | 184 | red:init_pipeline() 185 | red:set("cat", "Marry") 186 | red:set("horse", "Bob") 187 | red:get("cat") 188 | red:get("horse") 189 | local results, err = red:commit_pipeline() 190 | if not results then 191 | ngx.say("failed to commit the pipelined requests: ", err) 192 | return 193 | end 194 | 195 | for i, res in ipairs(results) do 196 | if type(res) == "table" then 197 | if res[1] == false then 198 | ngx.say("failed to run command ", i, ": ", res[2]) 199 | else 200 | ngx.say("cmd ", i, ": ", res) 201 | end 202 | else 203 | -- process the scalar value 204 | ngx.say("cmd ", i, ": ", res) 205 | end 206 | end 207 | 208 | -- put it into the connection pool of size 100, 209 | -- with 0 idle timeout 210 | local ok, err = red:set_keepalive(0, 100) 211 | if not ok then 212 | ngx.say("failed to set keepalive: ", err) 213 | return 214 | end 215 | 216 | -- or just close the connection right away: 217 | -- local ok, err = red:close() 218 | -- if not ok then 219 | -- ngx.say("failed to close: ", err) 220 | -- return 221 | -- end 222 | '; 223 | --- response_body 224 | set result: OK 225 | dog: an aniaml 226 | cmd 1: OK 227 | cmd 2: OK 228 | cmd 3: Marry 229 | cmd 4: Bob 230 | --- no_error_log 231 | [error] 232 | 233 | 234 | 235 | === TEST 5: redis return error in pipeline 236 | --- global_config eval: $::GlobalConfig 237 | --- server_config 238 | content_by_lua ' 239 | local redis = require "resty.redis" 240 | local red = redis:new() 241 | 242 | red:set_timeout(1000) -- 1 sec 243 | 244 | local ok, err = red:connect("127.0.0.1", 6379) 245 | if not ok then 246 | ngx.say("failed to connect: ", err) 247 | return 248 | end 249 | 250 | local res, err = red:del("dog") 251 | if not res then 252 | ngx.say("failed to del dog: ", err) 253 | return 254 | end 255 | 256 | red:init_pipeline() 257 | red:hkeys("dog") 258 | red:set("dog", "an animal") 259 | red:hkeys("dog") 260 | red:get("dog") 261 | local results, err = red:commit_pipeline() 262 | if not results then 263 | ngx.say("failed to commit the pipelined requests: ", err) 264 | return 265 | end 266 | 267 | for i, res in ipairs(results) do 268 | if type(res) == "table" then 269 | if res[1] == false then 270 | ngx.say("failed to run command ", i, ": ", res[2]) 271 | else 272 | ngx.say("cmd ", i, ": ", res) 273 | end 274 | else 275 | -- process the scalar value 276 | ngx.say("cmd ", i, ": ", res) 277 | end 278 | end 279 | 280 | -- put it into the connection pool of size 100, 281 | -- with 0 idle timeout 282 | local ok, err = red:set_keepalive(0, 100) 283 | if not ok then 284 | ngx.say("failed to set keepalive: ", err) 285 | return 286 | end 287 | '; 288 | --- response_body 289 | cmd 1: 290 | cmd 2: OK 291 | failed to run command 3: WRONGTYPE Operation against a key holding the wrong kind of value 292 | cmd 4: an animal 293 | --- no_error_log 294 | [error] 295 | -------------------------------------------------------------------------------- /t/pubsub.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use t::Test; 4 | 5 | repeat_each(2); 6 | 7 | plan tests => repeat_each() * (3 * blocks()); 8 | 9 | run_tests(); 10 | 11 | __DATA__ 12 | 13 | === TEST 1: single channel 14 | --- global_config eval: $::GlobalConfig 15 | --- server_config 16 | content_by_lua ' 17 | local cjson = require "cjson" 18 | local redis = require "resty.redis" 19 | 20 | local red = redis:new() 21 | local red2 = redis:new() 22 | 23 | red:set_timeout(1000) -- 1 sec 24 | red2:set_timeout(1000) -- 1 sec 25 | 26 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 27 | if not ok then 28 | ngx.say("1: failed to connect: ", err) 29 | return 30 | end 31 | 32 | ok, err = red2:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 33 | if not ok then 34 | ngx.say("2: failed to connect: ", err) 35 | return 36 | end 37 | 38 | local res, err = red:subscribe("dog") 39 | if not res then 40 | ngx.say("1: failed to subscribe: ", err) 41 | return 42 | end 43 | 44 | ngx.say("1: subscribe: ", cjson.encode(res)) 45 | 46 | res, err = red2:publish("dog", "Hello") 47 | if not res then 48 | ngx.say("2: failed to publish: ", err) 49 | return 50 | end 51 | 52 | ngx.say("2: publish: ", cjson.encode(res)) 53 | 54 | res, err = red:read_reply() 55 | if not res then 56 | ngx.say("1: failed to read reply: ", err) 57 | return 58 | end 59 | 60 | ngx.say("1: receive: ", cjson.encode(res)) 61 | 62 | red:close() 63 | red2:close() 64 | '; 65 | --- response_body 66 | 1: subscribe: ["subscribe","dog",1] 67 | 2: publish: 1 68 | 1: receive: ["message","dog","Hello"] 69 | --- no_error_log 70 | [error] 71 | 72 | 73 | 74 | === TEST 2: single channel (retry read_reply() after timeout) 75 | --- global_config eval: $::GlobalConfig 76 | --- server_config 77 | lua_socket_log_errors off; 78 | content_by_lua ' 79 | local cjson = require "cjson" 80 | local redis = require "resty.redis" 81 | 82 | local red = redis:new() 83 | local red2 = redis:new() 84 | 85 | red:set_timeout(1000) -- 1 sec 86 | red2:set_timeout(1000) -- 1 sec 87 | 88 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 89 | if not ok then 90 | ngx.say("1: failed to connect: ", err) 91 | return 92 | end 93 | 94 | ok, err = red2:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 95 | if not ok then 96 | ngx.say("2: failed to connect: ", err) 97 | return 98 | end 99 | 100 | local res, err = red:subscribe("dog") 101 | if not res then 102 | ngx.say("1: failed to subscribe: ", err) 103 | return 104 | end 105 | 106 | ngx.say("1: subscribe: ", cjson.encode(res)) 107 | 108 | red:set_timeout(1) 109 | for i = 1, 2 do 110 | res, err = red:read_reply() 111 | if not res then 112 | ngx.say("1: failed to read reply: ", err) 113 | if err ~= "timeout" then 114 | return 115 | end 116 | end 117 | end 118 | red:set_timeout(1000) 119 | 120 | res, err = red:unsubscribe("dog") 121 | if not res then 122 | ngx.say("1: failed to unsubscribe: ", err) 123 | else 124 | ngx.say("1: unsubscribe: ", cjson.encode(res)) 125 | end 126 | 127 | res, err = red2:publish("dog", "Hello") 128 | if not res then 129 | ngx.say("2: failed to publish: ", err) 130 | return 131 | end 132 | 133 | ngx.say("2: publish: ", cjson.encode(res)) 134 | 135 | res, err = red:read_reply() 136 | if not res then 137 | ngx.say("1: failed to read reply: ", err) 138 | 139 | else 140 | ngx.say("1: receive: ", cjson.encode(res)) 141 | end 142 | 143 | res, err = red:unsubscribe("dog") 144 | if not res then 145 | ngx.say("1: failed to unsubscribe: ", err) 146 | return 147 | end 148 | 149 | ngx.say("1: unsubscribe: ", cjson.encode(res)) 150 | 151 | red:close() 152 | red2:close() 153 | '; 154 | --- response_body 155 | 1: subscribe: ["subscribe","dog",1] 156 | 1: failed to read reply: timeout 157 | 1: failed to read reply: timeout 158 | 1: unsubscribe: ["unsubscribe","dog",0] 159 | 2: publish: 0 160 | 1: failed to read reply: not subscribed 161 | 1: failed to unsubscribe: not subscribed 162 | --- no_error_log 163 | [error] 164 | 165 | 166 | 167 | === TEST 3: multiple channels 168 | --- global_config eval: $::GlobalConfig 169 | --- server_config 170 | lua_socket_log_errors off; 171 | content_by_lua ' 172 | local cjson = require "cjson" 173 | local redis = require "resty.redis" 174 | 175 | local red = redis:new() 176 | local red2 = redis:new() 177 | 178 | red:set_timeout(1000) -- 1 sec 179 | red2:set_timeout(1000) -- 1 sec 180 | 181 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 182 | if not ok then 183 | ngx.say("1: failed to connect: ", err) 184 | return 185 | end 186 | 187 | ok, err = red2:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 188 | if not ok then 189 | ngx.say("2: failed to connect: ", err) 190 | return 191 | end 192 | 193 | local res, err = red:subscribe("dog") 194 | if not res then 195 | ngx.say("1: failed to subscribe: ", err) 196 | return 197 | end 198 | 199 | ngx.say("1: subscribe dog: ", cjson.encode(res)) 200 | 201 | res, err = red:subscribe("cat") 202 | if not res then 203 | ngx.say("1: failed to subscribe: ", err) 204 | return 205 | end 206 | 207 | ngx.say("1: subscribe cat: ", cjson.encode(res)) 208 | 209 | res, err = red2:publish("dog", "Hello") 210 | if not res then 211 | ngx.say("2: failed to publish: ", err) 212 | return 213 | end 214 | 215 | ngx.say("2: publish: ", cjson.encode(res)) 216 | 217 | res, err = red:read_reply() 218 | if not res then 219 | ngx.say("1: failed to read reply: ", err) 220 | else 221 | ngx.say("1: receive: ", cjson.encode(res)) 222 | end 223 | 224 | red:set_timeout(10) -- 10ms 225 | res, err = red:read_reply() 226 | if not res then 227 | ngx.say("1: failed to read reply: ", err) 228 | else 229 | ngx.say("1: receive: ", cjson.encode(res)) 230 | end 231 | red:set_timeout(1000) -- 1s 232 | 233 | res, err = red:unsubscribe() 234 | if not res then 235 | ngx.say("1: failed to unscribe: ", err) 236 | else 237 | ngx.say("1: unsubscribe: ", cjson.encode(res)) 238 | end 239 | 240 | res, err = red:read_reply() 241 | if not res then 242 | ngx.say("1: failed to read reply: ", err) 243 | else 244 | ngx.say("1: receive: ", cjson.encode(res)) 245 | end 246 | 247 | red:set_timeout(10) -- 10ms 248 | res, err = red:read_reply() 249 | if not res then 250 | ngx.say("1: failed to read reply: ", err) 251 | else 252 | ngx.say("1: receive: ", cjson.encode(res)) 253 | end 254 | red:set_timeout(1000) -- 1s 255 | 256 | red:close() 257 | red2:close() 258 | '; 259 | --- response_body_like chop 260 | ^1: subscribe dog: \["subscribe","dog",1\] 261 | 1: subscribe cat: \["subscribe","cat",2\] 262 | 2: publish: 1 263 | 1: receive: \["message","dog","Hello"\] 264 | 1: failed to read reply: timeout 265 | 1: unsubscribe: \[\["unsubscribe","(?:cat|dog)",1\],\["unsubscribe","(?:cat|dog)",0\]\] 266 | 1: failed to read reply: not subscribed 267 | 1: failed to read reply: not subscribed$ 268 | 269 | --- no_error_log 270 | [error] 271 | 272 | 273 | 274 | === TEST 4: call subscribe after read_reply() times out 275 | --- global_config eval: $::GlobalConfig 276 | --- server_config 277 | lua_socket_log_errors off; 278 | content_by_lua ' 279 | local cjson = require "cjson" 280 | local redis = require "resty.redis" 281 | 282 | local red = redis:new() 283 | local red2 = redis:new() 284 | 285 | red:set_timeout(1000) -- 1 sec 286 | red2:set_timeout(1000) -- 1 sec 287 | 288 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 289 | if not ok then 290 | ngx.say("1: failed to connect: ", err) 291 | return 292 | end 293 | 294 | ok, err = red2:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 295 | if not ok then 296 | ngx.say("2: failed to connect: ", err) 297 | return 298 | end 299 | 300 | local res, err = red:subscribe("dog") 301 | if not res then 302 | ngx.say("1: failed to subscribe: ", err) 303 | return 304 | end 305 | 306 | ngx.say("1: subscribe: ", cjson.encode(res)) 307 | 308 | red:set_timeout(1) 309 | for i = 1, 2 do 310 | res, err = red:read_reply() 311 | if not res then 312 | ngx.say("1: failed to read reply: ", err) 313 | if err ~= "timeout" then 314 | return 315 | end 316 | end 317 | end 318 | red:set_timeout(1000) 319 | 320 | res, err = red:subscribe("cat") 321 | if not res then 322 | ngx.say("1: failed to subscribe to cat: ", err) 323 | else 324 | ngx.say("1: subscribe: ", cjson.encode(res)) 325 | end 326 | 327 | red:close() 328 | red2:close() 329 | '; 330 | --- response_body 331 | 1: subscribe: ["subscribe","dog",1] 332 | 1: failed to read reply: timeout 333 | 1: failed to read reply: timeout 334 | 1: subscribe: ["subscribe","cat",2] 335 | --- no_error_log 336 | [error] 337 | 338 | 339 | 340 | === TEST 5: call set_keepalive in subscribed mode (previous read_reply calls timed out) 341 | --- global_config eval: $::GlobalConfig 342 | --- server_config 343 | lua_socket_log_errors off; 344 | content_by_lua ' 345 | local cjson = require "cjson" 346 | local redis = require "resty.redis" 347 | 348 | local red = redis:new() 349 | local red2 = redis:new() 350 | 351 | red:set_timeout(1000) -- 1 sec 352 | red2:set_timeout(1000) -- 1 sec 353 | 354 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 355 | if not ok then 356 | ngx.say("1: failed to connect: ", err) 357 | return 358 | end 359 | 360 | ok, err = red2:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 361 | if not ok then 362 | ngx.say("2: failed to connect: ", err) 363 | return 364 | end 365 | 366 | local res, err = red:subscribe("dog") 367 | if not res then 368 | ngx.say("1: failed to subscribe: ", err) 369 | return 370 | end 371 | 372 | ngx.say("1: subscribe: ", cjson.encode(res)) 373 | 374 | red:set_timeout(1) 375 | for i = 1, 2 do 376 | res, err = red:read_reply() 377 | if not res then 378 | ngx.say("1: failed to read reply: ", err) 379 | if err ~= "timeout" then 380 | return 381 | end 382 | end 383 | end 384 | red:set_timeout(1000) 385 | 386 | res, err = red:set_keepalive() 387 | if not res then 388 | ngx.say("1: failed to set keepalive: ", err) 389 | else 390 | ngx.say("1: set keepalive: ", cjson.encode(res)) 391 | end 392 | 393 | red:close() 394 | red2:close() 395 | '; 396 | --- response_body 397 | 1: subscribe: ["subscribe","dog",1] 398 | 1: failed to read reply: timeout 399 | 1: failed to read reply: timeout 400 | 1: failed to set keepalive: subscribed state 401 | --- no_error_log 402 | [error] 403 | 404 | 405 | 406 | === TEST 6: call set_keepalive in subscribed mode 407 | --- global_config eval: $::GlobalConfig 408 | --- server_config 409 | lua_socket_log_errors off; 410 | content_by_lua ' 411 | local cjson = require "cjson" 412 | local redis = require "resty.redis" 413 | 414 | local red = redis:new() 415 | local red2 = redis:new() 416 | 417 | red:set_timeout(1000) -- 1 sec 418 | red2:set_timeout(1000) -- 1 sec 419 | 420 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 421 | if not ok then 422 | ngx.say("1: failed to connect: ", err) 423 | return 424 | end 425 | 426 | ok, err = red2:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 427 | if not ok then 428 | ngx.say("2: failed to connect: ", err) 429 | return 430 | end 431 | 432 | local res, err = red:subscribe("dog") 433 | if not res then 434 | ngx.say("1: failed to subscribe: ", err) 435 | return 436 | end 437 | 438 | ngx.say("1: subscribe: ", cjson.encode(res)) 439 | 440 | res, err = red:set_keepalive() 441 | if not res then 442 | ngx.say("1: failed to set keepalive: ", err) 443 | else 444 | ngx.say("1: set keepalive: ", cjson.encode(res)) 445 | end 446 | 447 | red:close() 448 | red2:close() 449 | '; 450 | --- response_body 451 | 1: subscribe: ["subscribe","dog",1] 452 | 1: failed to set keepalive: subscribed state 453 | --- no_error_log 454 | [error] 455 | 456 | 457 | 458 | === TEST 7: call set_keepalive in unsubscribed mode 459 | --- global_config eval: $::GlobalConfig 460 | --- server_config 461 | lua_socket_log_errors off; 462 | content_by_lua ' 463 | local cjson = require "cjson" 464 | local redis = require "resty.redis" 465 | 466 | local red = redis:new() 467 | local red2 = redis:new() 468 | 469 | red:set_timeout(1000) -- 1 sec 470 | red2:set_timeout(1000) -- 1 sec 471 | 472 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 473 | if not ok then 474 | ngx.say("1: failed to connect: ", err) 475 | return 476 | end 477 | 478 | ok, err = red2:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 479 | if not ok then 480 | ngx.say("2: failed to connect: ", err) 481 | return 482 | end 483 | 484 | local res, err = red:subscribe("dog") 485 | if not res then 486 | ngx.say("1: failed to subscribe: ", err) 487 | return 488 | end 489 | 490 | ngx.say("1: subscribe: ", cjson.encode(res)) 491 | 492 | res, err = red:unsubscribe() 493 | if not res then 494 | ngx.say("1: failed to unsubscribe: ", err) 495 | return 496 | end 497 | 498 | ngx.say("1: unsubscribe: ", cjson.encode(res)) 499 | 500 | res, err = red:set_keepalive() 501 | if not res then 502 | ngx.say("1: failed to set keepalive: ", err) 503 | else 504 | ngx.say("1: set keepalive: ", cjson.encode(res)) 505 | end 506 | 507 | red:close() 508 | red2:close() 509 | '; 510 | --- response_body 511 | 1: subscribe: ["subscribe","dog",1] 512 | 1: unsubscribe: ["unsubscribe","dog",0] 513 | 1: set keepalive: 1 514 | 515 | --- no_error_log 516 | [error] 517 | 518 | 519 | 520 | === TEST 8: mix read_reply and other commands 521 | --- global_config eval: $::GlobalConfig 522 | --- server_config 523 | lua_socket_log_errors off; 524 | content_by_lua ' 525 | local cjson = require "cjson" 526 | local redis = require "resty.redis" 527 | 528 | local red = redis:new() 529 | local red2 = redis:new() 530 | 531 | red:set_timeout(1000) -- 1 sec 532 | red2:set_timeout(1000) -- 1 sec 533 | 534 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 535 | if not ok then 536 | ngx.say("1: failed to connect: ", err) 537 | return 538 | end 539 | 540 | ok, err = red2:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 541 | if not ok then 542 | ngx.say("2: failed to connect: ", err) 543 | return 544 | end 545 | 546 | local res, err = red:subscribe("dog") 547 | if not res then 548 | ngx.say("1: failed to subscribe: ", err) 549 | return 550 | end 551 | 552 | res, err = red2:publish("dog", "Hello") 553 | if not res then 554 | ngx.say("2: failed to publish: ", err) 555 | return 556 | end 557 | 558 | res, err = red:ping() 559 | if not res then 560 | ngx.say("1: failed to subscribe: ", err) 561 | return 562 | end 563 | 564 | res, err = red2:publish("dog", "World") 565 | if not res then 566 | ngx.say("2: failed to publish: ", err) 567 | return 568 | end 569 | 570 | res, err = red:read_reply() 571 | if not res then 572 | ngx.say("1: failed to read reply: ", err) 573 | else 574 | ngx.say("1: receive: ", cjson.encode(res)) 575 | end 576 | 577 | res, err = red:read_reply() 578 | if not res then 579 | ngx.say("1: failed to read reply: ", err) 580 | else 581 | ngx.say("1: receive: ", cjson.encode(res)) 582 | end 583 | 584 | res, err = red:unsubscribe() 585 | if not res then 586 | ngx.say("1: failed to unscribe: ", err) 587 | else 588 | ngx.say("1: unsubscribe: ", cjson.encode(res)) 589 | end 590 | 591 | red:set_timeout(1) -- 1s 592 | res, err = red:read_reply() 593 | if not res then 594 | ngx.say("1: failed to read reply: ", err) 595 | else 596 | ngx.say("1: receive: ", cjson.encode(res)) 597 | end 598 | 599 | red:close() 600 | red2:close() 601 | '; 602 | --- request 603 | GET /t 604 | --- response_body_like chop 605 | 1: receive: \["message","dog","Hello"\] 606 | 1: receive: \["message","dog","World"\] 607 | 1: unsubscribe: \["unsubscribe","dog",0\] 608 | 1: failed to read reply: not subscribed$ 609 | 610 | --- no_error_log 611 | [error] 612 | 613 | 614 | 615 | === TEST 9: multiple subscribe 616 | --- global_config eval: $::GlobalConfig 617 | --- server_config 618 | content_by_lua_block { 619 | local cjson = require "cjson" 620 | local redis = require "resty.redis" 621 | 622 | local red = redis:new() 623 | 624 | red:set_timeout(1000) -- 1 sec 625 | 626 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 627 | if not ok then 628 | ngx.say("1: failed to connect: ", err) 629 | return 630 | end 631 | 632 | local res, err = red:subscribe("dog", "cat") 633 | if not res then 634 | ngx.say("1: failed to subscribe: ", err) 635 | else 636 | ngx.say("1: subscribe: ", cjson.encode(res)) 637 | end 638 | 639 | res, err = red:unsubscribe("dog") 640 | if not res then 641 | ngx.say("1: failed to unsubscribe: ", err) 642 | else 643 | ngx.say("1: unsubscribe: ", cjson.encode(res)) 644 | end 645 | 646 | res, err = red:unsubscribe("cat") 647 | if not res then 648 | ngx.say("1: failed to unsubscribe: ", err) 649 | else 650 | ngx.say("1: unsubscribe: ", cjson.encode(res)) 651 | end 652 | 653 | red:close() 654 | } 655 | --- response_body 656 | 1: subscribe: [["subscribe","dog",1],["subscribe","cat",2]] 657 | 1: unsubscribe: ["unsubscribe","dog",1] 658 | 1: unsubscribe: ["unsubscribe","cat",0] 659 | --- no_error_log 660 | [error] 661 | 662 | 663 | 664 | === TEST 10: multiple unsubscribe 665 | --- global_config eval: $::GlobalConfig 666 | --- server_config 667 | content_by_lua_block { 668 | local cjson = require "cjson" 669 | local redis = require "resty.redis" 670 | 671 | local red = redis:new() 672 | 673 | red:set_timeout(1000) -- 1 sec 674 | 675 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 676 | if not ok then 677 | ngx.say("1: failed to connect: ", err) 678 | return 679 | end 680 | 681 | local res, err = red:subscribe("dog") 682 | if not res then 683 | ngx.say("1: failed to subscribe: ", err) 684 | return 685 | end 686 | 687 | res, err = red:subscribe("cat") 688 | if not res then 689 | ngx.say("1: failed to subscribe: ", err) 690 | return 691 | end 692 | 693 | res, err = red:unsubscribe() 694 | if not res then 695 | ngx.say("1: failed to unscribe: ", err) 696 | else 697 | ngx.say("1: unsubscribe: ", cjson.encode(res)) 698 | end 699 | 700 | red:close() 701 | } 702 | --- response_body_like 703 | ^1: unsubscribe: \[\["unsubscribe","(?:cat|dog)",1\],\["unsubscribe","(?:cat|dog)",0\]\]$ 704 | --- no_error_log 705 | [error] 706 | 707 | 708 | 709 | === TEST 11: multiple psubscribe 710 | --- global_config eval: $::GlobalConfig 711 | --- server_config 712 | content_by_lua_block { 713 | local cjson = require "cjson" 714 | local redis = require "resty.redis" 715 | 716 | local red = redis:new() 717 | 718 | red:set_timeout(1000) -- 1 sec 719 | 720 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 721 | if not ok then 722 | ngx.say("1: failed to connect: ", err) 723 | return 724 | end 725 | 726 | local res, err = red:psubscribe("dog", "cat") 727 | if not res then 728 | ngx.say("1: failed to subscribe: ", err) 729 | else 730 | ngx.say("1: subscribe: ", cjson.encode(res)) 731 | end 732 | 733 | res, err = red:punsubscribe("dog") 734 | if not res then 735 | ngx.say("1: failed to unsubscribe: ", err) 736 | else 737 | ngx.say("1: unsubscribe: ", cjson.encode(res)) 738 | end 739 | 740 | res, err = red:punsubscribe("cat") 741 | if not res then 742 | ngx.say("1: failed to unsubscribe: ", err) 743 | else 744 | ngx.say("1: unsubscribe: ", cjson.encode(res)) 745 | end 746 | 747 | red:close() 748 | } 749 | --- response_body 750 | 1: subscribe: [["psubscribe","dog",1],["psubscribe","cat",2]] 751 | 1: unsubscribe: ["punsubscribe","dog",1] 752 | 1: unsubscribe: ["punsubscribe","cat",0] 753 | --- no_error_log 754 | [error] 755 | 756 | 757 | 758 | === TEST 12: multiple punsubscribe 759 | --- global_config eval: $::GlobalConfig 760 | --- server_config 761 | content_by_lua_block { 762 | local cjson = require "cjson" 763 | local redis = require "resty.redis" 764 | 765 | local red = redis:new() 766 | 767 | red:set_timeout(1000) -- 1 sec 768 | 769 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 770 | if not ok then 771 | ngx.say("1: failed to connect: ", err) 772 | return 773 | end 774 | 775 | local res, err = red:psubscribe("dog") 776 | if not res then 777 | ngx.say("1: failed to subscribe: ", err) 778 | return 779 | end 780 | 781 | res, err = red:psubscribe("cat") 782 | if not res then 783 | ngx.say("1: failed to subscribe: ", err) 784 | return 785 | end 786 | 787 | res, err = red:punsubscribe() 788 | if not res then 789 | ngx.say("1: failed to unscribe: ", err) 790 | else 791 | ngx.say("1: unsubscribe: ", cjson.encode(res)) 792 | end 793 | 794 | red:close() 795 | } 796 | --- response_body_like 797 | ^1: unsubscribe: \[\["punsubscribe","(?:cat|dog)",1\],\["punsubscribe","(?:cat|dog)",0\]\]$ 798 | --- no_error_log 799 | [error] 800 | 801 | 802 | 803 | === TEST 13: mix read_reply, subscribe, and psubscribe 804 | --- global_config eval: $::GlobalConfig 805 | --- server_config 806 | content_by_lua_block { 807 | local cjson = require "cjson" 808 | local redis = require "resty.redis" 809 | 810 | local red = redis:new() 811 | local red2 = redis:new() 812 | 813 | red:set_timeout(1000) -- 1 sec 814 | red2:set_timeout(1000) -- 1 sec 815 | 816 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 817 | if not ok then 818 | ngx.say("1: failed to connect: ", err) 819 | return 820 | end 821 | 822 | ok, err = red2:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 823 | if not ok then 824 | ngx.say("2: failed to connect: ", err) 825 | return 826 | end 827 | 828 | local res, err = red:subscribe("two") 829 | if not res then 830 | ngx.say("1: failed to subscribe: ", err) 831 | return 832 | end 833 | 834 | res, err = red:psubscribe("t*o") 835 | if not res then 836 | ngx.say("1: failed to subscribe: ", err) 837 | return 838 | end 839 | 840 | res, err = red2:publish("two", "foo") 841 | if not res then 842 | ngx.say("2: failed to publish: ", err) 843 | return 844 | end 845 | 846 | res, err = red2:publish("too", "bar") 847 | if not res then 848 | ngx.say("2: failed to publish: ", err) 849 | return 850 | end 851 | 852 | res, err = red2:publish("too", "baz") 853 | if not res then 854 | ngx.say("2: failed to publish: ", err) 855 | return 856 | end 857 | 858 | res, err = red:read_reply() 859 | if not res then 860 | ngx.say("1: failed to read reply: ", err) 861 | else 862 | ngx.say("1: receive: ", cjson.encode(res)) 863 | end 864 | 865 | res, err = red:read_reply() 866 | if not res then 867 | ngx.say("1: failed to read reply: ", err) 868 | else 869 | ngx.say("1: receive: ", cjson.encode(res)) 870 | end 871 | 872 | res, err = red:punsubscribe() 873 | if not res then 874 | ngx.say("1: failed to unscribe: ", err) 875 | else 876 | ngx.say("1: unsubscribe: ", cjson.encode(res)) 877 | end 878 | 879 | local res, err = red:subscribe("three") 880 | if not res then 881 | ngx.say("1: failed to subscribe: ", err) 882 | return 883 | end 884 | 885 | res, err = red2:publish("three", "foo") 886 | if not res then 887 | ngx.say("2: failed to publish: ", err) 888 | return 889 | end 890 | 891 | res, err = red2:publish("two", "bar") 892 | if not res then 893 | ngx.say("2: failed to publish: ", err) 894 | return 895 | end 896 | 897 | res, err = red:unsubscribe("three") 898 | if not res then 899 | ngx.say("1: failed to unscribe: ", err) 900 | else 901 | ngx.say("1: unsubscribe: ", cjson.encode(res)) 902 | end 903 | 904 | res, err = red:read_reply() 905 | if not res then 906 | ngx.say("1: failed to read reply: ", err) 907 | else 908 | ngx.say("1: receive: ", cjson.encode(res)) 909 | end 910 | 911 | res, err = red:unsubscribe() 912 | if not res then 913 | ngx.say("1: failed to unscribe: ", err) 914 | else 915 | ngx.say("1: unsubscribe: ", cjson.encode(res)) 916 | end 917 | 918 | red:close() 919 | red2:close() 920 | } 921 | --- response_body_like chop 922 | ^1: receive: \["p?message",("two"|"t\*o","two"),"foo"\] 923 | 1: receive: \["p?message",("two"|"t\*o","two"),"foo"\] 924 | 1: unsubscribe: \["punsubscribe","t\*o",1\] 925 | 1: unsubscribe: \["unsubscribe","three",1\] 926 | 1: receive: \["message","two","bar"\] 927 | 1: unsubscribe: \["unsubscribe","two",0\] 928 | --- no_error_log 929 | [error] 930 | -------------------------------------------------------------------------------- /t/sanity.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use t::Test; 4 | 5 | repeat_each(2); 6 | 7 | plan tests => repeat_each() * (3 * blocks()); 8 | 9 | run_tests(); 10 | 11 | __DATA__ 12 | 13 | === TEST 1: set and get 14 | --- global_config eval: $::GlobalConfig 15 | --- server_config 16 | content_by_lua ' 17 | local redis = require "resty.redis" 18 | local red = redis:new() 19 | 20 | red:set_timeout(1000) -- 1 sec 21 | 22 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 23 | if not ok then 24 | ngx.say("failed to connect: ", err) 25 | return 26 | end 27 | 28 | ok, err = red:select(1) 29 | if not ok then 30 | ngx.say("failed to select: ", err) 31 | return 32 | end 33 | 34 | local res, err = red:set("dog", "an animal") 35 | if not res then 36 | ngx.say("failed to set dog: ", err) 37 | return 38 | end 39 | 40 | ngx.say("set dog: ", res) 41 | 42 | for i = 1, 2 do 43 | local res, err = red:get("dog") 44 | if err then 45 | ngx.say("failed to get dog: ", err) 46 | return 47 | end 48 | 49 | if not res then 50 | ngx.say("dog not found.") 51 | return 52 | end 53 | 54 | ngx.say("dog: ", res) 55 | end 56 | 57 | red:close() 58 | '; 59 | --- response_body 60 | set dog: OK 61 | dog: an animal 62 | dog: an animal 63 | --- no_error_log 64 | [error] 65 | 66 | 67 | 68 | === TEST 2: flushall 69 | --- global_config eval: $::GlobalConfig 70 | --- server_config 71 | content_by_lua ' 72 | local redis = require "resty.redis" 73 | local red = redis:new() 74 | 75 | red:set_timeout(1000) -- 1 sec 76 | 77 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 78 | if not ok then 79 | ngx.say("failed to connect: ", err) 80 | return 81 | end 82 | 83 | local res, err = red:flushall() 84 | if not res then 85 | ngx.say("failed to flushall: ", err) 86 | return 87 | end 88 | ngx.say("flushall: ", res) 89 | 90 | red:close() 91 | '; 92 | --- response_body 93 | flushall: OK 94 | --- no_error_log 95 | [error] 96 | 97 | 98 | 99 | === TEST 3: get nil bulk value 100 | --- global_config eval: $::GlobalConfig 101 | --- server_config 102 | content_by_lua ' 103 | local redis = require "resty.redis" 104 | local red = redis:new() 105 | 106 | red:set_timeout(1000) -- 1 sec 107 | 108 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 109 | if not ok then 110 | ngx.say("failed to connect: ", err) 111 | return 112 | end 113 | 114 | local res, err = red:flushall() 115 | if not res then 116 | ngx.say("failed to flushall: ", err) 117 | return 118 | end 119 | 120 | ngx.say("flushall: ", res) 121 | 122 | for i = 1, 2 do 123 | res, err = red:get("not_found") 124 | if err then 125 | ngx.say("failed to get: ", err) 126 | return 127 | end 128 | 129 | if res == ngx.null then 130 | ngx.say("not_found not found.") 131 | return 132 | end 133 | 134 | ngx.say("get not_found: ", res) 135 | end 136 | 137 | red:close() 138 | '; 139 | --- response_body 140 | flushall: OK 141 | not_found not found. 142 | --- no_error_log 143 | [error] 144 | 145 | 146 | 147 | === TEST 4: get nil list 148 | --- global_config eval: $::GlobalConfig 149 | --- server_config 150 | content_by_lua ' 151 | local redis = require "resty.redis" 152 | local red = redis:new() 153 | 154 | red:set_timeout(1000) -- 1 sec 155 | 156 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 157 | if not ok then 158 | ngx.say("failed to connect: ", err) 159 | return 160 | end 161 | 162 | local res, err = red:flushall() 163 | if not res then 164 | ngx.say("failed to flushall: ", err) 165 | return 166 | end 167 | 168 | ngx.say("flushall: ", res) 169 | 170 | for i = 1, 2 do 171 | res, err = red:lrange("nokey", 0, 1) 172 | if err then 173 | ngx.say("failed to get: ", err) 174 | return 175 | end 176 | 177 | if res == ngx.null then 178 | ngx.say("nokey not found.") 179 | return 180 | end 181 | 182 | ngx.say("get nokey: ", #res, " (", type(res), ")") 183 | end 184 | 185 | red:close() 186 | '; 187 | --- response_body 188 | flushall: OK 189 | get nokey: 0 (table) 190 | get nokey: 0 (table) 191 | --- no_error_log 192 | [error] 193 | 194 | 195 | 196 | === TEST 5: incr and decr 197 | --- global_config eval: $::GlobalConfig 198 | --- server_config 199 | content_by_lua ' 200 | local redis = require "resty.redis" 201 | local red = redis:new() 202 | 203 | red:set_timeout(1000) -- 1 sec 204 | 205 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 206 | if not ok then 207 | ngx.say("failed to connect: ", err) 208 | return 209 | end 210 | 211 | local res, err = red:set("connections", 10) 212 | if not res then 213 | ngx.say("failed to set connections: ", err) 214 | return 215 | end 216 | 217 | ngx.say("set connections: ", res) 218 | 219 | res, err = red:incr("connections") 220 | if not res then 221 | ngx.say("failed to set connections: ", err) 222 | return 223 | end 224 | 225 | ngx.say("incr connections: ", res) 226 | 227 | local res, err = red:get("connections") 228 | if err then 229 | ngx.say("failed to get connections: ", err) 230 | return 231 | end 232 | 233 | res, err = red:incr("connections") 234 | if not res then 235 | ngx.say("failed to incr connections: ", err) 236 | return 237 | end 238 | 239 | ngx.say("incr connections: ", res) 240 | 241 | res, err = red:decr("connections") 242 | if not res then 243 | ngx.say("failed to decr connections: ", err) 244 | return 245 | end 246 | 247 | ngx.say("decr connections: ", res) 248 | 249 | res, err = red:get("connections") 250 | if not res then 251 | ngx.say("connections not found.") 252 | return 253 | end 254 | 255 | ngx.say("connections: ", res) 256 | 257 | res, err = red:del("connections") 258 | if not res then 259 | ngx.say("failed to del connections: ", err) 260 | return 261 | end 262 | 263 | ngx.say("del connections: ", res) 264 | 265 | res, err = red:incr("connections") 266 | if not res then 267 | ngx.say("failed to set connections: ", err) 268 | return 269 | end 270 | 271 | ngx.say("incr connections: ", res) 272 | 273 | res, err = red:get("connections") 274 | if not res then 275 | ngx.say("connections not found.") 276 | return 277 | end 278 | 279 | ngx.say("connections: ", res) 280 | 281 | red:close() 282 | '; 283 | --- response_body 284 | set connections: OK 285 | incr connections: 11 286 | incr connections: 12 287 | decr connections: 11 288 | connections: 11 289 | del connections: 1 290 | incr connections: 1 291 | connections: 1 292 | --- no_error_log 293 | [error] 294 | 295 | 296 | 297 | === TEST 6: bad incr command format 298 | --- global_config eval: $::GlobalConfig 299 | --- server_config 300 | content_by_lua ' 301 | local redis = require "resty.redis" 302 | local red = redis:new() 303 | 304 | red:set_timeout(1000) -- 1 sec 305 | 306 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 307 | if not ok then 308 | ngx.say("failed to connect: ", err) 309 | return 310 | end 311 | 312 | local res, err = red:incr("connections", 12) 313 | if not res then 314 | ngx.say("failed to set connections: ", res, ": ", err) 315 | return 316 | end 317 | 318 | ngx.say("incr connections: ", res) 319 | 320 | red:close() 321 | '; 322 | --- response_body 323 | failed to set connections: false: ERR wrong number of arguments for 'incr' command 324 | --- no_error_log 325 | [error] 326 | 327 | 328 | 329 | === TEST 7: lpush and lrange 330 | --- global_config eval: $::GlobalConfig 331 | --- server_config 332 | content_by_lua ' 333 | local redis = require "resty.redis" 334 | local red = redis:new() 335 | 336 | red:set_timeout(1000) -- 1 sec 337 | 338 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 339 | if not ok then 340 | ngx.say("failed to connect: ", err) 341 | return 342 | end 343 | 344 | local res, err = red:flushall() 345 | if not res then 346 | ngx.say("failed to flushall: ", err) 347 | return 348 | end 349 | ngx.say("flushall: ", res) 350 | 351 | local res, err = red:lpush("mylist", "world") 352 | if not res then 353 | ngx.say("failed to lpush: ", err) 354 | return 355 | end 356 | ngx.say("lpush result: ", res) 357 | 358 | res, err = red:lpush("mylist", "hello") 359 | if not res then 360 | ngx.say("failed to lpush: ", err) 361 | return 362 | end 363 | ngx.say("lpush result: ", res) 364 | 365 | res, err = red:lrange("mylist", 0, -1) 366 | if not res then 367 | ngx.say("failed to lrange: ", err) 368 | return 369 | end 370 | local cjson = require "cjson" 371 | ngx.say("lrange result: ", cjson.encode(res)) 372 | 373 | red:close() 374 | '; 375 | --- response_body 376 | flushall: OK 377 | lpush result: 1 378 | lpush result: 2 379 | lrange result: ["hello","world"] 380 | --- no_error_log 381 | [error] 382 | 383 | 384 | 385 | === TEST 8: blpop expires its own timeout 386 | --- global_config eval: $::GlobalConfig 387 | --- server_config 388 | content_by_lua ' 389 | local redis = require "resty.redis" 390 | local red = redis:new() 391 | 392 | red:set_timeout(2500) -- 2.5 sec 393 | 394 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 395 | if not ok then 396 | ngx.say("failed to connect: ", err) 397 | return 398 | end 399 | 400 | local res, err = red:flushall() 401 | if not res then 402 | ngx.say("failed to flushall: ", err) 403 | return 404 | end 405 | ngx.say("flushall: ", res) 406 | 407 | local res, err = red:blpop("key", 1) 408 | if err then 409 | ngx.say("failed to blpop: ", err) 410 | return 411 | end 412 | 413 | if res == ngx.null then 414 | ngx.say("no element popped.") 415 | return 416 | end 417 | 418 | local cjson = require "cjson" 419 | ngx.say("blpop result: ", cjson.encode(res)) 420 | 421 | red:close() 422 | '; 423 | --- response_body 424 | flushall: OK 425 | no element popped. 426 | --- no_error_log 427 | [error] 428 | --- timeout: 3 429 | 430 | 431 | 432 | === TEST 9: blpop expires cosocket timeout 433 | --- global_config eval: $::GlobalConfig 434 | --- server_config 435 | content_by_lua ' 436 | local redis = require "resty.redis" 437 | local red = redis:new() 438 | 439 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 440 | if not ok then 441 | ngx.say("failed to connect: ", err) 442 | return 443 | end 444 | 445 | local res, err = red:flushall() 446 | if not res then 447 | ngx.say("failed to flushall: ", err) 448 | return 449 | end 450 | ngx.say("flushall: ", res) 451 | 452 | red:set_timeout(200) -- 200 ms 453 | 454 | local res, err = red:blpop("key", 1) 455 | if err then 456 | ngx.say("failed to blpop: ", err) 457 | return 458 | end 459 | 460 | if not res then 461 | ngx.say("no element popped.") 462 | return 463 | end 464 | 465 | local cjson = require "cjson" 466 | ngx.say("blpop result: ", cjson.encode(res)) 467 | 468 | red:close() 469 | '; 470 | --- response_body 471 | flushall: OK 472 | failed to blpop: timeout 473 | --- error_log 474 | lua tcp socket read timed out 475 | 476 | 477 | 478 | === TEST 10: set keepalive and get reused times 479 | --- global_config eval: $::GlobalConfig 480 | --- server_config 481 | resolver $TEST_NGINX_RESOLVER; 482 | content_by_lua ' 483 | local redis = require "resty.redis" 484 | local red = redis:new() 485 | 486 | red:set_timeout(1000) -- 1 sec 487 | 488 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 489 | if not ok then 490 | ngx.say("failed to connect: ", err) 491 | return 492 | end 493 | 494 | local times = red:get_reused_times() 495 | ngx.say("reused times: ", times) 496 | 497 | local ok, err = red:set_keepalive() 498 | if not ok then 499 | ngx.say("failed to set keepalive: ", err) 500 | return 501 | end 502 | 503 | ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 504 | if not ok then 505 | ngx.say("failed to connect: ", err) 506 | return 507 | end 508 | 509 | times = red:get_reused_times() 510 | ngx.say("reused times: ", times) 511 | '; 512 | --- response_body 513 | reused times: 0 514 | reused times: 1 515 | --- no_error_log 516 | [error] 517 | 518 | 519 | 520 | === TEST 11: mget 521 | --- global_config eval: $::GlobalConfig 522 | --- server_config 523 | content_by_lua ' 524 | local redis = require "resty.redis" 525 | local red = redis:new() 526 | 527 | red:set_timeout(1000) -- 1 sec 528 | 529 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 530 | if not ok then 531 | ngx.say("failed to connect: ", err) 532 | return 533 | end 534 | 535 | ok, err = red:flushall() 536 | if not ok then 537 | ngx.say("failed to flush all: ", err) 538 | return 539 | end 540 | 541 | local res, err = red:set("dog", "an animal") 542 | if not res then 543 | ngx.say("failed to set dog: ", err) 544 | return 545 | end 546 | 547 | ngx.say("set dog: ", res) 548 | 549 | for i = 1, 2 do 550 | local res, err = red:mget("dog", "cat", "dog") 551 | if err then 552 | ngx.say("failed to get dog: ", err) 553 | return 554 | end 555 | 556 | if not res then 557 | ngx.say("dog not found.") 558 | return 559 | end 560 | 561 | local cjson = require "cjson" 562 | ngx.say("res: ", cjson.encode(res)) 563 | end 564 | 565 | red:close() 566 | '; 567 | --- response_body 568 | set dog: OK 569 | res: ["an animal",null,"an animal"] 570 | res: ["an animal",null,"an animal"] 571 | --- no_error_log 572 | [error] 573 | 574 | 575 | 576 | === TEST 12: hmget array_to_hash 577 | --- global_config eval: $::GlobalConfig 578 | --- server_config 579 | content_by_lua ' 580 | local redis = require "resty.redis" 581 | local red = redis:new() 582 | 583 | red:set_timeout(1000) -- 1 sec 584 | 585 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 586 | if not ok then 587 | ngx.say("failed to connect: ", err) 588 | return 589 | end 590 | 591 | ok, err = red:flushall() 592 | if not ok then 593 | ngx.say("failed to flush all: ", err) 594 | return 595 | end 596 | 597 | local res, err = red:hmset("animals", { dog = "bark", cat = "meow", cow = "moo" }) 598 | if not res then 599 | ngx.say("failed to set animals: ", err) 600 | return 601 | end 602 | 603 | ngx.say("hmset animals: ", res) 604 | 605 | local res, err = red:hmget("animals", "dog", "cat", "cow") 606 | if not res then 607 | ngx.say("failed to get animals: ", err) 608 | return 609 | end 610 | 611 | ngx.say("hmget animals: ", res) 612 | 613 | local res, err = red:hgetall("animals") 614 | if err then 615 | ngx.say("failed to get animals: ", err) 616 | return 617 | end 618 | 619 | if not res then 620 | ngx.say("animals not found.") 621 | return 622 | end 623 | 624 | local h = red:array_to_hash(res) 625 | 626 | ngx.say("dog: ", h.dog) 627 | ngx.say("cat: ", h.cat) 628 | ngx.say("cow: ", h.cow) 629 | 630 | red:close() 631 | '; 632 | --- response_body 633 | hmset animals: OK 634 | hmget animals: barkmeowmoo 635 | dog: bark 636 | cat: meow 637 | cow: moo 638 | --- no_error_log 639 | [error] 640 | 641 | 642 | 643 | === TEST 13: boolean args 644 | --- global_config eval: $::GlobalConfig 645 | --- server_config 646 | content_by_lua ' 647 | local redis = require "resty.redis" 648 | local red = redis:new() 649 | 650 | red:set_timeout(1000) -- 1 sec 651 | 652 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 653 | if not ok then 654 | ngx.say("failed to connect: ", err) 655 | return 656 | end 657 | 658 | ok, err = red:set("foo", true) 659 | if not ok then 660 | ngx.say("failed to set: ", err) 661 | return 662 | end 663 | 664 | local res, err = red:get("foo") 665 | if not res then 666 | ngx.say("failed to get: ", err) 667 | return 668 | end 669 | 670 | ngx.say("foo: ", res, ", type: ", type(res)) 671 | 672 | ok, err = red:set("foo", false) 673 | if not ok then 674 | ngx.say("failed to set: ", err) 675 | return 676 | end 677 | 678 | local res, err = red:get("foo") 679 | if not res then 680 | ngx.say("failed to get: ", err) 681 | return 682 | end 683 | 684 | ngx.say("foo: ", res, ", type: ", type(res)) 685 | 686 | ok, err = red:set("foo", nil) 687 | if not ok then 688 | ngx.say("failed to set: ", err) 689 | end 690 | 691 | local res, err = red:get("foo") 692 | if not res then 693 | ngx.say("failed to get: ", err) 694 | return 695 | end 696 | 697 | ngx.say("foo: ", res, ", type: ", type(res)) 698 | 699 | local ok, err = red:set_keepalive(10, 10) 700 | if not ok then 701 | ngx.say("failed to set_keepalive: ", err) 702 | end 703 | '; 704 | --- response_body 705 | foo: true, type: string 706 | foo: false, type: string 707 | failed to set: ERR wrong number of arguments for 'set' command 708 | foo: false, type: string 709 | --- no_error_log 710 | [error] 711 | 712 | 713 | 714 | === TEST 14: set and get (key with underscores) 715 | --- global_config eval: $::GlobalConfig 716 | --- server_config 717 | content_by_lua ' 718 | local redis = require "resty.redis" 719 | local red = redis:new() 720 | 721 | red:set_timeout(1000) -- 1 sec 722 | 723 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 724 | if not ok then 725 | ngx.say("failed to connect: ", err) 726 | return 727 | end 728 | 729 | local res, err = red:set("a_dog", "an animal") 730 | if not res then 731 | ngx.say("failed to set a_dog: ", err) 732 | return 733 | end 734 | 735 | ngx.say("set a_dog: ", res) 736 | 737 | for i = 1, 2 do 738 | local res, err = red:get("a_dog") 739 | if err then 740 | ngx.say("failed to get a_dog: ", err) 741 | return 742 | end 743 | 744 | if not res then 745 | ngx.say("a_dog not found.") 746 | return 747 | end 748 | 749 | ngx.say("a_dog: ", res) 750 | end 751 | 752 | red:close() 753 | '; 754 | --- response_body 755 | set a_dog: OK 756 | a_dog: an animal 757 | a_dog: an animal 758 | --- no_error_log 759 | [error] 760 | 761 | 762 | 763 | === TEST 15: connection refused 764 | --- global_config eval: $::GlobalConfig 765 | --- server_config 766 | content_by_lua ' 767 | local redis = require "resty.redis" 768 | local red = redis:new() 769 | 770 | red:set_timeout(10000) -- 10 sec 771 | 772 | local ok, err = red:connect("127.0.0.1", 81) 773 | if not ok then 774 | ngx.say("failed to connect: ", err) 775 | return 776 | end 777 | 778 | ngx.say("connected") 779 | 780 | red:close() 781 | '; 782 | --- response_body 783 | failed to connect: connection refused 784 | --- timeout: 3 785 | --- no_error_log 786 | [alert] 787 | 788 | 789 | 790 | === TEST 16: set_timeouts() connect timeout 791 | --- global_config eval: $::GlobalConfig 792 | --- server_config 793 | content_by_lua_block { 794 | local redis = require "resty.redis" 795 | local red = redis:new() 796 | 797 | red:set_timeouts(100, 1000, 1000) -- 0.1 sec 798 | 799 | local ok, err = red:connect("127.0.0.2", 12345) 800 | if not ok then 801 | ngx.say("failed to connect: ", err) 802 | end 803 | } 804 | --- response_body 805 | failed to connect: timeout 806 | --- error_log 807 | lua tcp socket connect timed out 808 | 809 | 810 | 811 | === TEST 17: set_timeouts() send timeout 812 | --- global_config eval: $::GlobalConfig 813 | --- server_config 814 | content_by_lua_block { 815 | local redis = require "resty.redis" 816 | local red = redis:new() 817 | 818 | red:set_timeouts(1000, 100, 1000) -- 0.1 sec 819 | 820 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 821 | if not ok then 822 | ngx.say("failed to connect: ", err) 823 | return 824 | end 825 | 826 | local res, err = red:flushall() 827 | if not res then 828 | ngx.say("failed to flushall: ", err) 829 | return 830 | end 831 | 832 | ngx.say("flushall: ", res) 833 | 834 | local res, err = red:blpop("key", 1) 835 | if err then 836 | ngx.say("failed to blpop: ", err) 837 | end 838 | 839 | red:close() 840 | } 841 | --- response_body 842 | flushall: OK 843 | failed to blpop: timeout 844 | --- error_log 845 | lua tcp socket read timed out 846 | 847 | 848 | 849 | === TEST 18: set_timeouts() read timeout 850 | --- global_config eval: $::GlobalConfig 851 | --- server_config 852 | content_by_lua_block { 853 | local redis = require "resty.redis" 854 | local red = redis:new() 855 | 856 | red:set_timeouts(1000, 1000, 100) -- 0.1 sec 857 | 858 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 859 | if not ok then 860 | ngx.say("failed to connect: ", err) 861 | return 862 | end 863 | 864 | local res, err = red:flushall() 865 | if not res then 866 | ngx.say("failed to flushall: ", err) 867 | return 868 | end 869 | 870 | ngx.say("flushall: ", res) 871 | 872 | local res, err = red:blpop("key", 1) 873 | if err then 874 | ngx.say("failed to blpop: ", err) 875 | end 876 | 877 | red:close() 878 | } 879 | --- response_body 880 | flushall: OK 881 | failed to blpop: timeout 882 | --- error_log 883 | lua tcp socket read timed out 884 | 885 | 886 | 887 | === TEST 19: connect() bad host argument (boolean) 888 | --- global_config eval: $::GlobalConfig 889 | --- server_config 890 | content_by_lua_block { 891 | local redis = require "resty.redis" 892 | local red = redis:new() 893 | 894 | red:connect(true) 895 | } 896 | --- internal_server_error 897 | --- error_log 898 | bad argument #1 host: string expected, got boolean 899 | --- no_error_log 900 | [crit] 901 | 902 | 903 | 904 | === TEST 20: connect() bad host argument (nil) 905 | --- global_config eval: $::GlobalConfig 906 | --- server_config 907 | content_by_lua_block { 908 | local redis = require "resty.redis" 909 | local red = redis:new() 910 | 911 | red:connect(nil) 912 | } 913 | --- internal_server_error 914 | --- error_log 915 | bad argument #1 host: string expected, got nil 916 | --- no_error_log 917 | [crit] 918 | 919 | 920 | 921 | === TEST 21: connect() bad port argument (nil) 922 | --- global_config eval: $::GlobalConfig 923 | --- server_config 924 | content_by_lua_block { 925 | local redis = require "resty.redis" 926 | local red = redis:new() 927 | 928 | red:connect("127.0.0.1", nil) 929 | } 930 | --- internal_server_error 931 | --- error_log 932 | bad argument #2 port: number expected, got nil 933 | --- no_error_log 934 | [crit] 935 | 936 | 937 | 938 | === TEST 22: connect() bad port argument (boolean) 939 | --- global_config eval: $::GlobalConfig 940 | --- server_config 941 | content_by_lua_block { 942 | local redis = require "resty.redis" 943 | local red = redis:new() 944 | 945 | red:connect("127.0.0.1", true) 946 | } 947 | --- internal_server_error 948 | --- error_log 949 | bad argument #2 port: number expected, got boolean 950 | --- no_error_log 951 | [crit] 952 | 953 | 954 | 955 | === TEST 23: connect() bad port argument (string) 956 | --- global_config eval: $::GlobalConfig 957 | --- server_config 958 | content_by_lua_block { 959 | local redis = require "resty.redis" 960 | local red = redis:new() 961 | 962 | red:connect("127.0.0.1", "foo") 963 | } 964 | --- internal_server_error 965 | --- error_log 966 | bad argument #2 port: number expected, got string 967 | --- no_error_log 968 | [crit] 969 | 970 | 971 | 972 | === TEST 24: connect() accepts port argument as string 973 | --- global_config eval: $::GlobalConfig 974 | --- server_config 975 | content_by_lua_block { 976 | local redis = require "resty.redis" 977 | local red = redis:new() 978 | 979 | red:set_timeout(1000) -- 1 sec 980 | 981 | local ok, err = red:connect("127.0.0.1", tostring($TEST_NGINX_REDIS_PORT)) 982 | if not ok then 983 | ngx.say("failed to connect: ", err) 984 | return 985 | end 986 | 987 | ngx.say("ok") 988 | } 989 | --- response_body 990 | ok 991 | --- no_error_log 992 | [error] 993 | 994 | 995 | 996 | === TEST 25: connect() bad opts argument 997 | --- global_config eval: $::GlobalConfig 998 | --- server_config 999 | content_by_lua_block { 1000 | local redis = require "resty.redis" 1001 | local red = redis:new() 1002 | 1003 | red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT, true) 1004 | } 1005 | --- internal_server_error 1006 | --- error_log 1007 | bad argument #3 opts: nil or table expected, got boolean 1008 | --- no_error_log 1009 | [crit] 1010 | 1011 | 1012 | 1013 | === TEST 26: connect() bad opts argument for unix sockets 1014 | --- global_config eval: $::GlobalConfig 1015 | --- server_config 1016 | content_by_lua_block { 1017 | local redis = require "resty.redis" 1018 | local red = redis:new() 1019 | 1020 | red:connect("unix:", true) 1021 | } 1022 | --- internal_server_error 1023 | --- error_log 1024 | bad argument #2 opts: nil or table expected, got boolean 1025 | --- no_error_log 1026 | [crit] 1027 | 1028 | 1029 | 1030 | === TEST 27: connect() unix socket arguments when 'host' starts with 'unix:' 1031 | --- global_config eval: $::GlobalConfig 1032 | --- server_config 1033 | content_by_lua_block { 1034 | local redis = require "resty.redis" 1035 | local red = redis:new() 1036 | 1037 | local pok, perr = pcall(red.connect, red, "unix:", true) 1038 | if not pok then 1039 | ngx.say(perr) 1040 | end 1041 | 1042 | local pok, perr = pcall(red.connect, red, "_unix:", true) 1043 | if not pok then 1044 | ngx.say(perr) 1045 | end 1046 | } 1047 | --- response_body 1048 | bad argument #2 opts: nil or table expected, got boolean 1049 | bad argument #2 port: number expected, got boolean 1050 | --- no_error_log 1051 | [error] 1052 | -------------------------------------------------------------------------------- /t/ssl.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | use Test::Nginx::Socket::Lua; 3 | use Cwd qw(cwd); 4 | 5 | repeat_each(2); 6 | 7 | plan tests => repeat_each() * (3 * blocks()); 8 | 9 | $ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); 10 | $ENV{TEST_NGINX_REDIS_PORT} ||= 6379; 11 | $ENV{TEST_NGINX_STREAM_REDIS_PORT} ||= 12345; 12 | 13 | my $MainConfig = qq{ 14 | stream { 15 | server { 16 | listen unix:$ENV{TEST_NGINX_HTML_DIR}/nginx.sock; 17 | listen unix:$ENV{TEST_NGINX_HTML_DIR}/nginx-ssl.sock ssl; 18 | listen 127.0.0.1:$ENV{TEST_NGINX_STREAM_REDIS_PORT} ssl; 19 | 20 | ssl_certificate ../../cert/test.crt; 21 | ssl_certificate_key ../../cert/test.key; 22 | 23 | proxy_pass 127.0.0.1:$ENV{TEST_NGINX_REDIS_PORT}; 24 | } 25 | } 26 | }; 27 | 28 | my $pwd = cwd(); 29 | my $HttpConfig = qq{ 30 | lua_package_path "$pwd/lib/?.lua;;"; 31 | }; 32 | 33 | add_block_preprocessor(sub { 34 | my $block = shift; 35 | 36 | if (!defined $block->main_config) { 37 | $block->set_value("main_config", $MainConfig); 38 | } 39 | 40 | if (!defined $block->http_config) { 41 | $block->set_value("http_config", $HttpConfig); 42 | } 43 | 44 | if (!defined $block->request) { 45 | $block->set_value("request", "GET /t"); 46 | } 47 | 48 | if (!defined $block->no_error_log) { 49 | $block->set_value("no_error_log", "[error]"); 50 | } 51 | }); 52 | 53 | no_long_string(); 54 | #no_diff(); 55 | 56 | run_tests(); 57 | 58 | __DATA__ 59 | 60 | === TEST 1: ssl connection on non ssl server 61 | --- config 62 | location /t { 63 | content_by_lua_block { 64 | local redis = require "resty.redis" 65 | local red = redis:new() 66 | 67 | red:set_timeout(100) 68 | 69 | local ok, err = red:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock", { 70 | ssl = true 71 | }) 72 | if not ok then 73 | ngx.say("failed to connect: ", err) 74 | return 75 | end 76 | } 77 | } 78 | --- response_body 79 | failed to connect: failed to do ssl handshake: timeout 80 | 81 | 82 | 83 | === TEST 2: set and get on ssl connection 84 | --- config 85 | location /t { 86 | content_by_lua_block { 87 | local redis = require "resty.redis" 88 | local red = redis:new() 89 | 90 | red:set_timeout(1000) 91 | 92 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_STREAM_REDIS_PORT, { 93 | ssl = true 94 | }) 95 | if not ok then 96 | ngx.say("failed to connect: ", err) 97 | return 98 | end 99 | 100 | ok, err = red:select(1) 101 | if not ok then 102 | ngx.say("failed to select: ", err) 103 | return 104 | end 105 | 106 | local res, err = red:set("dog", "an animal") 107 | if not res then 108 | ngx.say("failed to set dog: ", err) 109 | return 110 | end 111 | 112 | ngx.say("set dog: ", res) 113 | 114 | for i = 1, 2 do 115 | local res, err = red:get("dog") 116 | if err then 117 | ngx.say("failed to get dog: ", err) 118 | return 119 | end 120 | 121 | if not res then 122 | ngx.say("dog not found.") 123 | return 124 | end 125 | 126 | ngx.say("dog: ", res) 127 | end 128 | 129 | red:close() 130 | } 131 | } 132 | --- response_body 133 | set dog: OK 134 | dog: an animal 135 | dog: an animal 136 | 137 | 138 | 139 | === TEST 3: set and get on ssl connection via unix socket 140 | --- config 141 | location /t { 142 | content_by_lua_block { 143 | local redis = require "resty.redis" 144 | local red = redis:new() 145 | 146 | red:set_timeout(1000) 147 | 148 | local ok, err = red:connect("unix:$TEST_NGINX_HTML_DIR/nginx-ssl.sock", { 149 | ssl = true 150 | }) 151 | if not ok then 152 | ngx.say("failed to connect: ", err) 153 | return 154 | end 155 | 156 | ok, err = red:select(1) 157 | if not ok then 158 | ngx.say("failed to select: ", err) 159 | return 160 | end 161 | 162 | local res, err = red:set("dog", "an animal") 163 | if not res then 164 | ngx.say("failed to set dog: ", err) 165 | return 166 | end 167 | 168 | ngx.say("set dog: ", res) 169 | 170 | for i = 1, 2 do 171 | local res, err = red:get("dog") 172 | if err then 173 | ngx.say("failed to get dog: ", err) 174 | return 175 | end 176 | 177 | if not res then 178 | ngx.say("dog not found.") 179 | return 180 | end 181 | 182 | ngx.say("dog: ", res) 183 | end 184 | 185 | red:close() 186 | } 187 | } 188 | --- response_body 189 | set dog: OK 190 | dog: an animal 191 | dog: an animal 192 | 193 | 194 | 195 | === TEST 4: ssl connection with ssl_verify (without CA) 196 | --- config 197 | lua_socket_log_errors off; 198 | 199 | location /t { 200 | content_by_lua_block { 201 | local redis = require "resty.redis" 202 | local red = redis:new() 203 | 204 | red:set_timeout(100) 205 | 206 | local ok, err = red:connect("unix:$TEST_NGINX_HTML_DIR/nginx-ssl.sock", { 207 | ssl = true, 208 | ssl_verify = true 209 | }) 210 | if not ok then 211 | ngx.say("failed to connect: ", err) 212 | return 213 | end 214 | } 215 | } 216 | --- response_body 217 | failed to connect: failed to do ssl handshake: 18: self signed certificate 218 | 219 | 220 | 221 | === TEST 5: ssl connection with ssl_verify (with CA) 222 | --- config 223 | lua_socket_log_errors off; 224 | lua_ssl_trusted_certificate ../../cert/test.crt; 225 | 226 | location /t { 227 | content_by_lua_block { 228 | local redis = require "resty.redis" 229 | local red = redis:new() 230 | 231 | red:set_timeout(100) 232 | 233 | local ok, err = red:connect("unix:$TEST_NGINX_HTML_DIR/nginx-ssl.sock", { 234 | ssl = true, 235 | ssl_verify = true 236 | }) 237 | if not ok then 238 | ngx.say("failed to connect: ", err) 239 | return 240 | end 241 | 242 | ngx.say("ok") 243 | } 244 | } 245 | --- response_body 246 | ok 247 | 248 | 249 | 250 | === TEST 6: non-ssl connection to unix socket (issue #187) 251 | --- config 252 | location /t { 253 | content_by_lua_block { 254 | local redis = require "resty.redis" 255 | local red = redis:new() 256 | 257 | red:set_timeout(100) 258 | 259 | local ok, err = red:connect("unix:$TEST_NGINX_HTML_DIR/nginx-ssl.sock") 260 | if not ok then 261 | ngx.say("failed to connect: ", err) 262 | return 263 | end 264 | 265 | ngx.say("ok") 266 | } 267 | } 268 | --- response_body 269 | ok 270 | 271 | 272 | 273 | === TEST 7: non-ssl connection to unix socket with second argument nil (issue #187) 274 | --- config 275 | location /t { 276 | content_by_lua_block { 277 | local redis = require "resty.redis" 278 | local red = redis:new() 279 | 280 | red:set_timeout(100) 281 | 282 | local ok, err = red:connect("unix:$TEST_NGINX_HTML_DIR/nginx-ssl.sock",nil) 283 | if not ok then 284 | ngx.say("failed to connect: ", err) 285 | return 286 | end 287 | 288 | ngx.say("ok") 289 | } 290 | } 291 | --- response_body 292 | ok 293 | 294 | 295 | 296 | === TEST 8: ssl reuse 297 | --- config 298 | location /t { 299 | content_by_lua_block { 300 | local redis = require "resty.redis" 301 | local red = redis:new() 302 | red:set_timeout(1000) 303 | 304 | local function connect_set(red) 305 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_STREAM_REDIS_PORT, { 306 | ssl = true 307 | }) 308 | if not ok then 309 | ngx.say("failed to connect: ", err) 310 | return 311 | end 312 | ngx.say("sock reusetimes: ", red:get_reused_times()) 313 | 314 | ok, err = red:select(1) 315 | if not ok then 316 | ngx.say("failed to select: ", err) 317 | return 318 | end 319 | 320 | local res, err = red:set("dog", "an animal") 321 | if not res then 322 | ngx.say("failed to set dog: ", err) 323 | return 324 | end 325 | 326 | ngx.say("set dog: ", res) 327 | 328 | local ok, err = red:set_keepalive() 329 | if not ok then 330 | ngx.say("failed to set keepalive: ", err) 331 | return 332 | end 333 | end 334 | 335 | connect_set(red) 336 | connect_set(red) 337 | } 338 | } 339 | --- response_body eval 340 | qr/sock reusetimes: (0|2) 341 | set dog: OK 342 | sock reusetimes: (1|3) 343 | set dog: OK/ 344 | -------------------------------------------------------------------------------- /t/transaction.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use t::Test; 4 | 5 | repeat_each(2); 6 | 7 | plan tests => repeat_each() * (3 * blocks()); 8 | 9 | run_tests(); 10 | 11 | __DATA__ 12 | 13 | === TEST 1: sanity 14 | --- global_config eval: $::GlobalConfig 15 | --- server_config 16 | content_by_lua ' 17 | local cjson = require "cjson" 18 | local redis = require "resty.redis" 19 | local red = redis:new() 20 | 21 | red:set_timeout(1000) -- 1 sec 22 | 23 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 24 | if not ok then 25 | ngx.say("failed to connect: ", err) 26 | return 27 | end 28 | 29 | local redis_key = "foo" 30 | 31 | local ok, err = red:multi() 32 | if not ok then 33 | ngx.say("failed to run multi: ", err) 34 | return 35 | end 36 | 37 | ngx.say("multi ans: ", cjson.encode(ok)) 38 | 39 | local ans, err = red:sort("log", "by", redis_key .. ":*->timestamp") 40 | if not ans then 41 | ngx.say("failed to run sort: ", err) 42 | return 43 | end 44 | 45 | ngx.say("sort ans: ", cjson.encode(ans)) 46 | 47 | ans, err = red:exec() 48 | 49 | ngx.say("exec ans: ", cjson.encode(ans)) 50 | 51 | local ok, err = red:set_keepalive(0, 1024) 52 | if not ok then 53 | ngx.say("failed to put the current redis connection into pool: ", err) 54 | return 55 | end 56 | '; 57 | --- response_body 58 | multi ans: "OK" 59 | sort ans: "QUEUED" 60 | exec ans: [{}] 61 | --- no_error_log 62 | [error] 63 | 64 | 65 | 66 | === TEST 2: redis cmd reference sample: redis does not halt on errors 67 | --- global_config eval: $::GlobalConfig 68 | --- server_config 69 | content_by_lua ' 70 | local cjson = require "cjson" 71 | local redis = require "resty.redis" 72 | local red = redis:new() 73 | 74 | red:set_timeout(1000) -- 1 sec 75 | 76 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 77 | if not ok then 78 | ngx.say("failed to connect: ", err) 79 | return 80 | end 81 | 82 | local ok, err = red:multi() 83 | if not ok then 84 | ngx.say("failed to run multi: ", err) 85 | return 86 | end 87 | 88 | ngx.say("multi ans: ", cjson.encode(ok)) 89 | 90 | local ans, err = red:set("a", "abc") 91 | if not ans then 92 | ngx.say("failed to run sort: ", err) 93 | return 94 | end 95 | 96 | ngx.say("set ans: ", cjson.encode(ans)) 97 | 98 | local ans, err = red:lpop("a") 99 | if not ans then 100 | ngx.say("failed to run sort: ", err) 101 | return 102 | end 103 | 104 | ngx.say("set ans: ", cjson.encode(ans)) 105 | 106 | ans, err = red:exec() 107 | 108 | ngx.say("exec ans: ", cjson.encode(ans)) 109 | 110 | red:close() 111 | '; 112 | --- response_body_like chop 113 | ^multi ans: "OK" 114 | set ans: "QUEUED" 115 | set ans: "QUEUED" 116 | exec ans: \["OK",\[false,"(?:ERR|WRONGTYPE) Operation against a key holding the wrong kind of value"\]\] 117 | $ 118 | --- no_error_log 119 | [error] 120 | -------------------------------------------------------------------------------- /t/user-cmds.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use t::Test; 4 | 5 | repeat_each(2); 6 | 7 | plan tests => repeat_each() * (3 * blocks()); 8 | 9 | run_tests(); 10 | 11 | __DATA__ 12 | 13 | === TEST 1: single channel 14 | --- global_config eval: $::GlobalConfig 15 | --- server_config 16 | content_by_lua ' 17 | local cjson = require "cjson" 18 | local redis = require "resty.redis" 19 | 20 | redis.add_commands("foo", "bar") 21 | 22 | local red = redis:new() 23 | 24 | red:set_timeout(1000) -- 1 sec 25 | 26 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 27 | if not ok then 28 | ngx.say("failed to connect: ", err) 29 | return 30 | end 31 | 32 | local res, err = red:foo("a") 33 | if not res then 34 | ngx.say("failed to foo: ", err) 35 | end 36 | 37 | res, err = red:bar() 38 | if not res then 39 | ngx.say("failed to bar: ", err) 40 | end 41 | '; 42 | --- response_body eval 43 | qr/\Afailed to foo: ERR unknown command [`']foo[`'](?:, with args beginning with: [`']a[`'],?\s*)? 44 | failed to bar: ERR unknown command [`']bar[`'](?:, with args beginning with:\s*)? 45 | \z/ 46 | --- no_error_log 47 | [error] 48 | -------------------------------------------------------------------------------- /t/version.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use t::Test; 4 | 5 | repeat_each(2); 6 | 7 | plan tests => repeat_each() * (3 * blocks()); 8 | 9 | run_tests(); 10 | 11 | __DATA__ 12 | 13 | === TEST 1: basic 14 | --- global_config eval: $::GlobalConfig 15 | --- server_config 16 | content_by_lua ' 17 | local redis = require "resty.redis" 18 | ngx.say(redis._VERSION) 19 | '; 20 | --- response_body_like chop 21 | ^\d+\.\d+$ 22 | --- no_error_log 23 | [error] 24 | -------------------------------------------------------------------------------- /valgrind.suppress: -------------------------------------------------------------------------------- 1 | { 2 | 3 | Memcheck:Param 4 | write(buf) 5 | fun:__write_nocancel 6 | fun:ngx_log_error_core 7 | fun:ngx_resolver_read_response 8 | } 9 | { 10 | 11 | Memcheck:Cond 12 | fun:ngx_sprintf_num 13 | fun:ngx_vslprintf 14 | fun:ngx_log_error_core 15 | fun:ngx_resolver_read_response 16 | fun:ngx_epoll_process_events 17 | fun:ngx_process_events_and_timers 18 | fun:ngx_single_process_cycle 19 | fun:main 20 | } 21 | { 22 | 23 | Memcheck:Addr1 24 | fun:ngx_vslprintf 25 | fun:ngx_snprintf 26 | fun:ngx_sock_ntop 27 | fun:ngx_event_accept 28 | } 29 | { 30 | 31 | Memcheck:Param 32 | write(buf) 33 | fun:__write_nocancel 34 | fun:ngx_log_error_core 35 | fun:ngx_resolver_read_response 36 | fun:ngx_event_process_posted 37 | fun:ngx_process_events_and_timers 38 | fun:ngx_single_process_cycle 39 | fun:main 40 | } 41 | { 42 | 43 | Memcheck:Cond 44 | fun:ngx_sprintf_num 45 | fun:ngx_vslprintf 46 | fun:ngx_log_error_core 47 | fun:ngx_resolver_read_response 48 | fun:ngx_event_process_posted 49 | fun:ngx_process_events_and_timers 50 | fun:ngx_single_process_cycle 51 | fun:main 52 | } 53 | { 54 | 55 | Memcheck:Leak 56 | fun:malloc 57 | fun:ngx_alloc 58 | obj:* 59 | } 60 | { 61 | 62 | exp-sgcheck:SorG 63 | fun:ngx_http_lua_ndk_set_var_get 64 | } 65 | { 66 | 67 | exp-sgcheck:SorG 68 | fun:ngx_http_variables_init_vars 69 | fun:ngx_http_block 70 | } 71 | { 72 | 73 | exp-sgcheck:SorG 74 | fun:ngx_conf_parse 75 | } 76 | { 77 | 78 | exp-sgcheck:SorG 79 | fun:ngx_vslprintf 80 | fun:ngx_log_error_core 81 | } 82 | { 83 | 84 | Memcheck:Leak 85 | fun:malloc 86 | fun:ngx_alloc 87 | fun:ngx_calloc 88 | fun:ngx_event_process_init 89 | } 90 | { 91 | 92 | Memcheck:Param 93 | epoll_ctl(event) 94 | fun:epoll_ctl 95 | } 96 | { 97 | 98 | Memcheck:Leak 99 | fun:malloc 100 | fun:ngx_alloc 101 | fun:ngx_event_process_init 102 | } 103 | { 104 | 105 | Memcheck:Cond 106 | fun:ngx_conf_flush_files 107 | fun:ngx_single_process_cycle 108 | } 109 | { 110 | 111 | Memcheck:Cond 112 | fun:memcpy 113 | fun:ngx_vslprintf 114 | fun:ngx_log_error_core 115 | fun:ngx_http_charset_header_filter 116 | } 117 | { 118 | 119 | Memcheck:Param 120 | socketcall.setsockopt(optval) 121 | fun:setsockopt 122 | fun:drizzle_state_connect 123 | } 124 | { 125 | 126 | Memcheck:Leak 127 | fun:malloc 128 | fun:ngx_alloc 129 | fun:ngx_pool_cleanup_add 130 | } 131 | { 132 | 133 | Memcheck:Cond 134 | fun:ngx_conf_flush_files 135 | fun:ngx_single_process_cycle 136 | fun:main 137 | } 138 | { 139 | 140 | Memcheck:Leak 141 | fun:malloc 142 | fun:ngx_alloc 143 | fun:ngx_palloc_large 144 | fun:ngx_palloc 145 | fun:ngx_array_push 146 | fun:ngx_http_get_variable_index 147 | fun:ngx_http_memc_add_variable 148 | fun:ngx_http_memc_init 149 | fun:ngx_http_block 150 | fun:ngx_conf_parse 151 | fun:ngx_init_cycle 152 | fun:main 153 | } 154 | { 155 | 156 | Memcheck:Leak 157 | fun:malloc 158 | fun:ngx_alloc 159 | fun:ngx_event_process_init 160 | fun:ngx_single_process_cycle 161 | fun:main 162 | } 163 | { 164 | 165 | Memcheck:Leak 166 | fun:malloc 167 | fun:ngx_alloc 168 | fun:ngx_crc32_table_init 169 | fun:main 170 | } 171 | { 172 | 173 | Memcheck:Leak 174 | fun:malloc 175 | fun:ngx_alloc 176 | fun:ngx_event_process_init 177 | fun:ngx_worker_process_init 178 | fun:ngx_worker_process_cycle 179 | fun:ngx_spawn_process 180 | fun:ngx_start_worker_processes 181 | fun:ngx_master_process_cycle 182 | fun:main 183 | } 184 | { 185 | 186 | Memcheck:Leak 187 | fun:malloc 188 | fun:ngx_alloc 189 | fun:ngx_palloc_large 190 | fun:ngx_palloc 191 | fun:ngx_pcalloc 192 | fun:ngx_hash_init 193 | fun:ngx_http_variables_init_vars 194 | fun:ngx_http_block 195 | fun:ngx_conf_parse 196 | fun:ngx_init_cycle 197 | fun:main 198 | } 199 | { 200 | 201 | Memcheck:Leak 202 | fun:malloc 203 | fun:ngx_alloc 204 | fun:ngx_palloc_large 205 | fun:ngx_palloc 206 | fun:ngx_pcalloc 207 | fun:ngx_http_upstream_drizzle_create_srv_conf 208 | fun:ngx_http_upstream 209 | fun:ngx_conf_parse 210 | fun:ngx_http_block 211 | fun:ngx_conf_parse 212 | fun:ngx_init_cycle 213 | fun:main 214 | } 215 | { 216 | 217 | Memcheck:Leak 218 | fun:malloc 219 | fun:ngx_alloc 220 | fun:ngx_palloc_large 221 | fun:ngx_palloc 222 | fun:ngx_pcalloc 223 | fun:ngx_hash_keys_array_init 224 | fun:ngx_http_variables_add_core_vars 225 | fun:ngx_http_core_preconfiguration 226 | fun:ngx_http_block 227 | fun:ngx_conf_parse 228 | fun:ngx_init_cycle 229 | fun:main 230 | } 231 | { 232 | 233 | Memcheck:Leak 234 | fun:malloc 235 | fun:ngx_alloc 236 | fun:ngx_palloc_large 237 | fun:ngx_palloc 238 | fun:ngx_array_push 239 | fun:ngx_hash_add_key 240 | fun:ngx_http_add_variable 241 | fun:ngx_http_echo_add_variables 242 | fun:ngx_http_echo_handler_init 243 | fun:ngx_http_block 244 | fun:ngx_conf_parse 245 | fun:ngx_init_cycle 246 | } 247 | { 248 | 249 | Memcheck:Leak 250 | fun:malloc 251 | fun:ngx_alloc 252 | fun:ngx_palloc_large 253 | fun:ngx_palloc 254 | fun:ngx_pcalloc 255 | fun:ngx_http_upstream_drizzle_create_srv_conf 256 | fun:ngx_http_core_server 257 | fun:ngx_conf_parse 258 | fun:ngx_http_block 259 | fun:ngx_conf_parse 260 | fun:ngx_init_cycle 261 | fun:main 262 | } 263 | { 264 | 265 | Memcheck:Leak 266 | fun:malloc 267 | fun:ngx_alloc 268 | fun:ngx_palloc_large 269 | fun:ngx_palloc 270 | fun:ngx_pcalloc 271 | fun:ngx_http_upstream_drizzle_create_srv_conf 272 | fun:ngx_http_block 273 | fun:ngx_conf_parse 274 | fun:ngx_init_cycle 275 | fun:main 276 | } 277 | { 278 | 279 | Memcheck:Leak 280 | fun:malloc 281 | fun:ngx_alloc 282 | fun:ngx_palloc_large 283 | fun:ngx_palloc 284 | fun:ngx_array_push 285 | fun:ngx_hash_add_key 286 | fun:ngx_http_variables_add_core_vars 287 | fun:ngx_http_core_preconfiguration 288 | fun:ngx_http_block 289 | fun:ngx_conf_parse 290 | fun:ngx_init_cycle 291 | fun:main 292 | } 293 | { 294 | 295 | Memcheck:Leak 296 | fun:malloc 297 | fun:ngx_alloc 298 | fun:ngx_palloc_large 299 | fun:ngx_palloc 300 | fun:ngx_pcalloc 301 | fun:ngx_init_cycle 302 | fun:main 303 | } 304 | { 305 | 306 | Memcheck:Leak 307 | fun:malloc 308 | fun:ngx_alloc 309 | fun:ngx_palloc_large 310 | fun:ngx_palloc 311 | fun:ngx_hash_init 312 | fun:ngx_http_upstream_init_main_conf 313 | fun:ngx_http_block 314 | fun:ngx_conf_parse 315 | fun:ngx_init_cycle 316 | fun:main 317 | } 318 | { 319 | 320 | Memcheck:Leak 321 | fun:malloc 322 | fun:ngx_alloc 323 | fun:ngx_palloc_large 324 | fun:ngx_palloc 325 | fun:ngx_pcalloc 326 | fun:ngx_http_drizzle_keepalive_init 327 | fun:ngx_http_upstream_drizzle_init 328 | fun:ngx_http_upstream_init_main_conf 329 | fun:ngx_http_block 330 | fun:ngx_conf_parse 331 | fun:ngx_init_cycle 332 | fun:main 333 | } 334 | { 335 | 336 | Memcheck:Leak 337 | fun:malloc 338 | fun:ngx_alloc 339 | fun:ngx_palloc_large 340 | fun:ngx_palloc 341 | fun:ngx_hash_init 342 | fun:ngx_http_variables_init_vars 343 | fun:ngx_http_block 344 | fun:ngx_conf_parse 345 | fun:ngx_init_cycle 346 | fun:main 347 | } 348 | { 349 | 350 | Memcheck:Cond 351 | fun:index 352 | fun:expand_dynamic_string_token 353 | fun:_dl_map_object 354 | fun:map_doit 355 | fun:_dl_catch_error 356 | fun:do_preload 357 | fun:dl_main 358 | fun:_dl_sysdep_start 359 | fun:_dl_start 360 | } 361 | { 362 | 363 | Memcheck:Leak 364 | match-leak-kinds: definite 365 | fun:malloc 366 | fun:ngx_alloc 367 | fun:ngx_set_environment 368 | fun:ngx_single_process_cycle 369 | } 370 | { 371 | 372 | Memcheck:Leak 373 | match-leak-kinds: definite 374 | fun:malloc 375 | fun:ngx_alloc 376 | fun:ngx_set_environment 377 | fun:ngx_worker_process_init 378 | fun:ngx_worker_process_cycle 379 | } 380 | --------------------------------------------------------------------------------