diff --git a/CHANGELOG.md b/CHANGELOG.md index a09feb6de957..7c0d5e10ce35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -80,6 +80,8 @@ as little as possible. During that time, the invalid sessions could cause failures and partial downtime. All existing sessions are invalidated when upgrading to this version. [#10199](https://github.com/Kong/kong/pull/10199) +- **Proxy Cache**: Add wildcard and parameter match support for content_type + [#10209](https://github.com/Kong/kong/pull/10209) ### Additions diff --git a/kong/plugins/proxy-cache/handler.lua b/kong/plugins/proxy-cache/handler.lua index d16ce5736598..af99c394caa7 100644 --- a/kong/plugins/proxy-cache/handler.lua +++ b/kong/plugins/proxy-cache/handler.lua @@ -1,7 +1,9 @@ local require = require local cache_key = require "kong.plugins.proxy-cache.cache_key" local utils = require "kong.tools.utils" -local kong_meta = require "kong.meta" +local kong_meta = require "kong.meta" +local mime_type = require "kong.tools.mime_type" +local nkeys = require "table.nkeys" local ngx = ngx @@ -20,6 +22,7 @@ local ngx_re_gmatch = ngx.re.gmatch local ngx_re_sub = ngx.re.gsub local ngx_re_match = ngx.re.match local parse_http_time = ngx.parse_http_time +local parse_mime_type = mime_type.parse_mime_type local tab_new = require("table.new") @@ -173,11 +176,27 @@ local function cacheable_response(conf, cc) return false end + local t, subtype, params = parse_mime_type(content_type) local content_match = false for i = 1, #conf.content_type do - if conf.content_type[i] == content_type then - content_match = true - break + local expected_ct = conf.content_type[i] + local exp_type, exp_subtype, exp_params = parse_mime_type(expected_ct) + if exp_type then + if (exp_type == "*" or t == exp_type) and + (exp_subtype == "*" or subtype == exp_subtype) then + local params_match = true + for key, value in pairs(exp_params or EMPTY) do + if value ~= (params or EMPTY)[key] then + params_match = false + break + end + end + if params_match and + (nkeys(params or EMPTY) == nkeys(exp_params or EMPTY)) then + content_match = true + break + end + end end end diff --git a/spec/03-plugins/31-proxy-cache/01-schema_spec.lua b/spec/03-plugins/31-proxy-cache/01-schema_spec.lua index befba9ec9e4c..9d2bab2d46a1 100644 --- a/spec/03-plugins/31-proxy-cache/01-schema_spec.lua +++ b/spec/03-plugins/31-proxy-cache/01-schema_spec.lua @@ -121,4 +121,32 @@ describe("proxy-cache schema", function() assert.is_nil(err) assert.is_truthy(entity) end) + + it("accepts wildcard content_type", function() + local entity, err = v({ + strategy = "memory", + content_type = { "application/*", "*/text" }, + }, proxy_cache_schema) + + assert.is_nil(err) + assert.is_truthy(entity) + + local entity, err = v({ + strategy = "memory", + content_type = { "*/*" }, + }, proxy_cache_schema) + + assert.is_nil(err) + assert.is_truthy(entity) + end) + + it("accepts content_type with parameter", function() + local entity, err = v({ + strategy = "memory", + content_type = { "application/json; charset=UTF-8" }, + }, proxy_cache_schema) + + assert.is_nil(err) + assert.is_truthy(entity) + end) end) 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 b0c75ca7262e..e0e7f27ac789 100644 --- a/spec/03-plugins/31-proxy-cache/02-access_spec.lua +++ b/spec/03-plugins/31-proxy-cache/02-access_spec.lua @@ -77,6 +77,16 @@ do local route16 = assert(bp.routes:insert({ hosts = { "route-16.com" }, })) + local route17 = assert(bp.routes:insert({ + hosts = { "route-17.com" }, + })) + local route18 = assert(bp.routes:insert({ + hosts = { "route-18.com" }, + })) + local route19 = assert(bp.routes:insert({ + hosts = { "route-19.com" }, + })) + local consumer1 = assert(bp.consumers:insert { username = "bob", @@ -242,6 +252,36 @@ do }, }) + assert(bp.plugins:insert { + name = "proxy-cache", + route = { id = route17.id }, + config = { + strategy = policy, + [policy] = policy_config, + content_type = { "*/*" }, + }, + }) + + assert(bp.plugins:insert { + name = "proxy-cache", + route = { id = route18.id }, + config = { + strategy = policy, + [policy] = policy_config, + content_type = { "application/xml; charset=UTF-8" }, + }, + }) + + assert(bp.plugins:insert { + name = "proxy-cache", + route = { id = route19.id }, + config = { + strategy = policy, + [policy] = policy_config, + content_type = { "application/xml;" }, -- invalid content_type + }, + }) + assert(helpers.start_kong({ plugins = "bundled", nginx_conf = "spec/fixtures/custom_nginx.template", @@ -1240,5 +1280,71 @@ do assert.matches("^%d+$", res.headers["X-Kong-Upstream-Latency"]) end) end) + + describe("content-type", function() + it("should cache a request with wildcard content_type(*/*)", function() + local request = { + method = "GET", + path = "/xml", + headers = { + host = "route-17.com", + }, + } + + local res = assert(client:send(request)) + assert.res_status(200, res) + assert.same("application/xml", res.headers["Content-Type"]) + assert.same("Miss", res.headers["X-Cache-Status"]) + + local res = assert(client:send(request)) + assert.res_status(200, res) + assert.same("application/xml", res.headers["Content-Type"]) + assert.same("Hit", res.headers["X-Cache-Status"]) + end) + + it("should not cache a request while parameter is not match", function() + local res = assert(client:send { + method = "GET", + path = "/xml", + headers = { + host = "route-18.com", + }, + }) + + assert.res_status(200, res) + assert.same("application/xml", res.headers["Content-Type"]) + assert.same("Bypass", res.headers["X-Cache-Status"]) + end) + + + it("should not cause error while upstream returns a invalid content type", function() + local res = assert(client:send { + method = "GET", + path = "/response-headers?Content-Type=application/xml;", + headers = { + host = "route-18.com", + }, + }) + + assert.res_status(200, res) + assert.same("application/xml;", res.headers["Content-Type"]) + assert.same("Bypass", res.headers["X-Cache-Status"]) + end) + + it("should not cause error while config.content_type has invalid element", function() + local res, err = client:send { + method = "GET", + path = "/xml", + headers = { + host = "route-19.com", + }, + } + + assert.is_nil(err) + assert.res_status(200, res) + assert.same("application/xml", res.headers["Content-Type"]) + assert.same("Bypass", res.headers["X-Cache-Status"]) + end) + end) end) end