@@ -19,100 +19,175 @@ if not ok then
1919end
2020
2121
22- local _M = {
23- _VERSION = ' 0.03' ,
22+ -- A metatable which prevents undefined fields from being created / accessed
23+ local fixed_field_metatable = {
24+ __index =
25+ function (t , k )
26+ error (" field " .. tostring (k ) .. " does not exist" , 2 )
27+ end ,
28+ __newindex =
29+ function (t , k , v )
30+ error (" attempt to create new field " .. tostring (k ), 2 )
31+ end ,
2432}
2533
26- local mt = { __index = _M }
2734
35+ -- Returns a new table, recursively copied from the one given, retaining
36+ -- metatable assignment.
37+ --
38+ -- @param table table to be copied
39+ -- @return table
40+ local function tbl_copy (orig )
41+ local orig_type = type (orig )
42+ local copy
43+ if orig_type == " table" then
44+ copy = {}
45+ for orig_key , orig_value in next , orig , nil do
46+ copy [tbl_copy (orig_key )] = tbl_copy (orig_value )
47+ end
48+ setmetatable (copy , tbl_copy (getmetatable (orig )))
49+ else -- number, string, boolean, etc
50+ copy = orig
51+ end
52+ return copy
53+ end
54+
55+
56+ -- Returns a new table, recursively copied from the combination of the given
57+ -- table `t1`, with any missing fields copied from `defaults`.
58+ --
59+ -- If `defaults` is of type "fixed field" and `t1` contains a field name not
60+ -- present in the defults, an error will be thrown.
61+ --
62+ -- @param table t1
63+ -- @param table defaults
64+ -- @return table a new table, recursively copied and merged
65+ local function tbl_copy_merge_defaults (t1 , defaults )
66+ if t1 == nil then t1 = {} end
67+ if defaults == nil then defaults = {} end
68+ if type (t1 ) == " table" and type (defaults ) == " table" then
69+ local mt = getmetatable (defaults )
70+ local copy = {}
71+ for t1_key , t1_value in next , t1 , nil do
72+ copy [tbl_copy (t1_key )] = tbl_copy_merge_defaults (
73+ t1_value , tbl_copy (defaults [t1_key ])
74+ )
75+ end
76+ for defaults_key , defaults_value in next , defaults , nil do
77+ if t1 [defaults_key ] == nil then
78+ copy [tbl_copy (defaults_key )] = tbl_copy (defaults_value )
79+ end
80+ end
81+ return copy
82+ else
83+ return t1 -- not a table
84+ end
85+ end
86+
87+
88+ local DEFAULTS = setmetatable ({
89+ connect_timeout = 100 ,
90+ read_timeout = 1000 ,
91+ connection_options = {}, -- pool, etc
92+
93+ keepalive_timeout = 60000 ,
94+ keepalive_poolsize = 30 ,
2895
29- local DEFAULTS = {
3096 host = " 127.0.0.1" ,
3197 port = 6379 ,
32- path = nil , -- /tmp/redis.sock
33- password = nil ,
98+ path = " " , -- /tmp/redis.sock
99+ password = " " ,
34100 db = 0 ,
101+
102+ url = " " , -- DSN url
103+
35104 master_name = " mymaster" ,
36- role = " master" , -- master | slave | any (tries master first, failover to a slave)
37- sentinels = nil ,
38- cluster_startup_nodes = {},
105+ role = " master" , -- master | slave | any
106+ sentinels = {},
107+
108+ cluster_startup_nodes = {}, -- TODO remove this until implemented?
109+ }, fixed_field_metatable )
110+
111+
112+ local _M = {
113+ _VERSION = ' 0.03' ,
39114}
40115
116+ local mt = { __index = _M }
117+
41118
42- function _M .new ()
43- return setmetatable ({
44- connect_timeout = 100 ,
45- read_timeout = 1000 ,
46- connection_options = nil , -- pool, etc
47- }, mt )
119+ function _M .new (config )
120+ local ok , config = pcall (tbl_copy_merge_defaults , config , DEFAULTS )
121+ if not ok then
122+ return nil , config -- err
123+ else
124+ return setmetatable ({
125+ config = config
126+ }, mt )
127+ end
48128end
49129
50130
51131function _M .set_connect_timeout (self , timeout )
52- self .connect_timeout = timeout
132+ self .config . connect_timeout = timeout
53133end
54134
55135
56136function _M .set_read_timeout (self , timeout )
57- self .read_timeout = timeout
137+ self .config . read_timeout = timeout
58138end
59139
60140
61141function _M .set_connection_options (self , options )
62- self .connection_options = options
142+ self .config . connection_options = options
63143end
64144
65145
66146local function parse_dsn (params )
67147 local url = params .url
68- if url then
148+ if url and url ~= " " then
69149 local url_pattern = [[ ^(?:(redis|sentinel)://)(?:([^@]*)@)?([^:/]+)(?::(\d+|[msa]+))/?(.*)$]]
70- local m , err = ngx_re_match (url , url_pattern , " " )
150+
151+ local m , err = ngx_re_match (url , url_pattern , " oj" )
71152 if not m then
72- ngx_log (ngx_ERR , " could not parse DSN: " , err )
73- else
74- local fields
75- if m [1 ] == " redis" then
76- fields = { " password" , " host" , " port" , " db" }
77- elseif m [1 ] == " sentinel" then
78- fields = { " password" , " master_name" , " role" , " db" }
79- end
153+ return nil , " could not parse DSN: " .. err
154+ end
80155
81- -- password may not be present
82- if # m < 5 then tbl_remove (fields , 1 ) end
156+ local fields
157+ if m [1 ] == " redis" then
158+ fields = { " password" , " host" , " port" , " db" }
159+ elseif m [1 ] == " sentinel" then
160+ fields = { " password" , " master_name" , " role" , " db" }
161+ end
83162
84- local roles = { m = " master" , s = " slave" , a = " any" }
163+ -- password may not be present
164+ if # m < 5 then tbl_remove (fields , 1 ) end
85165
86- for i ,v in ipairs (fields ) do
87- params [v ] = m [i + 1 ]
88- if v == " role" then
89- params [v ] = roles [params [v ]]
90- end
166+ local roles = { m = " master" , s = " slave" , a = " any" }
167+
168+ for i ,v in ipairs (fields ) do
169+ params [v ] = m [i + 1 ]
170+ if v == " role" then
171+ params [v ] = roles [params [v ]]
91172 end
92173 end
93174 end
175+
176+ return true , nil
94177end
95178
96179
97180function _M .connect (self , params )
98- -- If we have nothing, assume default host connection options apply
99- if not params or type (params ) ~= " table" then
100- params = {}
101- end
181+ local params = tbl_copy_merge_defaults (params , self .config )
102182
103183 if params .url then
104- parse_dsn (params )
184+ local ok , err = parse_dsn (params )
185+ if not ok then ngx_log (ngx_ERR , err ) end
105186 end
106187
107- if params .sentinels then
108- setmetatable (params , { __index = DEFAULTS } )
188+ if # params .sentinels > 0 then
109189 return self :connect_via_sentinel (params )
110- elseif params .startup_cluster_nodes then
111- setmetatable (params , { __index = DEFAULTS } )
112- -- TODO: Implement cluster
113- return nil , " Redis Cluster not yet implemented"
114190 else
115- setmetatable (params , { __index = DEFAULTS } )
116191 return self :connect_to_host (params )
117192 end
118193end
@@ -202,19 +277,21 @@ end
202277
203278function _M .connect_to_host (self , host )
204279 local r = redis .new ()
205- r :set_timeout (self .connect_timeout )
280+ local config = self .config
281+ r :set_timeout (config .connect_timeout )
206282
207283 local ok , err
208284 local socket = host .socket
285+ local opts = config .connection_options
209286 if socket then
210- if self . connection_options then
211- ok , err = r :connect (socket , self .connection_options )
287+ if opts then
288+ ok , err = r :connect (socket , config .connection_options )
212289 else
213290 ok , err = r :connect (socket )
214291 end
215292 else
216- if self . connection_options then
217- ok , err = r :connect (host .host , host .port , self .connection_options )
293+ if opts then
294+ ok , err = r :connect (host .host , host .port , config .connection_options )
218295 else
219296 ok , err = r :connect (host .host , host .port )
220297 end
@@ -224,10 +301,10 @@ function _M.connect_to_host(self, host)
224301 ngx_log (ngx_ERR , err , " for " , host .host , " :" , host .port )
225302 return nil , err
226303 else
227- r :set_timeout (self , self .read_timeout )
304+ r :set_timeout (self , config .read_timeout )
228305
229306 local password = host .password
230- if password then
307+ if password and password ~= " " then
231308 local res , err = r :auth (password )
232309 if err then
233310 ngx_log (ngx_ERR , err )
@@ -241,4 +318,17 @@ function _M.connect_to_host(self, host)
241318end
242319
243320
244- return _M
321+ local function set_keepalive (self , redis )
322+ -- Restore connection to "NORMAL" before putting into keepalive pool,
323+ -- ignoring any errors.
324+ redis :discard ()
325+
326+ local config = self .config
327+ return redis :set_keepalive (
328+ config .keepalive_timeout , config .keepalive_poolsize
329+ )
330+ end
331+ _M .set_keepalive = set_keepalive
332+
333+
334+ return setmetatable (_M , fixed_field_metatable )
0 commit comments