From 3fb07494ecaa3e489294f11c72a9e38b1db6bd3f Mon Sep 17 00:00:00 2001 From: agile6v Date: Sun, 23 Feb 2020 23:20:32 +0800 Subject: [PATCH 01/21] Add proxy-cache plugin. --- bin/apisix | 38 ++++- conf/config.yaml | 14 ++ lua/apisix/core/ctx.lua | 17 +- lua/apisix/plugins/proxy-cache.lua | 247 +++++++++++++++++++++++++++++ 4 files changed, 310 insertions(+), 6 deletions(-) create mode 100644 lua/apisix/plugins/proxy-cache.lua diff --git a/bin/apisix b/bin/apisix index 5e68c3f7170c..047d9f8cdda2 100755 --- a/bin/apisix +++ b/bin/apisix @@ -174,6 +174,13 @@ http { lua_shared_dict jwks 1m; # cache for JWKs lua_shared_dict introspection 10m; # cache for JWT verification results + {% if proxy_cache then %} + # for proxy cache + {% for _, cache in ipairs(proxy_cache.zone) do %} + proxy_cache_path {* cache.disk_path *} levels=1:2 keys_zone={* cache.name *}:{* cache.memory_size *} inactive=1d max_size={* cache.disk_size *}; + {% end %} + {% end %} + lua_ssl_verify_depth 5; ssl_session_timeout 86400; @@ -367,6 +374,8 @@ http { proxy_pass_header Server; proxy_pass_header Date; + ### the following x-forwarded-* headers is to send to upstream server + set $var_x_forwarded_for $remote_addr; set $var_x_forwarded_proto $scheme; set $var_x_forwarded_host $host; @@ -390,7 +399,34 @@ http { proxy_set_header X-Forwarded-Host $var_x_forwarded_host; proxy_set_header X-Forwarded-Port $var_x_forwarded_port; - proxy_pass $upstream_scheme://apisix_backend$upstream_uri; + {% if proxy_cache then %} + ### the following configuration is to cache response content from upstream server + + set $upstream_cache_zone off; + set $upstream_cache_key ''; + set $upstream_cache_bypass 'bypass'; + set $upstream_no_cache ''; + set $upstream_hdr_expires ''; + set $upstream_hdr_cache_control ''; + + proxy_cache $upstream_cache_zone; + proxy_cache_valid any {% if proxy_cache and proxy_cache.cache_ttl then %} {* proxy_cache.cache_ttl *} {% else %} 5s {% end %}; + proxy_cache_min_uses 1; + proxy_cache_methods GET HEAD; + proxy_cache_lock_timeout 5s; + proxy_cache_use_stale off; + proxy_cache_key $upstream_cache_key; + proxy_no_cache $upstream_no_cache; + proxy_cache_bypass $upstream_cache_bypass; + + proxy_hide_header Cache-Control; + proxy_hide_header Expires; + add_header Cache-Control $upstream_hdr_cache_control; + add_header Expires $upstream_hdr_expires; + add_header Apisix-Cache-Status $upstream_cache_status; + {% end %} + + proxy_pass $upstream_scheme://apisix_backend$upstream_uri; header_filter_by_lua_block { apisix.http_header_filter_phase() diff --git a/conf/config.yaml b/conf/config.yaml index 3281f4721738..7361b5cf8311 100644 --- a/conf/config.yaml +++ b/conf/config.yaml @@ -35,6 +35,19 @@ apisix: # enable_tcp_pp: true # Enable the proxy protocol for tcp proxy, it works for stream_proxy.tcp option # enable_tcp_pp_to_upstream: true # Enables the proxy protocol to the upstream server + #proxy_cache: # Proxy Caching configuration + # cache_ttl: 10s # The default caching time if the upstream does not specify the cache time + # zone: # The parameters of a cache + # - name: disk_cache_one # The name of the cache, administrator can be specify + # # which cache to use by name in the admin api + # memory_size: 50m # The size of shared memory, it's used to store the cache index + # disk_size: 1G # The size of disk, it's used to store the cache data + # disk_path: "/tmp/disk_cache_one" # The path to store the cache data + # - name: disk_cache_two + # memory_size: 50m + # disk_size: 1G + # disk_path: "/tmp/disk_cache_two" + # allow_admin: # http://nginx.org/en/docs/http/ngx_http_access_module.html#allow # - 127.0.0.0/24 # If we don't set any IP list, then any IP access is allowed by default. # - "::/64" @@ -109,6 +122,7 @@ plugins: # plugin list - fault-injection - udp-logger - wolf-rbac + - proxy-cache stream_plugins: - mqtt-proxy diff --git a/lua/apisix/core/ctx.lua b/lua/apisix/core/ctx.lua index 5e53b31c3677..0eae1f00515a 100644 --- a/lua/apisix/core/ctx.lua +++ b/lua/apisix/core/ctx.lua @@ -42,11 +42,18 @@ do } local ngx_var_names = { - upstream_scheme = true, - upstream_host = true, - upstream_upgrade = true, - upstream_connection = true, - upstream_uri = true, + upstream_scheme = true, + upstream_host = true, + upstream_upgrade = true, + upstream_connection = true, + upstream_uri = true, + + upstream_cache_zone = true, + upstream_no_cache = true, + upstream_cache_key = true, + upstream_cache_bypass = true, + upstream_hdr_expires = true, + upstream_hdr_cache_control = true, } local mt = { diff --git a/lua/apisix/plugins/proxy-cache.lua b/lua/apisix/plugins/proxy-cache.lua new file mode 100644 index 000000000000..e34702f06b9b --- /dev/null +++ b/lua/apisix/plugins/proxy-cache.lua @@ -0,0 +1,247 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one or more +-- contributor license agreements. See the NOTICE file distributed with +-- this work for additional information regarding copyright ownership. +-- The ASF licenses this file to You under the Apache License, Version 2.0 +-- (the "License"); you may not use this file except in compliance with +-- the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- + +local core = require("apisix.core") +local tab_insert = table.insert +local tab_concat = table.concat +local re_gmatch = ngx.re.gmatch +local ipairs = ipairs +local tostring = tostring + +local lrucache = core.lrucache.new({ + ttl = 300, count = 100 +}) + +local plugin_name = "proxy-cache" + +local schema = { + type = "object", + properties = { + cache_zone = { + type = "string", + minLength = 1 + }, + cache_key = { + type = "string", + minLength = 1 + }, + cache_http_status = { + type = "array", + minItems = 1, + items = { + description = "http response status", + type = "integer", + minimum = 200, + maximum = 599, + }, + uniqueItems = true, + default = {200, 301, 404}, + }, + cache_method = { + type = "array", + minItems = 1, + items = { + description = "http method", + type = "string", + enum = {"GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", + "OPTIONS", "CONNECT", "TRACE"}, + default = {"GET", "HEAD"}, + }, + uniqueItems = true, + default = {"GET", "HEAD"}, + }, + hide_cache_headers = { + type = "boolean", + default = false, + }, + cache_strategy = { + type = "string", + default = "disk", + enum = {"disk", "memory"}, + minLength = 0 + }, + cache_bypass = { + type = "string", + default = "1", + minLength = 0 + }, + no_cache = { + type = "string", + default = "0", + minLength = 0 + }, + }, + required = {"cache_zone", "cache_key"}, +} + +local _M = { + version = 0.1, + priority = 1007, + name = plugin_name, + schema = schema, +} + +function _M.check_schema(conf) + local ok, err = core.schema.check(schema, conf) + if not ok then + return false, err + end + + if conf.cache_strategy == "memory" then + return false, "memory cache is not yet supported." + end + + local t = {} + for _, method in ipairs(conf.cache_method) do + t[method] = "true" + end + + conf.cache_method = t + + local t = {} + for _, status in ipairs(conf.cache_http_status) do + t[tostring(status)] = "true" + end + + conf.cache_http_status = t + + return true +end + +-- Copy from redirect plugin, this function is useful. +-- It can be extracted as a public function. +local function parse_complex_value(complex_value) + + local reg = [[ (\\\$[0-9a-zA-Z_]+) | ]] -- \$host + .. [[ \$\{([0-9a-zA-Z_]+)\} | ]] -- ${host} + .. [[ \$([0-9a-zA-Z_]+) | ]] -- $host + .. [[ (\$|[^$\\]+) ]] -- $ or others + local iterator, err = re_gmatch(complex_value, reg, "jiox") + if not iterator then + return nil, err + end + + local t = {} + while true do + local m, err = iterator() + if err then + return nil, err + end + + if not m then + break + end + + tab_insert(t, m) + end + + return t +end + + +local tmp = {} +local function generate_complex_value(data, ctx) + local segs_value, err = lrucache(data, nil, parse_complex_value, data) + if not segs_value then + return nil, err + end + + local segs_value, err = parse_complex_value(data) + core.table.clear(tmp) + + for i, value in ipairs(segs_value) do + core.log.info("complex value(", data, ") seg-", i, ": ", core.json.delay_encode(value)) + + local pat1 = value[1] -- \$host + local pat2 = value[2] -- ${host} + local pat3 = value[3] -- $host + local pat4 = value[4] -- $ or others + + if pat2 or pat3 then + tab_insert(tmp, ctx.var[pat2 or pat3]) + else + tab_insert(tmp, pat1 or pat4) + end + end + + return tab_concat(tmp, "") +end + + +function _M.rewrite(conf, ctx) + core.log.info("proxy-cache plugin rewrite phase, conf: ", core.json.delay_encode(conf)) + + ctx.var.upstream_cache_zone = conf.cache_zone + + local value, err = generate_complex_value(conf.cache_key, ctx) + if not value then + core.log.error("failed to generate the complex value by: ", conf.cache_key, " error: ", err) + core.response.exit(500) + end + + ctx.var.upstream_cache_key = value + core.log.info("proxy-cache cache key value:", value) + + local value, err = generate_complex_value(conf.cache_bypass, ctx) + if not value then + core.log.error("failed to generate the complex value by: ", conf.cache_bypass, " error: ", err) + core.response.exit(500) + end + + ctx.var.upstream_cache_bypass = value + core.log.info("proxy-cache cache bypass value:", value) +end + + +function _M.header_filter(conf, ctx) + core.log.info("proxy-cache plugin header filter phase, conf: ", core.json.delay_encode(conf)) + + local no_cache = "1" + + if conf.cache_method[ctx.var.request_method] then + no_cache = "0" + end + + if conf.cache_http_status[tostring(ngx.status)] then + no_cache = "0" + end + + local value, err = generate_complex_value(conf.no_cache, ctx) + if not value then + core.log.error("failed to generate the complex value by: ", conf.no_cache, " error: ", err) + core.response.exit(500) + end + + core.log.info("proxy-cache no-cache value:", value) + + if value ~= nil and value ~= "" and value ~= "0" then + no_cache = "1" + end + + if conf.hide_cache_headers == true then + ctx.var.upstream_hdr_cache_control = "" + ctx.var.upstream_hdr_expires = "" + else + ctx.var.upstream_hdr_cache_control = ctx.var.upstream_http_cache_control + ctx.var.upstream_hdr_expires = ctx.var.upstream_http_expires + end + + ctx.var.upstream_no_cache = no_cache + core.log.info("proxy-cache no cache:", no_cache) +end + +return _M From 521bb532f360423cb93f4448832ab013db8096dc Mon Sep 17 00:00:00 2001 From: agile6v Date: Mon, 24 Feb 2020 00:06:08 +0800 Subject: [PATCH 02/21] remove unused code --- lua/apisix/plugins/proxy-cache.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/apisix/plugins/proxy-cache.lua b/lua/apisix/plugins/proxy-cache.lua index e34702f06b9b..5d41c6405fa4 100644 --- a/lua/apisix/plugins/proxy-cache.lua +++ b/lua/apisix/plugins/proxy-cache.lua @@ -19,6 +19,7 @@ local core = require("apisix.core") local tab_insert = table.insert local tab_concat = table.concat local re_gmatch = ngx.re.gmatch +local ngx = ngx local ipairs = ipairs local tostring = tostring @@ -160,7 +161,6 @@ local function generate_complex_value(data, ctx) return nil, err end - local segs_value, err = parse_complex_value(data) core.table.clear(tmp) for i, value in ipairs(segs_value) do From 27cd5c5fa5c038e78ac96593fcef42e11226ec96 Mon Sep 17 00:00:00 2001 From: agile6v Date: Mon, 24 Feb 2020 08:43:15 +0800 Subject: [PATCH 03/21] Break up the long line. --- lua/apisix/plugins/proxy-cache.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lua/apisix/plugins/proxy-cache.lua b/lua/apisix/plugins/proxy-cache.lua index 5d41c6405fa4..6654b3d5b358 100644 --- a/lua/apisix/plugins/proxy-cache.lua +++ b/lua/apisix/plugins/proxy-cache.lua @@ -198,7 +198,8 @@ function _M.rewrite(conf, ctx) local value, err = generate_complex_value(conf.cache_bypass, ctx) if not value then - core.log.error("failed to generate the complex value by: ", conf.cache_bypass, " error: ", err) + core.log.error("failed to generate the complex value by: ", + conf.cache_bypass, " error: ", err) core.response.exit(500) end From d9ceecab115a2b124a519382895c2eeb87b4e4f1 Mon Sep 17 00:00:00 2001 From: agile6v Date: Mon, 24 Feb 2020 09:02:16 +0800 Subject: [PATCH 04/21] Add proxy-cache plugin in the plugins.t --- t/admin/plugins.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/admin/plugins.t b/t/admin/plugins.t index 031eaff9f961..bca5a4fb2e04 100644 --- a/t/admin/plugins.t +++ b/t/admin/plugins.t @@ -30,6 +30,6 @@ __DATA__ --- request GET /apisix/admin/plugins/list --- response_body_like eval -qr/\["limit-req","limit-count","limit-conn","key-auth","basic-auth","prometheus","node-status","jwt-auth","zipkin","ip-restriction","grpc-transcode","serverless-pre-function","serverless-post-function","openid-connect","proxy-rewrite","redirect","response-rewrite","fault-injection","udp-logger","wolf-rbac"\]/ +qr/\["limit-req","limit-count","limit-conn","key-auth","basic-auth","prometheus","node-status","jwt-auth","zipkin","ip-restriction","grpc-transcode","serverless-pre-function","serverless-post-function","openid-connect","proxy-rewrite","redirect","response-rewrite","fault-injection","udp-logger","wolf-rbac","proxy-cache"\]/ --- no_error_log [error] From 677d9fdf58f4f6c4938760c413f8932a74acab87 Mon Sep 17 00:00:00 2001 From: agile6v Date: Mon, 24 Feb 2020 12:09:57 +0800 Subject: [PATCH 05/21] Modify the priority of the proxy-cache plugin. --- lua/apisix/plugins/proxy-cache.lua | 2 +- t/debug/debug-mode.t | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lua/apisix/plugins/proxy-cache.lua b/lua/apisix/plugins/proxy-cache.lua index 6654b3d5b358..2fd44f1dfef6 100644 --- a/lua/apisix/plugins/proxy-cache.lua +++ b/lua/apisix/plugins/proxy-cache.lua @@ -91,7 +91,7 @@ local schema = { local _M = { version = 0.1, - priority = 1007, + priority = 1009, name = plugin_name, schema = schema, } diff --git a/t/debug/debug-mode.t b/t/debug/debug-mode.t index b069a23d0eca..1d4154f4bd47 100644 --- a/t/debug/debug-mode.t +++ b/t/debug/debug-mode.t @@ -63,6 +63,7 @@ loaded plugin and sort by priority: 2555 name: wolf-rbac loaded plugin and sort by priority: 2520 name: basic-auth loaded plugin and sort by priority: 2510 name: jwt-auth loaded plugin and sort by priority: 2500 name: key-auth +loaded plugin and sort by priority: 1009 name: proxy-cache loaded plugin and sort by priority: 1008 name: proxy-rewrite loaded plugin and sort by priority: 1003 name: limit-conn loaded plugin and sort by priority: 1002 name: limit-count From 4f96265fb78b6a30061fae9f434efc4a29ab8022 Mon Sep 17 00:00:00 2001 From: agile6v Date: Mon, 24 Feb 2020 21:39:42 +0800 Subject: [PATCH 06/21] Improvement. --- lua/apisix/plugins/proxy-cache.lua | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/lua/apisix/plugins/proxy-cache.lua b/lua/apisix/plugins/proxy-cache.lua index 2fd44f1dfef6..b82493c7070b 100644 --- a/lua/apisix/plugins/proxy-cache.lua +++ b/lua/apisix/plugins/proxy-cache.lua @@ -21,7 +21,6 @@ local tab_concat = table.concat local re_gmatch = ngx.re.gmatch local ngx = ngx local ipairs = ipairs -local tostring = tostring local lrucache = core.lrucache.new({ ttl = 300, count = 100 @@ -106,20 +105,6 @@ function _M.check_schema(conf) return false, "memory cache is not yet supported." end - local t = {} - for _, method in ipairs(conf.cache_method) do - t[method] = "true" - end - - conf.cache_method = t - - local t = {} - for _, status in ipairs(conf.cache_http_status) do - t[tostring(status)] = "true" - end - - conf.cache_http_status = t - return true end @@ -213,12 +198,17 @@ function _M.header_filter(conf, ctx) local no_cache = "1" - if conf.cache_method[ctx.var.request_method] then - no_cache = "0" + -- Maybe there is no need for optimization here. + for _, method in ipairs(conf.cache_method) do + if method == ctx.var.request_method then + no_cache = "0" + end end - if conf.cache_http_status[tostring(ngx.status)] then - no_cache = "0" + for _, status in ipairs(conf.cache_http_status) do + if status == ngx.status then + no_cache = "0" + end end local value, err = generate_complex_value(conf.no_cache, ctx) From 9a97e349ed4ca6c9cf93a548d0cc6293eb3ecc67 Mon Sep 17 00:00:00 2001 From: agile6v Date: Fri, 28 Feb 2020 22:13:08 +0800 Subject: [PATCH 07/21] Do some optimizations. --- bin/apisix | 2 +- conf/config.yaml | 2 +- lua/apisix/plugins/proxy-cache.lua | 13 ++----------- 3 files changed, 4 insertions(+), 13 deletions(-) diff --git a/bin/apisix b/bin/apisix index 047d9f8cdda2..22aa02deb533 100755 --- a/bin/apisix +++ b/bin/apisix @@ -176,7 +176,7 @@ http { {% if proxy_cache then %} # for proxy cache - {% for _, cache in ipairs(proxy_cache.zone) do %} + {% for _, cache in ipairs(proxy_cache.zones) do %} proxy_cache_path {* cache.disk_path *} levels=1:2 keys_zone={* cache.name *}:{* cache.memory_size *} inactive=1d max_size={* cache.disk_size *}; {% end %} {% end %} diff --git a/conf/config.yaml b/conf/config.yaml index 7361b5cf8311..29350b9c8400 100644 --- a/conf/config.yaml +++ b/conf/config.yaml @@ -37,7 +37,7 @@ apisix: #proxy_cache: # Proxy Caching configuration # cache_ttl: 10s # The default caching time if the upstream does not specify the cache time - # zone: # The parameters of a cache + # zones: # The parameters of a cache # - name: disk_cache_one # The name of the cache, administrator can be specify # # which cache to use by name in the admin api # memory_size: 50m # The size of shared memory, it's used to store the cache index diff --git a/lua/apisix/plugins/proxy-cache.lua b/lua/apisix/plugins/proxy-cache.lua index b82493c7070b..baaf499a38a8 100644 --- a/lua/apisix/plugins/proxy-cache.lua +++ b/lua/apisix/plugins/proxy-cache.lua @@ -59,7 +59,6 @@ local schema = { type = "string", enum = {"GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", "CONNECT", "TRACE"}, - default = {"GET", "HEAD"}, }, uniqueItems = true, default = {"GET", "HEAD"}, @@ -68,12 +67,6 @@ local schema = { type = "boolean", default = false, }, - cache_strategy = { - type = "string", - default = "disk", - enum = {"disk", "memory"}, - minLength = 0 - }, cache_bypass = { type = "string", default = "1", @@ -101,10 +94,6 @@ function _M.check_schema(conf) return false, err end - if conf.cache_strategy == "memory" then - return false, "memory cache is not yet supported." - end - return true end @@ -202,12 +191,14 @@ function _M.header_filter(conf, ctx) for _, method in ipairs(conf.cache_method) do if method == ctx.var.request_method then no_cache = "0" + break end end for _, status in ipairs(conf.cache_http_status) do if status == ngx.status then no_cache = "0" + break end end From a4f5121b0b4719393781a7993404c323675e3f32 Mon Sep 17 00:00:00 2001 From: agile6v Date: Sat, 29 Feb 2020 22:31:13 +0800 Subject: [PATCH 08/21] Fixed some bugs. --- bin/apisix | 3 ++- conf/config.yaml | 16 ++++++++-------- lua/apisix/plugins/proxy-cache.lua | 9 +++++++-- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/bin/apisix b/bin/apisix index 22aa02deb533..05ef9be7f29b 100755 --- a/bin/apisix +++ b/bin/apisix @@ -410,6 +410,7 @@ http { set $upstream_hdr_cache_control ''; proxy_cache $upstream_cache_zone; + proxy_cache_valid 502 504 0s; proxy_cache_valid any {% if proxy_cache and proxy_cache.cache_ttl then %} {* proxy_cache.cache_ttl *} {% else %} 5s {% end %}; proxy_cache_min_uses 1; proxy_cache_methods GET HEAD; @@ -423,7 +424,7 @@ http { proxy_hide_header Expires; add_header Cache-Control $upstream_hdr_cache_control; add_header Expires $upstream_hdr_expires; - add_header Apisix-Cache-Status $upstream_cache_status; + add_header Apisix-Cache-Status $upstream_cache_status always; {% end %} proxy_pass $upstream_scheme://apisix_backend$upstream_uri; diff --git a/conf/config.yaml b/conf/config.yaml index 29350b9c8400..3bd4b1d4bfbb 100644 --- a/conf/config.yaml +++ b/conf/config.yaml @@ -35,14 +35,14 @@ apisix: # enable_tcp_pp: true # Enable the proxy protocol for tcp proxy, it works for stream_proxy.tcp option # enable_tcp_pp_to_upstream: true # Enables the proxy protocol to the upstream server - #proxy_cache: # Proxy Caching configuration - # cache_ttl: 10s # The default caching time if the upstream does not specify the cache time - # zones: # The parameters of a cache - # - name: disk_cache_one # The name of the cache, administrator can be specify - # # which cache to use by name in the admin api - # memory_size: 50m # The size of shared memory, it's used to store the cache index - # disk_size: 1G # The size of disk, it's used to store the cache data - # disk_path: "/tmp/disk_cache_one" # The path to store the cache data + proxy_cache: # Proxy Caching configuration + cache_ttl: 10s # The default caching time if the upstream does not specify the cache time + zones: # The parameters of a cache + - name: disk_cache_one # The name of the cache, administrator can be specify + # which cache to use by name in the admin api + memory_size: 50m # The size of shared memory, it's used to store the cache index + disk_size: 1G # The size of disk, it's used to store the cache data + disk_path: "/tmp/disk_cache_one" # The path to store the cache data # - name: disk_cache_two # memory_size: 50m # disk_size: 1G diff --git a/lua/apisix/plugins/proxy-cache.lua b/lua/apisix/plugins/proxy-cache.lua index baaf499a38a8..518e69036362 100644 --- a/lua/apisix/plugins/proxy-cache.lua +++ b/lua/apisix/plugins/proxy-cache.lua @@ -186,22 +186,27 @@ function _M.header_filter(conf, ctx) core.log.info("proxy-cache plugin header filter phase, conf: ", core.json.delay_encode(conf)) local no_cache = "1" + local match_method, match_status = false, false -- Maybe there is no need for optimization here. for _, method in ipairs(conf.cache_method) do if method == ctx.var.request_method then - no_cache = "0" + match_method = true break end end for _, status in ipairs(conf.cache_http_status) do if status == ngx.status then - no_cache = "0" + match_status = true break end end + if match_method and match_status then + no_cache = "0" + end + local value, err = generate_complex_value(conf.no_cache, ctx) if not value then core.log.error("failed to generate the complex value by: ", conf.no_cache, " error: ", err) From c7865b9e59b3695647fdc36e87aa359f4b3ba2d7 Mon Sep 17 00:00:00 2001 From: agile6v Date: Sun, 1 Mar 2020 22:18:54 +0800 Subject: [PATCH 09/21] Improvements. --- lua/apisix/plugins/proxy-cache.lua | 101 ++++----- t/plugin/proxy-cache.t | 347 +++++++++++++++++++++++++++++ 2 files changed, 391 insertions(+), 57 deletions(-) create mode 100644 t/plugin/proxy-cache.t diff --git a/lua/apisix/plugins/proxy-cache.lua b/lua/apisix/plugins/proxy-cache.lua index 518e69036362..60b97876bce6 100644 --- a/lua/apisix/plugins/proxy-cache.lua +++ b/lua/apisix/plugins/proxy-cache.lua @@ -19,6 +19,7 @@ local core = require("apisix.core") local tab_insert = table.insert local tab_concat = table.concat local re_gmatch = ngx.re.gmatch +local sub_str = string.sub local ngx = ngx local ipairs = ipairs @@ -36,8 +37,13 @@ local schema = { minLength = 1 }, cache_key = { - type = "string", - minLength = 1 + type = "array", + minItems = 1, + items = { + description = "a key for caching", + type = "string", + pattern = [[(^[^\$].+$|^\$[0-9a-zA-Z_]+$)]] + }, }, cache_http_status = { type = "array", @@ -68,14 +74,20 @@ local schema = { default = false, }, cache_bypass = { - type = "string", - default = "1", - minLength = 0 + type = "array", + minItems = 1, + items = { + type = "string", + pattern = [[(^[^\$].+$|^\$[0-9a-zA-Z_]+$)]] + }, }, no_cache = { - type = "string", - default = "0", - minLength = 0 + type = "array", + minItems = 1, + items = { + type = "string", + pattern = [[(^[^\$].+$|^\$[0-9a-zA-Z_]+$)]] + }, }, }, required = {"cache_zone", "cache_key"}, @@ -88,6 +100,7 @@ local _M = { schema = schema, } + function _M.check_schema(conf) local ok, err = core.schema.check(schema, conf) if not ok then @@ -97,58 +110,19 @@ function _M.check_schema(conf) return true end --- Copy from redirect plugin, this function is useful. --- It can be extracted as a public function. -local function parse_complex_value(complex_value) - - local reg = [[ (\\\$[0-9a-zA-Z_]+) | ]] -- \$host - .. [[ \$\{([0-9a-zA-Z_]+)\} | ]] -- ${host} - .. [[ \$([0-9a-zA-Z_]+) | ]] -- $host - .. [[ (\$|[^$\\]+) ]] -- $ or others - local iterator, err = re_gmatch(complex_value, reg, "jiox") - if not iterator then - return nil, err - end - - local t = {} - while true do - local m, err = iterator() - if err then - return nil, err - end - - if not m then - break - end - - tab_insert(t, m) - end - - return t -end - local tmp = {} local function generate_complex_value(data, ctx) - local segs_value, err = lrucache(data, nil, parse_complex_value, data) - if not segs_value then - return nil, err - end - core.table.clear(tmp) - for i, value in ipairs(segs_value) do - core.log.info("complex value(", data, ") seg-", i, ": ", core.json.delay_encode(value)) - - local pat1 = value[1] -- \$host - local pat2 = value[2] -- ${host} - local pat3 = value[3] -- $host - local pat4 = value[4] -- $ or others + core.log.info("proxy-cache complex value: ", core.json.delay_encode(data)) + for i, value in ipairs(data) do + core.log.info("proxy-cache complex value index-", i, ": ", value) - if pat2 or pat3 then - tab_insert(tmp, ctx.var[pat2 or pat3]) + if sub_str(value, 1, 1) == "$" then + tab_insert(tmp, ctx.var[sub_str(value, 2)]) else - tab_insert(tmp, pat1 or pat4) + tab_insert(tmp, value) end end @@ -182,10 +156,9 @@ function _M.rewrite(conf, ctx) end -function _M.header_filter(conf, ctx) - core.log.info("proxy-cache plugin header filter phase, conf: ", core.json.delay_encode(conf)) - - local no_cache = "1" +-- check whether the request method and response status +-- match the user defined. +function match_method_and_status(conf, ctx) local match_method, match_status = false, false -- Maybe there is no need for optimization here. @@ -204,6 +177,19 @@ function _M.header_filter(conf, ctx) end if match_method and match_status then + return true + end + + return false +end + + +function _M.header_filter(conf, ctx) + core.log.info("proxy-cache plugin header filter phase, conf: ", core.json.delay_encode(conf)) + + local no_cache = "1" + + if match_method_and_status(conf, ctx) then no_cache = "0" end @@ -231,4 +217,5 @@ function _M.header_filter(conf, ctx) core.log.info("proxy-cache no cache:", no_cache) end + return _M diff --git a/t/plugin/proxy-cache.t b/t/plugin/proxy-cache.t new file mode 100644 index 000000000000..98ef2b405ea8 --- /dev/null +++ b/t/plugin/proxy-cache.t @@ -0,0 +1,347 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +BEGIN { + if ($ENV{TEST_NGINX_CHECK_LEAK}) { + $SkipReason = "unavailable for the hup tests"; + + } else { + $ENV{TEST_NGINX_USE_HUP} = 1; + undef $ENV{TEST_NGINX_USE_STAP}; + } +} + +use t::APISIX 'no_plan'; + +repeat_each(1); +no_long_string(); +no_shuffle(); +no_root_location(); +log_level('info'); +run_tests; + +__DATA__ + +=== TEST 1: sanity check (missing required field) +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "plugins": { + "proxy-cache": { + "cache_bypass": ["$arg_bypass"], + "cache_method": ["GET"], + "cache_http_status": [200], + "hide_cache_headers": true, + "no_cache": ["$arg_no_cache"] + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- error_code: 400 +--- response_body eval +qr/failed to check the configuration of plugin proxy-cache/ +--- no_error_log +[error] + + + +=== TEST 2: sanity check (invalid type for cache_method) +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "plugins": { + "proxy-cache": { + "cache_zone": "disk_cache_one", + "cache_key": ["$uri"], + "cache_bypass": ["$arg_bypass"], + "cache_method": "GET", + "cache_http_status": [200], + "hide_cache_headers": true, + "no_cache": ["$arg_no_cache"] + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- error_code: 400 +--- response_body eval +qr/failed to check the configuration of plugin proxy-cache/ +--- no_error_log +[error] + + + +=== TEST 3: sanity check (invalid type for cache_key) +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "plugins": { + "proxy-cache": { + "cache_zone": "disk_cache_one", + "cache_key": "${uri}-cache-key", + "cache_bypass": ["$arg_bypass"], + "cache_method": ["GET"], + "cache_http_status": [200], + "hide_cache_headers": true, + "no_cache": ["$arg_no_cache"] + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1985": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- error_code: 400 +--- response_body eval +qr/failed to check the configuration of plugin proxy-cache/ +--- no_error_log +[error] + + + +=== TEST 4: sanity check (invalid type for cache_bypass) +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "plugins": { + "proxy-cache": { + "cache_zone": "disk_cache_one", + "cache_key": ["$uri", "-cache-id"], + "cache_bypass": "$arg_bypass", + "cache_method": ["GET"], + "cache_http_status": [200], + "hide_cache_headers": true, + "no_cache": ["$arg_no_cache"] + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1985": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- error_code: 400 +--- response_body eval +qr/failed to check the configuration of plugin proxy-cache/ +--- no_error_log +[error] + + + +=== TEST 5: sanity check (invalid type for no_cache) +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "plugins": { + "proxy-cache": { + "cache_zone": "disk_cache_one", + "cache_key": ["$uri", "-cache-id"], + "cache_bypass": ["$arg_bypass"], + "cache_method": ["GET"], + "cache_http_status": [200], + "hide_cache_headers": true, + "no_cache": "$arg_no_cache" + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1985": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- error_code: 400 +--- response_body eval +qr/failed to check the configuration of plugin proxy-cache/ +--- no_error_log +[error] + + + +=== TEST 6: sanity check (illegal character for cache_key) +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "plugins": { + "proxy-cache": { + "cache_zone": "disk_cache_one", + "cache_key": ["$uri-", "-cache-id"], + "cache_bypass": ["$arg_bypass"], + "cache_method": ["GET"], + "cache_http_status": [200], + "hide_cache_headers": true, + "no_cache": ["$arg_no_cache"] + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1985": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- error_code: 400 +--- response_body eval +qr/failed to check the configuration of plugin proxy-cache/ +--- no_error_log +[error] + + + +=== TEST 7: sanity check (normal case) +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "plugins": { + "proxy-cache": { + "cache_zone": "disk_cache_one", + "cache_key": ["$uri", "-cache-id"], + "cache_bypass": ["$arg_bypass"], + "cache_method": ["GET"], + "cache_http_status": [200], + "hide_cache_headers": true, + "no_cache": ["$arg_no_cache"] + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1985": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- error_code: 200 +--- response_body +passed +--- no_error_log +[error] From 7bec3ec8f730b96313d41a47295429a592da8d73 Mon Sep 17 00:00:00 2001 From: agile6v Date: Sun, 1 Mar 2020 22:41:39 +0800 Subject: [PATCH 10/21] Remove the proxy_cache_valid directive with 502 & 504. --- bin/apisix | 1 - 1 file changed, 1 deletion(-) diff --git a/bin/apisix b/bin/apisix index 05ef9be7f29b..3355c70271d9 100755 --- a/bin/apisix +++ b/bin/apisix @@ -410,7 +410,6 @@ http { set $upstream_hdr_cache_control ''; proxy_cache $upstream_cache_zone; - proxy_cache_valid 502 504 0s; proxy_cache_valid any {% if proxy_cache and proxy_cache.cache_ttl then %} {* proxy_cache.cache_ttl *} {% else %} 5s {% end %}; proxy_cache_min_uses 1; proxy_cache_methods GET HEAD; From 56a1f42fd1e8bd30575856758a51e8a21acf2e4c Mon Sep 17 00:00:00 2001 From: agile6v Date: Mon, 2 Mar 2020 09:45:56 +0800 Subject: [PATCH 11/21] Eliminate the warnings. --- lua/apisix/plugins/proxy-cache.lua | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lua/apisix/plugins/proxy-cache.lua b/lua/apisix/plugins/proxy-cache.lua index 60b97876bce6..f4ad4234b808 100644 --- a/lua/apisix/plugins/proxy-cache.lua +++ b/lua/apisix/plugins/proxy-cache.lua @@ -18,15 +18,10 @@ local core = require("apisix.core") local tab_insert = table.insert local tab_concat = table.concat -local re_gmatch = ngx.re.gmatch local sub_str = string.sub local ngx = ngx local ipairs = ipairs -local lrucache = core.lrucache.new({ - ttl = 300, count = 100 -}) - local plugin_name = "proxy-cache" local schema = { @@ -158,7 +153,7 @@ end -- check whether the request method and response status -- match the user defined. -function match_method_and_status(conf, ctx) +local function match_method_and_status(conf, ctx) local match_method, match_status = false, false -- Maybe there is no need for optimization here. From 7d7cd2af38104180ea69ddc9e585ad665926ea83 Mon Sep 17 00:00:00 2001 From: agile6v Date: Mon, 2 Mar 2020 21:13:04 +0800 Subject: [PATCH 12/21] Add support for cache purge. --- bin/apisix | 13 ++- lua/apisix/core/ctx.lua | 1 + lua/apisix/plugins/proxy-cache.lua | 138 +++++++++++++++++++++-------- 3 files changed, 112 insertions(+), 40 deletions(-) diff --git a/bin/apisix b/bin/apisix index 3355c70271d9..ae3051fdf66d 100755 --- a/bin/apisix +++ b/bin/apisix @@ -177,10 +177,19 @@ http { {% if proxy_cache then %} # for proxy cache {% for _, cache in ipairs(proxy_cache.zones) do %} - proxy_cache_path {* cache.disk_path *} levels=1:2 keys_zone={* cache.name *}:{* cache.memory_size *} inactive=1d max_size={* cache.disk_size *}; + proxy_cache_path {* cache.disk_path *} levels={* cache.cache_levels *} keys_zone={* cache.name *}:{* cache.memory_size *} inactive=1d max_size={* cache.disk_size *}; {% end %} {% end %} + {% if proxy_cache then %} + # for proxy cache + map $upstream_cache_zone $upstream_cache_zone_info { + {% for _, cache in ipairs(proxy_cache.zones) do %} + {* cache.name *} {* cache.disk_path *},{* cache.cache_levels *}; + {% end %} + } + {% end %} + lua_ssl_verify_depth 5; ssl_session_timeout 86400; @@ -404,7 +413,7 @@ http { set $upstream_cache_zone off; set $upstream_cache_key ''; - set $upstream_cache_bypass 'bypass'; + set $upstream_cache_bypass ''; set $upstream_no_cache ''; set $upstream_hdr_expires ''; set $upstream_hdr_cache_control ''; diff --git a/lua/apisix/core/ctx.lua b/lua/apisix/core/ctx.lua index 0eae1f00515a..31d0d8984fdf 100644 --- a/lua/apisix/core/ctx.lua +++ b/lua/apisix/core/ctx.lua @@ -49,6 +49,7 @@ do upstream_uri = true, upstream_cache_zone = true, + upstream_cache_zone_info = true, upstream_no_cache = true, upstream_cache_key = true, upstream_cache_bypass = true, diff --git a/lua/apisix/plugins/proxy-cache.lua b/lua/apisix/plugins/proxy-cache.lua index f4ad4234b808..a3a17dcfd452 100644 --- a/lua/apisix/plugins/proxy-cache.lua +++ b/lua/apisix/plugins/proxy-cache.lua @@ -16,11 +16,17 @@ -- local core = require("apisix.core") +local ngx_re = require("ngx.re") local tab_insert = table.insert local tab_concat = table.concat -local sub_str = string.sub +local string = string +local io_open = io.open +local io_close = io.close local ngx = ngx +local os = os local ipairs = ipairs +local pairs = pairs +local tonumber = tonumber local plugin_name = "proxy-cache" @@ -114,8 +120,8 @@ local function generate_complex_value(data, ctx) for i, value in ipairs(data) do core.log.info("proxy-cache complex value index-", i, ": ", value) - if sub_str(value, 1, 1) == "$" then - tab_insert(tmp, ctx.var[sub_str(value, 2)]) + if string.sub(value, 1, 1) == "$" then + tab_insert(tmp, ctx.var[string.sub(value, 2)]) else tab_insert(tmp, value) end @@ -125,32 +131,6 @@ local function generate_complex_value(data, ctx) end -function _M.rewrite(conf, ctx) - core.log.info("proxy-cache plugin rewrite phase, conf: ", core.json.delay_encode(conf)) - - ctx.var.upstream_cache_zone = conf.cache_zone - - local value, err = generate_complex_value(conf.cache_key, ctx) - if not value then - core.log.error("failed to generate the complex value by: ", conf.cache_key, " error: ", err) - core.response.exit(500) - end - - ctx.var.upstream_cache_key = value - core.log.info("proxy-cache cache key value:", value) - - local value, err = generate_complex_value(conf.cache_bypass, ctx) - if not value then - core.log.error("failed to generate the complex value by: ", - conf.cache_bypass, " error: ", err) - core.response.exit(500) - end - - ctx.var.upstream_cache_bypass = value - core.log.info("proxy-cache cache bypass value:", value) -end - - -- check whether the request method and response status -- match the user defined. local function match_method_and_status(conf, ctx) @@ -178,6 +158,91 @@ local function match_method_and_status(conf, ctx) return false end +-- refer to https://gist.github.com/titpetric/ed6ec548af160e82c650cf39074878fb +local function file_exists(name) + local f = io_open(name, "r") + if f~=nil then io_close(f) return true else return false end +end + + +local function explode(d, p) + local t, ll + t={} + ll=0 + if(#p == 1) then return {p} end + while true do + local l=string.find(p, d, ll, true) -- find the next d in the string + if l~=nil then -- if "not not" found then.. + tab_insert(t, string.sub(p, ll, l-1)) -- Save it in our array. + ll=l+1 -- save just after where we found it for searching next time. + else + tab_insert(t, string.sub(p, ll)) -- Save what's left in our array. + break -- Break at end, as it should be, according to the lua manual. + end + end + return t +end + + +local function generate_cache_filename(cache_path, cache_levels, cache_key) + local md5sum = ngx.md5(cache_key) + local levels = explode(":", cache_levels) + local filename = "" + + local index = string.len(md5sum) + for k, v in pairs(levels) do + local length = tonumber(v) + -- add trailing [length] chars to index + index = index - length; + filename = filename .. md5sum:sub(index+1, index+length) .. "/"; + end + if cache_path:sub(-1) ~= "/" then + cache_path = cache_path .. "/"; + end + filename = cache_path .. filename .. md5sum + return filename +end + + +local function cache_purge(conf, ctx) + local cache_zone_info = ngx_re.split(ctx.var.upstream_cache_zone_info, ",") + + local filename = generate_cache_filename(cache_zone_info[1], cache_zone_info[2], + ctx.var.upstream_cache_key) + if file_exists(filename) then + os.remove(filename) + return nil + end + + return "Not found" +end + + +function _M.rewrite(conf, ctx) + core.log.info("proxy-cache plugin rewrite phase, conf: ", core.json.delay_encode(conf)) + + ctx.var.upstream_cache_zone = conf.cache_zone + + local value = generate_complex_value(conf.cache_key, ctx) + ctx.var.upstream_cache_key = value + core.log.info("proxy-cache cache key value:", value) + + if ctx.var.request_method == "PURGE" then + local err = cache_purge(conf, ctx) + if err ~= nil then + return 404 + end + + return 200 + end + + if conf.cache_bypass ~= nil then + local value = generate_complex_value(conf.cache_bypass, ctx) + ctx.var.upstream_cache_bypass = value + core.log.info("proxy-cache cache bypass value:", value) + end +end + function _M.header_filter(conf, ctx) core.log.info("proxy-cache plugin header filter phase, conf: ", core.json.delay_encode(conf)) @@ -188,16 +253,13 @@ function _M.header_filter(conf, ctx) no_cache = "0" end - local value, err = generate_complex_value(conf.no_cache, ctx) - if not value then - core.log.error("failed to generate the complex value by: ", conf.no_cache, " error: ", err) - core.response.exit(500) - end - - core.log.info("proxy-cache no-cache value:", value) + if conf.no_cache ~= nil then + local value = generate_complex_value(conf.no_cache, ctx) + core.log.info("proxy-cache no-cache value:", value) - if value ~= nil and value ~= "" and value ~= "0" then - no_cache = "1" + if value ~= nil and value ~= "" and value ~= "0" then + no_cache = "1" + end end if conf.hide_cache_headers == true then From b3ccdb372520e2849871afe017591a148be4664c Mon Sep 17 00:00:00 2001 From: agile6v Date: Mon, 2 Mar 2020 21:15:17 +0800 Subject: [PATCH 13/21] Add support for cache purge. --- conf/config.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/conf/config.yaml b/conf/config.yaml index 3bd4b1d4bfbb..ae0b0a23ef39 100644 --- a/conf/config.yaml +++ b/conf/config.yaml @@ -43,10 +43,12 @@ apisix: memory_size: 50m # The size of shared memory, it's used to store the cache index disk_size: 1G # The size of disk, it's used to store the cache data disk_path: "/tmp/disk_cache_one" # The path to store the cache data + cache_levels: "1:2" # The hierarchy levels of a cache # - name: disk_cache_two # memory_size: 50m # disk_size: 1G # disk_path: "/tmp/disk_cache_two" + # cache_levels: "1:2" # allow_admin: # http://nginx.org/en/docs/http/ngx_http_access_module.html#allow # - 127.0.0.0/24 # If we don't set any IP list, then any IP access is allowed by default. From d85188d2044fd44de2657062a470fcdf43d5db2b Mon Sep 17 00:00:00 2001 From: agile6v Date: Tue, 3 Mar 2020 21:01:21 +0800 Subject: [PATCH 14/21] Add doc for proxy-cache plugin. --- doc/plugins/proxy-cache-cn.md | 146 ++++++++++++++++++++++++++++++++++ doc/plugins/proxy-cache.md | 146 ++++++++++++++++++++++++++++++++++ 2 files changed, 292 insertions(+) create mode 100644 doc/plugins/proxy-cache-cn.md create mode 100644 doc/plugins/proxy-cache.md diff --git a/doc/plugins/proxy-cache-cn.md b/doc/plugins/proxy-cache-cn.md new file mode 100644 index 000000000000..95f3bdaf7d0f --- /dev/null +++ b/doc/plugins/proxy-cache-cn.md @@ -0,0 +1,146 @@ + + +[English](proxy-cache.md) + +# proxy-cache + +代理缓存插件,该插件提供缓存后端响应数据的能力,它可以和其他插件一起使用。该插件支持基于磁盘的缓存,未来也会支持基于内存的缓存。目前可以根据响应码、请求 Method 来指定需要缓存的数据,另外也可以通过 no_cache 和 cache_bypass 配置更复杂的缓存策略。 + +基于磁盘的缓存需要注意: +1. 不能动态配置缓存的过期时间,只能通过后端服务响应头 Expires 或 Cache-Control 来设置过期时间,如果后端响应头中没有 Expires 或 Cache-Control,那么 APISIX 将默认只缓存10秒钟 +2. 如果后端服务不可用, APISIX 将返回502或504,那么502或504将被缓存10秒钟 + +### 参数 + +|名称 |必须|类型|描述| +|------- |-----|------|------| +|cache_zone|是|string|指定使用哪个缓存区域,不同的缓存区域可以配置不同的路径,在conf/config.yaml文件中可以预定义使用的缓存区域| +|cache_key|是|array[string]|缓存key,可以使用变量。例如:["$host", "$uri", "-cache-id"]| +|cache_bypass|否|array[string]|是否跳过缓存检索,即不在缓存中查找数据,可以使用变量,需要注意当此参数的值不为空或非'0'时将会跳过缓存的检索。例如:["$arg_bypass"]| +|cache_method|否|array[string]|根据请求method决定是否需要缓存| +|cache_http_status|否|array[integer]|根据响应码决定是否需要缓存| +|hide_cache_headers|否|boolean|是否将 Expires 和 Cache-Control 响应头返回给客户端,默认为 false| +|no_cache|否|array[string]|是否缓存数据,可以使用变量,需要注意当此参数的值不为空或非'0'时将不会缓存数据。| + +注:变量以$开头,也可以使用变量和字符串的结合,但是需要以数组的形式分开写,最终变量被解析后会和字符串拼接在一起。 + +### 示例 + +#### 启用插件 + +示例1:为特定路由启用 `proxy-cache` 插件: + +```shell +curl http://127.0.0.1:9080/apisix/admin/routes/1 -X PUT -d ' +{ + "plugins": { + "proxy-cache": { + "cache_zone": "disk_cache_one", + "cache_key": ["$uri", "-cache-id"], + "cache_bypass": ["$arg_bypass"], + "cache_method": ["GET"], + "cache_http_status": [200], + "hide_cache_headers": true, + "no_cache": ["$arg_test"] + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1999": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" +}' +``` + +测试: + +```shell +$ curl http://127.0.0.1:9080/hello -i +HTTP/1.1 200 OK +Content-Type: application/octet-stream +Content-Length: 6 +Connection: keep-alive +Server: APISIX web server +Date: Tue, 03 Mar 2020 10:45:36 GMT +Last-Modified: Tue, 03 Mar 2020 10:36:38 GMT +Apisix-Cache-Status: MISS + +hello +``` + +> http status 返回`200`并且响应头中包含`Apisix-Cache-Status`,表示该插件已启用。 + +示例2:验证文件是否被缓存,再次请求上边的地址: + +测试: + +```shell +$ curl http://127.0.0.1:9080/hello -i +HTTP/1.1 200 OK +Content-Type: application/octet-stream +Content-Length: 6 +Connection: keep-alive +Server: APISIX web server +Date: Tue, 03 Mar 2020 11:14:46 GMT +Last-Modified: Thu, 20 Feb 2020 14:21:41 GMT +Apisix-Cache-Status: HIT + +hello +``` + +> 响应头 Apisix-Cache-Status 值变为了 HIT,说明文件已经被缓存 + +示例3:如何清理缓存的文件,只需要指定请求的 method 为 PURGE: + +测试: + +```shell +$ curl -i http://127.0.0.1:9080/hello -X PURGE +HTTP/1.1 200 OK +Date: Tue, 03 Mar 2020 11:17:35 GMT +Content-Type: text/plain +Transfer-Encoding: chunked +Connection: keep-alive +Server: APISIX web server +``` + +> 响应码为200即表示删除成功,如果文件未找到将返回404 + +#### 禁用插件 + +移除插件配置中相应的 JSON 配置可立即禁用该插件,无需重启服务: + +```shell +curl http://127.0.0.1:9080/apisix/admin/routes/1 -X PUT -d ' +{ + "uri": "/hello", + "plugins": {}, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1999": 1 + } + } +}' +``` + +这时该插件已被禁用。 diff --git a/doc/plugins/proxy-cache.md b/doc/plugins/proxy-cache.md new file mode 100644 index 000000000000..de8688fc74a1 --- /dev/null +++ b/doc/plugins/proxy-cache.md @@ -0,0 +1,146 @@ + + +[Chinese](proxy-cache-cn.md) + +# proxy-cache + +The proxy-cache plugin, which provides the ability to cache upstream response data and can be used with other plugins. The plugin supports disk-based caching and will support the memory-based caching in the future. The data that needs to be cached can be determined by the response code or request method and more complex caching policies can be configured by no_cache and cache_bypass attributes. + +*Note*: +1. The cache expiration time cannot be configured dynamically. The expiration time can only be set by the upstream response header `Expires` or `Cache-Control`, and the default cache expiration time is 10s if there is no `Expires` or `Cache-Control` in the upstream response header +2. If the upstream service is not available and APISIX will return 502 or 504, then 502 or 504 will be cached for 10s. + +## Attributes + +|Name |Requirement | Type |Description| +|------- |-----|------|------| +|cache_zone|required|string|Specify which cache area to use, each cache area can be configured with different paths. In addition, cache areas can be predefined in conf/config.yaml file| +|cache_key|required|array[string]|key of a cache, can use variables. For example: ["$host", "$uri", "-cache-id"]| +|cache_bypass|optional|array[string]|Whether to skip cache retrieval. That is, do not look for data in the cache. It can use variables, and note that cache data retrieval will be skipped when the value of this attribute is not empty or not '0'. For example: ["$arg_bypass"]| +|cache_method|optional|array[string]|Decide whether to be cached according to the request method| +|cache_http_status|optional|array[integer]|Decide whether to be cached according to the upstream response status| +|hide_cache_headers|optional|boolean|Whether to return the Expires and Cache-Control response headers to the client, the default is false| +|no_cache|optional|array[string]|Whether to cache data, it can use variables, and note that the data will not be cached when the value of this attribute is not empty or not '0'.| + +Note: +1. The variable starts with $. +2. The attribute can use a combination of the variable and the string, but it needs to be written separately as an array, and the final values are stitched together after the variable is parsed. + +### Examples + +#### Enable the plugin + +1: enable the proxy-cache plugin for a specific route : + +```shell +curl http://127.0.0.1:9080/apisix/admin/routes/1 -X PUT -d ' +{ + "plugins": { + "proxy-cache": { + "cache_zone": "disk_cache_one", + "cache_key": ["$uri", "-cache-id"], + "cache_bypass": ["$arg_bypass"], + "cache_method": ["GET"], + "cache_http_status": [200], + "hide_cache_headers": true, + "no_cache": ["$arg_test"] + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1999": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" +}' +``` + +Test Plugin: + +```shell +$ curl http://127.0.0.1:9080/hello -i +HTTP/1.1 200 OK +Content-Type: application/octet-stream +Content-Length: 6 +Connection: keep-alive +Server: APISIX web server +Date: Tue, 03 Mar 2020 10:45:36 GMT +Last-Modified: Tue, 03 Mar 2020 10:36:38 GMT +Apisix-Cache-Status: MISS + +hello +``` +> http status is '200' and the response header contains 'Apisix-Cache-Status' to indicate that the plug-in is enabled. + +2: Verify that the file is cached, request the address above again: + + +```shell +$ curl http://127.0.0.1:9080/hello -i +HTTP/1.1 200 OK +Content-Type: application/octet-stream +Content-Length: 6 +Connection: keep-alive +Server: APISIX web server +Date: Tue, 03 Mar 2020 11:14:46 GMT +Last-Modified: Thu, 20 Feb 2020 14:21:41 GMT +Apisix-Cache-Status: HIT + +hello +``` + +> Response header Apisix-Cache-Status has changed to HIT, indicating that the file has been cached. + +3: How to clean up the cached file, simply specify the request method as PURGE: + + +```shell +$ curl -i http://127.0.0.1:9080/hello -X PURGE +HTTP/1.1 200 OK +Date: Tue, 03 Mar 2020 11:17:35 GMT +Content-Type: text/plain +Transfer-Encoding: chunked +Connection: keep-alive +Server: APISIX web server +``` + +> The response status is 200, indicating that the file was deleted successfully. And return 404 if the file is not found. + +## Disable Plugin + +Remove the corresponding JSON in the plugin configuration to disable the plugin immediately without restarting the service: + + +```shell +curl http://127.0.0.1:9080/apisix/admin/routes/1 -X PUT -d ' +{ + "uri": "/hello", + "plugins": {}, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1999": 1 + } + } +}' +``` + +The plugin has been disabled now. From 95465bce2b5c9350a31ed15fbbd29f767cfc1c7f Mon Sep 17 00:00:00 2001 From: agile6v Date: Tue, 3 Mar 2020 22:09:27 +0800 Subject: [PATCH 15/21] Remove trailing white spaces. --- doc/plugins/proxy-cache.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/plugins/proxy-cache.md b/doc/plugins/proxy-cache.md index de8688fc74a1..c8e668c81077 100644 --- a/doc/plugins/proxy-cache.md +++ b/doc/plugins/proxy-cache.md @@ -39,9 +39,9 @@ The proxy-cache plugin, which provides the ability to cache upstream response da |hide_cache_headers|optional|boolean|Whether to return the Expires and Cache-Control response headers to the client, the default is false| |no_cache|optional|array[string]|Whether to cache data, it can use variables, and note that the data will not be cached when the value of this attribute is not empty or not '0'.| -Note: -1. The variable starts with $. -2. The attribute can use a combination of the variable and the string, but it needs to be written separately as an array, and the final values are stitched together after the variable is parsed. +Note: +1. The variable starts with $. +2. The attribute can use a combination of the variable and the string, but it needs to be written separately as an array, and the final values are stitched together after the variable is parsed. ### Examples From b18a40e043ae6bf5dfce6007722a5e89769b5bf9 Mon Sep 17 00:00:00 2001 From: agile6v Date: Thu, 5 Mar 2020 21:38:31 +0800 Subject: [PATCH 16/21] Remove unused code. --- lua/apisix/plugins/proxy-cache.lua | 30 +++++------------------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/lua/apisix/plugins/proxy-cache.lua b/lua/apisix/plugins/proxy-cache.lua index a3a17dcfd452..35d83503394c 100644 --- a/lua/apisix/plugins/proxy-cache.lua +++ b/lua/apisix/plugins/proxy-cache.lua @@ -158,46 +158,26 @@ local function match_method_and_status(conf, ctx) return false end --- refer to https://gist.github.com/titpetric/ed6ec548af160e82c650cf39074878fb + local function file_exists(name) local f = io_open(name, "r") if f~=nil then io_close(f) return true else return false end end -local function explode(d, p) - local t, ll - t={} - ll=0 - if(#p == 1) then return {p} end - while true do - local l=string.find(p, d, ll, true) -- find the next d in the string - if l~=nil then -- if "not not" found then.. - tab_insert(t, string.sub(p, ll, l-1)) -- Save it in our array. - ll=l+1 -- save just after where we found it for searching next time. - else - tab_insert(t, string.sub(p, ll)) -- Save what's left in our array. - break -- Break at end, as it should be, according to the lua manual. - end - end - return t -end - - local function generate_cache_filename(cache_path, cache_levels, cache_key) local md5sum = ngx.md5(cache_key) - local levels = explode(":", cache_levels) + local levels = ngx_re.split(cache_levels, ":") local filename = "" local index = string.len(md5sum) for k, v in pairs(levels) do local length = tonumber(v) - -- add trailing [length] chars to index - index = index - length; - filename = filename .. md5sum:sub(index+1, index+length) .. "/"; + index = index - length + filename = filename .. md5sum:sub(index+1, index+length) .. "/" end if cache_path:sub(-1) ~= "/" then - cache_path = cache_path .. "/"; + cache_path = cache_path .. "/" end filename = cache_path .. filename .. md5sum return filename From 21d1d04eb58fba085dc44a575c07edc38d28c28d Mon Sep 17 00:00:00 2001 From: agile6v Date: Wed, 11 Mar 2020 15:30:04 +0800 Subject: [PATCH 17/21] Update test cases. --- t/APISIX.pm | 23 ++++ t/plugin/proxy-cache.t | 248 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 269 insertions(+), 2 deletions(-) diff --git a/t/APISIX.pm b/t/APISIX.pm index f7887ab0bf44..19d248c96959 100644 --- a/t/APISIX.pm +++ b/t/APISIX.pm @@ -249,6 +249,29 @@ _EOC_ set \$upstream_connection ''; set \$upstream_uri ''; + set \$upstream_cache_zone off; + set \$upstream_cache_key ''; + set \$upstream_cache_bypass ''; + set \$upstream_no_cache ''; + set \$upstream_hdr_expires ''; + set \$upstream_hdr_cache_control ''; + + proxy_cache \$upstream_cache_zone; + proxy_cache_valid any 10s; + proxy_cache_min_uses 1; + proxy_cache_methods GET HEAD; + proxy_cache_lock_timeout 5s; + proxy_cache_use_stale off; + proxy_cache_key \$upstream_cache_key; + proxy_no_cache \$upstream_no_cache; + proxy_cache_bypass \$upstream_cache_bypass; + + proxy_hide_header Cache-Control; + proxy_hide_header Expires; + add_header Cache-Control \$upstream_hdr_cache_control; + add_header Expires \$upstream_hdr_expires; + add_header Apisix-Cache-Status \$upstream_cache_status always; + access_by_lua_block { -- wait for etcd sync ngx.sleep($wait_etcd_sync) diff --git a/t/plugin/proxy-cache.t b/t/plugin/proxy-cache.t index 98ef2b405ea8..70234c2b5a8c 100644 --- a/t/plugin/proxy-cache.t +++ b/t/plugin/proxy-cache.t @@ -31,6 +31,40 @@ no_long_string(); no_shuffle(); no_root_location(); log_level('info'); + +add_block_preprocessor(sub { + my ($block) = @_; + + my $http_config = $block->http_config // <<_EOC_; + + # for proxy cache + proxy_cache_path /tmp/disk_cache_one levels=1:2 keys_zone=disk_cache_one:50m inactive=1d max_size=1G; + proxy_cache_path /tmp/disk_cache_two levels=1:2 keys_zone=disk_cache_two:50m inactive=1d max_size=1G; + + # for proxy cache + map \$upstream_cache_zone \$upstream_cache_zone_info { + disk_cache_one /tmp/disk_cache_one,1:2; + disk_cache_two /tmp/disk_cache_two,1:2; + } + + server { + listen 1986; + server_tokens off; + + location / { + expires 60s; + return 200 "hello world!"; + } + + location /hello-not-found { + return 404; + } + } +_EOC_ + + $block->set_value("http_config", $http_config); +}); + run_tests; __DATA__ @@ -324,11 +358,189 @@ qr/failed to check the configuration of plugin proxy-cache/ }, "upstream": { "nodes": { - "127.0.0.1:1985": 1 + "127.0.0.1:1986": 1 }, "type": "roundrobin" }, - "uri": "/hello" + "uri": "/hello*" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- error_code: 200 +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 8: hit route (cache miss) +--- request +GET /hello +--- response_body chop +hello world! +--- response_headers +Apisix-Cache-Status: MISS +--- no_error_log +[error] + + + +=== TEST 9: hit route (cache hit) +--- request +GET /hello +--- response_body chop +hello world! +--- response_headers +Apisix-Cache-Status: HIT +--- raw_response_headers_unlike +Expires: +--- no_error_log +[error] + + + +=== TEST 10: hit route (cache bypass) +--- request +GET /hello?bypass=1 +--- response_body chop +hello world! +--- response_headers +Apisix-Cache-Status: BYPASS +--- no_error_log +[error] + + + +=== TEST 11: purge cache +--- request +PURGE /hello +--- error_code: 200 +--- no_error_log +[error] + + + +=== TEST 12: hit route (nocache) +--- request +GET /hello?no_cache=1 +--- response_body chop +hello world! +--- response_headers +Apisix-Cache-Status: MISS +--- no_error_log +[error] + + + +=== TEST 13: hit route (there's no cache indeed) +--- request +GET /hello +--- response_body chop +hello world! +--- response_headers +Apisix-Cache-Status: MISS +--- raw_response_headers_unlike +Expires: +--- no_error_log +[error] + + + +=== TEST 14: hit route (will be cached) +--- request +GET /hello +--- response_body chop +hello world! +--- response_headers +Apisix-Cache-Status: HIT +--- no_error_log +[error] + + + +=== TEST 15: hit route (not found) +--- request +GET /hello-not-found +--- error_code: 404 +--- response_body eval +qr/404 Not Found/ +--- response_headers +Apisix-Cache-Status: MISS +--- no_error_log +[error] + + + +=== TEST 16: hit route (404 there's no cache indeed) +--- request +GET /hello-not-found +--- error_code: 404 +--- response_body eval +qr/404 Not Found/ +--- response_headers +Apisix-Cache-Status: MISS +--- no_error_log +[error] + + + +=== TEST 17: hit route (HEAD method) +--- request +HEAD /hello-world +--- error_code: 200 +--- response_headers +Apisix-Cache-Status: MISS +--- no_error_log +[error] + + + +=== TEST 18: hit route (HEAD method there's no cache) +--- request +HEAD /hello-world +--- error_code: 200 +--- response_headers +Apisix-Cache-Status: MISS +--- no_error_log +[error] + + + +=== TEST 19: hide cache headers = false +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "plugins": { + "proxy-cache": { + "cache_zone": "disk_cache_one", + "cache_key": ["$uri", "-cache-id"], + "cache_bypass": ["$arg_bypass"], + "cache_method": ["GET"], + "cache_http_status": [200], + "hide_cache_headers": false, + "no_cache": ["$arg_no_cache"] + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1986": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello*" }]] ) @@ -345,3 +557,35 @@ GET /t passed --- no_error_log [error] + + + +=== TEST 20: hit route (catch the cache headers) +--- request +GET /hello +--- response_body chop +hello world! +--- response_headers +Apisix-Cache-Status: HIT +--- response_headers_like +Cache-Control: +--- no_error_log +[error] + + + +=== TEST 21: purge cache +--- request +PURGE /hello +--- error_code: 200 +--- no_error_log +[error] + + + +=== TEST 22: purge cache (not found) +--- request +PURGE /hello-world +--- error_code: 404 +--- no_error_log +[error] From f6a78e40359bd870ab5872ea7d9da4ecda764887 Mon Sep 17 00:00:00 2001 From: agile6v Date: Thu, 12 Mar 2020 20:50:37 +0800 Subject: [PATCH 18/21] Update README --- doc/README.md | 1 + doc/README_CN.md | 1 + 2 files changed, 2 insertions(+) diff --git a/doc/README.md b/doc/README.md index 7b01f22f5b38..6e66f7bf9299 100644 --- a/doc/README.md +++ b/doc/README.md @@ -58,6 +58,7 @@ Plugins * [redirect](plugins/redirect.md): URI redirect. * [response-rewrite](plugins/response-rewrite.md): Set customized response status code, body and header to the client. * [fault-injection](plugins/fault-injection.md): The specified response body, response code, and response time can be returned, which provides processing capabilities in different failure scenarios, such as service failure, service overload, and high service delay. +* [proxy-cache](plugins/proxy-cache.md): Provides the ability to cache upstream response data. Deploy to the Cloud ======= diff --git a/doc/README_CN.md b/doc/README_CN.md index a60d55833bed..b5f5ef2fa48e 100644 --- a/doc/README_CN.md +++ b/doc/README_CN.md @@ -58,4 +58,5 @@ Reference document * [redirect](plugins/redirect-cn.md): URI 重定向。 * [response-rewrite](plugins/response-rewrite-cn.md): 支持自定义修改返回内容的 `status code`、`body`、`headers`。 * [fault-injection](plugins/fault-injection-cn.md):故障注入,可以返回指定的响应体、响应码和响应时间,从而提供了不同的失败场景下处理的能力,例如服务失败、服务过载、服务高延时等。 +* [proxy-cache](plugins/proxy-cache-cn.md):代理缓存插件提供缓存后端响应数据的能力。 From 67a5424fff2b64f4d6bdecf4df4649ee4dd52abf Mon Sep 17 00:00:00 2001 From: agile6v Date: Sun, 15 Mar 2020 16:03:56 +0800 Subject: [PATCH 19/21] Add default value for cache_key. --- lua/apisix/plugins/proxy-cache.lua | 5 ++- t/plugin/proxy-cache.t | 69 +++++++++++++++++++++++------- 2 files changed, 57 insertions(+), 17 deletions(-) diff --git a/lua/apisix/plugins/proxy-cache.lua b/lua/apisix/plugins/proxy-cache.lua index 35d83503394c..b84368a686cc 100644 --- a/lua/apisix/plugins/proxy-cache.lua +++ b/lua/apisix/plugins/proxy-cache.lua @@ -43,8 +43,9 @@ local schema = { items = { description = "a key for caching", type = "string", - pattern = [[(^[^\$].+$|^\$[0-9a-zA-Z_]+$)]] + pattern = [[(^[^\$].+$|^\$[0-9a-zA-Z_]+$)]], }, + default = {"$host", "$uri"} }, cache_http_status = { type = "array", @@ -91,7 +92,7 @@ local schema = { }, }, }, - required = {"cache_zone", "cache_key"}, + required = {"cache_zone"}, } local _M = { diff --git a/t/plugin/proxy-cache.t b/t/plugin/proxy-cache.t index 70234c2b5a8c..e7e9e54ff2de 100644 --- a/t/plugin/proxy-cache.t +++ b/t/plugin/proxy-cache.t @@ -14,16 +14,6 @@ # See the License for the specific language governing permissions and # limitations under the License. # -BEGIN { - if ($ENV{TEST_NGINX_CHECK_LEAK}) { - $SkipReason = "unavailable for the hup tests"; - - } else { - $ENV{TEST_NGINX_USE_HUP} = 1; - undef $ENV{TEST_NGINX_USE_STAP}; - } -} - use t::APISIX 'no_plan'; repeat_each(1); @@ -123,7 +113,6 @@ qr/failed to check the configuration of plugin proxy-cache/ "plugins": { "proxy-cache": { "cache_zone": "disk_cache_one", - "cache_key": ["$uri"], "cache_bypass": ["$arg_bypass"], "cache_method": "GET", "cache_http_status": [200], @@ -213,7 +202,6 @@ qr/failed to check the configuration of plugin proxy-cache/ "plugins": { "proxy-cache": { "cache_zone": "disk_cache_one", - "cache_key": ["$uri", "-cache-id"], "cache_bypass": "$arg_bypass", "cache_method": ["GET"], "cache_http_status": [200], @@ -258,7 +246,6 @@ qr/failed to check the configuration of plugin proxy-cache/ "plugins": { "proxy-cache": { "cache_zone": "disk_cache_one", - "cache_key": ["$uri", "-cache-id"], "cache_bypass": ["$arg_bypass"], "cache_method": ["GET"], "cache_http_status": [200], @@ -348,7 +335,6 @@ qr/failed to check the configuration of plugin proxy-cache/ "plugins": { "proxy-cache": { "cache_zone": "disk_cache_one", - "cache_key": ["$uri", "-cache-id"], "cache_bypass": ["$arg_bypass"], "cache_method": ["GET"], "cache_http_status": [200], @@ -526,7 +512,6 @@ Apisix-Cache-Status: MISS "plugins": { "proxy-cache": { "cache_zone": "disk_cache_one", - "cache_key": ["$uri", "-cache-id"], "cache_bypass": ["$arg_bypass"], "cache_method": ["GET"], "cache_http_status": [200], @@ -589,3 +574,57 @@ PURGE /hello-world --- error_code: 404 --- no_error_log [error] + + + +=== TEST 23: invalid cache zone +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "plugins": { + "proxy-cache": { + "cache_zone": "invalid_disk_cache", + "cache_bypass": ["$arg_bypass"], + "cache_method": ["GET"], + "cache_http_status": [200], + "hide_cache_headers": false, + "no_cache": ["$arg_no_cache"] + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1986": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello*" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- error_code: 200 +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 24: hit route (invalid cache zone) +--- request +GET /hello +--- error_code: 500 +--- error_log +cache "invalid_disk_cache" not found + From 20b12d39a310c6060fb0a0ada8f6e84b74e063f9 Mon Sep 17 00:00:00 2001 From: agile6v Date: Sun, 15 Mar 2020 16:17:40 +0800 Subject: [PATCH 20/21] Update default value for cache_key --- lua/apisix/plugins/proxy-cache.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/apisix/plugins/proxy-cache.lua b/lua/apisix/plugins/proxy-cache.lua index b84368a686cc..60f73057c9ab 100644 --- a/lua/apisix/plugins/proxy-cache.lua +++ b/lua/apisix/plugins/proxy-cache.lua @@ -45,7 +45,7 @@ local schema = { type = "string", pattern = [[(^[^\$].+$|^\$[0-9a-zA-Z_]+$)]], }, - default = {"$host", "$uri"} + default = {"$host", "$request_uri"} }, cache_http_status = { type = "array", From c3fc9098a1300c8d25bbbbb49bd439aa63d389a6 Mon Sep 17 00:00:00 2001 From: agile6v Date: Sun, 15 Mar 2020 22:07:25 +0800 Subject: [PATCH 21/21] Check if the cache_zone is valid. --- lua/apisix/plugins/proxy-cache.lua | 13 +++++++++++++ t/plugin/proxy-cache.t | 15 +++------------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/lua/apisix/plugins/proxy-cache.lua b/lua/apisix/plugins/proxy-cache.lua index 60f73057c9ab..05c1f5a6d95c 100644 --- a/lua/apisix/plugins/proxy-cache.lua +++ b/lua/apisix/plugins/proxy-cache.lua @@ -109,6 +109,19 @@ function _M.check_schema(conf) return false, err end + local found = false + local local_conf = core.config.local_conf() + if local_conf.apisix.proxy_cache then + for _, cache in ipairs(local_conf.apisix.proxy_cache.zones) do + if cache.name == conf.cache_zone then + found = true + end + end + + if found == false then + return false, "cache_zone " .. conf.cache_zone .. " not found" + end + end return true end diff --git a/t/plugin/proxy-cache.t b/t/plugin/proxy-cache.t index e7e9e54ff2de..cb485173c90e 100644 --- a/t/plugin/proxy-cache.t +++ b/t/plugin/proxy-cache.t @@ -613,18 +613,9 @@ PURGE /hello-world } --- request GET /t ---- error_code: 200 ---- response_body -passed +--- error_code: 400 +--- response_body eval +qr/cache_zone invalid_disk_cache not found/ --- no_error_log [error] - - -=== TEST 24: hit route (invalid cache zone) ---- request -GET /hello ---- error_code: 500 ---- error_log -cache "invalid_disk_cache" not found -