Skip to content

Commit

Permalink
feat(limit-count): make rate-limit response headers configurable (#11831
Browse files Browse the repository at this point in the history
)
  • Loading branch information
shreemaan-abhishek authored Dec 18, 2024
1 parent 844b396 commit 945e077
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 10 deletions.
5 changes: 3 additions & 2 deletions apisix/plugins/limit-count.lua
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@ local _M = {
priority = 1002,
name = plugin_name,
schema = limit_count.schema,
metadata_schema = limit_count.metadata_schema,
}


function _M.check_schema(conf)
return limit_count.check_schema(conf)
function _M.check_schema(conf, schema_type)
return limit_count.check_schema(conf, schema_type)
end


Expand Down
54 changes: 46 additions & 8 deletions apisix/plugins/limit-count/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,30 @@ local group_conf_lru = core.lrucache.new({
type = 'plugin',
})

local metadata_defaults = {
limit_header = "X-RateLimit-Limit",
remaining_header = "X-RateLimit-Remaining",
reset_header = "X-RateLimit-Reset",
}

local metadata_schema = {
type = "object",
properties = {
limit_header = {
type = "string",
default = metadata_defaults.limit_header,
},
remaining_header = {
type = "string",
default = metadata_defaults.remaining_header,
},
reset_header = {
type = "string",
default = metadata_defaults.reset_header,
},
},
}

local schema = {
type = "object",
properties = {
Expand Down Expand Up @@ -91,7 +115,8 @@ local schema = {
local schema_copy = core.table.deepcopy(schema)

local _M = {
schema = schema
schema = schema,
metadata_schema = metadata_schema,
}


Expand All @@ -100,7 +125,12 @@ local function group_conf(conf)
end


function _M.check_schema(conf)

function _M.check_schema(conf, schema_type)
if schema_type == core.schema.TYPE_METADATA then
return core.schema.check(metadata_schema, conf)
end

local ok, err = core.schema.check(schema, conf)
if not ok then
return false, err
Expand Down Expand Up @@ -250,14 +280,22 @@ function _M.rate_limit(conf, ctx, name, cost)
delay, remaining, reset = lim:incoming(key, cost)
end

local metadata = apisix_plugin.plugin_metadata("limit-count")
if metadata then
metadata = metadata.value
else
metadata = metadata_defaults
end
core.log.info("limit-count plugin-metadata: ", core.json.delay_encode(metadata))

if not delay then
local err = remaining
if err == "rejected" then
-- show count limit header when rejected
if conf.show_limit_quota_header then
core.response.set_header("X-RateLimit-Limit", conf.count,
"X-RateLimit-Remaining", 0,
"X-RateLimit-Reset", reset)
core.response.set_header(metadata.limit_header, conf.count,
metadata.remaining_header, 0,
metadata.reset_header, reset)
end

if conf.rejected_msg then
Expand All @@ -274,9 +312,9 @@ function _M.rate_limit(conf, ctx, name, cost)
end

if conf.show_limit_quota_header then
core.response.set_header("X-RateLimit-Limit", conf.count,
"X-RateLimit-Remaining", remaining,
"X-RateLimit-Reset", reset)
core.response.set_header(metadata.limit_header, conf.count,
metadata.remaining_header, remaining,
metadata.reset_header, reset)
end
end

Expand Down
59 changes: 59 additions & 0 deletions docs/en/latest/plugins/limit-count.md
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,65 @@ Server: APISIX web server
{"error_msg":"Requests are too frequent, please try again later."}
```

### Customize Rate Limiting Headers

The following example demonstrates how you can use plugin metadata to customize the rate limiting response header names, which are by default `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset`.

Configure the plugin metadata for this plugin and update the headers:

```shell
curl "http://127.0.0.1:9180/apisix/admin/plugin_metadata/limit-count" -X PUT -d '
{
"log_format": {
"limit_header": "X-Custom-RateLimit-Limit",
"remaining_header": "X-Custom-RateLimit-Remaining",
"reset_header": "X-Custom-RateLimit-Reset"
}
}'
```

Create a route with `limit-count` plugin that allows for a quota of 1 within a 30-second window per remote address:

```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "limit-count-route",
"uri": "/get",
"plugins": {
"limit-count": {
"count": 1,
"time_window": 30,
"rejected_code": 429,
"key_type": "var",
"key": "remote_addr",
"window_type": "sliding"
}
# highlight-end
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```

Send a request to verify:

```shell
curl -i "http://127.0.0.1:9080/get"
```

You should receive an `HTTP/1.1 200 OK` response and see the following headers:

```text
X-Custom-RateLimit-Limit: 1
X-Custom-RateLimit-Remaining: 0
X-Custom-RateLimit-Reset: 28
```

## Delete Plugin

To remove the `limit-count` Plugin, you can delete the corresponding JSON configuration from the Plugin configuration. APISIX will automatically reload and you do not have to restart for this to take effect.
Expand Down
63 changes: 63 additions & 0 deletions t/plugin/limit-count5.t
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,66 @@ passed
["GET /hello", "GET /hello", "GET /hello", "GET /hello"]
--- error_code eval
[200, 200, 503, 503]
=== TEST 4: customize rate limit headers by plugin metadata
--- 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": 10,
"time_window": 60,
"rejected_code": 503,
"key_type": "var",
"key": "remote_addr"
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say("fail")
return
end
local code, meta_body = t('/apisix/admin/plugin_metadata/limit-count',
ngx.HTTP_PUT,
[[{
"limit_header":"APISIX-RATELIMIT-QUOTA",
"remaining_header":"APISIX-RATELIMIT-REMAINING",
"reset_header":"APISIX-RATELIMIT-RESET"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say("fail")
return
end
ngx.say("passed")
}
}
--- response_body
passed
=== TEST 5: check rate limit headers
--- request
GET /hello
--- response_headers_like
APISIX-RATELIMIT-QUOTA: 10
APISIX-RATELIMIT-REMAINING: 9
APISIX-RATELIMIT-RESET: \d+

0 comments on commit 945e077

Please sign in to comment.