blob: 326b529fe50f2ad09ecdb571b069300c0d05cacd [file] [log] [blame]
--
-- 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 os = os
local ipairs = ipairs
local pairs = pairs
local tonumber = tonumber
local plugin_name = "proxy-cache"
local schema = {
type = "object",
properties = {
cache_zone = {
type = "string",
minLength = 1,
maxLength = 100,
default = "disk_cache_one",
},
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 = core.schema.method_schema,
uniqueItems = true,
default = {"GET", "HEAD"},
},
hide_cache_headers = {
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_]+$)]]
},
},
},
}
local _M = {
version = 0.1,
priority = 1009,
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
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
local tmp = {}
local function 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)]
else
tmp[i] = value
end
end
return tab_concat(tmp, "")
end
-- check whether the request method and response status
-- match the user defined.
local function match_method_and_status(conf, ctx)
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
match_method = true
break
end
end
for _, status in ipairs(conf.cache_http_status) do
if status == ngx.status then
match_status = true
break
end
end
if match_method and match_status then
return true
end
return false
end
local function file_exists(name)
local f = io_open(name, "r")
if f ~= nil then
io_close(f)
return true
end
return false
end
local function 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
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))
local no_cache = "1"
if match_method_and_status(conf, ctx) then
no_cache = "0"
end
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"
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