diff --git a/apisix/plugins/limit-count.lua b/apisix/plugins/limit-count.lua index e3b4c0f6c8bb..0c547fc6cb92 100644 --- a/apisix/plugins/limit-count.lua +++ b/apisix/plugins/limit-count.lua @@ -23,11 +23,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 diff --git a/apisix/plugins/limit-count/init.lua b/apisix/plugins/limit-count/init.lua index e7d03028e077..08a4c9763551 100644 --- a/apisix/plugins/limit-count/init.lua +++ b/apisix/plugins/limit-count/init.lua @@ -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 = { @@ -91,7 +115,8 @@ local schema = { local schema_copy = core.table.deepcopy(schema) local _M = { - schema = schema + schema = schema, + metadata_schema = metadata_schema, } @@ -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 @@ -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 @@ -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 diff --git a/docs/en/latest/plugins/limit-count.md b/docs/en/latest/plugins/limit-count.md index 4c019332a899..c5c2250173e5 100644 --- a/docs/en/latest/plugins/limit-count.md +++ b/docs/en/latest/plugins/limit-count.md @@ -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. diff --git a/t/plugin/limit-count5.t b/t/plugin/limit-count5.t index cb4615990fb1..4227b4f10891 100644 --- a/t/plugin/limit-count5.t +++ b/t/plugin/limit-count5.t @@ -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+