Skip to content

Commit

Permalink
feat(limit-conn): support multiple variables as key (#5354)
Browse files Browse the repository at this point in the history
  • Loading branch information
Xunzhuo authored Nov 2, 2021
1 parent 957878f commit 7b06fcc
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 6 deletions.
7 changes: 4 additions & 3 deletions apisix/plugins/limit-conn.lua
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ local schema = {
burst = {type = "integer", minimum = 0},
default_conn_delay = {type = "number", exclusiveMinimum = 0},
only_use_default_delay = {type = "boolean", default = false},
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
Expand Down
25 changes: 24 additions & 1 deletion apisix/plugins/limit-conn/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,30 @@ function _M.increase(conf, ctx)
return 500
end

local key = (ctx.var[conf.key] or "") .. ctx.conf_type .. ctx.conf_version
local conf_key = conf.key
local key
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
else
key = ctx.var[conf_key]
end

if key == nil then
core.log.info("bypass the limit conn as the key is empty")
-- Bypass the limit conn 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)
Expand Down
3 changes: 2 additions & 1 deletion docs/en/latest/plugins/limit-conn.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ Limiting request concurrency plugin.
| burst | integer | required | | burst >= 0 | the number of excessive concurrent requests (or connections) allowed to be delayed. |
| default_conn_delay | number | required | | default_conn_delay > 0 | the latency seconds of request when concurrent requests exceeding `conn` but below (`conn` + `burst`). |
| only_use_default_delay | boolean | optional | false | [true,false] | enable the strict mode of the latency seconds. If you set this option to `true`, it will run strictly according to the latency seconds you set without additional calculation logic. |
| key | object | required | | ["remote_addr", "server_addr", "http_x_real_ip", "http_x_forwarded_for", "consumer_name"] | to limit the concurrency level. <br />For example, one can use the host name (or server zone) as the key so that we limit concurrency per host name. Otherwise, we can also use the client address as the key so that we can avoid a single client from flooding our service with too many parallel connections or requests. <br /> 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 | string | optional | 503 | [200,...,599] | the HTTP status code returned when the request exceeds `conn` + `burst` will be rejected. |
| rejected_msg | string | optional | | non-empty | the response body returned when the request exceeds `conn` + `burst` will be rejected. |
| allow_degradation | boolean | optional | false | | Whether to enable plugin degradation when the limit-conn function is temporarily unavailable. Allow requests to continue when the value is set to true, default false. |
Expand Down
3 changes: 2 additions & 1 deletion docs/zh/latest/plugins/limit-conn.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ title: limit-conn
| burst | integer | required | | burst >= 0 | 允许被延迟处理的并发请求数。 |
| default_conn_delay | number | required | | default_conn_delay > 0 | 默认的典型连接(或请求)的处理延迟时间。 |
| only_use_default_delay | boolean | optional | false | [true,false] | 延迟时间的严格模式。 如果设置为`true`的话,将会严格按照设置的时间来进行延迟 |
| key | object | required | | ["remote_addr", "server_addr", "http_x_real_ip", "http_x_forwarded_for", "consumer_name"] | 用户指定的限制并发级别的关键字,可以是客户端 IP 或服务端 IP。<br />例如,可以使用主机名(或服务器区域)作为关键字,以便限制每个主机名的并发性。 否则,我们也可以使用客户端地址作为关键字,这样我们就可以避免单个客户端用太多的并行连接或请求淹没我们的服务。 <br />当前接受的 key 有:"remote_addr"(客户端 IP 地址), "server_addr"(服务端 IP 地址), 请求头中的"X-Forwarded-For" 或 "X-Real-IP", "consumer_name"(consumer 的 username)。 |
| 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 | string | optional | 503 | [200,...,599] | 当请求超过 `conn` + `burst` 这个阈值时,返回的 HTTP 状态码 |
| rejected_msg | string | 可选 | | 非空 | 当请求超过 `conn` + `burst` 这个阈值时,返回的响应体。 |
| allow_degradation | boolean | 可选 | false | | 当插件功能临时不可用时是否允许请求继续。当值设置为 true 时则自动允许请求继续,默认值是 false。|
Expand Down
131 changes: 131 additions & 0 deletions t/plugin/limit-conn2.t
Original file line number Diff line number Diff line change
Expand Up @@ -308,3 +308,134 @@ request latency is nil
--- error_code: 400
--- response_body
{"error_msg":"failed to check the configuration of plugin limit-conn err: property \"rejected_msg\" validation failed: string too short, expected at least 1, got 0"}
=== TEST 9: set key type to 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-conn": {
"conn": 2,
"burst": 1,
"default_conn_delay": 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": "/limit_conn"
}]],
[[{
"node": {
"value": {
"plugins": {
"limit-conn": {
"conn": 2,
"burst": 1,
"default_conn_delay": 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": "/limit_conn"
},
"key": "/apisix/routes/1"
},
"action": "set"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
--- no_error_log
[error]
=== TEST 10: 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
.. "/limit_conn"
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
--- timeout: 10s
--- response_body
[200,200]
--- no_error_log
[error]
=== TEST 11: 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
.. "/limit_conn"
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 conn as the key is empty

0 comments on commit 7b06fcc

Please sign in to comment.