From 5e09745add686a32359201903d12f62138d51489 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Lajoie?= Date: Tue, 8 Aug 2023 11:24:30 +0200 Subject: [PATCH] feat(proxy): Allow to remove the proxy cache headers (#10445) The following headers: * X-Cache-Status * X-Cache-Key * Age are now set only if the header kong-debug is set to 1 And they are removed from the cache value Keep previous behavior with default configuration. Debug header allows to get all info, each header has a configuration field to be or not into the response --- CHANGELOG.md | 2 + kong/clustering/compat/removed_fields.lua | 3 + kong/plugins/proxy-cache/handler.lua | 47 +- kong/plugins/proxy-cache/schema.lua | 10 + .../31-proxy-cache/02-access_spec.lua | 467 ++++++------------ .../03-plugins/31-proxy-cache/03-api_spec.lua | 136 ++--- .../31-proxy-cache/04-invalidations_spec.lua | 128 ++--- 7 files changed, 288 insertions(+), 505 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e0266ed1ef6..b72b9c1847a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -238,6 +238,8 @@ - Tracing: tracing_sampling_rate defaults to 0.01 (trace one of every 100 requests) instead of the previous 1 (trace all requests). Tracing all requests is inappropriate for most production systems [#10774](https://github.com/Kong/kong/pull/10774) +- **Proxy Cache**: Add option to remove the proxy cache headers from the response + [#10445](https://github.com/Kong/kong/pull/10445) ### Additions diff --git a/kong/clustering/compat/removed_fields.lua b/kong/clustering/compat/removed_fields.lua index 4bbdbd99cca0..d41af4bf0f63 100644 --- a/kong/clustering/compat/removed_fields.lua +++ b/kong/clustering/compat/removed_fields.lua @@ -92,5 +92,8 @@ return { rate_limiting = { "sync_rate", }, + proxy_cache = { + "response_headers", + }, }, } diff --git a/kong/plugins/proxy-cache/handler.lua b/kong/plugins/proxy-cache/handler.lua index 4681133e1ecd..e6ff113b3f27 100644 --- a/kong/plugins/proxy-cache/handler.lua +++ b/kong/plugins/proxy-cache/handler.lua @@ -56,6 +56,23 @@ local function overwritable_header(header) and not ngx_re_match(n_header, "ratelimit-remaining") end +local function set_header(conf, header, value) + if ngx.var.http_kong_debug or conf.response_headers[header] then + kong.response.set_header(header, value) + end +end + +local function reset_res_header(res) + res.headers["Age"] = nil + res.headers["X-Cache-Status"] = nil + res.headers["X-Cache-Key"] = nil +end + +local function set_res_header(res, header, value, conf) + if ngx.var.http_kong_debug or conf.response_headers[header] then + res.headers[header] = value + end +end local function parse_directive_header(h) if not h then @@ -219,12 +236,11 @@ end -- indicate that we should attempt to cache the response to this request -local function signal_cache_req(ctx, cache_key, cache_status) +local function signal_cache_req(ctx, conf, cache_key, cache_status) ctx.proxy_cache = { cache_key = cache_key, } - - kong.response.set_header("X-Cache-Status", cache_status or "Miss") + set_header(conf, "X-Cache-Status", cache_status or "Miss") end @@ -280,7 +296,7 @@ function ProxyCacheHandler:access(conf) -- if we know this request isnt cacheable, bail out if not cacheable_request(conf, cc) then - kong.response.set_header("X-Cache-Status", "Bypass") + set_header(conf, "X-Cache-Status", "Bypass") return end @@ -305,7 +321,7 @@ function ProxyCacheHandler:access(conf) return end - kong.response.set_header("X-Cache-Key", cache_key) + set_header(conf, "X-Cache-Key", cache_key) -- try to fetch the cached object from the computed cache key local strategy = require(STRATEGY_PATH)({ @@ -328,7 +344,7 @@ function ProxyCacheHandler:access(conf) -- this request is cacheable but wasn't found in the data store -- make a note that we should store it in cache later, -- and pass the request upstream - return signal_cache_req(ctx, cache_key) + return signal_cache_req(ctx, conf, cache_key) elseif err then kong.log.err(err) @@ -338,29 +354,29 @@ function ProxyCacheHandler:access(conf) if res.version ~= CACHE_VERSION then kong.log.notice("cache format mismatch, purging ", cache_key) strategy:purge(cache_key) - return signal_cache_req(ctx, cache_key, "Bypass") + return signal_cache_req(ctx, conf, cache_key, "Bypass") end -- figure out if the client will accept our cache value if conf.cache_control then if cc["max-age"] and time() - res.timestamp > cc["max-age"] then - return signal_cache_req(ctx, cache_key, "Refresh") + return signal_cache_req(ctx, conf, cache_key, "Refresh") end if cc["max-stale"] and time() - res.timestamp - res.ttl > cc["max-stale"] then - return signal_cache_req(ctx, cache_key, "Refresh") + return signal_cache_req(ctx, conf, cache_key, "Refresh") end if cc["min-fresh"] and res.ttl - (time() - res.timestamp) < cc["min-fresh"] then - return signal_cache_req(ctx, cache_key, "Refresh") + return signal_cache_req(ctx, conf, cache_key, "Refresh") end else -- don't serve stale data; res may be stored for up to `conf.storage_ttl` secs if time() - res.timestamp > conf.cache_ttl then - return signal_cache_req(ctx, cache_key, "Refresh") + return signal_cache_req(ctx, conf, cache_key, "Refresh") end end @@ -385,8 +401,11 @@ function ProxyCacheHandler:access(conf) end end - res.headers["Age"] = floor(time() - res.timestamp) - res.headers["X-Cache-Status"] = "Hit" + + reset_res_header(res) + set_res_header(res, "Age", floor(time() - res.timestamp), conf) + set_res_header(res, "X-Cache-Status", "Hit", conf) + set_res_header(res, "X-Cache-Key", cache_key, conf) return kong.response.exit(res.status, res.body, res.headers) end @@ -411,7 +430,7 @@ function ProxyCacheHandler:header_filter(conf) proxy_cache.res_ttl = conf.cache_control and resource_ttl(cc) or conf.cache_ttl else - kong.response.set_header("X-Cache-Status", "Bypass") + set_header(conf, "X-Cache-Status", "Bypass") ctx.proxy_cache = nil end diff --git a/kong/plugins/proxy-cache/schema.lua b/kong/plugins/proxy-cache/schema.lua index e9b249258719..2e34dd482c0e 100644 --- a/kong/plugins/proxy-cache/schema.lua +++ b/kong/plugins/proxy-cache/schema.lua @@ -74,6 +74,16 @@ return { { vary_headers = { description = "Relevant headers considered for the cache key. If undefined, none of the headers are taken into consideration.", type = "array", elements = { type = "string" }, }}, + { response_headers = { + type = "record", + fields = { + { age = {type = "boolean", default = true} }, + { ["X-Cache-Status"] = {type = "boolean", default = true} }, + { ["X-Cache-Key"] = {type = "boolean", default = true} }, + }, + }}, + + }, } }, diff --git a/spec/03-plugins/31-proxy-cache/02-access_spec.lua b/spec/03-plugins/31-proxy-cache/02-access_spec.lua index 5716a6ea458e..9498929906b9 100644 --- a/spec/03-plugins/31-proxy-cache/02-access_spec.lua +++ b/spec/03-plugins/31-proxy-cache/02-access_spec.lua @@ -1,6 +1,13 @@ local helpers = require "spec.helpers" local strategies = require("kong.plugins.proxy-cache.strategies") +local function get(client, host) + return assert(client:get("/get", { + headers = { + host = host, + }, + })) +end --local TIMEOUT = 10 -- default timeout for non-memory strategies @@ -92,7 +99,9 @@ do local route21 = assert(bp.routes:insert { hosts = { "route-21.com" }, }) - + local route22 = assert(bp.routes:insert({ + hosts = { "route-22.com" }, + })) local consumer1 = assert(bp.consumers:insert { username = "bob", @@ -312,6 +321,21 @@ do }, }) + assert(bp.plugins:insert { + name = "proxy-cache", + route = { id = route22.id }, + config = { + strategy = policy, + content_type = { "text/plain", "application/json" }, + [policy] = policy_config, + response_headers = { + age = false, + ["X-Cache-Status"] = false, + ["X-Cache-Key"] = false + }, + }, + }) + assert(helpers.start_kong({ plugins = "bundled", nginx_conf = "spec/fixtures/custom_nginx.template", @@ -344,13 +368,7 @@ do end) it("caches a simple request", function() - local res = assert(client:send { - method = "GET", - path = "/get", - headers = { - host = "route-1.com", - } - }) + local res = assert(get(client, "route-1.com")) local body1 = assert.res_status(200, res) assert.same("Miss", res.headers["X-Cache-Status"]) @@ -365,13 +383,7 @@ do -- return strategy:fetch(cache_key1) ~= nil --end, TIMEOUT) - local res = client:send { - method = "GET", - path = "/get", - headers = { - host = "route-1.com", - } - } + local res = assert(get(client, "route-1.com")) local body2 = assert.res_status(200, res) assert.same("Hit", res.headers["X-Cache-Status"]) @@ -384,15 +396,28 @@ do -- examine this cache key against another plugin's cache key for the same req --cache_key = cache_key1 end) + it("No X-Cache* neither age headers on the response without debug header in the query", function() + local res = assert(get(client, "route-22.com")) + assert.res_status(200, res) + assert.is_nil(res.headers["X-Cache-Status"]) + res = assert(get(client, "route-22.com")) + assert.res_status(200, res) + assert.is_nil(res.headers["X-Cache-Status"]) + assert.is_nil(res.headers["X-Cache-Key"]) + assert.is_nil(res.headers["Age"]) + res = assert(client:get("/get", { + headers = { + Host = "route-22.com", + ["kong-debug"] = 1, + }, + })) + assert.same("Hit", res.headers["X-Cache-Status"]) + assert.is_not_nil(res.headers["Age"]) + assert.is_not_nil(res.headers["X-Cache-Key"]) + end) it("respects cache ttl", function() - local res = assert(client:send { - method = "GET", - path = "/get", - headers = { - host = "route-6.com", - } - }) + local res = assert(get(client, "route-6.com")) --local cache_key2 = res.headers["X-Cache-Key"] assert.res_status(200, res) @@ -403,13 +428,7 @@ do -- return strategy:fetch(cache_key2) ~= nil --end, TIMEOUT) - res = client:send { - method = "GET", - path = "/get", - headers = { - host = "route-6.com", - } - } + res = assert(get(client, "route-6.com")) assert.res_status(200, res) assert.same("Hit", res.headers["X-Cache-Status"]) @@ -427,13 +446,7 @@ do --end, TIMEOUT) -- and go through the cycle again - res = assert(client:send { - method = "GET", - path = "/get", - headers = { - host = "route-6.com", - } - }) + res = assert(get(client, "route-6.com")) assert.res_status(200, res) assert.same("Miss", res.headers["X-Cache-Status"]) @@ -444,25 +457,13 @@ do -- return strategy:fetch(cache_key) ~= nil --end, TIMEOUT) - res = assert(client:send { - method = "GET", - path = "/get", - headers = { - host = "route-6.com", - } - }) + res = assert(get(client, "route-6.com")) assert.res_status(200, res) assert.same("Hit", res.headers["X-Cache-Status"]) -- examine the behavior of keeping cache in memory for longer than ttl - res = assert(client:send { - method = "GET", - path = "/get", - headers = { - host = "route-9.com", - } - }) + res = assert(get(client, "route-9.com")) assert.res_status(200, res) assert.same("Miss", res.headers["X-Cache-Status"]) @@ -473,13 +474,7 @@ do -- return strategy:fetch(cache_key) ~= nil --end, TIMEOUT) - res = assert(client:send { - method = "GET", - path = "/get", - headers = { - host = "route-9.com", - } - }) + res = assert(get(client, "route-9.com")) assert.res_status(200, res) assert.same("Hit", res.headers["X-Cache-Status"]) @@ -498,37 +493,23 @@ do --end, TIMEOUT) -- and go through the cycle again - res = assert(client:send { - method = "GET", - path = "/get", - headers = { - host = "route-9.com", - } - }) + res = assert(get(client, "route-9.com")) assert.res_status(200, res) assert.same("Refresh", res.headers["X-Cache-Status"]) - res = assert(client:send { - method = "GET", - path = "/get", - headers = { - host = "route-9.com", - } - }) + res = assert(get(client, "route-9.com")) assert.res_status(200, res) assert.same("Hit", res.headers["X-Cache-Status"]) end) it("respects cache ttl via cache control", function() - local res = assert(client:send { - method = "GET", - path = "/cache/2", + local res = assert(client:get("/cache/2", { headers = { host = "route-7.com", } - }) + })) assert.res_status(200, res) assert.same("Miss", res.headers["X-Cache-Status"]) @@ -539,13 +520,11 @@ do -- return strategy:fetch(cache_key) ~= nil --end, TIMEOUT) - res = assert(client:send { - method = "GET", - path = "/cache/2", + res = assert(client:get("/cache/2", { headers = { host = "route-7.com", } - }) + })) assert.res_status(200, res) assert.same("Hit", res.headers["X-Cache-Status"]) @@ -561,13 +540,11 @@ do --end, TIMEOUT) -- and go through the cycle again - res = assert(client:send { - method = "GET", - path = "/cache/2", + res = assert(client:get("/cache/2", { headers = { host = "route-7.com", } - }) + })) assert.res_status(200, res) assert.same("Miss", res.headers["X-Cache-Status"]) @@ -577,36 +554,30 @@ do -- return strategy:fetch(cache_key) ~= nil --end, TIMEOUT) - res = assert(client:send { - method = "GET", - path = "/cache/2", + res = assert(client:get("/cache/2", { headers = { host = "route-7.com", } - }) + })) assert.res_status(200, res) assert.same("Hit", res.headers["X-Cache-Status"]) -- assert that max-age=0 never results in caching - res = assert(client:send { - method = "GET", - path = "/cache/0", + res = assert(client:get("/cache/0", { headers = { host = "route-7.com", } - }) + })) assert.res_status(200, res) assert.same("Bypass", res.headers["X-Cache-Status"]) - res = assert(client:send { - method = "GET", - path = "/cache/0", + res = assert(client:get("/cache/0", { headers = { host = "route-7.com", } - }) + })) assert.res_status(200, res) assert.same("Bypass", res.headers["X-Cache-Status"]) @@ -615,26 +586,22 @@ do it("public not present in Cache-Control, but max-age is", function() -- httpbin's /cache endpoint always sets "Cache-Control: public" -- necessary to set it manually using /response-headers instead - local res = assert(client:send { - method = "GET", - path = "/response-headers?Cache-Control=max-age%3D604800", + local res = assert(client:get("/response-headers?Cache-Control=max-age%3D604800", { headers = { host = "route-7.com", } - }) + })) assert.res_status(200, res) assert.same("Miss", res.headers["X-Cache-Status"]) end) it("Cache-Control contains s-maxage only", function() - local res = assert(client:send { - method = "GET", - path = "/response-headers?Cache-Control=s-maxage%3D604800", + local res = assert(client:get("/response-headers?Cache-Control=s-maxage%3D604800", { headers = { host = "route-7.com", } - }) + })) assert.res_status(200, res) assert.same("Miss", res.headers["X-Cache-Status"]) @@ -642,14 +609,12 @@ do it("Expires present, Cache-Control absent", function() local httpdate = ngx.escape_uri(os.date("!%a, %d %b %Y %X %Z", os.time()+5000)) - local res = assert(client:send { - method = "GET", - path = "/response-headers", + local res = assert(client:get("/response-headers", { query = "Expires=" .. httpdate, headers = { host = "route-7.com", } - }) + })) assert.res_status(200, res) assert.same("Miss", res.headers["X-Cache-Status"]) @@ -658,28 +623,24 @@ do describe("respects cache-control", function() it("min-fresh", function() -- bypass via unsatisfied min-fresh - local res = assert(client:send { - method = "GET", - path = "/cache/2", + local res = assert(client:get("/cache/2", { headers = { host = "route-7.com", - ["Cache-Control"] = "min-fresh=30" + ["Cache-Control"] = "min-fresh=30", } - }) + })) assert.res_status(200, res) assert.same("Refresh", res.headers["X-Cache-Status"]) end) it("max-age", function() - local res = assert(client:send { - method = "GET", - path = "/cache/10", + local res = assert(client:get("/cache/10", { headers = { host = "route-7.com", - ["Cache-Control"] = "max-age=2" + ["Cache-Control"] = "max-age=2", } - }) + })) assert.res_status(200, res) assert.same("Miss", res.headers["X-Cache-Status"]) @@ -690,14 +651,12 @@ do -- return strategy:fetch(cache_key) ~= nil --end, TIMEOUT) - res = assert(client:send { - method = "GET", - path = "/cache/10", + res = assert(client:get("/cache/10", { headers = { host = "route-7.com", - ["Cache-Control"] = "max-age=2" + ["Cache-Control"] = "max-age=2", } - }) + })) assert.res_status(200, res) assert.same("Hit", res.headers["X-Cache-Status"]) @@ -714,27 +673,23 @@ do -- return ngx.time() - obj.timestamp > 2 --end, TIMEOUT) - res = assert(client:send { - method = "GET", - path = "/cache/10", + res = assert(client:get("/cache/10", { headers = { host = "route-7.com", - ["Cache-Control"] = "max-age=2" + ["Cache-Control"] = "max-age=2", } - }) + })) assert.res_status(200, res) assert.same("Refresh", res.headers["X-Cache-Status"]) end) it("max-stale", function() - local res = assert(client:send { - method = "GET", - path = "/cache/2", + local res = assert(client:get("/cache/2", { headers = { host = "route-8.com", } - }) + })) assert.res_status(200, res) assert.same("Miss", res.headers["X-Cache-Status"]) @@ -745,13 +700,11 @@ do -- return strategy:fetch(cache_key) ~= nil --end, TIMEOUT) - res = assert(client:send { - method = "GET", - path = "/cache/2", + res = assert(client:get("/cache/2", { headers = { host = "route-8.com", } - }) + })) assert.res_status(200, res) assert.same("Hit", res.headers["X-Cache-Status"]) @@ -767,41 +720,35 @@ do -- return ngx.time() - obj.timestamp - obj.ttl > 2 --end, TIMEOUT) - res = assert(client:send { - method = "GET", - path = "/cache/2", + res = assert(client:get("/cache/2", { headers = { host = "route-8.com", ["Cache-Control"] = "max-stale=1", } - }) + })) assert.res_status(200, res) assert.same("Refresh", res.headers["X-Cache-Status"]) end) it("only-if-cached", function() - local res = assert(client:send { - method = "GET", - path = "/get?not=here", + local res = assert(client:get("/get?not=here", { headers = { host = "route-8.com", ["Cache-Control"] = "only-if-cached", } - }) + })) assert.res_status(504, res) end) end) it("caches a streaming request", function() - local res = assert(client:send { - method = "GET", - path = "/stream/3", + local res = assert(client:get("/stream/3", { headers = { host = "route-1.com", } - }) + })) local body1 = assert.res_status(200, res) assert.same("Miss", res.headers["X-Cache-Status"]) @@ -813,13 +760,11 @@ do -- return strategy:fetch(cache_key) ~= nil --end, TIMEOUT) - res = assert(client:send { - method = "GET", - path = "/stream/3", + res = assert(client:get("/stream/3", { headers = { host = "route-1.com", } - }) + })) local body2 = assert.res_status(200, res) assert.same("Hit", res.headers["X-Cache-Status"]) @@ -827,25 +772,21 @@ do end) it("uses a separate cache key for the same consumer between routes", function() - local res = assert(client:send { - method = "GET", - path = "/get", + local res = assert(client:get("/get", { headers = { host = "route-13.com", apikey = "bob", } - }) + })) assert.res_status(200, res) local cache_key1 = res.headers["X-Cache-Key"] - local res = assert(client:send { - method = "GET", - path = "/get", + local res = assert(client:get("/get", { headers = { host = "route-14.com", apikey = "bob", } - }) + })) assert.res_status(200, res) local cache_key2 = res.headers["X-Cache-Key"] @@ -853,25 +794,21 @@ do end) it("uses a separate cache key for the same consumer between routes/services", function() - local res = assert(client:send { - method = "GET", - path = "/get", + local res = assert(client:get("/get", { headers = { host = "route-15.com", apikey = "bob", } - }) + })) assert.res_status(200, res) local cache_key1 = res.headers["X-Cache-Key"] - local res = assert(client:send { - method = "GET", - path = "/get", + local res = assert(client:get("/get", { headers = { host = "route-16.com", apikey = "bob", } - }) + })) assert.res_status(200, res) local cache_key2 = res.headers["X-Cache-Key"] @@ -879,13 +816,7 @@ do end) it("uses an separate cache key between routes-specific and a global plugin", function() - local res = assert(client:send { - method = "GET", - path = "/get", - headers = { - host = "route-3.com", - } - }) + local res = assert(get(client, "route-3.com")) assert.res_status(200, res) assert.same("Miss", res.headers["X-Cache-Status"]) @@ -894,13 +825,7 @@ do assert.matches("^[%w%d]+$", cache_key1) assert.equals(64, #cache_key1) - res = assert(client:send { - method = "GET", - path = "/get", - headers = { - host = "route-4.com", - } - }) + res = assert(get(client, "route-4.com")) assert.res_status(200, res) @@ -910,13 +835,7 @@ do end) it("differentiates caches between instances", function() - local res = assert(client:send { - method = "GET", - path = "/get", - headers = { - host = "route-2.com", - } - }) + local res = assert(get(client, "route-2.com")) assert.res_status(200, res) assert.same("Miss", res.headers["X-Cache-Status"]) @@ -930,13 +849,7 @@ do -- return strategy:fetch(cache_key1) ~= nil --end, TIMEOUT) - res = assert(client:send { - method = "GET", - path = "/get", - headers = { - host = "route-2.com", - } - }) + res = assert(get(client, "route-2.com")) local cache_key2 = res.headers["X-Cache-Key"] assert.res_status(200, res) @@ -945,69 +858,57 @@ do end) it("uses request params as part of the cache key", function() - local res = assert(client:send { - method = "GET", - path = "/get?a=b&b=c", + local res = assert(client:get("/get?a=b&b=c", { headers = { host = "route-1.com", } - }) + })) assert.res_status(200, res) assert.same("Miss", res.headers["X-Cache-Status"]) - res = assert(client:send { - method = "GET", - path = "/get?a=c", + res = assert(client:get("/get?a=c", { headers = { host = "route-1.com", } - }) + })) assert.res_status(200, res) assert.same("Miss", res.headers["X-Cache-Status"]) - res = assert(client:send { - method = "GET", - path = "/get?b=c&a=b", + res = assert(client:get("/get?b=c&a=b", { headers = { host = "route-1.com", } - }) + })) assert.res_status(200, res) assert.same("Hit", res.headers["X-Cache-Status"]) - res = assert(client:send { - method = "GET", - path = "/get?a&b", + res = assert(client:get("/get?a&b", { headers = { host = "route-1.com", } - }) + })) assert.res_status(200, res) assert.same("Miss", res.headers["X-Cache-Status"]) - res = assert(client:send { - method = "GET", - path = "/get?a&b", + res = assert(client:get("/get?a&b", { headers = { host = "route-1.com", } - }) + })) assert.res_status(200, res) assert.same("Hit", res.headers["X-Cache-Status"]) end) it("can focus only in a subset of the query arguments", function() - local res = assert(client:send { - method = "GET", - path = "/get?foo=b&b=c", + local res = assert(client:get("/get?foo=b&b=c", { headers = { host = "route-12.com", } - }) + })) assert.res_status(200, res) assert.same("Miss", res.headers["X-Cache-Status"]) @@ -1019,13 +920,11 @@ do -- return strategy:fetch(cache_key) ~= nil --end, TIMEOUT) - res = assert(client:send { - method = "GET", - path = "/get?b=d&foo=b", + res = assert(client:get("/get?b=d&foo=b", { headers = { host = "route-12.com", } - }) + })) assert.res_status(200, res) @@ -1033,14 +932,12 @@ do end) it("uses headers if instructed to do so", function() - local res = assert(client:send { - method = "GET", - path = "/get", + local res = assert(client:get("/get", { headers = { host = "route-11.com", - foo = "bar" + foo = "bar", } - }) + })) assert.res_status(200, res) assert.same("Miss", res.headers["X-Cache-Status"]) --local cache_key = res.headers["X-Cache-Key"] @@ -1050,88 +947,70 @@ do -- return strategy:fetch(cache_key) ~= nil --end, TIMEOUT) - res = assert(client:send { - method = "GET", - path = "/get", + res = assert(client:get("/get", { headers = { host = "route-11.com", - foo = "bar" + foo = "bar", } - }) + })) assert.res_status(200, res) assert.same("Hit", res.headers["X-Cache-Status"]) - res = assert(client:send { - method = "GET", - path = "/get", + res = assert(client:get("/get", { headers = { host = "route-11.com", - foo = "baz" + foo = "baz", } - }) + })) assert.res_status(200, res) assert.same("Miss", res.headers["X-Cache-Status"]) end) describe("handles authenticated routes", function() it("by ignoring cache if the request is unauthenticated", function() - local res = assert(client:send { - method = "GET", - path = "/get", - headers = { - host = "route-5.com", - } - }) + local res = assert(get(client, "route-5.com")) assert.res_status(401, res) assert.is_nil(res.headers["X-Cache-Status"]) end) it("by maintaining a separate cache per consumer", function() - local res = assert(client:send { - method = "GET", - path = "/get", + local res = assert(client:get("/get", { headers = { host = "route-5.com", apikey = "bob", } - }) + })) assert.res_status(200, res) assert.same("Miss", res.headers["X-Cache-Status"]) - res = assert(client:send { - method = "GET", - path = "/get", + res = assert(client:get("/get", { headers = { host = "route-5.com", apikey = "bob", } - }) + })) assert.res_status(200, res) assert.same("Hit", res.headers["X-Cache-Status"]) - local res = assert(client:send { - method = "GET", - path = "/get", + local res = assert(client:get("/get", { headers = { host = "route-5.com", apikey = "alice", } - }) + })) assert.res_status(200, res) assert.same("Miss", res.headers["X-Cache-Status"]) - res = assert(client:send { - method = "GET", - path = "/get", + res = assert(client:get("/get", { headers = { host = "route-5.com", apikey = "alice", } - }) + })) assert.res_status(200, res) assert.same("Hit", res.headers["X-Cache-Status"]) @@ -1141,9 +1020,7 @@ do describe("bypasses cache for uncacheable requests: ", function() it("request method", function() - local res = assert(client:send { - method = "POST", - path = "/post", + local res = assert(client:post("/post", { headers = { host = "route-1.com", ["Content-Type"] = "application/json", @@ -1151,7 +1028,7 @@ do { foo = "bar", }, - }) + })) assert.res_status(200, res) assert.same("Bypass", res.headers["X-Cache-Status"]) @@ -1160,26 +1037,22 @@ do describe("bypasses cache for uncacheable responses:", function() it("response status", function() - local res = assert(client:send { - method = "GET", - path = "/status/418", + local res = assert(client:get("/status/418", { headers = { host = "route-1.com", }, - }) + })) assert.res_status(418, res) assert.same("Bypass", res.headers["X-Cache-Status"]) end) it("response content type", function() - local res = assert(client:send { - method = "GET", - path = "/xml", + local res = assert(client:get("/xml", { headers = { host = "route-1.com", }, - }) + })) assert.res_status(200, res) assert.same("Bypass", res.headers["X-Cache-Status"]) @@ -1188,9 +1061,7 @@ do describe("caches non-default", function() it("request methods", function() - local res = assert(client:send { - method = "POST", - path = "/post", + local res = assert(client:post("/post", { headers = { host = "route-10.com", ["Content-Type"] = "application/json", @@ -1198,7 +1069,7 @@ do { foo = "bar", }, - }) + })) assert.res_status(200, res) assert.same("Miss", res.headers["X-Cache-Status"]) @@ -1209,9 +1080,7 @@ do -- return strategy:fetch(cache_key) ~= nil --end, TIMEOUT) - res = assert(client:send { - method = "POST", - path = "/post", + res = assert(client:post("/post", { headers = { host = "route-10.com", ["Content-Type"] = "application/json", @@ -1219,31 +1088,27 @@ do { foo = "bar", }, - }) + })) assert.res_status(200, res) assert.same("Hit", res.headers["X-Cache-Status"]) end) it("response status", function() - local res = assert(client:send { - method = "GET", - path = "/status/417", + local res = assert(client:get("/status/417", { headers = { host = "route-10.com", }, - }) + })) assert.res_status(417, res) assert.same("Miss", res.headers["X-Cache-Status"]) - res = assert(client:send { - method = "GET", - path = "/status/417", + res = assert(client:get("/status/417", { headers = { host = "route-10.com", }, - }) + })) assert.res_status(417, res) assert.same("Hit", res.headers["X-Cache-Status"]) @@ -1253,25 +1118,21 @@ do describe("displays Kong core headers:", function() it("X-Kong-Proxy-Latency", function() - local res = assert(client:send { - method = "GET", - path = "/get?show-me=proxy-latency", + local res = assert(client:get("/get?show-me=proxy-latency", { headers = { host = "route-1.com", } - }) + })) assert.res_status(200, res) assert.same("Miss", res.headers["X-Cache-Status"]) assert.matches("^%d+$", res.headers["X-Kong-Proxy-Latency"]) - res = assert(client:send { - method = "GET", - path = "/get?show-me=proxy-latency", + res = assert(client:get("/get?show-me=proxy-latency", { headers = { host = "route-1.com", } - }) + })) assert.res_status(200, res) assert.same("Hit", res.headers["X-Cache-Status"]) @@ -1279,13 +1140,11 @@ do end) it("X-Kong-Upstream-Latency", function() - local res = assert(client:send { - method = "GET", - path = "/get?show-me=upstream-latency", + local res = assert(client:get("/get?show-me=upstream-latency", { headers = { host = "route-1.com", } - }) + })) assert.res_status(200, res) assert.same("Miss", res.headers["X-Cache-Status"]) @@ -1297,13 +1156,11 @@ do -- return strategy:fetch(cache_key) ~= nil --end, TIMEOUT) - res = assert(client:send { - method = "GET", - path = "/get?show-me=upstream-latency", + res = assert(client:get("/get?show-me=upstream-latency", { headers = { host = "route-1.com", } - }) + })) assert.res_status(200, res) assert.same("Hit", res.headers["X-Cache-Status"]) diff --git a/spec/03-plugins/31-proxy-cache/03-api_spec.lua b/spec/03-plugins/31-proxy-cache/03-api_spec.lua index 376a2aafe0a2..ddc6200fc1de 100644 --- a/spec/03-plugins/31-proxy-cache/03-api_spec.lua +++ b/spec/03-plugins/31-proxy-cache/03-api_spec.lua @@ -71,9 +71,7 @@ describe("Plugin: proxy-cache", function() local body it("accepts an array of numbers as strings", function() - local res = assert(admin_client:send { - method = "POST", - path = "/plugins", + local res = assert(admin_client:post("/plugins", { body = { name = "proxy-cache", config = { @@ -90,7 +88,7 @@ describe("Plugin: proxy-cache", function() headers = { ["Content-Type"] = "application/json", }, - }) + })) body = assert.res_status(201, res) end) it("casts an array of response_code values to number types", function() @@ -205,13 +203,12 @@ describe("Plugin: proxy-cache", function() describe("(API)", function() describe("DELETE", function() it("delete a cache entry", function() - local res = assert(proxy_client:send { - method = "GET", - path = "/get", + local res = assert(proxy_client:get("/get", { headers = { host = "route-1.com", + ["kong-debug"] = 1, } - }) + })) assert.res_status(200, res) assert.same("Miss", res.headers["X-Cache-Status"]) @@ -222,13 +219,12 @@ describe("Plugin: proxy-cache", function() assert.equals(64, #cache_key1) cache_key = cache_key1 - res = assert(proxy_client:send { - method = "GET", - path = "/get", + res = assert(proxy_client:get("/get", { headers = { host = "route-1.com", + ["kong-debug"] = 1, } - }) + })) assert.res_status(200, res) assert.same("Hit", res.headers["X-Cache-Status"]) @@ -236,61 +232,51 @@ describe("Plugin: proxy-cache", function() assert.same(cache_key1, cache_key2) -- delete the key - res = assert(admin_client:send { - method = "DELETE", - path = "/proxy-cache/" .. plugin1.id .. "/caches/" .. cache_key, - }) + res = assert(admin_client:delete("/proxy-cache/" .. plugin1.id .. "/caches/" .. cache_key)) assert.res_status(204, res) - local res = assert(proxy_client:send { - method = "GET", - path = "/get", + local res = assert(proxy_client:get("/get", { headers = { host = "route-1.com", + ["kong-debug"] = 1, } - }) + })) assert.res_status(200, res) assert.same("Miss", res.headers["X-Cache-Status"]) -- delete directly, having to look up all proxy-cache instances - res = assert(admin_client:send { - method = "DELETE", - path = "/proxy-cache/" .. cache_key, - }) + res = assert(admin_client:delete("/proxy-cache/" .. cache_key)) assert.res_status(204, res) - local res = assert(proxy_client:send { - method = "GET", - path = "/get", + local res = assert(proxy_client:get("/get", { headers = { host = "route-1.com", + ["kong-debug"] = 1, } - }) + })) assert.res_status(200, res) assert.same("Miss", res.headers["X-Cache-Status"]) end) it("purge all the cache entries", function() -- make a `Hit` request to `route-1` - local res = assert(proxy_client:send { - method = "GET", - path = "/get", + local res = assert(proxy_client:get("/get", { headers = { host = "route-1.com", + ["kong-debug"] = 1, } - }) + })) assert.res_status(200, res) assert.same("Hit", res.headers["X-Cache-Status"]) -- make a `Miss` request to `route-2` - local res = assert(proxy_client:send { - method = "GET", - path = "/get", + local res = assert(proxy_client:get("/get", { headers = { host = "route-2.com", + ["kong-debug"] = 1, } - }) + })) assert.res_status(200, res) assert.same("Miss", res.headers["X-Cache-Status"]) @@ -301,13 +287,12 @@ describe("Plugin: proxy-cache", function() assert.equals(64, #cache_key1) -- make a `Hit` request to `route-1` - res = assert(proxy_client:send { - method = "GET", - path = "/get", + res = assert(proxy_client:get("/get", { headers = { host = "route-2.com", + ["kong-debug"] = 1, } - }) + })) assert.res_status(200, res) assert.same("Hit", res.headers["X-Cache-Status"]) @@ -315,109 +300,76 @@ describe("Plugin: proxy-cache", function() assert.same(cache_key1, cache_key2) -- delete all the cache keys - res = assert(admin_client:send { - method = "DELETE", - path = "/proxy-cache", - }) + res = assert(admin_client:delete("/proxy-cache")) assert.res_status(204, res) - local res = assert(proxy_client:send { - method = "GET", - path = "/get", + local res = assert(proxy_client:get("/get", { headers = { host = "route-1.com", + ["kong-debug"] = 1, } - }) + })) assert.res_status(200, res) assert.same("Miss", res.headers["X-Cache-Status"]) - local res = assert(proxy_client:send { - method = "GET", - path = "/get", + local res = assert(proxy_client:get("/get", { headers = { host = "route-2.com", + ["kong-debug"] = 1, } - }) + })) assert.res_status(200, res) assert.same("Miss", res.headers["X-Cache-Status"]) end) it("delete a non-existing cache key", function() -- delete all the cache keys - local res = assert(admin_client:send { - method = "DELETE", - path = "/proxy-cache", - }) + local res = assert(admin_client:delete("/proxy-cache")) assert.res_status(204, res) - local res = assert(admin_client:send { - method = "DELETE", - path = "/proxy-cache/" .. plugin1.id .. "/caches/" .. "123", - }) + local res = assert(admin_client:delete("/proxy-cache/" .. plugin1.id .. "/caches/" .. "123")) assert.res_status(404, res) end) it("delete a non-existing plugins's cache key", function() -- delete all the cache keys - local res = assert(admin_client:send { - method = "DELETE", - path = "/proxy-cache", - }) + local res = assert(admin_client:delete("/proxy-cache")) assert.res_status(204, res) - local res = assert(admin_client:send { - method = "DELETE", - path = "/proxy-cache/" .. route1.id .. "/caches/" .. "123", - }) + local res = assert(admin_client:delete("/proxy-cache/" .. route1.id .. "/caches/" .. "123")) assert.res_status(404, res) end) end) describe("GET", function() it("get a non-existing cache", function() -- delete all the cache keys - local res = assert(admin_client:send { - method = "DELETE", - path = "/proxy-cache", - }) + local res = assert(admin_client:delete("/proxy-cache")) assert.res_status(204, res) - local res = assert(admin_client:send { - method = "GET", - path = "/proxy-cache/" .. plugin1.id .. "/caches/" .. cache_key, - }) + local res = assert(admin_client:get("/proxy-cache/" .. plugin1.id .. "/caches/" .. cache_key)) assert.res_status(404, res) -- attempt to list an entry directly via cache key - local res = assert(admin_client:send { - method = "GET", - path = "/proxy-cache/" .. cache_key, - }) + local res = assert(admin_client:get("/proxy-cache/" .. cache_key)) assert.res_status(404, res) end) it("get a existing cache", function() -- add request to cache - local res = assert(proxy_client:send { - method = "GET", - path = "/get", + local res = assert(proxy_client:get("/get", { headers = { host = "route-1.com", + ["kong-debug"] = 1, } - }) + })) assert.res_status(200, res) - local res = assert(admin_client:send { - method = "GET", - path = "/proxy-cache/" .. plugin1.id .. "/caches/" .. cache_key, - }) + local res = assert(admin_client:get("/proxy-cache/" .. plugin1.id .. "/caches/" .. cache_key)) local body = assert.res_status(200, res) local json_body = cjson.decode(body) assert.same(cache_key, json_body.headers["X-Cache-Key"]) -- list an entry directly via cache key - local res = assert(admin_client:send { - method = "GET", - path = "/proxy-cache/" .. cache_key, - }) + local res = assert(admin_client:get("/proxy-cache/" .. cache_key)) local body = assert.res_status(200, res) local json_body = cjson.decode(body) assert.same(cache_key, json_body.headers["X-Cache-Key"]) diff --git a/spec/03-plugins/31-proxy-cache/04-invalidations_spec.lua b/spec/03-plugins/31-proxy-cache/04-invalidations_spec.lua index 7cf893c655ed..fad2a933c38b 100644 --- a/spec/03-plugins/31-proxy-cache/04-invalidations_spec.lua +++ b/spec/03-plugins/31-proxy-cache/04-invalidations_spec.lua @@ -1,8 +1,17 @@ -local helpers = require "spec.helpers" +local helpers = require "spec.helpers" + local POLL_INTERVAL = 0.3 +local function get(client, host) + return assert(client:get("/get", { + headers = { + Host = host, + ["kong-debug"] = 1, + }, + })) +end for _, strategy in helpers.each_strategy() do describe("proxy-cache invalidations via: " .. strategy, function() @@ -112,125 +121,69 @@ describe("proxy-cache invalidations via: " .. strategy, function() setup(function() -- prime cache entries on both instances - local res_1 = assert(client_1:send { - method = "GET", - path = "/get", - headers = { - Host = "route-1.com", - }, - }) + local res_1 = get(client_1, "route-1.com") assert.res_status(200, res_1) assert.same("Miss", res_1.headers["X-Cache-Status"]) cache_key = res_1.headers["X-Cache-Key"] - local res_2 = assert(client_2:send { - method = "GET", - path = "/get", - headers = { - host = "route-1.com", - }, - }) + local res_2 = get(client_2, "route-1.com") assert.res_status(200, res_2) assert.same("Miss", res_2.headers["X-Cache-Status"]) assert.same(cache_key, res_2.headers["X-Cache-Key"]) - res_1 = assert(client_1:send { - method = "GET", - path = "/get", - headers = { - host = "route-2.com", - }, - }) + res_1 = get(client_1, "route-2.com") assert.res_status(200, res_1) assert.same("Miss", res_1.headers["X-Cache-Status"]) cache_key2 = res_1.headers["X-Cache-Key"] assert.not_same(cache_key, cache_key2) - res_2 = assert(client_2:send { - method = "GET", - path = "/get", - headers = { - host = "route-2.com", - }, - }) + local res_2 = get(client_2, "route-2.com") assert.res_status(200, res_2) assert.same("Miss", res_2.headers["X-Cache-Status"]) end) it("propagates purges via cluster events mechanism", function() - local res_1 = assert(client_1:send { - method = "GET", - path = "/get", - headers = { - host = "route-1.com", - }, - }) + local res_1 = get(client_1, "route-1.com") assert.res_status(200, res_1) assert.same("Hit", res_1.headers["X-Cache-Status"]) - local res_2 = assert(client_2:send { - method = "GET", - path = "/get", - headers = { - host = "route-1.com", - }, - }) + local res_2 = get(client_2, "route-1.com") assert.res_status(200, res_2) assert.same("Hit", res_2.headers["X-Cache-Status"]) -- now purge the entry - local res = assert(admin_client_1:send { - method = "DELETE", - path = "/proxy-cache/" .. plugin1.id .. "/caches/" .. cache_key, - }) + local res = assert(admin_client_1:delete("/proxy-cache/" .. plugin1.id .. "/caches/" .. cache_key)) assert.res_status(204, res) helpers.wait_until(function() -- assert that the entity was purged from the second instance - res = assert(admin_client_2:send { - method = "GET", - path = "/proxy-cache/" .. plugin1.id .. "/caches/" .. cache_key, - }) + res = assert(admin_client_2:get("/proxy-cache/" .. plugin1.id .. "/caches/" .. cache_key, { + })) res:read_body() return res.status == 404 end, 10) -- refresh and purge with our second endpoint - res_1 = assert(client_1:send { - method = "GET", - path = "/get", - headers = { - Host = "route-1.com", - }, - }) + res_1 = get(client_1, "route-1.com") assert.res_status(200, res_1) assert.same("Miss", res_1.headers["X-Cache-Status"]) - res_2 = assert(client_2:send { - method = "GET", - path = "/get", - headers = { - host = "route-1.com", - }, - }) + res_2 = get(client_2, "route-1.com") assert.res_status(200, res_2) assert.same("Miss", res_2.headers["X-Cache-Status"]) assert.same(cache_key, res_2.headers["X-Cache-Key"]) -- now purge the entry - res = assert(admin_client_1:send { - method = "DELETE", - path = "/proxy-cache/" .. cache_key, - }) + res = assert(admin_client_1:delete("/proxy-cache/" .. cache_key)) assert.res_status(204, res) @@ -239,55 +192,42 @@ describe("proxy-cache invalidations via: " .. strategy, function() helpers.wait_until(function() -- assert that the entity was purged from the second instance - res = assert(admin_client_2:send { - method = "GET", - path = "/proxy-cache/" .. cache_key, - }) + res = assert(admin_client_2:get("/proxy-cache/" .. cache_key, { + })) res:read_body() return res.status == 404 end, 10) end) it("does not affect cache entries under other plugin instances", function() - local res = assert(admin_client_1:send { - method = "GET", - path = "/proxy-cache/" .. plugin2.id .. "/caches/" .. cache_key2, - }) + local res = assert(admin_client_1:get("/proxy-cache/" .. plugin2.id .. "/caches/" .. cache_key2, { + })) assert.res_status(200, res) - res = assert(admin_client_2:send { - method = "GET", - path = "/proxy-cache/" .. plugin2.id .. "/caches/" .. cache_key2, - }) + res = assert(admin_client_2:get("/proxy-cache/" .. plugin2.id .. "/caches/" .. cache_key2, { + })) assert.res_status(200, res) end) it("propagates global purges", function() do - local res = assert(admin_client_1:send { - method = "DELETE", - path = "/proxy-cache/", - }) - + local res = assert(admin_client_1:delete("/proxy-cache/")) + assert.res_status(204, res) end helpers.wait_until(function() - local res = assert(admin_client_1:send { - method = "GET", - path = "/proxy-cache/" .. plugin2.id .. "/caches/" .. cache_key2, - }) + local res = assert(admin_client_1:get("/proxy-cache/" .. plugin2.id .. "/caches/" .. cache_key2, { + })) res:read_body() return res.status == 404 end, 10) helpers.wait_until(function() - local res = assert(admin_client_2:send { - method = "GET", - path = "/proxy-cache/" .. plugin2.id .. "/caches/" .. cache_key2, - }) + local res = assert(admin_client_2:get("/proxy-cache/" .. plugin2.id .. "/caches/" .. cache_key2, { + })) res:read_body() return res.status == 404 end, 10)