feat: limit count plugin support X-RateLimit-Reset#8578
Conversation
…and return header when rejected
| type = 'plugin', | ||
| }) | ||
|
|
||
| local resetlru = core.lrucache.new({ |
There was a problem hiding this comment.
| local resetlru = core.lrucache.new({ | |
| local reset_lru = core.lrucache.new({ |
| } | ||
| end | ||
|
|
||
| return resetlru(key,"",create,conf) |
There was a problem hiding this comment.
Don't forget to add space around the operator
| end | ||
|
|
||
| local function get_end_time(conf,key) | ||
| local create = function() |
There was a problem hiding this comment.
Please avoid creating new function per call
| ## 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`: |
There was a problem hiding this comment.
Why the English version doesn't match the Chinese one?
| 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) |
There was a problem hiding this comment.
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:
- 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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
I'm using ngx.shared with expire time instead.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
Can we put this into the eval script to make it atomic and reduce roundtrip?
| __index = _M | ||
| } | ||
|
|
||
| function _M.set_endtime(self,key,time_window) |
There was a problem hiding this comment.
Look like we can merge set_endtime into the incoming method, especially after implementing read_reset via eval?
| {% 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"] *}; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
I removed plugin-limit-count-reset-header in config
| return setmetatable(self, mt) | ||
| end | ||
|
|
||
| function _M.set_endtime(self,key,time_window) |
There was a problem hiding this comment.
We don't need to expose these methods now?
|
|
||
| local remaining, err = red:eval(script, 1, key, limit, window) | ||
| local res, err = red:eval(script, 1, key, limit, window) | ||
| local remaining = res[1] |
There was a problem hiding this comment.
Please use the res after checking the err
| internal-status: 20m | ||
| plugin-limit-req: 20m | ||
| plugin-limit-count: 20m | ||
| plugin-limit-count-reset-header: 20m |
There was a problem hiding this comment.
We don't need this setting, right?
There was a problem hiding this comment.
Yes, I forget to remove
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
| local delay, remaining = self.limit_count:incoming(key, commit) | |
| local delay, remaining = self.limit_count:incoming(key, commit) |
| if conf.show_limit_quota_header then | ||
| core.response.set_header("X-RateLimit-Limit", conf.count, | ||
| "X-RateLimit-Remaining", remaining) | ||
| "X-RateLimit-Remaining", remaining, |
There was a problem hiding this comment.
Why change 4-spaces indentation to 8-spaces here?
| -- 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, |
| -- set an end time | ||
| local end_time = ngx_time() + time_window | ||
| -- save to dict by key | ||
| self.dict:set(key, end_time, time_window) |
There was a problem hiding this comment.
Better to check the err returned from set.
| function _M.incoming(self, key) | ||
| local conf = self.conf | ||
| local red, err = redis_cli(conf) | ||
| if err then |
There was a problem hiding this comment.
| if err then | |
| if not red then |
| local conf = self.conf | ||
| local red, err = redis_cli(conf) | ||
| if err then | ||
| return red, err |
There was a problem hiding this comment.
| return red, err | |
| return nil, err, 0 |
Please follow the code style: https://github.com/apache/apisix/blob/master/CODE_STYLE.md#error-handling
Description
limit count plugin support returns a new header
X-RateLimit-Resetand return the header when RejectedFixes #8381
Checklist