Skip to content

Commit ac29785

Browse files
feat: enhance rate limiting rules support
Signed-off-by: Abhishek Choudhary <shreemaan.abhishek@gmail.com>
1 parent 5396af9 commit ac29785

File tree

8 files changed

+1014
-67
lines changed

8 files changed

+1014
-67
lines changed

apisix/core/utils.lua

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,10 @@ do
310310
local v = _ctx[segs[1]]
311311

312312
if v == nil then
313-
return segs[2] or ""
313+
if #segs == 1 then
314+
return ""
315+
end
316+
v = segs[2]
314317
end
315318
n_resolved = n_resolved + 1
316319
if _escaper then

apisix/plugins/ai-rate-limiting.lua

Lines changed: 79 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -76,17 +76,50 @@ local schema = {
7676
rejected_msg = {
7777
type = "string", minLength = 1
7878
},
79+
rules = {
80+
type = "array",
81+
items = {
82+
type = "object",
83+
properties = {
84+
count = {
85+
oneOf = {
86+
{type = "integer", exclusiveMinimum = 0},
87+
{type = "string"},
88+
},
89+
},
90+
time_window = {
91+
oneOf = {
92+
{type = "integer", exclusiveMinimum = 0},
93+
{type = "string"},
94+
},
95+
},
96+
key = {type = "string"},
97+
header_prefix = {
98+
type = "string",
99+
description = "prefix for rate limit headers"
100+
},
101+
},
102+
required = {"count", "time_window", "key"},
103+
},
104+
},
79105
},
80106
dependencies = {
81107
limit = {"time_window"},
82108
time_window = {"limit"}
83109
},
84-
anyOf = {
110+
oneOf = {
85111
{
86-
required = {"limit", "time_window"}
112+
anyOf = {
113+
{
114+
required = {"limit", "time_window"}
115+
},
116+
{
117+
required = {"instances"}
118+
}
119+
}
87120
},
88121
{
89-
required = {"instances"}
122+
required = {"rules"},
90123
}
91124
}
92125
}
@@ -109,6 +142,26 @@ end
109142

110143

111144
local function transform_limit_conf(plugin_conf, instance_conf, instance_name)
145+
local limit_conf = {
146+
rejected_code = plugin_conf.rejected_code,
147+
rejected_msg = plugin_conf.rejected_msg,
148+
show_limit_quota_header = plugin_conf.show_limit_quota_header,
149+
150+
-- we may expose those fields to ai-rate-limiting later
151+
policy = "local",
152+
key_type = "constant",
153+
allow_degradation = false,
154+
sync_interval = -1,
155+
limit_header = "X-AI-RateLimit-Limit",
156+
remaining_header = "X-AI-RateLimit-Remaining",
157+
reset_header = "X-AI-RateLimit-Reset",
158+
}
159+
if plugin_conf.rules and #plugin_conf.rules > 0 then
160+
limit_conf.rules = plugin_conf.rules
161+
limit_conf._meta = plugin_conf._meta
162+
return limit_conf
163+
end
164+
112165
local key = plugin_name .. "#global"
113166
local limit = plugin_conf.limit
114167
local time_window = plugin_conf.time_window
@@ -119,26 +172,15 @@ local function transform_limit_conf(plugin_conf, instance_conf, instance_name)
119172
limit = instance_conf.limit
120173
time_window = instance_conf.time_window
121174
end
122-
return {
123-
_vid = key,
124-
125-
key = key,
126-
_meta = plugin_conf._meta,
127-
count = limit,
128-
time_window = time_window,
129-
rejected_code = plugin_conf.rejected_code,
130-
rejected_msg = plugin_conf.rejected_msg,
131-
show_limit_quota_header = plugin_conf.show_limit_quota_header,
132-
-- limit-count need these fields
133-
policy = "local",
134-
key_type = "constant",
135-
allow_degradation = false,
136-
sync_interval = -1,
137-
138-
limit_header = "X-AI-RateLimit-Limit-" .. name,
139-
remaining_header = "X-AI-RateLimit-Remaining-" .. name,
140-
reset_header = "X-AI-RateLimit-Reset-" .. name,
141-
}
175+
limit_conf._vid = key
176+
limit_conf.key = key
177+
limit_conf._meta = plugin_conf._meta
178+
limit_conf.count = limit
179+
limit_conf.time_window = time_window
180+
limit_conf.limit_header = "X-AI-RateLimit-Limit-" .. name
181+
limit_conf.remaining_header = "X-AI-RateLimit-Remaining-" .. name
182+
limit_conf.reset_header = "X-AI-RateLimit-Reset-" .. name
183+
return limit_conf
142184
end
143185

144186

@@ -169,8 +211,13 @@ function _M.access(conf, ctx)
169211
return
170212
end
171213

172-
local limit_conf_kvs = limit_conf_cache(conf, nil, fetch_limit_conf_kvs, conf)
173-
local limit_conf = limit_conf_kvs[ai_instance_name]
214+
local limit_conf
215+
if conf.rules and #conf.rules > 0 then
216+
limit_conf = transform_limit_conf(conf)
217+
else
218+
local limit_conf_kvs = limit_conf_cache(conf, nil, fetch_limit_conf_kvs, conf)
219+
limit_conf = limit_conf_kvs[ai_instance_name]
220+
end
174221
if not limit_conf then
175222
return
176223
end
@@ -244,8 +291,13 @@ function _M.log(conf, ctx)
244291

245292
core.log.info("instance name: ", instance_name, " used tokens: ", used_tokens)
246293

247-
local limit_conf_kvs = limit_conf_cache(conf, nil, fetch_limit_conf_kvs, conf)
248-
local limit_conf = limit_conf_kvs[instance_name]
294+
local limit_conf
295+
if conf.rules and #conf.rules > 0 then
296+
limit_conf = transform_limit_conf(conf)
297+
else
298+
local limit_conf_kvs = limit_conf_cache(conf, nil, fetch_limit_conf_kvs, conf)
299+
limit_conf = limit_conf_kvs[instance_name]
300+
end
249301
if limit_conf then
250302
limit_count.rate_limit(limit_conf, ctx, plugin_name, used_tokens)
251303
end

apisix/plugins/limit-conn.lua

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,38 @@ local schema = {
5454
rejected_msg = {
5555
type = "string", minLength = 1
5656
},
57-
allow_degradation = {type = "boolean", default = false}
57+
allow_degradation = {type = "boolean", default = false},
58+
rules = {
59+
type = "array",
60+
items = {
61+
type = "object",
62+
properties = {
63+
conn = {
64+
oneOf = {
65+
{type = "integer", exclusiveMinimum = 0},
66+
{type = "string"},
67+
},
68+
},
69+
burst = {
70+
oneOf = {
71+
{type = "integer", minimum = 0},
72+
{type = "string"},
73+
},
74+
},
75+
key = {type = "string"},
76+
},
77+
required = {"conn", "burst", "key"},
78+
},
79+
},
80+
},
81+
oneOf = {
82+
{
83+
required = {"conn", "burst", "default_conn_delay", "key"},
84+
},
85+
{
86+
required = {"default_conn_delay", "rules"},
87+
}
5888
},
59-
required = {"conn", "burst", "default_conn_delay", "key"},
6089
["if"] = {
6190
properties = {
6291
policy = {

apisix/plugins/limit-conn/init.lua

Lines changed: 89 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -40,60 +40,100 @@ end
4040
local _M = {}
4141

4242

43-
local function create_limit_obj(ctx, conf)
44-
core.log.info("create new limit-conn plugin instance")
45-
46-
local conn = conf.conn
47-
if type(conn) == "string" then
43+
local function resolve_var(ctx, value)
44+
if type(value) == "string" then
4845
local err, _
49-
conn, err, _ = core.utils.resolve_var(conn, ctx.var)
46+
value, err, _ = core.utils.resolve_var(value, ctx.var)
5047
if err then
51-
return nil, "could not resolve vars in conn: " .. err
48+
return nil, "could not resolve var for value: " .. value .. ", err: " .. err
5249
end
53-
conn = tonumber(conn)
54-
if not conn then
55-
return nil, "resolved conn is not a number: " .. tostring(conn)
50+
value = tonumber(value)
51+
if not value then
52+
return nil, "resolved value is not a number: " .. tostring(value)
5653
end
5754
end
55+
return value
56+
end
5857

59-
local burst = conf.burst
60-
if type(burst) == "string" then
61-
local err, _
62-
burst, err, _ = core.utils.resolve_var(burst, ctx.var)
58+
59+
local function get_rules(ctx, conf)
60+
if not conf.rules then
61+
local conn, err = resolve_var(ctx, conf.conn)
62+
if err then
63+
return nil, err
64+
end
65+
local burst, err2 = resolve_var(ctx, conf.burst)
66+
if err2 then
67+
return nil, err2
68+
end
69+
return {
70+
{
71+
conn = conn,
72+
burst = burst,
73+
key = conf.key,
74+
key_type = conf.key_type,
75+
}
76+
}
77+
end
78+
79+
local rules = {}
80+
for _, rule in ipairs(conf.rules) do
81+
local conn, err = resolve_var(ctx, rule.conn)
6382
if err then
64-
return nil, "could not resolve vars in burst: " .. err
83+
goto CONTINUE
84+
end
85+
local burst, err2 = resolve_var(ctx, rule.burst)
86+
if err2 then
87+
goto CONTINUE
6588
end
66-
burst = tonumber(burst)
67-
if not burst then
68-
return nil, "resolved burst is not a number: " .. tostring(burst)
89+
90+
local key, _, n_resolved = core.utils.resolve_var(rule.key, ctx.var)
91+
if n_resolved == 0 then
92+
goto CONTINUE
6993
end
94+
core.table.insert(rules, {
95+
conn = conn,
96+
burst = burst,
97+
key_type = "constant",
98+
key = key,
99+
})
100+
101+
::CONTINUE::
70102
end
103+
return rules
104+
end
105+
106+
107+
local function create_limit_obj(conf, rule, default_conn_delay)
108+
core.log.info("create new limit-conn plugin instance")
109+
110+
local conn = rule.conn
111+
local burst = rule.burst
71112

72113
core.log.info("limit conn: ", conn, ", burst: ", burst)
73114

74115
if conf.policy == "redis" then
75116
core.log.info("create new limit-conn redis plugin instance")
76117

77118
return redis_single_new("plugin-limit-conn", conf, conn, burst,
78-
conf.default_conn_delay)
119+
default_conn_delay)
79120

80121
elseif conf.policy == "redis-cluster" then
81122

82123
core.log.info("create new limit-conn redis-cluster plugin instance")
83124

84125
return redis_cluster_new("plugin-limit-conn", conf, conn, burst,
85-
conf.default_conn_delay)
126+
default_conn_delay)
86127
else
87128
core.log.info("create new limit-conn plugin instance")
88129
return limit_conn_new(shdict_name, conn, burst,
89-
conf.default_conn_delay)
130+
default_conn_delay)
90131
end
91132
end
92133

93134

94-
function _M.increase(conf, ctx)
95-
core.log.info("ver: ", ctx.conf_version)
96-
local lim, err = create_limit_obj(ctx, conf)
135+
local function run_limit_conn(conf, rule, ctx)
136+
local lim, err = create_limit_obj(conf, rule, conf.default_conn_delay)
97137
if not lim then
98138
core.log.error("failed to instantiate a resty.limit.conn object: ", err)
99139
if conf.allow_degradation then
@@ -102,9 +142,9 @@ function _M.increase(conf, ctx)
102142
return 500
103143
end
104144

105-
local conf_key = conf.key
145+
local conf_key = rule.key
106146
local key
107-
if conf.key_type == "var_combination" then
147+
if rule.key_type == "var_combination" then
108148
local err, n_resolved
109149
key, err, n_resolved = core.utils.resolve_var(conf_key, ctx.var)
110150
if err then
@@ -114,6 +154,8 @@ function _M.increase(conf, ctx)
114154
if n_resolved == 0 then
115155
key = nil
116156
end
157+
elseif rule.key_type == "constant" then
158+
key = conf_key
117159
else
118160
key = ctx.var[conf_key]
119161
end
@@ -157,6 +199,27 @@ function _M.increase(conf, ctx)
157199
end
158200

159201

202+
function _M.increase(conf, ctx)
203+
core.log.info("ver: ", ctx.conf_version)
204+
205+
local rules, err = get_rules(ctx, conf)
206+
if not rules or #rules == 0 then
207+
core.log.error("failed to get limit conn rules: ", err)
208+
if conf.allow_degradation then
209+
return
210+
end
211+
return 500
212+
end
213+
214+
for _, rule in ipairs(rules) do
215+
local code, msg = run_limit_conn(conf, rule, ctx)
216+
if code then
217+
return code, msg
218+
end
219+
end
220+
end
221+
222+
160223
function _M.decrease(conf, ctx)
161224
local limit_conn = ctx.limit_conn
162225
if not limit_conn then

0 commit comments

Comments
 (0)