Skip to content

Commit

Permalink
Kong#732 Add message body to response-ratelimiting plugin, block requ…
Browse files Browse the repository at this point in the history
…ests via access.lua, not header_filter.lua
  • Loading branch information
vagrant committed Nov 21, 2015
1 parent bbd609e commit 86b4b48
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 18 deletions.
15 changes: 15 additions & 0 deletions kong/plugins/response-ratelimiting/access.lua
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,21 @@ function _M.execute(conf)
-- Load current metric for configured period
local usage = get_current_usage(api_id, identifier, current_timestamp, conf.limits)
ngx.ctx.usage = usage -- For later use

--local usage = ngx.ctx.usage -- Load current usage
local stop
for limit_name, v in pairs(usage) do
for period_name, lv in pairs(usage[limit_name]) do
if lv.remaining <= 0 then
stop = true -- No more
end
end
end

if stop then
ngx.ctx.stop_log = true
return responses.send(429, "API quota exceeded")
end
end

return _M
11 changes: 1 addition & 10 deletions kong/plugins/response-ratelimiting/header_filter.lua
Original file line number Diff line number Diff line change
Expand Up @@ -33,26 +33,17 @@ function _M.execute(conf)

local usage = ngx.ctx.usage -- Load current usage

local stop
-- Set headers describing the relevant limits to API users
for limit_name, v in pairs(usage) do
for period_name, lv in pairs(usage[limit_name]) do
ngx.header[constants.HEADERS.RATELIMIT_LIMIT.."-"..limit_name.."-"..period_name] = lv.limit
ngx.header[constants.HEADERS.RATELIMIT_REMAINING.."-"..limit_name.."-"..period_name] = math.max(0, lv.remaining - (increments[limit_name] and increments[limit_name] or 0)) -- increment_value for this current request

if increments[limit_name] and increments[limit_name] > 0 and lv.remaining <= 0 then
stop = true -- No more
end
end
end

-- Remove header
ngx.header[conf.header_name] = nil

-- If limit is exceeded, terminate the request
if stop then
ngx.ctx.stop_log = true
return responses.send(429) -- Don't set a body
end
end

return _M
41 changes: 33 additions & 8 deletions spec/plugins/response-ratelimiting/access_spec.lua
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
local spec_helper = require "spec.spec_helpers"
local http_client = require "kong.tools.http_client"
local timestamp = require "kong.tools.timestamp"
local cjson = require "cjson"

local PROXY_URL = spec_helper.PROXY_URL
local SLEEP_VALUE = "0.5"
local RESPONSE_BLOCKED_MESSAGE = "API quota exceeded"

local function wait()
-- If the minute elapses in the middle of the test, then the test will
Expand Down Expand Up @@ -68,8 +70,10 @@ describe("RateLimiting Plugin", function()
end

-- Additonal request, while limit is 6/minute
local _, status = http_client.get(PROXY_URL.."/response-headers", {["x-kong-limit"] = "video=1"}, {host = "test1.com"})
local response, status = http_client.get(PROXY_URL.."/response-headers", {["x-kong-limit"] = "video=1"}, {host = "test1.com"})
local body = cjson.decode(response)
assert.are.equal(429, status)
assert.are.equal(RESPONSE_BLOCKED_MESSAGE, body.message)

end)

Expand All @@ -88,9 +92,10 @@ describe("RateLimiting Plugin", function()
os.execute("sleep "..SLEEP_VALUE) -- The increment happens in log_by_lua, give it some time
end

local _, status, headers = http_client.get(PROXY_URL.."/response-headers", {["x-kong-limit"] = "video=2, image = 1"}, {host = "test2.com"})

local response, status, headers = http_client.get(PROXY_URL.."/response-headers", {["x-kong-limit"] = "video=2, image = 1"}, {host = "test2.com"})
local body = cjson.decode(response)
assert.are.equal(429, status)
assert.are.equal(RESPONSE_BLOCKED_MESSAGE, body.message)
assert.are.equal("0", headers["x-ratelimit-remaining-video-minute"])
assert.are.equal("4", headers["x-ratelimit-remaining-video-hour"])
assert.are.equal("1", headers["x-ratelimit-remaining-image-minute"])
Expand All @@ -115,8 +120,10 @@ describe("RateLimiting Plugin", function()
end

-- Third query, while limit is 2/minute
local _, status, headers = http_client.get(PROXY_URL.."/response-headers", {apikey = "apikey123", ["x-kong-limit"] = "video=1"}, {host = "test3.com"})
local response, status, headers = http_client.get(PROXY_URL.."/response-headers", {apikey = "apikey123", ["x-kong-limit"] = "video=1"}, {host = "test3.com"})
local body = cjson.decode(response)
assert.are.equal(429, status)
assert.are.equal(RESPONSE_BLOCKED_MESSAGE, body.message)
assert.are.equal("0", headers["x-ratelimit-remaining-video-minute"])
assert.are.equal("2", headers["x-ratelimit-limit-video-minute"])
end)
Expand All @@ -125,19 +132,35 @@ describe("RateLimiting Plugin", function()
-- Default ratelimiting plugin for this API says 6/minute
local limit = 6

for i = 1, limit do
-- Do 5 requests while incrementing the limit
for i = 1, (limit-1) do
local _, status, headers = http_client.get(PROXY_URL.."/response-headers", {apikey = "apikey124", ["x-kong-limit"] = "video=1"}, {host = "test3.com"})
assert.are.equal(200, status)
assert.are.same(tostring(limit), headers["x-ratelimit-limit-video-minute"])
assert.are.same(tostring(limit - i), headers["x-ratelimit-remaining-video-minute"])
os.execute("sleep "..SLEEP_VALUE) -- The increment happens in log_by_lua, give it some time
end

-- Third query, while limit is 2/minute
-- Do a 6th request that doesn't increment the limit:
local _, status, headers = http_client.get(PROXY_URL.."/response-headers", {apikey = "apikey124"}, {host = "test3.com"})
assert.are.equal(200, status)
assert.are.equal("1", headers["x-ratelimit-remaining-video-minute"])
assert.are.equal("6", headers["x-ratelimit-limit-video-minute"])

-- Do a 7th request that increments the limit and should NOT be blocked because the previous didn't increment:
local _, status, headers = http_client.get(PROXY_URL.."/response-headers", {apikey = "apikey124", ["x-kong-limit"] = "video=1"}, {host = "test3.com"})
assert.are.equal(200, status)
assert.are.equal("0", headers["x-ratelimit-remaining-video-minute"])
assert.are.equal("6", headers["x-ratelimit-limit-video-minute"])

-- Do a another request that gets blocked:
local response, status, headers = http_client.get(PROXY_URL.."/response-headers", {apikey = "apikey124", ["x-kong-limit"] = "video=1"}, {host = "test3.com"})
local body = cjson.decode(response)
assert.are.equal(429, status)
assert.are.equal(RESPONSE_BLOCKED_MESSAGE, body.message)
assert.are.equal("0", headers["x-ratelimit-remaining-video-minute"])
assert.are.equal("6", headers["x-ratelimit-limit-video-minute"])

end)

it("should get blocked if exceeding limit", function()
Expand All @@ -152,9 +175,11 @@ describe("RateLimiting Plugin", function()
os.execute("sleep "..SLEEP_VALUE) -- The increment happens in log_by_lua, give it some time
end

-- Third query, while limit is 2/minute
local _, status, headers = http_client.get(PROXY_URL.."/response-headers", {apikey = "apikey125", ["x-kong-limit"] = "video=1"}, {host = "test3.com"})
-- Seventh query, while limit is 6/minute
local response, status, headers = http_client.get(PROXY_URL.."/response-headers", {apikey = "apikey125", ["x-kong-limit"] = "video=1"}, {host = "test3.com"})
local body = cjson.decode(response)
assert.are.equal(429, status)
assert.are.equal(RESPONSE_BLOCKED_MESSAGE, body.message)
assert.are.equal("0", headers["x-ratelimit-remaining-video-minute"])
assert.are.equal("6", headers["x-ratelimit-limit-video-minute"])
end)
Expand Down

0 comments on commit 86b4b48

Please sign in to comment.