Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: limit count plugin support X-RateLimit-Reset #8578

Merged
merged 15 commits into from
Jan 10, 2023

Conversation

mscb402
Copy link
Contributor

@mscb402 mscb402 commented Dec 28, 2022

Description

limit count plugin support returns a new header X-RateLimit-Reset and return the header when Rejected

Fixes #8381

Checklist

  • I have explained the need for this PR and the problem it solves
  • I have explained the changes or the new features added to this PR
  • I have added tests corresponding to this change

@@ -38,7 +39,9 @@ local lrucache = core.lrucache.new({
local group_conf_lru = core.lrucache.new({
type = 'plugin',
})

local resetlru = core.lrucache.new({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
local resetlru = core.lrucache.new({
local reset_lru = core.lrucache.new({

}
end

return resetlru(key,"",create,conf)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't forget to add space around the operator

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -242,6 +245,15 @@ local function gen_limit_obj(conf, ctx)
return core.lrucache.plugin_ctx(lrucache, ctx, extra_key, create_limit_obj, conf)
end

local function get_end_time(conf,key)
local create = function()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please avoid creating new function per call

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@@ -254,7 +254,7 @@ curl -i http://127.0.0.1:9180/apisix/admin/routes/1 \

## Example usage

The above configuration limits to 2 requests in 60 seconds. The first two requests will work and the response headers will contain the headers `X-RateLimit-Limit` and `X-RateLimit-Remaining`:
The above configuration limits to 2 requests in 60 seconds. The first two requests will work and the response headers will contain the headers `X-RateLimit-Limit` and `X-RateLimit-Remaining` and `X-RateLimit-Reset`:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the English version doesn't match the Chinese one?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

local end_time = ngx_time() + conf.time_window
-- save to lrucache by key
reset = conf.time_window
local end_time_obj = get_end_time(conf,key)
Copy link
Member

@spacewander spacewander Dec 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By default, the client IP will be used as key, which means there will be a great number of keys per conf. The size of lrucache is limited.

There come two solutions:

  1. store the end time info in the actual backend like redis cluster.
    2. write a new cache with dynamical expire support to store the end time info, but this way is not memory-effective.(it doesn't work)

Personally, I prefer solution 1.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

solution 1 does not work either. Because by default limit count will not use Redis as the backend. So we can't save it to Redis.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm using ngx.shared with expire time instead.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ngx.shared only works with the default backend which also uses ngx.shared. If we use other backends, only one instance will execute remaining == conf.count - 1 and the end time can only be found in that instance.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, you can review it again.

function _M.read_reset(self, key)
local red = self.red_cli
key = self.plugin_name .. tostring(key)
local ttl, err = red:ttl(key)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we put this into the eval script to make it atomic and reduce roundtrip?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

__index = _M
}

function _M.set_endtime(self,key,time_window)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Look like we can merge set_endtime into the incoming method, especially after implementing read_reset via eval?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, fixed

@@ -276,6 +276,7 @@ http {
{% if enabled_plugins["limit-count"] then %}
lua_shared_dict plugin-limit-count {* http.lua_shared_dict["plugin-limit-count"] *};
lua_shared_dict plugin-limit-count-redis-cluster-slot-lock {* http.lua_shared_dict["plugin-limit-count-redis-cluster-slot-lock"] *};
lua_shared_dict plugin-limit-count-reset-header {* http.lua_shared_dict["plugin-limit-count-reset-header"] *};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can let the size of plugin-limit-count-reset-header be equal to plugin-limit-count, so that people don't need to configure it twice? As these two shdict store similar data.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed plugin-limit-count-reset-header in config

return setmetatable(self, mt)
end

function _M.set_endtime(self,key,time_window)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to expose these methods now?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, removed

@@ -91,16 +92,18 @@ function _M.incoming(self, key)
local window = self.window
key = self.plugin_name .. tostring(key)

local remaining, err = red:eval(script, 1, key, limit, window)
local res, err = red:eval(script, 1, key, limit, window)
local remaining = res[1]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use the res after checking the err

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@@ -807,6 +807,7 @@ nginx_config:
internal-status: 20m
plugin-limit-req: 20m
plugin-limit-count: 20m
plugin-limit-count-reset-header: 20m
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need this setting, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I forget to remove

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I'm removed this. Pls review again

end

function _M.incoming(self, key, commit, conf)
local delay, remaining = self.limit_count:incoming(key, commit)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
local delay, remaining = self.limit_count:incoming(key, commit)
local delay, remaining = self.limit_count:incoming(key, commit)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -302,7 +309,8 @@ function _M.rate_limit(conf, ctx)

if conf.show_limit_quota_header then
core.response.set_header("X-RateLimit-Limit", conf.count,
"X-RateLimit-Remaining", remaining)
"X-RateLimit-Remaining", remaining,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why change 4-spaces indentation to 8-spaces here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

-- 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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

-- set an end time
local end_time = ngx_time() + time_window
-- save to dict by key
self.dict:set(key, end_time, time_window)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to check the err returned from set.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

function _M.incoming(self, key)
local conf = self.conf
local red, err = redis_cli(conf)
if err then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if err then
if not red then

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

local conf = self.conf
local red, err = redis_cli(conf)
if err then
return red, err
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return red, err
return nil, err, 0

Please follow the code style: https://github.com/apache/apisix/blob/master/CODE_STYLE.md#error-handling

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@spacewander spacewander merged commit bca13a3 into apache:master Jan 10, 2023
@mscb402 mscb402 deleted the feat/support_X-RateLimit-Reset branch January 17, 2023 06:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants