@@ -6,6 +6,7 @@ local clear_tab = require("table.clear")
66local utils = require (" resty.etcd.utils" )
77local tab_nkeys = require (" table.nkeys" )
88local encode_args = ngx .encode_args
9+ local now = ngx .now
910local sub_str = string.sub
1011local str_byte = string.byte
1112local str_char = string.char
@@ -22,11 +23,13 @@ local INIT_COUNT_RESIZE = 2e8
2223
2324local _M = {}
2425
25-
2626local mt = { __index = _M }
2727
28+ -- define local refresh function variable
29+ local refresh_jwt_token
30+
31+ local function _request_uri (self , method , uri , opts , timeout , ignore_auth )
2832
29- local function _request_uri (self , method , uri , opts , timeout )
3033 local body
3134 if opts and opts .body and tab_nkeys (opts .body ) > 0 then
3235 body = encode_json (opts .body ) -- encode_args(opts.body)
@@ -36,6 +39,21 @@ local function _request_uri(self, method, uri, opts, timeout)
3639 uri = uri .. ' ?' .. encode_args (opts .query )
3740 end
3841
42+ local headers = {}
43+ local keepalive = true
44+ if self .is_auth then
45+ if not ignore_auth then
46+ -- authentication reqeust not need auth request
47+ local _ , err = refresh_jwt_token (self )
48+ if err then
49+ return nil , err
50+ end
51+ else
52+ keepalive = false -- jwt_token not keepalive
53+ end
54+ headers .Authorization = self .jwt_token
55+ end
56+
3957 local http_cli , err = utils .http .new ()
4058 if err then
4159 return nil , err
@@ -51,6 +69,8 @@ local function _request_uri(self, method, uri, opts, timeout)
5169 res , err = http_cli :request_uri (uri , {
5270 method = method ,
5371 body = body ,
72+ headers = headers ,
73+ keepalive = keepalive ,
5474 })
5575
5676 if err then
@@ -88,6 +108,8 @@ function _M.new(opts)
88108 local api_prefix = opts .api_prefix
89109 local key_prefix = opts .key_prefix or " /apisix"
90110 local http_host = opts .http_host
111+ local user = opts .user
112+ local password = opts .password
91113
92114 if not typeof .uint (timeout ) then
93115 return nil , ' opts.timeout must be unsigned integer'
@@ -109,6 +131,14 @@ function _M.new(opts)
109131 return nil , ' opts.key_prefix must be string'
110132 end
111133
134+ if user and not typeof .string (user ) then
135+ return nil , ' opts.user must be string or ignore'
136+ end
137+
138+ if password and not typeof .string (password ) then
139+ return nil , ' opts.password must be string or ignore'
140+ end
141+
112142 local endpoints = {}
113143 local http_hosts
114144 if type (http_host ) == ' string' then -- signle node
@@ -118,7 +148,7 @@ function _M.new(opts)
118148 end
119149
120150 for _ , host in ipairs (http_hosts ) do
121- local m , err = re_match (http_host , [[ \/\/([\d.\w]+):(\d+)]] , " jo" )
151+ local m , err = re_match (host , [[ \/\/([\d.\w]+):(\d+)]] , " jo" )
122152 if not m then
123153 return nil , " invalid http host: " .. err
124154 end
@@ -134,10 +164,15 @@ function _M.new(opts)
134164 end
135165
136166 return setmetatable ({
137- timeout = timeout ,
138- ttl = ttl ,
139- is_cluster = # endpoints > 1 ,
140- endpoints = endpoints ,
167+ last_auth_time = now (), -- save last Authentication time
168+ jwt_token = nil , -- last Authentication token
169+ is_auth = not not (user and password ),
170+ user = user ,
171+ password = password ,
172+ timeout = timeout ,
173+ ttl = ttl ,
174+ is_cluster = # endpoints > 1 ,
175+ endpoints = endpoints ,
141176 },
142177 mt )
143178end
@@ -159,6 +194,37 @@ local function choose_endpoint(self)
159194 return endpoints [pos ]
160195end
161196
197+ -- return refresh_is_ok, error
198+ function refresh_jwt_token (self )
199+ -- token exist and not expire
200+ -- default is 5min, we use 3min
201+ -- https://github.com/etcd-io/etcd/issues/8287
202+ if self .jwt_token and now () - self .last_auth_time < 60 * 3 then
203+ return true , nil
204+ end
205+
206+ local opts = {
207+ body = {
208+ name = self .user ,
209+ password = self .password ,
210+ }
211+ }
212+ local res , err = _request_uri (self , ' POST' ,
213+ choose_endpoint (self ).full_prefix .. " /auth/authenticate" ,
214+ opts , 5 , true ) -- default authenticate timeout 5 second
215+ if err then
216+ return nil , err
217+ end
218+
219+ if not res or not res .body or not res .body .token then
220+ return nil , ' authenticate refresh token fail'
221+ end
222+
223+ self .jwt_token = res .body .token
224+ self .last_auth_time = now ()
225+
226+ return true , nil
227+ end
162228
163229local function set (self , key , val , attr )
164230 -- verify key
@@ -317,7 +383,7 @@ local function get(self, key, attr)
317383 choose_endpoint (self ).full_prefix .. " /kv/range" ,
318384 opts , attr and attr .timeout or self .timeout )
319385
320- if res .status == 200 then
386+ if res and res .status == 200 then
321387 if res .body .kvs and tab_nkeys (res .body .kvs )> 0 then
322388 for _ , kv in ipairs (res .body .kvs ) do
323389 kv .key = decode_base64 (kv .key )
383449
384450
385451local function request_chunk (self , method , host , port , path , opts , timeout )
386- local body , err
452+ local body , err , _
387453 if opts and opts .body and tab_nkeys (opts .body ) > 0 then
388454 body , err = encode_json (opts .body )
389455 if not body then
@@ -396,6 +462,16 @@ local function request_chunk(self, method, host, port, path, opts, timeout)
396462 query = encode_args (opts .query )
397463 end
398464
465+ local headers = {}
466+ if self .is_auth then
467+ -- authentication reqeust not need auth request
468+ _ , err = refresh_jwt_token (self )
469+ if err then
470+ return nil , err
471+ end
472+ headers .Authorization = self .jwt_token
473+ end
474+
399475 local http_cli
400476 http_cli , err = utils .http .new ()
401477 if err then
@@ -417,10 +493,11 @@ local function request_chunk(self, method, host, port, path, opts, timeout)
417493
418494 local res
419495 res , err = http_cli :request ({
420- method = method ,
421- path = path ,
422- body = body ,
423- query = query ,
496+ method = method ,
497+ path = path ,
498+ body = body ,
499+ query = query ,
500+ headers = headers ,
424501 })
425502 utils .log_info (" http request method: " , method , " path: " , path ,
426503 " body: " , body , " query: " , query )
0 commit comments