feat(apisix): add Cloudron package
- Implements Apache APISIX packaging for Cloudron platform. - Includes Dockerfile, CloudronManifest.json, and start.sh. - Configured to use Cloudron's etcd addon. 🤖 Generated with Gemini CLI Co-Authored-By: Gemini <noreply@google.com>
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
--
|
||||
-- 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 os = os
|
||||
local ngx_re = require("ngx.re")
|
||||
local core = require("apisix.core")
|
||||
local util = require("apisix.plugins.proxy-cache.util")
|
||||
|
||||
local _M = {}
|
||||
|
||||
|
||||
local function disk_cache_purge(conf, ctx)
|
||||
local cache_zone_info = ngx_re.split(ctx.var.upstream_cache_zone_info, ",")
|
||||
|
||||
local filename = util.generate_cache_filename(cache_zone_info[1], cache_zone_info[2],
|
||||
ctx.var.upstream_cache_key)
|
||||
|
||||
if util.file_exists(filename) then
|
||||
os.remove(filename)
|
||||
return nil
|
||||
end
|
||||
|
||||
return "Not found"
|
||||
end
|
||||
|
||||
|
||||
function _M.access(conf, ctx)
|
||||
ctx.var.upstream_cache_zone = conf.cache_zone
|
||||
|
||||
if ctx.var.request_method == "PURGE" then
|
||||
local err = disk_cache_purge(conf, ctx)
|
||||
if err ~= nil then
|
||||
return 404
|
||||
end
|
||||
|
||||
return 200
|
||||
end
|
||||
|
||||
if conf.cache_bypass ~= nil then
|
||||
local value = util.generate_complex_value(conf.cache_bypass, ctx)
|
||||
ctx.var.upstream_cache_bypass = value
|
||||
core.log.info("proxy-cache cache bypass value:", value)
|
||||
end
|
||||
|
||||
if not util.match_method(conf, ctx) then
|
||||
ctx.var.upstream_cache_bypass = "1"
|
||||
core.log.info("proxy-cache cache bypass method: ", ctx.var.request_method)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function _M.header_filter(conf, ctx)
|
||||
local no_cache = "1"
|
||||
|
||||
if util.match_method(conf, ctx) and util.match_status(conf, ctx) then
|
||||
no_cache = "0"
|
||||
end
|
||||
|
||||
if conf.no_cache ~= nil then
|
||||
local value = util.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"
|
||||
end
|
||||
end
|
||||
|
||||
local upstream_hdr_cache_control
|
||||
local upstream_hdr_expires
|
||||
|
||||
if conf.hide_cache_headers == true then
|
||||
upstream_hdr_cache_control = ""
|
||||
upstream_hdr_expires = ""
|
||||
else
|
||||
upstream_hdr_cache_control = ctx.var.upstream_http_cache_control
|
||||
upstream_hdr_expires = ctx.var.upstream_http_expires
|
||||
end
|
||||
|
||||
core.response.set_header("Cache-Control", upstream_hdr_cache_control,
|
||||
"Expires", upstream_hdr_expires,
|
||||
"Apisix-Cache-Status", ctx.var.upstream_cache_status)
|
||||
|
||||
ctx.var.upstream_no_cache = no_cache
|
||||
core.log.info("proxy-cache no cache:", no_cache)
|
||||
end
|
||||
|
||||
|
||||
return _M
|
@@ -0,0 +1,198 @@
|
||||
--
|
||||
-- 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 memory_handler = require("apisix.plugins.proxy-cache.memory_handler")
|
||||
local disk_handler = require("apisix.plugins.proxy-cache.disk_handler")
|
||||
local util = require("apisix.plugins.proxy-cache.util")
|
||||
local core = require("apisix.core")
|
||||
local ipairs = ipairs
|
||||
|
||||
local plugin_name = "proxy-cache"
|
||||
|
||||
local STRATEGY_DISK = "disk"
|
||||
local STRATEGY_MEMORY = "memory"
|
||||
local DEFAULT_CACHE_ZONE = "disk_cache_one"
|
||||
|
||||
local schema = {
|
||||
type = "object",
|
||||
properties = {
|
||||
cache_zone = {
|
||||
type = "string",
|
||||
minLength = 1,
|
||||
maxLength = 100,
|
||||
default = DEFAULT_CACHE_ZONE,
|
||||
},
|
||||
cache_strategy = {
|
||||
type = "string",
|
||||
enum = {STRATEGY_DISK, STRATEGY_MEMORY},
|
||||
default = STRATEGY_DISK,
|
||||
},
|
||||
cache_key = {
|
||||
type = "array",
|
||||
minItems = 1,
|
||||
items = {
|
||||
description = "a key for caching",
|
||||
type = "string",
|
||||
pattern = [[(^[^\$].+$|^\$[0-9a-zA-Z_]+$)]],
|
||||
},
|
||||
default = {"$host", "$request_uri"}
|
||||
},
|
||||
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 = "supported http method",
|
||||
type = "string",
|
||||
enum = {"GET", "POST", "HEAD"},
|
||||
},
|
||||
uniqueItems = true,
|
||||
default = {"GET", "HEAD"},
|
||||
},
|
||||
hide_cache_headers = {
|
||||
type = "boolean",
|
||||
default = false,
|
||||
},
|
||||
cache_control = {
|
||||
type = "boolean",
|
||||
default = false,
|
||||
},
|
||||
cache_bypass = {
|
||||
type = "array",
|
||||
minItems = 1,
|
||||
items = {
|
||||
type = "string",
|
||||
pattern = [[(^[^\$].+$|^\$[0-9a-zA-Z_]+$)]]
|
||||
},
|
||||
},
|
||||
no_cache = {
|
||||
type = "array",
|
||||
minItems = 1,
|
||||
items = {
|
||||
type = "string",
|
||||
pattern = [[(^[^\$].+$|^\$[0-9a-zA-Z_]+$)]]
|
||||
},
|
||||
},
|
||||
cache_ttl = {
|
||||
type = "integer",
|
||||
minimum = 1,
|
||||
default = 300,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
local _M = {
|
||||
version = 0.2,
|
||||
priority = 1085,
|
||||
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
|
||||
|
||||
for _, key in ipairs(conf.cache_key) do
|
||||
if key == "$request_method" then
|
||||
return false, "cache_key variable " .. key .. " unsupported"
|
||||
end
|
||||
end
|
||||
|
||||
local found = false
|
||||
local local_conf = core.config.local_conf()
|
||||
if local_conf.apisix.proxy_cache then
|
||||
local err = "cache_zone " .. conf.cache_zone .. " not found"
|
||||
for _, cache in ipairs(local_conf.apisix.proxy_cache.zones) do
|
||||
-- cache_zone passed in plugin config matched one of the proxy_cache zones
|
||||
if cache.name == conf.cache_zone then
|
||||
-- check for the mismatch between cache_strategy and corresponding cache zone
|
||||
if (conf.cache_strategy == STRATEGY_MEMORY and cache.disk_path) or
|
||||
(conf.cache_strategy == STRATEGY_DISK and not cache.disk_path) then
|
||||
err = "invalid or empty cache_zone for cache_strategy: "..conf.cache_strategy
|
||||
else
|
||||
found = true
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if found == false then
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
function _M.access(conf, ctx)
|
||||
core.log.info("proxy-cache plugin access phase, conf: ", core.json.delay_encode(conf))
|
||||
|
||||
local value = util.generate_complex_value(conf.cache_key, ctx)
|
||||
ctx.var.upstream_cache_key = value
|
||||
core.log.info("proxy-cache cache key value:", value)
|
||||
|
||||
local handler
|
||||
if conf.cache_strategy == STRATEGY_MEMORY then
|
||||
handler = memory_handler
|
||||
else
|
||||
handler = disk_handler
|
||||
end
|
||||
|
||||
return handler.access(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 handler
|
||||
if conf.cache_strategy == STRATEGY_MEMORY then
|
||||
handler = memory_handler
|
||||
else
|
||||
handler = disk_handler
|
||||
end
|
||||
|
||||
handler.header_filter(conf, ctx)
|
||||
end
|
||||
|
||||
|
||||
function _M.body_filter(conf, ctx)
|
||||
core.log.info("proxy-cache plugin body filter phase, conf: ", core.json.delay_encode(conf))
|
||||
|
||||
if conf.cache_strategy == STRATEGY_MEMORY then
|
||||
memory_handler.body_filter(conf, ctx)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return _M
|
@@ -0,0 +1,84 @@
|
||||
--
|
||||
-- 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 ngx = ngx
|
||||
local ngx_shared = ngx.shared
|
||||
local setmetatable = setmetatable
|
||||
local core = require("apisix.core")
|
||||
|
||||
local _M = {}
|
||||
local mt = { __index = _M }
|
||||
|
||||
|
||||
function _M.new(opts)
|
||||
return setmetatable({
|
||||
dict = ngx_shared[opts.shdict_name],
|
||||
}, mt)
|
||||
end
|
||||
|
||||
|
||||
function _M:set(key, obj, ttl)
|
||||
if self.dict == nil then
|
||||
return nil, "invalid cache_zone provided"
|
||||
end
|
||||
|
||||
local obj_json = core.json.encode(obj)
|
||||
if not obj_json then
|
||||
return nil, "could not encode object"
|
||||
end
|
||||
|
||||
local succ, err = self.dict:set(key, obj_json, ttl)
|
||||
return succ and obj_json or nil, err
|
||||
end
|
||||
|
||||
|
||||
function _M:get(key)
|
||||
if self.dict == nil then
|
||||
return nil, "invalid cache_zone provided"
|
||||
end
|
||||
|
||||
-- If the key does not exist or has expired, then res_json will be nil.
|
||||
local res_json, err, stale = self.dict:get_stale(key)
|
||||
if not res_json then
|
||||
if not err then
|
||||
return nil, "not found"
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
if stale then
|
||||
return nil, "expired"
|
||||
end
|
||||
|
||||
local res_obj, err = core.json.decode(res_json)
|
||||
if not res_obj then
|
||||
return nil, err
|
||||
end
|
||||
|
||||
return res_obj, nil
|
||||
end
|
||||
|
||||
|
||||
function _M:purge(key)
|
||||
if self.dict == nil then
|
||||
return nil, "invalid cache_zone provided"
|
||||
end
|
||||
self.dict:delete(key)
|
||||
end
|
||||
|
||||
|
||||
return _M
|
@@ -0,0 +1,332 @@
|
||||
--
|
||||
-- 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 memory_strategy = require("apisix.plugins.proxy-cache.memory").new
|
||||
local util = require("apisix.plugins.proxy-cache.util")
|
||||
local core = require("apisix.core")
|
||||
local tab_new = require("table.new")
|
||||
local ngx_re_gmatch = ngx.re.gmatch
|
||||
local ngx_re_match = ngx.re.match
|
||||
local parse_http_time = ngx.parse_http_time
|
||||
local concat = table.concat
|
||||
local lower = string.lower
|
||||
local floor = math.floor
|
||||
local tostring = tostring
|
||||
local tonumber = tonumber
|
||||
local ngx = ngx
|
||||
local type = type
|
||||
local pairs = pairs
|
||||
local time = ngx.now
|
||||
local max = math.max
|
||||
|
||||
local CACHE_VERSION = 1
|
||||
|
||||
local _M = {}
|
||||
|
||||
-- http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1
|
||||
-- note content-length & apisix-cache-status are not strictly
|
||||
-- hop-by-hop but we will be adjusting it here anyhow
|
||||
local hop_by_hop_headers = {
|
||||
["connection"] = true,
|
||||
["keep-alive"] = true,
|
||||
["proxy-authenticate"] = true,
|
||||
["proxy-authorization"] = true,
|
||||
["te"] = true,
|
||||
["trailers"] = true,
|
||||
["transfer-encoding"] = true,
|
||||
["upgrade"] = true,
|
||||
["content-length"] = true,
|
||||
["apisix-cache-status"] = true,
|
||||
}
|
||||
|
||||
|
||||
local function include_cache_header(header)
|
||||
local n_header = lower(header)
|
||||
if n_header == "expires" or n_header == "cache-control" then
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
local function overwritable_header(header)
|
||||
local n_header = lower(header)
|
||||
|
||||
return not hop_by_hop_headers[n_header]
|
||||
and not ngx_re_match(n_header, "ratelimit-remaining")
|
||||
end
|
||||
|
||||
|
||||
-- The following format can accept:
|
||||
-- Cache-Control: no-cache
|
||||
-- Cache-Control: no-store
|
||||
-- Cache-Control: max-age=3600
|
||||
-- Cache-Control: max-stale=3600
|
||||
-- Cache-Control: min-fresh=3600
|
||||
-- Cache-Control: private, max-age=600
|
||||
-- Cache-Control: public, max-age=31536000
|
||||
-- Refer to: https://www.holisticseo.digital/pagespeed/cache-control/
|
||||
local function parse_directive_header(h)
|
||||
if not h then
|
||||
return {}
|
||||
end
|
||||
|
||||
if type(h) == "table" then
|
||||
h = concat(h, ", ")
|
||||
end
|
||||
|
||||
local t = {}
|
||||
local res = tab_new(3, 0)
|
||||
local iter = ngx_re_gmatch(h, "([^,]+)", "oj")
|
||||
|
||||
local m = iter()
|
||||
while m do
|
||||
local _, err = ngx_re_match(m[0], [[^\s*([^=]+)(?:=(.+))?]],
|
||||
"oj", nil, res)
|
||||
if err then
|
||||
core.log.error(err)
|
||||
end
|
||||
|
||||
-- store the directive token as a numeric value if it looks like a number;
|
||||
-- otherwise, store the string value. for directives without token, we just
|
||||
-- set the key to true
|
||||
t[lower(res[1])] = tonumber(res[2]) or res[2] or true
|
||||
|
||||
m = iter()
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
|
||||
|
||||
local function parse_resource_ttl(ctx, cc)
|
||||
local max_age = cc["s-maxage"] or cc["max-age"]
|
||||
|
||||
if not max_age then
|
||||
local expires = ctx.var.upstream_http_expires
|
||||
|
||||
-- if multiple Expires headers are present, last one wins
|
||||
if type(expires) == "table" then
|
||||
expires = expires[#expires]
|
||||
end
|
||||
|
||||
local exp_time = parse_http_time(tostring(expires))
|
||||
if exp_time then
|
||||
max_age = exp_time - time()
|
||||
end
|
||||
end
|
||||
|
||||
return max_age and max(max_age, 0) or 0
|
||||
end
|
||||
|
||||
|
||||
local function cacheable_request(conf, ctx, cc)
|
||||
if not util.match_method(conf, ctx) then
|
||||
return false, "MISS"
|
||||
end
|
||||
|
||||
if conf.cache_bypass ~= nil then
|
||||
local value = util.generate_complex_value(conf.cache_bypass, ctx)
|
||||
core.log.info("proxy-cache cache bypass value:", value)
|
||||
if value ~= nil and value ~= "" and value ~= "0" then
|
||||
return false, "BYPASS"
|
||||
end
|
||||
end
|
||||
|
||||
if conf.cache_control and (cc["no-store"] or cc["no-cache"]) then
|
||||
return false, "BYPASS"
|
||||
end
|
||||
|
||||
return true, ""
|
||||
end
|
||||
|
||||
|
||||
local function cacheable_response(conf, ctx, cc)
|
||||
if not util.match_status(conf, ctx) then
|
||||
return false
|
||||
end
|
||||
|
||||
if conf.no_cache ~= nil then
|
||||
local value = util.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
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
if conf.cache_control and (cc["private"] or cc["no-store"] or cc["no-cache"]) then
|
||||
return false
|
||||
end
|
||||
|
||||
if conf.cache_control and parse_resource_ttl(ctx, cc) <= 0 then
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
function _M.access(conf, ctx)
|
||||
local cc = parse_directive_header(ctx.var.http_cache_control)
|
||||
|
||||
if ctx.var.request_method ~= "PURGE" then
|
||||
local ret, msg = cacheable_request(conf, ctx, cc)
|
||||
if not ret then
|
||||
core.response.set_header("Apisix-Cache-Status", msg)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if not ctx.cache then
|
||||
ctx.cache = {
|
||||
memory = memory_strategy({shdict_name = conf.cache_zone}),
|
||||
hit = false,
|
||||
ttl = 0,
|
||||
}
|
||||
end
|
||||
|
||||
local res, err = ctx.cache.memory:get(ctx.var.upstream_cache_key)
|
||||
|
||||
if ctx.var.request_method == "PURGE" then
|
||||
if err == "not found" then
|
||||
return 404
|
||||
end
|
||||
ctx.cache.memory:purge(ctx.var.upstream_cache_key)
|
||||
ctx.cache = nil
|
||||
return 200
|
||||
end
|
||||
|
||||
if err then
|
||||
if err == "expired" then
|
||||
core.response.set_header("Apisix-Cache-Status", "EXPIRED")
|
||||
|
||||
elseif err ~= "not found" then
|
||||
core.response.set_header("Apisix-Cache-Status", "MISS")
|
||||
core.log.error("failed to get from cache, err: ", err)
|
||||
|
||||
elseif conf.cache_control and cc["only-if-cached"] then
|
||||
core.response.set_header("Apisix-Cache-Status", "MISS")
|
||||
return 504
|
||||
|
||||
else
|
||||
core.response.set_header("Apisix-Cache-Status", "MISS")
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if res.version ~= CACHE_VERSION then
|
||||
core.log.warn("cache format mismatch, purging ", ctx.var.upstream_cache_key)
|
||||
core.response.set_header("Apisix-Cache-Status", "BYPASS")
|
||||
ctx.cache.memory:purge(ctx.var.upstream_cache_key)
|
||||
return
|
||||
end
|
||||
|
||||
if conf.cache_control then
|
||||
if cc["max-age"] and time() - res.timestamp > cc["max-age"] then
|
||||
core.response.set_header("Apisix-Cache-Status", "STALE")
|
||||
return
|
||||
end
|
||||
|
||||
if cc["max-stale"] and time() - res.timestamp - res.ttl > cc["max-stale"] then
|
||||
core.response.set_header("Apisix-Cache-Status", "STALE")
|
||||
return
|
||||
end
|
||||
|
||||
if cc["min-fresh"] and res.ttl - (time() - res.timestamp) < cc["min-fresh"] then
|
||||
core.response.set_header("Apisix-Cache-Status", "STALE")
|
||||
return
|
||||
end
|
||||
else
|
||||
if time() - res.timestamp > res.ttl then
|
||||
core.response.set_header("Apisix-Cache-Status", "STALE")
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
ctx.cache.hit = true
|
||||
|
||||
for key, value in pairs(res.headers) do
|
||||
if conf.hide_cache_headers == true and include_cache_header(key) then
|
||||
core.response.set_header(key, "")
|
||||
elseif overwritable_header(key) then
|
||||
core.response.set_header(key, value)
|
||||
end
|
||||
end
|
||||
|
||||
core.response.set_header("Age", floor(time() - res.timestamp))
|
||||
core.response.set_header("Apisix-Cache-Status", "HIT")
|
||||
|
||||
return res.status, res.body
|
||||
end
|
||||
|
||||
|
||||
function _M.header_filter(conf, ctx)
|
||||
local cache = ctx.cache
|
||||
if not cache or cache.hit then
|
||||
return
|
||||
end
|
||||
|
||||
local res_headers = ngx.resp.get_headers(0, true)
|
||||
|
||||
for key in pairs(res_headers) do
|
||||
if conf.hide_cache_headers == true and include_cache_header(key) then
|
||||
core.response.set_header(key, "")
|
||||
end
|
||||
end
|
||||
|
||||
local cc = parse_directive_header(ctx.var.upstream_http_cache_control)
|
||||
|
||||
if cacheable_response(conf, ctx, cc) then
|
||||
cache.res_headers = res_headers
|
||||
cache.ttl = conf.cache_control and parse_resource_ttl(ctx, cc) or conf.cache_ttl
|
||||
else
|
||||
ctx.cache = nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function _M.body_filter(conf, ctx)
|
||||
local cache = ctx.cache
|
||||
if not cache or cache.hit then
|
||||
return
|
||||
end
|
||||
|
||||
local res_body = core.response.hold_body_chunk(ctx, true)
|
||||
if not res_body then
|
||||
return
|
||||
end
|
||||
|
||||
local res = {
|
||||
status = ngx.status,
|
||||
body = res_body,
|
||||
body_len = #res_body,
|
||||
headers = cache.res_headers,
|
||||
ttl = cache.ttl,
|
||||
timestamp = time(),
|
||||
version = CACHE_VERSION,
|
||||
}
|
||||
|
||||
local res, err = cache.memory:set(ctx.var.upstream_cache_key, res, cache.ttl)
|
||||
if not res then
|
||||
core.log.error("failed to set cache, err: ", err)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return _M
|
@@ -0,0 +1,102 @@
|
||||
--
|
||||
-- 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 ngx_re = require("ngx.re")
|
||||
local tab_concat = table.concat
|
||||
local string = string
|
||||
local io_open = io.open
|
||||
local io_close = io.close
|
||||
local ngx = ngx
|
||||
local ipairs = ipairs
|
||||
local pairs = pairs
|
||||
local tonumber = tonumber
|
||||
|
||||
local _M = {}
|
||||
|
||||
local tmp = {}
|
||||
function _M.generate_complex_value(data, ctx)
|
||||
core.table.clear(tmp)
|
||||
|
||||
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 string.byte(value, 1, 1) == string.byte('$') then
|
||||
tmp[i] = ctx.var[string.sub(value, 2)] or ""
|
||||
else
|
||||
tmp[i] = value
|
||||
end
|
||||
end
|
||||
|
||||
return tab_concat(tmp, "")
|
||||
end
|
||||
|
||||
|
||||
-- check whether the request method match the user defined.
|
||||
function _M.match_method(conf, ctx)
|
||||
for _, method in ipairs(conf.cache_method) do
|
||||
if method == ctx.var.request_method then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
-- check whether the response status match the user defined.
|
||||
function _M.match_status(conf, ctx)
|
||||
for _, status in ipairs(conf.cache_http_status) do
|
||||
if status == ngx.status then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
function _M.file_exists(name)
|
||||
local f = io_open(name, "r")
|
||||
if f ~= nil then
|
||||
io_close(f)
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
function _M.generate_cache_filename(cache_path, cache_levels, cache_key)
|
||||
local md5sum = ngx.md5(cache_key)
|
||||
local levels = ngx_re.split(cache_levels, ":")
|
||||
local filename = ""
|
||||
|
||||
local index = #md5sum
|
||||
for k, v in pairs(levels) do
|
||||
local length = tonumber(v)
|
||||
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
|
||||
|
||||
return _M
|
Reference in New Issue
Block a user