Codebase list lua-nginx-redis-connector / 3fe627f
Update upstream source from tag 'upstream/0.06' Update to upstream version '0.06' with Debian dir 9be0ec0fd3e95d0dac7b67defa3d3955c862a277 ChangZhuo Chen (陳昌倬) 6 years ago
8 changed file(s) with 429 addition(s) and 109 deletion(s). Raw diff Collapse all Expand all
00 t/servroot/
11 t/error.log
22 luacov.*
3 *.src.rock
4 lua-resty-redis-connector*.tar.gz
3838 connect_timeout = 50,
3939 read_timeout = 5000,
4040 keepalive_timeout = 30000,
41
41
4242 host = "127.0.0.1",
4343 port = 6379,
4444 db = 2,
5252 local ok, err = rc:set_keepalive(redis)
5353 ```
5454
55 `connect` can be used to override defaults given in `new`
55 [connect](#connect) can be used to override some defaults given in [new](#new), which are pertinent to this connection only.
5656
5757
5858 ```lua
7070
7171 ## DSN format
7272
73 If the `params.url` field is present then it will be parsed, overriding values supplied in the parameters table.
73 If the `params.url` field is present then it will be parsed to set the other params. Any manually specified params will override values given in the DSN.
74
75 *Note: this is a behaviour change as of v0.06. Previously, the DSN values would take precedence.*
7476
7577 ### Direct Redis connections
7678
8688
8789 `sentinel://PASSWORD@MASTER_NAME:ROLE/DB`
8890
89 Again, `PASSWORD` and `DB` are optional. `ROLE` must be any of `m`, `s` or `a`, meaning:
90
91 * `m`: master
92 * `s`: slave
93 * `a`: any (first tries the master, but will failover to a slave if required)
91 Again, `PASSWORD` and `DB` are optional. `ROLE` must be either `m` or `s` for master / slave respectively.
9492
9593 A table of `sentinels` must also be supplied. e.g.
9694
103101 }
104102 ```
105103
104 ## Proxy Mode
105
106 Enable the `connection_is_proxied` parameter if connecting to Redis through a proxy service (e.g. Twemproxy).
107 These proxies generally only support a limited sub-set of Redis commands, those which do not require state and do not affect multiple keys.
108 Databases and transactions are also not supported.
109
110 Proxy mode will disable switching to a DB on connect.
111 Unsupported commands (defaults to those not supported by Twemproxy) will return `nil, err` immediately rather than being sent to the proxy, which can result in dropped connections.
112
113 `discard` will not be sent when adding connections to the keepalive pool
114
115
116 ## Disabled commands
117
118 If configured as a table of commands, the command methods will be replaced by a function which immediately returns `nil, err` without forwarding the command to the server
106119
107120 ## Default Parameters
108121
114127 connection_options = {}, -- pool, etc
115128 keepalive_timeout = 60000,
116129 keepalive_poolsize = 30,
117
130
118131 host = "127.0.0.1",
119132 port = "6379",
120133 path = "", -- unix socket path, e.g. /tmp/redis.sock
121134 password = "",
122135 db = 0,
123
136
124137 master_name = "mymaster",
125 role = "master", -- master | slave | any
138 role = "master", -- master | slave
126139 sentinels = {},
140
141 connection_is_proxied = false,
142
143 disabled_commands = {},
127144 }
128145 ```
129146
132149
133150 * [new](#new)
134151 * [connect](#connect)
152 * [set_keepalive](#set_keepalive)
135153 * [Utilities](#utilities)
136154 * [connect_via_sentinel](#connect_via_sentinel)
137155 * [try_hosts](#try_hosts)
142160
143161 ### new
144162
145 `syntax: rc = redis_connector.new()`
146
147 Creates the Redis Connector object. In case of failures, returns `nil` and a string describing the error.
163 `syntax: rc = redis_connector.new(params)`
164
165 Creates the Redis Connector object, overring default params with the ones given. In case of failures, returns `nil` and a string describing the error.
148166
149167
150168 ### connect
151169
152170 `syntax: redis, err = rc:connect(params)`
153171
154 Attempts to create a connection, according to the [params](#parameters) supplied. If a connection cannot be made, returns `nil` and a string describing the reason.
172 Attempts to create a connection, according to the [params](#parameters) supplied, falling back to defaults given in `new` or the predefined defaults. If a connection cannot be made, returns `nil` and a string describing the reason.
173
174 Note that `params` given here do not change the connector's own configuration, and are only used to alter this particular connection operation. As such, the following parameters have no meaning when given in `connect`.
175
176 * `keepalive_poolsize`
177 * `keepalive_timeout`
178 * `connection_is_proxied`
179 * `disabled_commands`
180
181
182 ### set_keepalive
183
184 `syntax: ok, err = rc:set_keepalive(redis)`
185
186 Attempts to place the given Redis connection on the keepalive pool, according to timeout and poolsize params given in `new` or the predefined defaults.
187
188 This allows an application to release resources without having to keep track of application wide keepalive settings.
189
190 Returns `1` or in the case of error, `nil` and a string describing the error.
155191
156192
157193 ## Utilities
9999 url = "", -- DSN url
100100
101101 master_name = "mymaster",
102 role = "master", -- master | slave | any
102 role = "master", -- master | slave
103103 sentinels = {},
104104
105 -- Redis proxies typically don't support full Redis capabilities
106 connection_is_proxied = false,
107
108 disabled_commands = {},
109
105110 }, fixed_field_metatable)
106111
107112
113 -- This is the set of commands unsupported by Twemproxy
114 local default_disabled_commands = {
115 "migrate", "move", "object", "randomkey", "rename", "renamenx", "scan",
116 "bitop", "msetnx", "blpop", "brpop", "brpoplpush", "psubscribe", "publish",
117 "punsubscribe", "subscribe", "unsubscribe", "discard", "exec", "multi",
118 "unwatch", "watch", "script", "auth", "echo", "select", "bgrewriteaof",
119 "bgsave", "client", "config", "dbsize", "debug", "flushall", "flushdb",
120 "info", "lastsave", "monitor", "save", "shutdown", "slaveof", "slowlog",
121 "sync", "time"
122 }
123
124
108125 local _M = {
109 _VERSION = '0.04',
126 _VERSION = '0.06',
110127 }
111128
112129 local mt = { __index = _M }
113
114
115 function _M.new(config)
116 local ok, config = pcall(tbl_copy_merge_defaults, config, DEFAULTS)
117 if not ok then
118 return nil, config -- err
119 else
120 return setmetatable({
121 config = setmetatable(config, fixed_field_metatable)
122 }, mt)
123 end
124 end
125130
126131
127132 local function parse_dsn(params)
134139 return nil, "could not parse DSN: " .. tostring(err)
135140 end
136141
142 -- TODO: Support a 'protocol' for proxied Redis?
137143 local fields
138144 if m[1] == "redis" then
139145 fields = { "password", "host", "port", "db" }
144150 -- password may not be present
145151 if #m < 5 then tbl_remove(fields, 1) end
146152
147 local roles = { m = "master", s = "slave", a = "any" }
153 local roles = { m = "master", s = "slave" }
154
155 local parsed_params = {}
148156
149157 for i,v in ipairs(fields) do
150 params[v] = m[i + 1]
158 parsed_params[v] = m[i + 1]
151159 if v == "role" then
152 params[v] = roles[params[v]]
153 end
154 end
155 end
156
157 return true, nil
160 parsed_params[v] = roles[parsed_params[v]]
161 end
162 end
163
164 return tbl_copy_merge_defaults(params, parsed_params)
165 end
158166 end
159167 _M.parse_dsn = parse_dsn
160168
161169
170 function _M.new(config)
171 -- Fill out gaps in config with any dsn params
172 if config and config.url then
173 local err
174 config, err = parse_dsn(config)
175 if not ok then ngx_log(ngx_ERR, err) end
176 end
177
178 local ok, config = pcall(tbl_copy_merge_defaults, config, DEFAULTS)
179 if not ok then
180 return nil, config -- err
181 else
182 -- In proxied Redis mode disable default commands
183 if config.connection_is_proxied == true and
184 not next(config.disabled_commands) then
185
186 config.disabled_commands = default_disabled_commands
187 end
188
189 return setmetatable({
190 config = setmetatable(config, fixed_field_metatable)
191 }, mt)
192 end
193 end
194
195
162196 function _M.connect(self, params)
163 local params = tbl_copy_merge_defaults(params, self.config)
164
165 if params.url then
166 local ok, err = parse_dsn(params)
197 if params and params.url then
198 local err
199 params, err = parse_dsn(params)
167200 if not ok then ngx_log(ngx_ERR, err) end
168201 end
202
203 params = tbl_copy_merge_defaults(params, self.config)
169204
170205 if #params.sentinels > 0 then
171206 return self:connect_via_sentinel(params)
196231 return nil, err, previous_errors
197232 end
198233
199 if role == "master" or role == "any" then
234 if role == "master" then
200235 local master, err = get_master(sentnl, master_name)
201236 if master then
202237 master.db = db
212247 end
213248 end
214249 end
215 end
216
217 -- We either wanted a slave, or are failing over to a slave "any"
218 local slaves, err = get_slaves(sentnl, master_name)
219 sentnl:set_keepalive()
220
221 if not slaves then
222 return nil, err
223 end
224
225 -- Put any slaves on 127.0.0.1 at the front
226 tbl_sort(slaves, sort_by_localhost)
227
228 if db or password then
229 for i,slave in ipairs(slaves) do
230 slave.db = db
231 slave.password = password
232 end
233 end
234
235 local slave, err, previous_errors = self:try_hosts(slaves)
236 if not slave then
237 return nil, err, previous_errors
238 else
239 return slave
250
251 else
252 -- We want a slave
253 local slaves, err = get_slaves(sentnl, master_name)
254 sentnl:set_keepalive()
255
256 if not slaves then
257 return nil, err
258 end
259
260 -- Put any slaves on 127.0.0.1 at the front
261 tbl_sort(slaves, sort_by_localhost)
262
263 if db or password then
264 for i,slave in ipairs(slaves) do
265 slave.db = db
266 slave.password = password
267 end
268 end
269
270 local slave, err, previous_errors = self:try_hosts(slaves)
271 if not slave then
272 return nil, err, previous_errors
273 else
274 return slave
275 end
240276 end
241277 end
242278
263299 local r = redis.new()
264300 local config = self.config
265301 r:set_timeout(config.connect_timeout)
302
303 -- Stub out methods for disabled commands
304 if next(config.disabled_commands) then
305 for _, cmd in ipairs(config.disabled_commands) do
306 r[cmd] = function(...)
307 return nil, ("Command "..cmd.." is disabled")
308 end
309 end
310 end
266311
267312 local ok, err
268313 local path = host.path
295340 end
296341 end
297342
298 if host.db ~= nil then
343 -- No support for DBs in proxied Redis.
344 if config.connection_is_proxied ~= true and host.db ~= nil then
299345 r:select(host.db)
300346 end
301347 return r, nil
306352 local function set_keepalive(self, redis)
307353 -- Restore connection to "NORMAL" before putting into keepalive pool,
308354 -- ignoring any errors.
309 redis:discard()
355 -- Proxied Redis does not support transactions.
356 if self.config.connection_is_proxied ~= true then
357 redis:discard()
358 end
310359
311360 local config = self.config
312361 return redis:set_keepalive(
99
1010
1111 local _M = {
12 _VERSION = '0.04'
12 _VERSION = '0.06'
1313 }
1414
1515
+0
-27
lua-resty-redis-connector-0.04-0.rockspec less more
0 package = "lua-resty-redis-connector"
1 version = "0.04-0"
2 source = {
3 url = "git://github.com/pintsized/lua-resty-redis-connector",
4 tag = "v0.04"
5 }
6 description = {
7 summary = "Connection utilities for lua-resty-redis.",
8 detailed = [[
9 Connection utilities for lua-resty-redis, making it easy and
10 reliable to connect to Redis hosts, either directly or via Redis
11 Sentinel.
12 ]],
13 homepage = "https://github.com/pintsized/lua-resty-redis-connector",
14 license = "2-clause BSD",
15 maintainer = "James Hurst <james@pintsized.co.uk>"
16 }
17 dependencies = {
18 "lua >= 5.1",
19 }
20 build = {
21 type = "builtin",
22 modules = {
23 ["resty.redis.connector"] = "lib/resty/redis/connector.lua",
24 ["resty.redis.sentinel"] = "lib/resty/redis/sentinel.lua"
25 }
26 }
0 package = "lua-resty-redis-connector"
1 version = "0.06-0"
2 source = {
3 url = "git://github.com/pintsized/lua-resty-redis-connector",
4 tag = "v0.06"
5 }
6 description = {
7 summary = "Connection utilities for lua-resty-redis.",
8 detailed = [[
9 Connection utilities for lua-resty-redis, making it easy and
10 reliable to connect to Redis hosts, either directly or via Redis
11 Sentinel.
12 ]],
13 homepage = "https://github.com/pintsized/lua-resty-redis-connector",
14 license = "2-clause BSD",
15 maintainer = "James Hurst <james@pintsized.co.uk>"
16 }
17 dependencies = {
18 "lua >= 5.1",
19 }
20 build = {
21 type = "builtin",
22 modules = {
23 ["resty.redis.connector"] = "lib/resty/redis/connector.lua",
24 ["resty.redis.sentinel"] = "lib/resty/redis/sentinel.lua"
25 }
26 }
222222 path = "unix://tmp/redis.sock",
223223 }):connect()
224224
225 assert(not redis and err == "no such file or directory",
226 "bad domain socket should fail")
225 assert(not redis and err == "no such file or directory",
226 "bad domain socket should fail")
227227 }
228228 }
229229 --- request
240240 content_by_lua_block {
241241 local rc = require("resty.redis.connector")
242242
243 local params = {
244 url = "redis://foo@127.0.0.1:$TEST_NGINX_REDIS_PORT/4"
245 }
246
247 local ok, err = rc.parse_dsn(params)
248 assert(ok and not err,
249 "url should parse without error: " .. tostring(err))
243 local user_params = {
244 url = "redis://foo@127.0.0.1:$TEST_NGINX_REDIS_PORT/4"
245 }
246
247 local params, err = rc.parse_dsn(user_params)
248 assert(params and not err,
249 "url should parse without error: " .. tostring(err))
250250
251251 assert(params.host == "127.0.0.1", "host should be localhost")
252252 assert(tonumber(params.port) == $TEST_NGINX_REDIS_PORT,
255255 assert(params.password == "foo", "password should be foo")
256256
257257
258 local params = {
258 local user_params = {
259259 url = "sentinel://foo@foomaster:s/2"
260260 }
261261
262 local ok, err = rc.parse_dsn(params)
263 assert(ok and not err,
262 local params, err = rc.parse_dsn(user_params)
263 assert(params and not err,
264264 "url should parse without error: " .. tostring(err))
265265
266266 assert(params.master_name == "foomaster", "master_name should be foomaster")
281281 GET /t
282282 --- no_error_log
283283 [error]
284
285
286 === TEST 9: params override dsn components
287 --- http_config eval: $::HttpConfig
288 --- config
289 location /t {
290 lua_socket_log_errors Off;
291 content_by_lua_block {
292 local rc = require("resty.redis.connector")
293
294 local user_params = {
295 url = "redis://foo@127.0.0.1:6381/4",
296 db = 2,
297 password = "bar",
298 host = "example.com",
299 }
300
301 local params, err = rc.parse_dsn(user_params)
302 assert(params and not err,
303 "url should parse without error: " .. tostring(err))
304
305 assert(tonumber(params.db) == 2, "db should be 2")
306 assert(params.password == "bar", "password should be bar")
307 assert(params.host == "example.com", "host should be example.com")
308
309 assert(tonumber(params.port) == 6381, "ort should still be 6381")
310
311 }
312 }
313 --- request
314 GET /t
315 --- no_error_log
316 [error]
317
318
319 === TEST 9: Integration test for parse_dsn
320 --- http_config eval: $::HttpConfig
321 --- config
322 location /t {
323 lua_socket_log_errors Off;
324 content_by_lua_block {
325 local user_params = {
326 url = "redis://foo.example:$TEST_NGINX_REDIS_PORT/4",
327 db = 2,
328 host = "127.0.0.1",
329 }
330
331 local rc, err = require("resty.redis.connector").new(user_params)
332 assert(rc and not err, "new should return positively")
333
334 local redis, err = rc:connect()
335 assert(redis and not err, "connect should return positively")
336 assert(redis:set("cat", "dog") and redis:get("cat") == "dog")
337
338 local redis, err = rc:connect({
339 url = "redis://foo.example:$TEST_NGINX_REDIS_PORT/4",
340 db = 2,
341 host = "127.0.0.1",
342 })
343 assert(redis and not err, "connect should return positively")
344 assert(redis:set("cat", "dog") and redis:get("cat") == "dog")
345
346
347 local rc2, err = require("resty.redis.connector").new()
348 local redis, err = rc2:connect({
349 url = "redis://foo.example:$TEST_NGINX_REDIS_PORT/4",
350 db = 2,
351 host = "127.0.0.1",
352 })
353 assert(redis and not err, "connect should return positively")
354 assert(redis:set("cat", "dog") and redis:get("cat") == "dog")
355
356 }
357 }
358 --- request
359 GET /t
360 --- no_error_log
361 [error]
0 use Test::Nginx::Socket 'no_plan';
1 use Cwd qw(cwd);
2
3 my $pwd = cwd();
4
5 our $HttpConfig = qq{
6 lua_package_path "$pwd/lib/?.lua;;";
7
8 init_by_lua_block {
9 require("luacov.runner").init()
10 }
11 };
12
13 $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8';
14 $ENV{TEST_NGINX_REDIS_PORT} ||= 6379;
15
16 no_long_string();
17 run_tests();
18
19 __DATA__
20
21 === TEST 1: Proxy mode disables commands
22 --- http_config eval: $::HttpConfig
23 --- config
24 location /t {
25 content_by_lua_block {
26 local rc = require("resty.redis.connector").new({
27 port = $TEST_NGINX_REDIS_PORT,
28 connection_is_proxied = true
29 })
30
31 local redis, err = assert(rc:connect(params),
32 "connect should return positively")
33
34 assert(redis:set("dog", "an animal"),
35 "redis:set should return positively")
36
37 local ok, err = redis:multi()
38 assert(ok == nil, "redis:multi should return nil")
39 assert(err == "Command multi is disabled")
40
41 redis:close()
42 }
43 }
44 --- request
45 GET /t
46 --- no_error_log
47 [error]
48
49
50 === TEST 2: Proxy mode disables custom commands
51 --- http_config eval: $::HttpConfig
52 --- config
53 location /t {
54 content_by_lua_block {
55 local rc = require("resty.redis.connector").new({
56 port = $TEST_NGINX_REDIS_PORT,
57 connection_is_proxied = true,
58 disabled_commands = { "foobar", "hget"}
59 })
60
61 local redis, err = assert(rc:connect(params),
62 "connect should return positively")
63
64 assert(redis:set("dog", "an animal"),
65 "redis:set should return positively")
66
67 assert(redis:multi(),
68 "redis:multi should return positively")
69
70 local ok, err = redis:hget()
71 assert(ok == nil, "redis:hget should return nil")
72 assert(err == "Command hget is disabled")
73
74 local ok, err = redis:foobar()
75 assert(ok == nil, "redis:foobar should return nil")
76 assert(err == "Command foobar is disabled")
77
78 redis:close()
79 }
80 }
81 --- request
82 GET /t
83 --- no_error_log
84 [error]
85
86 === TEST 3: Proxy mode does not switch DB
87 --- http_config eval: $::HttpConfig
88 --- config
89 location /t {
90 content_by_lua_block {
91 local redis = require("resty.redis.connector").new({
92 port = $TEST_NGINX_REDIS_PORT,
93 db = 2
94 }):connect()
95
96 local proxy = require("resty.redis.connector").new({
97 port = $TEST_NGINX_REDIS_PORT,
98 connection_is_proxied = true,
99 db = 2
100 }):connect()
101
102 assert(redis:set("proxy", "test"),
103 "redis:set should return positively")
104
105 assert(proxy:get("proxy") == ngx.null,
106 "proxy key should not exist in proxy")
107
108 redis:seelct(2)
109 assert(redis:get("proxy") == "test",
110 "proxy key should be 'test' in db 1")
111
112 redis:close()
113 }
114 }
115 --- request
116 GET /t
117 --- no_error_log
118 [error]
119
120
121 === TEST 4: Commands are disabled without proxy mode
122 --- http_config eval: $::HttpConfig
123 --- config
124 location /t {
125 content_by_lua_block {
126 local rc = require("resty.redis.connector").new({
127 port = $TEST_NGINX_REDIS_PORT,
128 disabled_commands = { "foobar", "hget"}
129 })
130
131 local redis, err = assert(rc:connect(params),
132 "connect should return positively")
133
134 assert(redis:set("dog", "an animal"),
135 "redis:set should return positively")
136
137 assert(redis:multi(),
138 "redis:multi should return positively")
139
140 local ok, err = redis:hget()
141 assert(ok == nil, "redis:hget should return nil")
142 assert(err == "Command hget is disabled")
143
144 local ok, err = redis:foobar()
145 assert(ok == nil, "redis:foobar should return nil")
146 assert(err == "Command foobar is disabled")
147
148 redis:close()
149 }
150 }
151 --- request
152 GET /t
153 --- no_error_log
154 [error]