This is an automated email from the ASF dual-hosted git repository.

shreemaanabhishek pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git


The following commit(s) were added to refs/heads/master by this push:
     new f37c19013 feat: support configuring variables in limit-conn, 
limit-count and ai-rate-limiting (#12967)
f37c19013 is described below

commit f37c19013afe4d515c950dc28d0b388d29f32d85
Author: Shreemaan Abhishek <[email protected]>
AuthorDate: Thu Feb 5 13:51:47 2026 +0545

    feat: support configuring variables in limit-conn, limit-count and 
ai-rate-limiting (#12967)
---
 apisix/plugins/ai-rate-limiting.lua              |  28 ++-
 apisix/plugins/limit-conn.lua                    |  14 +-
 apisix/plugins/limit-conn/init.lua               |  44 ++++-
 apisix/plugins/limit-count/init.lua              |  80 +++++----
 apisix/plugins/limit-count/limit-count-local.lua |   8 +-
 t/plugin/ai-rate-limiting.t                      | 206 +++++++++++++++++++++++
 t/plugin/limit-conn-variable.t                   | 178 ++++++++++++++++++++
 t/plugin/limit-conn.t                            |   6 +-
 t/plugin/limit-count-redis-cluster.t             |   3 -
 t/plugin/limit-count-redis.t                     |   3 -
 t/plugin/limit-count-variable.t                  | 145 ++++++++++++++++
 t/plugin/limit-count.t                           |  12 +-
 12 files changed, 665 insertions(+), 62 deletions(-)

diff --git a/apisix/plugins/ai-rate-limiting.lua 
b/apisix/plugins/ai-rate-limiting.lua
index c674bbef8..2bbfa8aca 100644
--- a/apisix/plugins/ai-rate-limiting.lua
+++ b/apisix/plugins/ai-rate-limiting.lua
@@ -27,8 +27,18 @@ local instance_limit_schema = {
     type = "object",
     properties = {
         name = {type = "string"},
-        limit = {type = "integer", minimum = 1},
-        time_window = {type = "integer", minimum = 1}
+        limit = {
+            oneOf = {
+                {type = "integer", minimum = 1},
+                {type = "string"},
+            },
+        },
+        time_window = {
+            oneOf = {
+                {type = "integer", minimum = 1},
+                {type = "string"},
+            },
+        }
     },
     required = {"name", "limit", "time_window"}
 }
@@ -36,8 +46,18 @@ local instance_limit_schema = {
 local schema = {
     type = "object",
     properties = {
-        limit = {type = "integer", exclusiveMinimum = 0},
-        time_window = {type = "integer",  exclusiveMinimum = 0},
+        limit = {
+            oneOf = {
+                {type = "integer", exclusiveMinimum = 0},
+                {type = "string"},
+            },
+        },
+        time_window = {
+            oneOf = {
+                {type = "integer", exclusiveMinimum = 0},
+                {type = "string"},
+            },
+        },
         show_limit_quota_header = {type = "boolean", default = true},
         limit_strategy = {
             type = "string",
diff --git a/apisix/plugins/limit-conn.lua b/apisix/plugins/limit-conn.lua
index 6fdefd1f4..dd88162af 100644
--- a/apisix/plugins/limit-conn.lua
+++ b/apisix/plugins/limit-conn.lua
@@ -24,8 +24,18 @@ local workflow                           = 
require("apisix.plugins.workflow")
 local schema = {
     type = "object",
     properties = {
-        conn = {type = "integer", exclusiveMinimum = 0},               -- 
limit.conn max
-        burst = {type = "integer",  minimum = 0},
+        conn = {
+            oneOf = {
+                {type = "integer", exclusiveMinimum = 0},
+                {type = "string"},
+            },
+        },
+        burst = {
+            oneOf = {
+                {type = "integer", minimum = 0},
+                {type = "string"},
+            },
+        },
         default_conn_delay = {type = "number", exclusiveMinimum = 0},
         only_use_default_delay = {type = "boolean", default = false},
         key = {type = "string"},
diff --git a/apisix/plugins/limit-conn/init.lua 
b/apisix/plugins/limit-conn/init.lua
index 22ce44a12..6c5c823e1 100644
--- a/apisix/plugins/limit-conn/init.lua
+++ b/apisix/plugins/limit-conn/init.lua
@@ -18,6 +18,9 @@ local limit_conn_new = require("resty.limit.conn").new
 local core = require("apisix.core")
 local is_http = ngx.config.subsystem == "http"
 local sleep = core.sleep
+local tonumber = tonumber
+local type = type
+local tostring = tostring
 local shdict_name = "plugin-limit-conn"
 if ngx.config.subsystem == "stream" then
     shdict_name = shdict_name .. "-stream"
@@ -34,30 +37,55 @@ do
 end
 
 
-local lrucache = core.lrucache.new({
-    type = "plugin",
-})
 local _M = {}
 
 
-local function create_limit_obj(conf)
+local function create_limit_obj(ctx, conf)
     core.log.info("create new limit-conn plugin instance")
 
+    local conn = conf.conn
+    if type(conn) == "string" then
+        local err, _
+        conn, err, _ = core.utils.resolve_var(conn, ctx.var)
+        if err then
+            return nil, "could not resolve vars in conn: " .. err
+        end
+        conn = tonumber(conn)
+        if not conn then
+            return nil, "resolved conn is not a number: " .. tostring(conn)
+        end
+    end
+
+    local burst = conf.burst
+    if type(burst) == "string" then
+        local err, _
+        burst, err, _ = core.utils.resolve_var(burst, ctx.var)
+        if err then
+            return nil, "could not resolve vars in burst: " .. err
+        end
+        burst = tonumber(burst)
+        if not burst then
+            return nil, "resolved burst is not a number: " .. tostring(burst)
+        end
+    end
+
+    core.log.info("limit conn: ", conn, ", burst: ", burst)
+
     if conf.policy == "redis" then
         core.log.info("create new limit-conn redis plugin instance")
 
-        return redis_single_new("plugin-limit-conn", conf, conf.conn, 
conf.burst,
+        return redis_single_new("plugin-limit-conn", conf, conn, burst,
                                 conf.default_conn_delay)
 
     elseif conf.policy == "redis-cluster" then
 
         core.log.info("create new limit-conn redis-cluster plugin instance")
 
-        return redis_cluster_new("plugin-limit-conn", conf, conf.conn, 
conf.burst,
+        return redis_cluster_new("plugin-limit-conn", conf, conn, burst,
                                  conf.default_conn_delay)
     else
         core.log.info("create new limit-conn plugin instance")
-        return limit_conn_new(shdict_name, conf.conn, conf.burst,
+        return limit_conn_new(shdict_name, conn, burst,
                               conf.default_conn_delay)
     end
 end
@@ -65,7 +93,7 @@ end
 
 function _M.increase(conf, ctx)
     core.log.info("ver: ", ctx.conf_version)
-    local lim, err = lrucache(conf, nil, create_limit_obj, conf)
+    local lim, err = create_limit_obj(ctx, conf)
     if not lim then
         core.log.error("failed to instantiate a resty.limit.conn object: ", 
err)
         if conf.allow_degradation then
diff --git a/apisix/plugins/limit-count/init.lua 
b/apisix/plugins/limit-count/init.lua
index c7b8579d0..7d5fe7ca9 100644
--- a/apisix/plugins/limit-count/init.lua
+++ b/apisix/plugins/limit-count/init.lua
@@ -22,6 +22,9 @@ local pairs = pairs
 local redis_schema = require("apisix.utils.redis-schema")
 local policy_to_additional_properties = redis_schema.schema
 local get_phase = ngx.get_phase
+local tonumber = tonumber
+local type = type
+local tostring = tostring
 
 local limit_redis_cluster_new
 local limit_redis_new
@@ -36,9 +39,6 @@ do
     local cluster_src = "apisix.plugins.limit-count.limit-count-redis-cluster"
     limit_redis_cluster_new = require(cluster_src).new
 end
-local lrucache = core.lrucache.new({
-    type = 'plugin', serial_creating = true,
-})
 local group_conf_lru = core.lrucache.new({
     type = 'plugin',
 })
@@ -70,8 +70,18 @@ local metadata_schema = {
 local schema = {
     type = "object",
     properties = {
-        count = {type = "integer", exclusiveMinimum = 0},
-        time_window = {type = "integer",  exclusiveMinimum = 0},
+        count = {
+            oneOf = {
+                {type = "integer", exclusiveMinimum = 0},
+                {type = "string"},
+            },
+        },
+        time_window = {
+            oneOf = {
+                {type = "integer", exclusiveMinimum = 0},
+                {type = "string"},
+            },
+        },
         group = {type = "string"},
         key = {type = "string", default = "remote_addr"},
         key_type = {type = "string",
@@ -174,22 +184,47 @@ function _M.check_schema(conf, schema_type)
 end
 
 
-local function create_limit_obj(conf, plugin_name)
+local function create_limit_obj(conf, ctx, plugin_name)
     core.log.info("create new " .. plugin_name .. " plugin instance")
 
+    local count = conf.count
+    if type(count) == "string" then
+        local err, _
+        count, err, _ = core.utils.resolve_var(count, ctx.var)
+        if err then
+            return nil, "could not resolve vars in count: " .. err
+        end
+        count = tonumber(count)
+        if not count then
+            return nil, "resolved count is not a number: " .. tostring(count)
+        end
+    end
+
+    local time_window = conf.time_window
+    if type(time_window) == "string" then
+        local err, _
+        time_window, err, _ = core.utils.resolve_var(time_window, ctx.var)
+        if err then
+            return nil, "could not resolve vars in time_window: " .. err
+        end
+        time_window = tonumber(time_window)
+        if not time_window then
+            return nil, "resolved time_window is not a number: " .. 
tostring(time_window)
+        end
+    end
+
+    core.log.info("limit count: ", count, ", time_window: ", time_window)
+
     if not conf.policy or conf.policy == "local" then
-        return limit_local_new("plugin-" .. plugin_name, conf.count,
-                               conf.time_window)
+        return limit_local_new("plugin-" .. plugin_name, count, time_window)
     end
 
     if conf.policy == "redis" then
-        return limit_redis_new("plugin-" .. plugin_name,
-                               conf.count, conf.time_window, conf)
+        return limit_redis_new("plugin-" .. plugin_name, count, time_window, 
conf)
     end
 
     if conf.policy == "redis-cluster" then
-        return limit_redis_cluster_new("plugin-" .. plugin_name, conf.count,
-                                       conf.time_window, conf)
+        return limit_redis_cluster_new("plugin-" .. plugin_name, count, 
time_window, conf)
     end
 
     return nil
@@ -223,26 +258,11 @@ local function gen_limit_key(conf, ctx, key)
 end
 
 
-local function gen_limit_obj(conf, ctx, plugin_name)
-    if conf.group then
-        return lrucache(conf.group, "", create_limit_obj, conf, plugin_name)
-    end
-
-    local extra_key
-    if conf._vid then
-        extra_key = conf.policy .. '#' .. conf._vid
-    else
-        extra_key = conf.policy
-    end
-
-    return core.lrucache.plugin_ctx(lrucache, ctx, extra_key, 
create_limit_obj, conf, plugin_name)
-end
-
 function _M.rate_limit(conf, ctx, name, cost, dry_run)
     core.log.info("ver: ", ctx.conf_version)
     core.log.info("conf: ", core.json.delay_encode(conf, true))
 
-    local lim, err = gen_limit_obj(conf, ctx, name)
+    local lim, err = create_limit_obj(conf, ctx, name)
 
     if not lim then
         core.log.error("failed to fetch limit.count object: ", err)
@@ -307,7 +327,7 @@ function _M.rate_limit(conf, ctx, name, cost, dry_run)
         if err == "rejected" then
             -- show count limit header when rejected
             if conf.show_limit_quota_header and set_header then
-                core.response.set_header(set_limit_headers.limit_header, 
conf.count,
+                core.response.set_header(set_limit_headers.limit_header, 
lim.limit,
                 set_limit_headers.remaining_header, 0,
                 set_limit_headers.reset_header, reset)
             end
@@ -326,7 +346,7 @@ function _M.rate_limit(conf, ctx, name, cost, dry_run)
     end
 
     if conf.show_limit_quota_header and set_header then
-        core.response.set_header(set_limit_headers.limit_header, conf.count,
+        core.response.set_header(set_limit_headers.limit_header, lim.limit,
             set_limit_headers.remaining_header, remaining,
             set_limit_headers.reset_header, reset)
     end
diff --git a/apisix/plugins/limit-count/limit-count-local.lua 
b/apisix/plugins/limit-count/limit-count-local.lua
index b6f319ae0..9ad33d89c 100644
--- a/apisix/plugins/limit-count/limit-count-local.lua
+++ b/apisix/plugins/limit-count/limit-count-local.lua
@@ -57,7 +57,9 @@ function _M.new(plugin_name, limit, window)
 
     local self = {
         limit_count = limit_count.new(plugin_name, limit, window),
-        dict = ngx.shared[plugin_name .. "-reset-header"]
+        dict = ngx.shared[plugin_name .. "-reset-header"],
+        limit = limit,
+        window = window,
     }
 
     return setmetatable(self, mt)
@@ -67,8 +69,8 @@ function _M.incoming(self, key, commit, conf, cost)
     local delay, remaining = self.limit_count:incoming(key, commit, cost)
     local reset
 
-    if remaining == conf.count - cost then
-        reset = set_endtime(self, key, conf.time_window)
+    if remaining == self.limit - cost then
+        reset = set_endtime(self, key, self.window)
     else
         reset = read_reset(self, key)
     end
diff --git a/t/plugin/ai-rate-limiting.t b/t/plugin/ai-rate-limiting.t
index 66aa0b07f..c6112b9ab 100644
--- a/t/plugin/ai-rate-limiting.t
+++ b/t/plugin/ai-rate-limiting.t
@@ -984,3 +984,209 @@ passed
 Authorization: Bearer token
 --- error_code eval
 [200, 200, 200, 200, 200, 200, 200, 503, 503]
+
+
+
+=== TEST 21: use variable in count and time_window with default value
+--- config
+    location /t {
+        content_by_lua_block {
+            local core = require("apisix.core")
+            local data = {
+                uri = "/ai",
+                plugins = {
+                    ["ai-proxy-multi"] = {
+                        fallback_strategy = 
"instance_health_and_rate_limiting",
+                        instances = {
+                            {
+                                name = "deepseek",
+                                provider = "openai",
+                                weight = 1,
+                                priority = 1,
+                                auth = {
+                                    header = {
+                                        Authorization = "Bearer token"
+                                    }
+                                },
+                                override = {
+                                    endpoint = "http://localhost:16724";
+                                }
+                            },
+                            {
+                                name = "openai",
+                                provider = "openai",
+                                weight = 1,
+                                priority = 0,
+                                auth = {
+                                    header = {
+                                        Authorization = "Bearer token"
+                                    }
+                                },
+                                override = {
+                                    endpoint = "http://localhost:16724";
+                                }
+                            }
+                        },
+                        ssl_verify = false
+                    },
+                    ["ai-rate-limiting"] = {
+                        limit = "${http_count ?? 10}",
+                        time_window = "${http_time_window ?? 60}",
+                        instances = {
+                            {
+                                name = "openai",
+                                limit = "${http_openai_count ?? 20}",
+                                time_window = "${http_time_window ?? 60}"
+                            }
+                        }
+                    }
+                },
+                upstream = {
+                    type = "roundrobin",
+                    nodes = {
+                        ["canbeanything.com"] = 1
+                    }
+                }
+            }
+
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 core.json.encode(data)
+            )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 22: request with default variable values
+--- config
+    location /t {
+        content_by_lua_block {
+            local http = require("resty.http")
+
+            local test_cases = {
+                { code = 200 },
+                { code = 200 },
+                { code = 200 },
+                { code = 503 },
+            }
+
+            local httpc = http.new()
+            for i, case in ipairs(test_cases) do
+                local res = httpc:request_uri(
+                    "http://127.0.0.1:"; .. ngx.var.server_port .. "/ai",
+                    {
+                        method = "POST",
+                        body = [[{
+                            "messages": [
+                                { "role": "system", "content": "You are a 
mathematician" },
+                                { "role": "user", "content": "What is 1+1?" }
+                            ]
+                        }]],
+                        headers = {
+                            ["Content-Type"] = "application/json",
+                        }
+                    }
+                )
+                if res.status ~= case.code then
+                    ngx.say( i  .. "th request should return " .. case.code .. 
", but got " .. res.status)
+                    return
+                end
+            end
+
+            ngx.say("passed")
+        }
+    }
+--- request
+GET /t
+--- timeout: 10
+--- response_body
+passed
+--- grep_error_log eval
+qr/picked instance: [^,]+/
+--- grep_error_log_out
+picked instance: deepseek
+picked instance: openai
+picked instance: openai
+picked instance: nil
+
+
+
+=== TEST 23: request with custom count/time_window headers
+--- config
+    location /t {
+        content_by_lua_block {
+            local http = require("resty.http")
+
+            local test_cases = {
+                { count = 20, openai_count = 30, time_window = 2, code = 200 },
+                { count = 20, openai_count = 30, time_window = 2, code = 200 },
+                { count = 20, openai_count = 30, time_window = 2, code = 200 },
+                { count = 20, openai_count = 30, time_window = 2, code = 200 },
+                { count = 20, openai_count = 30, time_window = 2, code = 200 },
+                { count = 20, openai_count = 30, time_window = 2, code = 503 },
+            }
+
+            local run_tests = function()
+                local httpc = http.new()
+                for i, case in ipairs(test_cases) do
+                    local res = httpc:request_uri(
+                        "http://127.0.0.1:"; .. ngx.var.server_port .. "/ai",
+                        {
+                            method = "POST",
+                            body = [[{
+                                "messages": [
+                                    { "role": "system", "content": "You are a 
mathematician" },
+                                    { "role": "user", "content": "What is 
1+1?" }
+                                ]
+                            }]],
+                            headers = {
+                                ["Content-Type"] = "application/json",
+                                ["count"] = tostring(case.count),
+                                ["time-window"] = tostring(case.time_window),
+                                ["openai-count"] = tostring(case.openai_count),
+                            }
+                        }
+                    )
+                    if res.status ~= case.code then
+                        ngx.say( i  .. "th request should return " .. 
case.code .. ", but got " .. res.status)
+                        ngx.exit(500)
+                    end
+                end
+            end
+
+            run_tests()
+            ngx.sleep(2)
+            run_tests()
+
+            ngx.say("passed")
+        }
+    }
+--- request
+GET /t
+--- timeout: 10
+--- response_body
+passed
+--- grep_error_log eval
+qr/picked instance: [^,]+/
+--- grep_error_log_out
+picked instance: deepseek
+picked instance: deepseek
+picked instance: openai
+picked instance: openai
+picked instance: openai
+picked instance: nil
+picked instance: deepseek
+picked instance: deepseek
+picked instance: openai
+picked instance: openai
+picked instance: openai
+picked instance: nil
diff --git a/t/plugin/limit-conn-variable.t b/t/plugin/limit-conn-variable.t
new file mode 100644
index 000000000..8c82100e4
--- /dev/null
+++ b/t/plugin/limit-conn-variable.t
@@ -0,0 +1,178 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+BEGIN {
+    if ($ENV{TEST_NGINX_CHECK_LEAK}) {
+        $SkipReason = "unavailable for the hup tests";
+
+    } else {
+        $ENV{TEST_NGINX_USE_HUP} = 1;
+        undef $ENV{TEST_NGINX_USE_STAP};
+    }
+}
+
+use t::APISIX 'no_plan';
+
+repeat_each(1);
+no_long_string();
+no_shuffle();
+no_root_location();
+log_level('info');
+
+
+add_block_preprocessor(sub {
+    my ($block) = @_;
+    my $port = $ENV{TEST_NGINX_SERVER_PORT};
+
+    my $config = $block->config // <<_EOC_;
+    location /access_root_dir {
+        content_by_lua_block {
+            local httpc = require "resty.http"
+            local hc = httpc:new()
+
+            local res, err = 
hc:request_uri('http://127.0.0.1:$port/limit_conn', {
+                headers = ngx.req.get_headers()
+            })
+            if res then
+                ngx.exit(res.status)
+            end
+        }
+    }
+
+    location /test_concurrency {
+        content_by_lua_block {
+            local reqs = {}
+            for i = 1, 10 do
+                reqs[i] = { "/access_root_dir" }
+            end
+            local resps = { ngx.location.capture_multi(reqs) }
+            for i, resp in ipairs(resps) do
+                ngx.say(resp.status)
+            end
+        }
+    }
+_EOC_
+
+    $block->set_value("config", $config);
+});
+
+run_tests;
+
+__DATA__
+
+=== TEST 1: use variable in conn and burst with default value
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "plugins": {
+                            "limit-conn": {
+                                "conn": "${http_conn ?? 5}",
+                                "burst": "${http_burst ?? 2}",
+                                "default_conn_delay": 0.1,
+                                "rejected_code": 503,
+                                "key": "remote_addr"
+                            }
+                        },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "uri": "/limit_conn"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+
+
+
+=== TEST 2: request without conn/burst headers
+--- request
+GET /test_concurrency
+--- timeout: 10s
+--- response_body
+200
+200
+200
+200
+200
+200
+200
+503
+503
+503
+--- error_log
+limit conn: 5, burst: 2
+
+
+
+=== TEST 3: request with conn header
+--- request
+GET /test_concurrency
+--- more_headers
+conn: 3
+--- timeout: 10s
+--- response_body
+200
+200
+200
+200
+200
+503
+503
+503
+503
+503
+--- error_log
+limit conn: 3, burst: 2
+
+
+
+=== TEST 4: request with conn and burst header
+--- request
+GET /test_concurrency
+--- more_headers
+conn: 3
+burst: 4
+--- timeout: 10s
+--- response_body
+200
+200
+200
+200
+200
+200
+200
+503
+503
+503
+--- error_log
+limit conn: 3, burst: 4
diff --git a/t/plugin/limit-conn.t b/t/plugin/limit-conn.t
index 0182019ee..364282f49 100644
--- a/t/plugin/limit-conn.t
+++ b/t/plugin/limit-conn.t
@@ -362,7 +362,7 @@ GET /t
 GET /t
 --- error_code: 400
 --- response_body
-{"error_msg":"failed to check the configuration of plugin limit-conn err: 
property \"conn\" validation failed: expected -1 to be greater than 0"}
+{"error_msg":"failed to check the configuration of plugin limit-conn err: 
property \"conn\" validation failed: value should match only one schema, but 
matches none"}
 
 
 
@@ -441,7 +441,7 @@ GET /t
 GET /t
 --- error_code: 400
 --- response_body
-{"error_msg":"failed to check the configuration of plugin limit-conn err: 
property \"conn\" validation failed: expected -1 to be greater than 0"}
+{"error_msg":"failed to check the configuration of plugin limit-conn err: 
property \"conn\" validation failed: value should match only one schema, but 
matches none"}
 
 
 
@@ -868,7 +868,7 @@ GET /test_concurrency
 --- request
 GET /t
 --- response_body
-property "conn" validation failed: expected 0 to be greater than 0
+property "conn" validation failed: value should match only one schema, but 
matches none
 property "default_conn_delay" validation failed: expected 0 to be greater than 0
 done
 
diff --git a/t/plugin/limit-count-redis-cluster.t 
b/t/plugin/limit-count-redis-cluster.t
index 7a4798a60..1263f80a3 100644
--- a/t/plugin/limit-count-redis-cluster.t
+++ b/t/plugin/limit-count-redis-cluster.t
@@ -167,9 +167,6 @@ passed
 === TEST 4: up the limit
 --- request
 GET /hello
---- error_log
-try to lock with key route#1#redis-cluster
-unlock with key route#1#redis-cluster
 
 
 
diff --git a/t/plugin/limit-count-redis.t b/t/plugin/limit-count-redis.t
index d06188050..db13ba4e4 100644
--- a/t/plugin/limit-count-redis.t
+++ b/t/plugin/limit-count-redis.t
@@ -169,9 +169,6 @@ passed
 === TEST 4: up the limit
 --- request
 GET /hello
---- error_log
-try to lock with key route#1#redis
-unlock with key route#1#redis
 
 
 
diff --git a/t/plugin/limit-count-variable.t b/t/plugin/limit-count-variable.t
new file mode 100644
index 000000000..ad021c2d5
--- /dev/null
+++ b/t/plugin/limit-count-variable.t
@@ -0,0 +1,145 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+use t::APISIX 'no_plan';
+
+no_long_string();
+no_shuffle();
+no_root_location();
+log_level('info');
+
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if (!$block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    if (!$block->error_log && !$block->no_error_log) {
+        $block->set_value("no_error_log", "[error]\n[alert]");
+    }
+});
+
+run_tests;
+
+__DATA__
+
+=== TEST 1: use variable in count and time_window with default value
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "methods": ["GET"],
+                        "plugins": {
+                            "limit-count": {
+                                "count": "${http_count ?? 2}",
+                                "time_window": "${http_time_window ?? 5}",
+                                "rejected_code": 503,
+                                "key_type": "var",
+                                "key": "remote_addr",
+                                "policy": "local"
+                            }
+                        },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "uri": "/hello"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 2: request without count/time_window headers
+--- pipelined_requests eval
+["GET /hello", "GET /hello", "GET /hello"]
+--- error_code eval
+[200, 200, 503]
+
+
+
+=== TEST 3: request with count header
+--- pipelined_requests eval
+["GET /hello", "GET /hello", "GET /hello"]
+--- more_headers
+count: 5
+--- error_code eval
+[200, 200, 200, 200, 200, 503]
+
+
+
+=== TEST 4: request with count and time_window header
+--- config
+    location /t {
+        content_by_lua_block {
+            local http = require("resty.http")
+            local core = require("apisix.core")
+
+            local uri = "http://127.0.0.1:"; .. ngx.var.server_port .. "/hello"
+            local opt = {method = "GET", headers = { ["count"] = 3, 
["time-window"] = "2" }}
+            local httpc = http.new()
+
+            for i = 1, 3, 1 do
+                local res = httpc:request_uri(uri, opt)
+                if res.status ~= 200 then
+                    ngx.say("first two requests should return 200, but got " 
.. res.status)
+                    return
+                end
+                if res.headers["X-RateLimit-Limit"] ~= "3" then
+                    ngx.say("X-RateLimit-Limit should be 3, but got " .. 
core.json.encode(res.headers))
+                    return
+                end
+            end
+            local res = httpc:request_uri(uri, opt)
+            if res.status ~= 503 then
+                ngx.say("third requests should return 503, but got " .. 
res.status)
+                return
+            end
+
+            ngx.sleep(2)
+
+            for i = 1, 3, 1 do
+                local res = httpc:request_uri(uri, opt)
+                if res.status ~= 200 then
+                    ngx.say("two requests after sleep 2s should return 200, 
but got " .. res.status)
+                    return
+                end
+            end
+            ngx.say("passed")
+        }
+    }
+--- request
+GET /t
+--- timeout: 10
+--- response_body
+passed
+--- error_log
+limit count: 3, time_window: 2
diff --git a/t/plugin/limit-count.t b/t/plugin/limit-count.t
index f150c01d0..402c93dd2 100644
--- a/t/plugin/limit-count.t
+++ b/t/plugin/limit-count.t
@@ -253,7 +253,7 @@ passed
     }
 --- error_code: 400
 --- response_body
-{"error_msg":"failed to check the configuration of plugin limit-count err: 
property \"count\" validation failed: expected -100 to be greater than 0"}
+{"error_msg":"failed to check the configuration of plugin limit-count err: 
property \"count\" validation failed: value should match only one schema, but 
matches none"}
 
 
 
@@ -291,7 +291,7 @@ passed
     }
 --- error_code: 400
 --- response_body
-{"error_msg":"failed to check the configuration of plugin limit-count err: 
property \"count\" validation failed: expected -100 to be greater than 0"}
+{"error_msg":"failed to check the configuration of plugin limit-count err: 
property \"count\" validation failed: value should match only one schema, but 
matches none"}
 
 
 
@@ -363,7 +363,7 @@ passed
     }
 --- error_code: 400
 --- response_body
-{"error_msg":"failed to check the configuration of plugin limit-count err: 
property \"count\" validation failed: expected -100 to be greater than 0"}
+{"error_msg":"failed to check the configuration of plugin limit-count err: 
property \"count\" validation failed: value should match only one schema, but 
matches none"}
 
 
 
@@ -400,7 +400,7 @@ passed
     }
 --- error_code: 400
 --- response_body
-{"error_msg":"failed to check the configuration of plugin limit-count err: 
property \"count\" validation failed: expected -100 to be greater than 0"}
+{"error_msg":"failed to check the configuration of plugin limit-count err: 
property \"count\" validation failed: value should match only one schema, but 
matches none"}
 
 
 
@@ -1171,7 +1171,7 @@ passed
         }
     }
 --- response_body eval
-qr/property \"count\" validation failed: expected 0 to be greater than 0/
+qr/property \"count\" validation failed: value should match only one schema, 
but matches none/
 
 
 
@@ -1189,4 +1189,4 @@ qr/property \"count\" validation failed: expected 0 to be 
greater than 0/
         }
     }
 --- response_body eval
-qr/property \"time_window\" validation failed: expected 0 to be greater than 0/
+qr/property \"time_window\" validation failed: value should match only one 
schema, but matches none/

Reply via email to