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