You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by sp...@apache.org on 2021/10/22 00:50:06 UTC
[apisix] branch master updated: feat(limit-req): support multiple
variables as key (#5302)
This is an automated email from the ASF dual-hosted git repository.
spacewander 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 2f250d5 feat(limit-req): support multiple variables as key (#5302)
2f250d5 is described below
commit 2f250d53698b0fa617ea981d2d488ebd756bb655
Author: 罗泽轩 <sp...@gmail.com>
AuthorDate: Fri Oct 22 08:49:58 2021 +0800
feat(limit-req): support multiple variables as key (#5302)
---
apisix/plugins/limit-req.lua | 33 ++++++---
docs/en/latest/plugins/limit-req.md | 3 +-
docs/zh/latest/plugins/limit-req.md | 5 +-
t/admin/plugins.t | 2 +-
t/plugin/limit-req.t | 5 +-
t/plugin/limit-req2.t | 137 ++++++++++++++++++++++++++++++++++++
6 files changed, 169 insertions(+), 16 deletions(-)
diff --git a/apisix/plugins/limit-req.lua b/apisix/plugins/limit-req.lua
index bd7ef4a..6768dc1 100644
--- a/apisix/plugins/limit-req.lua
+++ b/apisix/plugins/limit-req.lua
@@ -29,9 +29,10 @@ local schema = {
properties = {
rate = {type = "number", exclusiveMinimum = 0},
burst = {type = "number", minimum = 0},
- key = {type = "string",
- enum = {"remote_addr", "server_addr", "http_x_real_ip",
- "http_x_forwarded_for", "consumer_name"},
+ key = {type = "string"},
+ key_type = {type = "string",
+ enum = {"var", "var_combination"},
+ default = "var",
},
rejected_code = {
type = "integer", minimum = 200, maximum = 599, default = 503
@@ -83,17 +84,31 @@ function _M.access(conf, ctx)
return 500
end
+ local conf_key = conf.key
local key
- if conf.key == "consumer_name" then
- if not ctx.consumer_name then
- core.log.error("consumer not found.")
- return 500, { message = "Consumer not found."}
+ if conf.key_type == "var_combination" then
+ local err, n_resolved
+ key, err, n_resolved = core.utils.resolve_var(conf_key, ctx.var);
+ if err then
+ core.log.error("could not resolve vars in ", conf_key, " error: ", err)
+ end
+
+ if n_resolved == 0 then
+ key = nil
end
- key = ctx.consumer_name .. ctx.conf_type .. ctx.conf_version
else
- key = (ctx.var[conf.key] or "") .. ctx.conf_type .. ctx.conf_version
+ key = ctx.var[conf_key]
end
+
+ if key == nil then
+ core.log.info("bypass the limit req as the key is empty")
+ -- Bypass the limit req when the key is empty.
+ -- This behavior is the same as Nginx
+ return
+ end
+
+ key = key .. ctx.conf_type .. ctx.conf_version
core.log.info("limit key: ", key)
local delay, err = lim:incoming(key, true)
diff --git a/docs/en/latest/plugins/limit-req.md b/docs/en/latest/plugins/limit-req.md
index 1792adb..166fa1b 100644
--- a/docs/en/latest/plugins/limit-req.md
+++ b/docs/en/latest/plugins/limit-req.md
@@ -40,7 +40,8 @@ limit request rate using the "leaky bucket" method.
| ------------- | ------- | ----------- | ------- | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| rate | integer | required | | rate > 0 | the specified request rate (number per second) threshold. Requests exceeding this rate (and below `burst`) will get delayed to conform to the rate. |
| burst | integer | required | | burst >= 0 | the number of excessive requests per second allowed to be delayed. Requests exceeding this hard limit will get rejected immediately. |
-| key | string | required | | ["remote_addr", "server_addr", "http_x_real_ip", "http_x_forwarded_for", "consumer_name"] | the user specified key to limit the rate, now accept those as key: "remote_addr"(client's IP), "server_addr"(server's IP), "X-Forwarded-For/X-Real-IP" in request header, "consumer_name"(consumer's username). |
+| key_type | string | optional | "var" | ["var", "var_combination"] | the type of key. |
+| key | string | required | | | the user specified key to limit the rate. If the `key_type` is "var", the key will be treated as a name of variable, like "remote_addr" or "consumer_name". If the `key_type` is "var_combination", the key will be a combination of variables, like "$remote_addr|$consumer_name". |
| rejected_code | integer | optional | 503 | [200,...,599] | The HTTP status code returned when the request exceeds the threshold is rejected. |
| rejected_msg | string | optional | | non-empty | The response body returned when the request exceeds the threshold is rejected. |
| nodelay | boolean | optional | false | | If nodelay flag is true, bursted requests will not get delayed |
diff --git a/docs/zh/latest/plugins/limit-req.md b/docs/zh/latest/plugins/limit-req.md
index 7b57c15..fb7b7d5 100644
--- a/docs/zh/latest/plugins/limit-req.md
+++ b/docs/zh/latest/plugins/limit-req.md
@@ -39,8 +39,9 @@ title: limit-req
| 名称 | 类型 | 必选项 | 默认值 | 有效值 | 描述 |
| ------------- | ------- | ------ | ------ | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| rate | integer | 必须 | | rate > 0 | 指定的请求速率(以秒为单位),请求速率超过 `rate` 但没有超过 (`rate` + `brust`)的请求会被加上延时。 |
-| burst | integer | 必须 | | burst >= 0 | t请求速率超过 (`rate` + `brust`)的请求会被直接拒绝。 |
-| key | string | 必须 | | ["remote_addr", "server_addr", "http_x_real_ip", "http_x_forwarded_for", "consumer_name"] | 用来做请求计数的依据,当前接受的 key 有:"remote_addr"(客户端IP地址), "server_addr"(服务端 IP 地址), 请求头中的"X-Forwarded-For" 或 "X-Real-IP","consumer_name"(consumer 的 username)。 |
+| burst | integer | 必须 | | burst >= 0 | 请求速率超过 (`rate` + `brust`)的请求会被直接拒绝。 |
+| key_type | string | 可选 | "var" | ["var", "var_combination"] | key 的类型 |
+| key | string | 必须 | | | 用来做请求计数的依据。如果 `key_type` 为 "var",那么 key 会被当作变量名称,如 "remote_addr" 和 "consumer_name"。如果 `key_type` 为 "var_combination",那么 key 会当作变量组合,如 "$remote_addr|$consumer_name"。 |
| rejected_code | integer | 可选 | 503 | [200,...,599] | 当请求超过阈值被拒绝时,返回的 HTTP 状态码。 |
| rejected_msg | string | 可选 | | 非空 | 当请求超过阈值被拒绝时,返回的响应体。 |
| nodelay | boolean | 可选 | false | | 如果 nodelay 为 true, 请求速率超过 `rate` 但没有超过 (`rate` + `brust`)的请求不会加上延迟, 如果是 false,则会加上延迟。 |
diff --git a/t/admin/plugins.t b/t/admin/plugins.t
index b0d5d8f..b5196dd 100644
--- a/t/admin/plugins.t
+++ b/t/admin/plugins.t
@@ -66,7 +66,7 @@ GET /apisix/admin/plugins
ngx.HTTP_GET,
nil,
[[
-{"properties":{"rate":{"exclusiveMinimum":0,"type":"number"},"burst":{"minimum":0,"type":"number"},"key":{"enum":["remote_addr","server_addr","http_x_real_ip","http_x_forwarded_for","consumer_name"],"type":"string"},"rejected_code":{"type":"integer","default":503,"minimum":200,"maximum":599}},"required":["rate","burst","key"],"type":"object"}
+ {"type":"object","required":["rate","burst","key"],"properties":{"rate":{"type":"number","exclusiveMinimum":0},"key_type":{"type":"string","enum":["var","var_combination"],"default":"var"},"burst":{"type":"number","minimum":0},"disable":{"type":"boolean"},"nodelay":{"type":"boolean","default":false},"key":{"type":"string"},"rejected_code":{"type":"integer","minimum":200,"maximum":599,"default":503},"rejected_msg":{"type":"string","minLength":1},"allow_degradation":{"type" [...]
]]
)
diff --git a/t/plugin/limit-req.t b/t/plugin/limit-req.t
index dd1a355..d3d03e3 100644
--- a/t/plugin/limit-req.t
+++ b/t/plugin/limit-req.t
@@ -693,11 +693,10 @@ passed
=== TEST 18: get "consumer_name" is empty
--- request
GET /hello
---- error_code: 500
--- response_body
-{"message":"Consumer not found."}
+hello world
--- error_log
-[error]
+bypass the limit req as the key is empty
diff --git a/t/plugin/limit-req2.t b/t/plugin/limit-req2.t
index 9ed83e7..dc3e4ce 100644
--- a/t/plugin/limit-req2.t
+++ b/t/plugin/limit-req2.t
@@ -119,3 +119,140 @@ passed
["GET /hello", "GET /hello"]
--- error_code eval
[200, 200]
+
+
+
+=== TEST 5: key type is var_combination
+--- 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-req": {
+ "rate": 0.1,
+ "burst": 0.1,
+ "rejected_code": 503,
+ "key": "$http_a $http_b",
+ "key_type": "var_combination"
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ },
+ "uri": "/hello"
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 6: exceed the burst
+--- config
+ location /t {
+ content_by_lua_block {
+ local json = require "t.toolkit.json"
+ local http = require "resty.http"
+ local uri = "http://127.0.0.1:" .. ngx.var.server_port
+ .. "/hello"
+
+ local ress = {}
+ for i = 1, 2 do
+ local httpc = http.new()
+ local res, err = httpc:request_uri(uri, {headers = {a = 1}})
+ if not res then
+ ngx.say(err)
+ return
+ end
+ table.insert(ress, res.status)
+ end
+ ngx.say(json.encode(ress))
+ }
+ }
+--- request
+GET /t
+--- no_error_log
+[error]
+--- response_body
+[200,503]
+
+
+
+=== TEST 7: don't exceed the burst
+--- config
+ location /t {
+ content_by_lua_block {
+ local json = require "t.toolkit.json"
+ local http = require "resty.http"
+ local uri = "http://127.0.0.1:" .. ngx.var.server_port
+ .. "/hello"
+
+ local ress = {}
+ for i = 1, 2 do
+ local httpc = http.new()
+ local res, err = httpc:request_uri(uri, {headers = {a = i}})
+ if not res then
+ ngx.say(err)
+ return
+ end
+ table.insert(ress, res.status)
+ end
+ ngx.say(json.encode(ress))
+ }
+ }
+--- request
+GET /t
+--- no_error_log
+[error]
+--- response_body
+[200,200]
+
+
+
+=== TEST 8: bypass empty key
+--- config
+ location /t {
+ content_by_lua_block {
+ local json = require "t.toolkit.json"
+ local http = require "resty.http"
+ local uri = "http://127.0.0.1:" .. ngx.var.server_port
+ .. "/hello"
+
+ local ress = {}
+ for i = 1, 2 do
+ local httpc = http.new()
+ local res, err = httpc:request_uri(uri)
+ if not res then
+ ngx.say(err)
+ return
+ end
+ table.insert(ress, res.status)
+ end
+ ngx.say(json.encode(ress))
+ }
+ }
+--- request
+GET /t
+--- no_error_log
+[error]
+--- response_body
+[200,200]
+--- error_log
+bypass the limit req as the key is empty