You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by me...@apache.org on 2020/10/06 10:18:32 UTC
[apisix] branch master updated: feature: support `consumer_name` as
key for `limit-req` plugin. (#2270)
This is an automated email from the ASF dual-hosted git repository.
membphis 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 c3de84e feature: support `consumer_name` as key for `limit-req` plugin. (#2270)
c3de84e is described below
commit c3de84e28519e74f3b07d25d06a6cab0cad4bdc4
Author: Firstsawyou <52...@users.noreply.github.com>
AuthorDate: Tue Oct 6 18:18:22 2020 +0800
feature: support `consumer_name` as key for `limit-req` plugin. (#2270)
fix #2267
---
apisix/plugins/limit-req.lua | 14 +-
doc/plugins/limit-req.md | 112 +++++++++++++--
doc/zh-cn/plugins/limit-req.md | 110 +++++++++++++--
t/admin/plugins.t | 2 +-
t/plugin/limit-req.t | 313 ++++++++++++++++++++++++++++++++++++++++-
5 files changed, 524 insertions(+), 27 deletions(-)
diff --git a/apisix/plugins/limit-req.lua b/apisix/plugins/limit-req.lua
index 1caadce..7602e9b 100644
--- a/apisix/plugins/limit-req.lua
+++ b/apisix/plugins/limit-req.lua
@@ -27,7 +27,7 @@ local schema = {
burst = {type = "number", minimum = 0},
key = {type = "string",
enum = {"remote_addr", "server_addr", "http_x_real_ip",
- "http_x_forwarded_for"},
+ "http_x_forwarded_for", "consumer_name"},
},
rejected_code = {type = "integer", minimum = 200, default = 503},
},
@@ -67,7 +67,17 @@ function _M.access(conf, ctx)
return 500
end
- local key = (ctx.var[conf.key] or "") .. ctx.conf_type .. ctx.conf_version
+ local key
+ if conf.key == "consumer_name" then
+ if not ctx.consumer_id then
+ core.log.error("consumer not found.")
+ return 500, { message = "Consumer not found."}
+ end
+ key = ctx.consumer_id .. ctx.conf_type .. ctx.conf_version
+
+ else
+ key = (ctx.var[conf.key] or "") .. ctx.conf_type .. ctx.conf_version
+ end
core.log.info("limit key: ", key)
local delay, err = lim:incoming(key, true)
diff --git a/doc/plugins/limit-req.md b/doc/plugins/limit-req.md
index ca090d9..c3d983e 100644
--- a/doc/plugins/limit-req.md
+++ b/doc/plugins/limit-req.md
@@ -20,14 +20,14 @@
- [中文](../zh-cn/plugins/limit-req.md)
# Summary
+ - [Introduction](#introduction)
+ - [Attributes](#attributes)
+ - [Example](#example)
+ - [How to enable on the `route` or `serivce`](#how-to-enable-on-the-route-or-serivce)
+ - [How to enable on the `consumer`](#how-to-enable-on-the-consumer)
+ - [Disable Plugin](#disable-plugin)
-- [**Name**](#name)
-- [**Attributes**](#attributes)
-- [**How To Enable**](#how-to-enable)
-- [**Test Plugin**](#test-plugin)
-- [**Disable Plugin**](#disable-plugin)
-
-## Name
+## Introduction
limit request rate using the "leaky bucket" method.
@@ -37,14 +37,16 @@ limit request rate using the "leaky bucket" method.
| ------------- | ------- | ----------- | ------- | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| rate | integer | required | | [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 | | [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"] | 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. |
-| rejected_code | string | optional | 503 | [200,...] | The HTTP status code returned when the request exceeds the threshold is rejected. The default is 503. |
+| 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). |
+| rejected_code | integer | optional | 503 | [200,...] | The HTTP status code returned when the request exceeds the threshold is rejected. |
**Key can be customized by the user, only need to modify a line of code of the plug-in to complete. It is a security consideration that is not open in the plugin.**
-## How To Enable
+## Example
+
+### How to enable on the `route` or `serivce`
-Here's an example, enable the limit req plugin on the specified route:
+Take `route` as an example (the use of `service` is the same method), enable the `limit-req` plugin on the specified route.
```shell
curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
@@ -76,7 +78,7 @@ Then add limit-req plugin:
![add plugin](../images/plugin/limit-req-2.png)
-## Test Plugin
+**Test Plugin**
The above configuration limits the request rate to 1 per second. If it is greater than 1 and less than 3, the delay will be added. If the rate exceeds 3, it will be rejected:
@@ -104,6 +106,78 @@ Server: APISIX web server
This means that the limit req plugin is in effect.
+### How to enable on the `consumer`
+
+To enable the `limit-req` plugin on the consumer, it needs to be used together with the authorization plugin. Here, the key-auth authorization plugin is taken as an example.
+
+1. Bind the `limit-req` plugin to the consumer
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+ "username": "consumer_jack",
+ "plugins": {
+ "key-auth": {
+ "key": "auth-jack"
+ },
+ "limit-req": {
+ "rate": 1,
+ "burst": 1,
+ "rejected_code": 403,
+ "key": "consumer_name"
+ }
+ }
+}'
+```
+
+2. Create a `route` and enable the `key-auth` plugin
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+ "methods": ["GET"],
+ "uri": "/index.html",
+ "plugins": {
+ "key-auth": {
+ "key": "auth-jack"
+ }
+ },
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": {
+ "127.0.0.1:1980": 1
+ }
+ }
+}'
+```
+
+**Test Plugin**
+
+The value of `rate + burst` is not exceeded.
+
+```shell
+curl -i http://127.0.0.1:9080/index.html -H 'apikey: auth-jack'
+HTTP/1.1 200 OK
+......
+```
+
+When the value of `rate + burst` is exceeded.
+
+```shell
+curl -i http://127.0.0.1:9080/index.html -H 'apikey: auth-jack'
+HTTP/1.1 403 Forbidden
+.....
+<html>
+<head><title>403 Forbidden</title></head>
+<body>
+<center><h1>403 Forbidden</h1></center>
+<hr><center>openresty</center>
+</body>
+</html>
+```
+
+Explains that the `limit-req` plugin tied to `consumer` has taken effect.
+
## Disable Plugin
When you want to disable the limit req plugin, it is very simple,
@@ -127,4 +201,18 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13
}'
```
+Remove the `limit-req` plugin on `consumer`.
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+ "username": "consumer_jack",
+ "plugins": {
+ "key-auth": {
+ "key": "auth-jack"
+ }
+ }
+}'
+```
+
The limit req plugin has been disabled now. It works for other plugins.
diff --git a/doc/zh-cn/plugins/limit-req.md b/doc/zh-cn/plugins/limit-req.md
index b6b1de1..2aacd57 100644
--- a/doc/zh-cn/plugins/limit-req.md
+++ b/doc/zh-cn/plugins/limit-req.md
@@ -19,26 +19,34 @@
- [English](../../plugins/limit-req.md)
-# limit-req
+# 目录
+ - [简介](#简介)
+ - [属性](#属性)
+ - [示例](#示例)
+ - [如何在 `route` 或 `service` 上使用](#如何在`route`或`service`上使用)
+ - [如何在 `consumer` 上使用](#如何在`consumer`上使用)
+ - [移除插件](#移除插件)
+
+## 简介
限制请求速度的插件,使用的是漏桶算法。
-## 参数
+## 属性
| 名称 | 类型 | 必选项 | 默认值 | 有效值 | 描述 |
| ------------- | ------- | ------ | ------ | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| rate | integer | 必须 | | [0,...] | 指定的请求速率(以秒为单位),请求速率超过 `rate` 但没有超过 (`rate` + `brust`)的请求会被加上延时。 |
| burst | integer | 必须 | | [0,...] | t请求速率超过 (`rate` + `brust`)的请求会被直接拒绝。 |
-| key | string | 必须 | | ["remote_addr", "server_addr", "http_x_real_ip", "http_x_forwarded_for"] | 用来做请求计数的依据,当前接受的 key 有:"remote_addr"(客户端IP地址), "server_addr"(服务端 IP 地址), 请求头中的"X-Forwarded-For" 或 "X-Real-IP"。 |
-| rejected_code | string | 可选 | 503 | [200,...] | 当请求超过阈值被拒绝时,返回的 HTTP 状态码 |
+| 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)。 |
+| rejected_code | integer | 可选 | 503 | [200,...] | 当请求超过阈值被拒绝时,返回的 HTTP 状态码。 |
**key 是可以被用户自定义的,只需要修改插件的一行代码即可完成。并没有在插件中放开是处于安全的考虑。**
## 示例
-### 开启插件
+### 如何在`route`或`service`上使用
-下面是一个示例,在指定的 route 上开启了 limit req 插件:
+这里以`route`为例(`service`的使用是同样的方法),在指定的 `route` 上启用 `limit-req` 插件。
```shell
curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
@@ -70,7 +78,7 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13
![添加插件](../../images/plugin/limit-req-2.png)
-### 测试插件
+**测试插件**
上述配置限制了每秒请求速率为 1,大于 1 小于 3 的会被加上延时,速率超过 3 就会被拒绝:
@@ -98,7 +106,79 @@ Server: APISIX web server
这就表示 limit req 插件生效了。
-### 移除插件
+### 如何在`consumer`上使用
+
+consumer上开启`limit-req`插件,需要与授权插件一起配合使用,这里以key-auth授权插件为例。
+
+1、将`limit-req`插件绑定到consumer上
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+ "username": "consumer_jack",
+ "plugins": {
+ "key-auth": {
+ "key": "auth-jack"
+ },
+ "limit-req": {
+ "rate": 1,
+ "burst": 1,
+ "rejected_code": 403,
+ "key": "consumer_name"
+ }
+ }
+}'
+```
+
+2、创建`route`并开启`key-auth`插件
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+ "methods": ["GET"],
+ "uri": "/index.html",
+ "plugins": {
+ "key-auth": {
+ "key": "auth-jack"
+ }
+ },
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": {
+ "127.0.0.1:1980": 1
+ }
+ }
+}'
+```
+
+**测试插件**
+
+未超过`rate + burst` 的值
+
+```shell
+curl -i http://127.0.0.1:9080/index.html -H 'apikey: auth-jack'
+HTTP/1.1 200 OK
+......
+```
+
+当超过`rate + burst` 的值
+
+```shell
+curl -i http://127.0.0.1:9080/index.html -H 'apikey: auth-jack'
+HTTP/1.1 403 Forbidden
+.....
+<html>
+<head><title>403 Forbidden</title></head>
+<body>
+<center><h1>403 Forbidden</h1></center>
+<hr><center>openresty</center>
+</body>
+</html>
+```
+
+说明绑在`consumer`上的 `limit-req`插件生效了
+
+## 移除插件
当你想去掉 limit req 插件的时候,很简单,在插件的配置中把对应的 json 配置删除即可,无须重启服务,即刻生效:
@@ -116,4 +196,18 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13
}'
```
+移除`consumer`上的 `limit-req` 插件
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+ "username": "consumer_jack",
+ "plugins": {
+ "key-auth": {
+ "key": "auth-jack"
+ }
+ }
+}'
+```
+
现在就已经移除了 limit req 插件了。其他插件的开启和移除也是同样的方法。
diff --git a/t/admin/plugins.t b/t/admin/plugins.t
index 6ca86af..6c72d25 100644
--- a/t/admin/plugins.t
+++ b/t/admin/plugins.t
@@ -51,7 +51,7 @@ GET /apisix/admin/plugins
--- request
GET /apisix/admin/plugins/limit-req
--- response_body
-{"properties":{"rate":{"minimum":0,"type":"number"},"burst":{"minimum":0,"type":"number"},"key":{"enum":["remote_addr","server_addr","http_x_real_ip","http_x_forwarded_for"],"type":"string"},"rejected_code":{"type":"integer","default":503,"minimum":200}},"required":["rate","burst","key"],"type":"object"}
+{"properties":{"rate":{"minimum":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}},"required":["rate","burst","key"],"type":"object"}
--- no_error_log
[error]
diff --git a/t/plugin/limit-req.t b/t/plugin/limit-req.t
index 001d975..15a6016 100644
--- a/t/plugin/limit-req.t
+++ b/t/plugin/limit-req.t
@@ -102,7 +102,7 @@ done
},
"type": "roundrobin"
},
- "desc": "上游节点",
+ "desc": "upstream_node",
"uri": "/hello"
}]],
[[{
@@ -122,7 +122,7 @@ done
},
"type": "roundrobin"
},
- "desc": "上游节点",
+ "desc": "upstream_node",
"uri": "/hello"
},
"key": "/apisix/routes/1"
@@ -362,7 +362,7 @@ passed
},
"type": "roundrobin"
},
- "desc": "上游节点",
+ "desc": "upstream_node",
"uri": "/hello"
}]]
)
@@ -403,7 +403,7 @@ passed
},
"type": "roundrobin"
},
- "desc": "上游节点",
+ "desc": "upstream_node",
"uri": "/hello"
}]],
[[{
@@ -432,3 +432,308 @@ GET /t
passed
--- no_error_log
[error]
+
+
+
+=== TEST 12: consumer binds the limit-req plugin and `key` is `consumer_name`
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/consumers',
+ ngx.HTTP_PUT,
+ [[{
+ "username": "new_consumer",
+ "plugins": {
+ "key-auth": {
+ "key": "auth-jack"
+ },
+ "limit-req": {
+ "rate": 3,
+ "burst": 2,
+ "rejected_code": 403,
+ "key": "consumer_name"
+ }
+ }
+ }]],
+ [[{
+ "node": {
+ "value": {
+ "username": "new_consumer",
+ "plugins": {
+ "key-auth": {
+ "key": "auth-jack"
+ },
+ "limit-req": {
+ "rate": 3,
+ "burst": 2,
+ "rejected_code": 403,
+ "key": "consumer_name"
+ }
+ }
+ }
+ },
+ "action": "set"
+ }]]
+ )
+
+ ngx.status = code
+ ngx.say(body)
+ }
+ }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 13: route add "key-auth" plugin
+--- 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": {
+ "key-auth": {}
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ },
+ "desc": "upstream_node",
+ "uri": "/hello"
+ }]],
+ [[{
+ "node": {
+ "value": {
+ "plugins": {
+ "key-auth": {}
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ },
+ "desc": "upstream_node",
+ "uri": "/hello"
+ },
+ "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 14: not exceeding the burst
+--- pipelined_requests eval
+["GET /hello", "GET /hello", "GET /hello"]
+--- more_headers
+apikey: auth-jack
+--- error_code eval
+[200, 200, 200]
+--- no_error_log
+[error]
+
+
+
+=== TEST 15: update the limit-req plugin
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/consumers',
+ ngx.HTTP_PUT,
+ [[{
+ "username": "new_consumer",
+ "plugins": {
+ "key-auth": {
+ "key": "auth-jack"
+ },
+ "limit-req": {
+ "rate": 0.1,
+ "burst": 0.1,
+ "rejected_code": 403,
+ "key": "consumer_name"
+ }
+ }
+ }]],
+ [[{
+ "node": {
+ "value": {
+ "username": "new_consumer",
+ "plugins": {
+ "key-auth": {
+ "key": "auth-jack"
+ },
+ "limit-req": {
+ "rate": 0.1,
+ "burst": 0.1,
+ "rejected_code": 403,
+ "key": "consumer_name"
+ }
+ }
+ }
+ },
+ "action": "set"
+ }]]
+ )
+
+ ngx.status = code
+ ngx.say(body)
+ }
+ }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 16: exceeding the burst
+--- pipelined_requests eval
+["GET /hello", "GET /hello", "GET /hello", "GET /hello"]
+--- more_headers
+apikey: auth-jack
+--- error_code eval
+[403, 403, 403, 403]
+--- no_error_log
+[error]
+
+
+
+=== TEST 17: key is consumer_name
+--- 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": 2,
+ "burst": 1,
+ "key": "consumer_name"
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ },
+ "desc": "upstream_node",
+ "uri": "/hello"
+ }]],
+ [[{
+ "node": {
+ "value": {
+ "plugins": {
+ "limit-req": {
+ "rate": 2,
+ "burst": 1,
+ "key": "consumer_name"
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ },
+ "desc": "upstream_node",
+ "uri": "/hello"
+ },
+ "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 18: get "consumer_name" is empty
+--- request
+GET /hello
+--- error_code: 500
+--- response_body
+{"message":"Consumer not found."}
+--- error_log
+[error]
+
+
+
+=== TEST 19: delete consumer
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/consumers/new_consumer', ngx.HTTP_DELETE)
+
+ ngx.status = code
+ ngx.say(body)
+ }
+ }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 20: delete route
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/routes/1', ngx.HTTP_DELETE)
+
+ ngx.status =code
+ ngx.say(body)
+ }
+ }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]