Skip to content

Commit

Permalink
add "scope" of the key and "error_settings" and define "minimum"
Browse files Browse the repository at this point in the history
  • Loading branch information
y-tabata committed Apr 20, 2018
1 parent e66bb69 commit 4ab2ac8
Show file tree
Hide file tree
Showing 5 changed files with 316 additions and 95 deletions.
6 changes: 3 additions & 3 deletions examples/policies/rate_limit_configuration.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,20 @@ local rate_limit_policy = require('apicast.policy.rate_limit').new({
limiters = {
{
name = "connections",
key = "limit1",
key = {name = "limit1"},
conn = 20,
burst = 10,
delay = 0.5
},
{
name = "leaky_bucket",
key = "limit2",
key = {name = "limit2"},
rate = 18,
burst = 9
},
{
name = "fixed_window",
key = "limit3",
key = {name = "limit3"},
count = 10,
window = 10
}},
Expand Down
166 changes: 145 additions & 21 deletions gateway/src/apicast/policy/rate_limit/apicast-policy.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"description": "List of limiters to be applied",
"type": "array",
"items": {
"oneOf": [{
"anyOf": [{
"type": "object",
"properties": {
"name": {
Expand All @@ -20,20 +20,45 @@
"description": "Limiting request concurrency (or concurrent connections)"
},
"key": {
"description": "The user key corresponding to the limiter object. This must be unique in the scope.",
"type": "string"
"description": "The key corresponding to the limiter object",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name of the key, must be unique in the scope"
},
"scope": {
"type": "string",
"description": "Scope of the key",
"default": "global",
"oneOf": [{
"enum": ["global"],
"description": "Global scope, affecting to all services"
}, {
"enum": ["service"],
"description": "Service scope, affecting to one service"
}]
},
"service_name": {
"type": "string",
"description": "Name of service, necessary for service scope"
}
}
},
"conn": {
"type": "integer",
"description": "The maximum number of concurrent requests allowed"
"description": "The maximum number of concurrent requests allowed",
"exclusiveminimum": 0
},
"burst": {
"type": "integer",
"description": "The number of excessive concurrent requests (or connections) allowed to be delayed"
"description": "The number of excessive concurrent requests (or connections) allowed to be delayed",
"minimum": 0
},
"delay": {
"type": "number",
"description": "The default processing latency of a typical connection (or request)"
"description": "The default processing latency of a typical connection (or request)",
"exclusiveMinimum": 0
}
}
}, {
Expand All @@ -45,16 +70,40 @@
"description": "Limiting request rate"
},
"key": {
"description": "The user key corresponding to the limiter object. This must be unique in the scope.",
"type": "string"
"description": "The key corresponding to the limiter object",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name of the key, must be unique in the scope"
},
"scope": {
"type": "string",
"description": "Scope of the key",
"default": "global",
"oneOf": [{
"enum": ["global"],
"description": "Global scope, affecting to all services"
}, {
"enum": ["service"],
"description": "Service scope, affecting to one service"
}]
},
"service_name": {
"type": "string",
"description": "Name of service, necessary for service scope"
}
}
},
"rate": {
"type": "integer",
"description": "The specified request rate (number per second) threshold"
"description": "The specified request rate (number per second) threshold",
"exclusiveMinimum": 0
},
"burst": {
"type": "integer",
"description": "The number of excessive requests per second allowed to be delayed"
"description": "The number of excessive requests per second allowed to be delayed",
"minimum": 0
}
}
}, {
Expand All @@ -66,16 +115,40 @@
"description": "Limiting request counts"
},
"key": {
"description": "The user key corresponding to the limiter object. This must be unique in the scope.",
"type": "string"
"description": "The key corresponding to the limiter object",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name of the key, must be unique in the scope"
},
"scope": {
"type": "string",
"description": "Scope of the key",
"default": "global",
"oneOf": [{
"enum": ["global"],
"description": "Global scope, affecting to all services"
}, {
"enum": ["service"],
"description": "Service scope, affecting to one service"
}]
},
"service_name": {
"type": "string",
"description": "Name of service, necessary for service scope"
}
}
},
"count": {
"type": "integer",
"description": "The specified number of requests threshold"
"description": "The specified number of requests threshold",
"exclusiveMinimum": 0
},
"window": {
"type": "integer",
"description": "The time window in seconds before the request count is reset"
"description": "The time window in seconds before the request count is reset",
"exclusiveMinimum": 0
}
}
}]
Expand All @@ -85,13 +158,64 @@
"description": "URL of Redis",
"type": "string"
},
"status_code_rejected": {
"type": "integer",
"description": "The status code when requests over the limit, default 429"
},
"logging_only": {
"type": "boolean",
"description": "If true, the request goes through when there is some issue with rate limiting, default false"
"error_settings": {
"description": "List of error settings",
"type": "array",
"items": {
"anyOf": [{
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["limits_exceeded"],
"description": "The error setting when requests over the limit"
},
"status_code": {
"type": "integer",
"description": "The status code when requests over the limit",
"default": 429
},
"error_handling": {
"type": "string",
"description": "How to handle an error",
"default": "exit",
"oneOf": [{
"enum": ["exit"],
"description": "Respond with an error"
}, {
"enum": ["log"],
"description": "Let the request go through and only output logs"
}]
}
}
}, {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["configuration_issue"],
"description": "The error setting when there is some configuration issue"
},
"status_code": {
"type": "integer",
"description": "The status code when there is some configuration issue",
"default": 500
},
"error_handling": {
"type": "string",
"description": "How to handle an error",
"default": "exit",
"oneOf": [{
"enum": ["exit"],
"description": "Respond with an error"
}, {
"enum": ["log"],
"description": "Let the request go through and only output logs"
}]
}
}
}]
}
}
}
}
Expand Down
63 changes: 49 additions & 14 deletions gateway/src/apicast/policy/rate_limit/rate_limit.lua
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,17 @@ local traffic_limiters = {
end
}

local default_error_settings = {
limits_exceeded = {
status_code = 429,
error_handling = "exit"
},
configuration_issue = {
status_code = 500,
error_handling = "exit"
}
}

local function redis_shdict(url)
local options = { url = url }
local redis, err = ts.connect_redis(options)
Expand Down Expand Up @@ -61,19 +72,35 @@ local function redis_shdict(url)
}
end

local function error(logging_only, status_code)
if not logging_only then
return ngx.exit(status_code)
local function error(error_settings, type)
if error_settings[type]["error_handling"] == "exit" then
return ngx.exit(error_settings[type]["status_code"])
end
end

local function init_error_settings(config_error_settings)
local error_settings = default_error_settings
if config_error_settings then
for _, error_setting in pairs(config_error_settings) do
if error_setting.type then
if error_setting.status_code then
error_settings[error_setting.type]["status_code"] = error_setting.status_code
end
if error_setting.error_handling then
error_settings[error_setting.type]["error_handling"] = error_setting.error_handling
end
end
end
end
return error_settings
end

function _M.new(config)
local self = new()
self.config = config or {}
self.limiters = config.limiters
self.redis_url = config.redis_url
self.status_code_rejected = config.status_code_rejected or 429
self.logging_only = config.logging_only or false
self.error_settings = init_error_settings(config.error_settings)

return self
end
Expand All @@ -88,7 +115,7 @@ function _M:access()
red, rederr = redis_shdict(self.redis_url)
if not red then
ngx.log(ngx.ERR, "failed to connect Redis: ", rederr)
error(self.logging_only, 500)
error(self.error_settings, "configuration_issue")
return
end
end
Expand All @@ -97,14 +124,22 @@ function _M:access()
local lim, initerr = traffic_limiters[limiter.name](limiter)
if not lim then
ngx.log(ngx.ERR, "unknown limiter: ", limiter.name, ", err: ", initerr)
error(self.logging_only, 500)
error(self.error_settings, "configuration_issue")
return
end

lim.dict = red or lim.dict

table.insert(limiters, lim)
table.insert(keys, limiter.key)

local key
if limiter.key.scope == "service" then
key = limiter.key.service_name.."_"..limiter.name.."_"..limiter.key.name
else
key = limiter.name.."_"..limiter.key.name
end

table.insert(keys, key)

end

Expand All @@ -116,11 +151,11 @@ function _M:access()
if not delay then
if comerr == "rejected" then
ngx.log(ngx.WARN, "Requests over the limit.")
error(self.logging_only, self.status_code_rejected)
error(self.error_settings, "limits_exceeded")
return
end
ngx.log(ngx.ERR, "failed to limit traffic: ", comerr)
error(self.logging_only, 500)
error(self.error_settings, "configuration_issue")
return
end

Expand All @@ -144,7 +179,7 @@ function _M:access()

end

local function checkin(_, ctx, time, semaphore, redis_url, logging_only)
local function checkin(_, ctx, time, semaphore, redis_url, error_settings)
local limiters = ctx.limiters
local keys = ctx.keys
local latency = tonumber(time)
Expand All @@ -155,7 +190,7 @@ local function checkin(_, ctx, time, semaphore, redis_url, logging_only)
red, rederr = redis_shdict(redis_url)
if not red then
ngx.log(ngx.ERR, "failed to connect Redis: ", rederr)
error(logging_only, 500)
error(error_settings, "configuration_issue")
return
end
end
Expand All @@ -166,7 +201,7 @@ local function checkin(_, ctx, time, semaphore, redis_url, logging_only)
local conn, err = lim:leaving(keys[i], latency)
if not conn then
ngx.log(ngx.ERR, "failed to record the connection leaving request: ", err)
error(logging_only, 500)
error(error_settings, "configuration_issue")
return
end
end
Expand All @@ -182,7 +217,7 @@ function _M:log()
local limiters = ctx.limiters
if limiters and next(limiters) ~= nil then
local semaphore = ngx_semaphore.new()
ngx.timer.at(0, checkin, ngx.ctx, ngx.var.request_time, semaphore, self.redis_url, self.logging_only)
ngx.timer.at(0, checkin, ngx.ctx, ngx.var.request_time, semaphore, self.redis_url, self.error_settings)
semaphore:wait(10)
end
end
Expand Down
Loading

0 comments on commit 4ab2ac8

Please sign in to comment.