diff --git a/config.default/kong.yml b/config.default/kong.yml index e8887895bdb..a3d348956bb 100644 --- a/config.default/kong.yml +++ b/config.default/kong.yml @@ -16,3 +16,7 @@ databases_available: timeout: 1000 keyspace: kong keepalive: 60000 + +# Cache configuration +cache: + expiration: 5 # In seconds \ No newline at end of file diff --git a/config.default/nginx.conf b/config.default/nginx.conf index 7a6bb26fb20..04e4aaec738 100644 --- a/config.default/nginx.conf +++ b/config.default/nginx.conf @@ -46,6 +46,8 @@ http { resolver 8.8.8.8; charset UTF-8; + lua_shared_dict cache 512m; + init_by_lua "kong = require 'kong'; kong.init()"; server { diff --git a/spec/unit/utils_spec.lua b/spec/unit/utils_spec.lua index f41c07066bc..8d72b120702 100644 --- a/spec/unit/utils_spec.lua +++ b/spec/unit/utils_spec.lua @@ -3,6 +3,19 @@ local cjson = require "cjson" describe("Utils #utils", function() + describe("Cache", function() + it("should return a valid API cache key", function() + assert.are.equal("apis/httpbin.org", utils.cache_api_key("httpbin.org")) + end) + it("should return a valid PLUGIN cache key", function() + assert.are.equal("plugins/authentication/api123/app123", utils.cache_plugin_key("authentication", "api123", "app123")) + assert.are.equal("plugins/authentication/api123", utils.cache_plugin_key("authentication", "api123")) + end) + it("should return a valid Application cache key", function() + assert.are.equal("applications/username", utils.cache_application_key("username")) + end) + end) + describe("HTTP", function() describe("GET", function() it("should return a valid GET response", function() diff --git a/src/kong/constants.lua b/src/kong/constants.lua index ba94c51d808..47b61f820e2 100644 --- a/src/kong/constants.lua +++ b/src/kong/constants.lua @@ -16,6 +16,11 @@ return { RATELIMIT_LIMIT = "X-RateLimit-Limit", RATELIMIT_REMAINING = "X-RateLimit-Remaining" }, + CACHE = { + APIS = "apis", + PLUGINS = "plugins", + APPLICATIONS = "applications" + }, AUTHENTICATION = { QUERY = "query", BASIC = "basic", diff --git a/src/kong/core/access.lua b/src/kong/core/access.lua index 718d6737599..ede8ff1ed8a 100644 --- a/src/kong/core/access.lua +++ b/src/kong/core/access.lua @@ -1,4 +1,6 @@ local stringy = require "stringy" +local constants = require "kong.constants" +local url = require("socket.url") local _M = {} @@ -16,6 +18,19 @@ local function get_backend_url(api) return result end +function get_host_header(val) + local parsed_url = url.parse(val) + + local port + if parsed_url.port then + port = parsed_url.port + elseif parsed_url.scheme == "https" then + port = 443 + end + + return parsed_url.host..(port and ":"..port or "") +end + local function skip_authentication(headers) -- Skip upload request that expect a 100 Continue response return headers["expect"] and _M.starts_with(headers["expect"], "100") @@ -23,20 +38,24 @@ end function _M.execute(conf) -- Retrieving the API from the Host that has been requested - local apis, err = dao.apis:find_by_keys({public_dns = stringy.split(ngx.var.http_host, ":")[1]}) - if err then - ngx.log(ngx.ERR, err.message) - utils.show_error(500) - elseif not apis or #apis == 0 then - utils.not_found("API not found") - end + local host = stringy.strip(stringy.split(ngx.var.http_host, ":")[1]) - local api = apis[1] + local api = utils.cache_get_and_set(utils.cache_api_key(host), function() + local apis, err = dao.apis:find_by_keys({public_dns = host}) + if err then + ngx.log(ngx.ERR, err.message) + utils.show_error(500) + elseif not apis or #apis == 0 then + utils.not_found("API not found") + end + return apis[1] + end) -- Setting the backend URL for the proxy_pass directive ngx.var.backend_url = get_backend_url(api) .. ngx.var.request_uri - -- TODO: Move this away from here + ngx.req.set_header("host", get_host_header(ngx.var.backend_url)) + -- There are some requests whose authentication needs to be skipped if skip_authentication(ngx.req.get_headers()) then return -- Returning and keeping the Lua code running to the next handler diff --git a/src/kong/plugins/authentication/access.lua b/src/kong/plugins/authentication/access.lua index bead5ef029f..d8382d05bdb 100644 --- a/src/kong/plugins/authentication/access.lua +++ b/src/kong/plugins/authentication/access.lua @@ -162,13 +162,17 @@ function _M.execute(conf) -- Make sure we are not sending an empty table to find_by_keys if public_key then - local applications, err = dao.applications:find_by_keys { public_key = public_key } - if err then - ngx.log(ngx.ERR, err.message) - utils.show_error(500) - elseif #applications > 0 then - application = applications[1] - end + application = utils.cache_get_and_set(utils.cache_application_key(public_key), function() + local applications, err = dao.applications:find_by_keys { public_key = public_key } + local result + if err then + ngx.log(ngx.ERR, err.message) + utils.show_error(500) + elseif #applications > 0 then + result = applications[1] + end + return result + end) end if not validate_credentials[conf.authentication_type](application, public_key, secret_key) then diff --git a/src/kong/tools/utils.lua b/src/kong/tools/utils.lua index dd180c028e8..93b9c6bd5cf 100644 --- a/src/kong/tools/utils.lua +++ b/src/kong/tools/utils.lua @@ -203,6 +203,70 @@ function _M.create_timer(func, data) end end +-- +-- Cache utils +-- + +function _M.cache_set(key, value, exptime) + if exptime == nil then exptime = 0 end -- By default never expire + local cache = ngx.shared.cache + if value then + value = cjson.encode(value) + end + if ngx then + ngx.log(ngx.DEBUG, " saving cache key \""..key.."\": "..value) + end + local succ, err, forcible = cache:set(key, value, exptime) + return succ, err, forcible +end + +function _M.cache_get(key) + if ngx then + ngx.log(ngx.DEBUG, " Try to get cache key \""..key.."\"") + end + + local cache = ngx.shared.cache + local value, flags = cache:get(key) + if value then + if ngx then + ngx.log(ngx.DEBUG, " Found cache value for key \""..key.."\": "..value) + end + value = cjson.decode(value) + end + return value, flags +end + +function _M.cache_delete(key) + local cache = ngx.shared.cache + cache:delete(key) +end + +function _M.cache_api_key(host) + return constants.CACHE.APIS.."/"..host +end + +function _M.cache_plugin_key(name, api_id, application_id) + return constants.CACHE.PLUGINS.."/"..name.."/"..api_id..(application_id and "/"..application_id or "") +end + +function _M.cache_application_key(public_key) + return constants.CACHE.APPLICATIONS.."/"..public_key +end + +function _M.cache_get_and_set(key, cb) + local val = _M.cache_get(key) + if not val then + val = cb() + if val then + local succ, err, forcible = _M.cache_set(key, val) + if not succ and ngx then + ngx.log(ngx.ERR, err) + end + end + end + return val +end + -- -- Disk I/O utils -- diff --git a/src/main.lua b/src/main.lua index 1cf409ed9da..9f1efed3f71 100644 --- a/src/main.lua +++ b/src/main.lua @@ -33,25 +33,31 @@ local plugins = {} local _M = {} local function load_plugin_conf(api_id, application_id, plugin_name) - local rows, err = dao.plugins:find_by_keys { - api_id = api_id, - application_id = application_id ~= nil and application_id or constants.DATABASE_NULL_ID, - name = plugin_name - } + local cache_key = utils.cache_plugin_key(plugin_name, api_id, application_id) + + local plugin = utils.cache_get_and_set(cache_key, function() + local rows, err = dao.plugins:find_by_keys { + api_id = api_id, + application_id = application_id ~= nil and application_id or constants.DATABASE_NULL_ID, + name = plugin_name + } + if err then + ngx.log(ngx.ERR, err.message) + utils.show_error(500) + end - if err then - ngx.log(ngx.ERR, err.message) - utils.show_error(500) - end + if #rows > 0 then + return table.remove(rows, 1) + else + return {null=true} + end + end) - if #rows > 0 then - local plugin = table.remove(rows, 1) - if plugin.enabled then - return plugin - end + if plugin and not plugin.null and plugin.enabled then + return plugin + else + return nil end - - return nil end function _M.init()