Skip to content

Commit

Permalink
Merge pull request #708 from Mashape/refactor/resolver
Browse files Browse the repository at this point in the history
[refactor/resolver] lighter kong.lua
  • Loading branch information
thibaultcha committed Nov 10, 2015
2 parents ef43926 + 96200df commit 058c1c2
Show file tree
Hide file tree
Showing 30 changed files with 428 additions and 352 deletions.
13 changes: 5 additions & 8 deletions kong-0.5.2-1.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,11 @@ build = {
["kong.tools.config_loader"] = "kong/tools/config_loader.lua",
["kong.tools.dao_loader"] = "kong/tools/dao_loader.lua",

["kong.resolver.handler"] = "kong/resolver/handler.lua",
["kong.resolver.access"] = "kong/resolver/access.lua",
["kong.resolver.header_filter"] = "kong/resolver/header_filter.lua",
["kong.resolver.certificate"] = "kong/resolver/certificate.lua",

["kong.reports.handler"] = "kong/reports/handler.lua",
["kong.reports.init_worker"] = "kong/reports/init_worker.lua",
["kong.reports.log"] = "kong/reports/log.lua",
["kong.core.handler"] = "kong/core/handler.lua",
["kong.core.certificate"] = "kong/core/certificate.lua",
["kong.core.resolver"] = "kong/core/resolver.lua",
["kong.core.plugins_iterator"] = "kong/core/plugins_iterator.lua",
["kong.core.reports"] = "kong/core/reports.lua",

["kong.dao.cassandra.schema.migrations"] = "kong/dao/cassandra/schema/migrations.lua",
["kong.dao.error"] = "kong/dao/error.lua",
Expand Down
9 changes: 5 additions & 4 deletions kong.yml
Original file line number Diff line number Diff line change
Expand Up @@ -225,18 +225,19 @@ nginx: |
default_type 'text/plain';
# These properties will be used later by proxy_pass
set $backend_host nil;
set $backend_url nil;
set $upstream_host nil;
set $upstream_url nil;
# Authenticate the user and load the API info
access_by_lua 'kong.exec_plugins_access()';
# Proxy the request
# Proxy the request
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $backend_host;
proxy_pass $backend_url;
proxy_set_header Host $upstream_host;
proxy_pass $upstream_url;
proxy_pass_header Server;
# Add additional response headers
Expand Down
4 changes: 2 additions & 2 deletions kong/constants.lua
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ return {
-- Non standard headers, specific to Kong
HEADERS = {
HOST_OVERRIDE = "X-Host-Override",
PROXY_TIME = "X-Kong-Proxy-Time",
API_TIME = "X-Kong-Api-Time",
PROXY_LATENCY = "X-Kong-Proxy-Latency",
UPSTREAM_LATENCY = "X-Kong-Upstream-Latency",
CONSUMER_ID = "X-Consumer-ID",
CONSUMER_CUSTOM_ID = "X-Consumer-Custom-ID",
CONSUMER_USERNAME = "X-Consumer-Username",
Expand Down
10 changes: 6 additions & 4 deletions kong/resolver/certificate.lua → kong/core/certificate.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ local function find_api(hosts)
local sanitized_host = stringy.split(host, ":")[1]

retrieved_api, err = cache.get_or_set(cache.api_key(sanitized_host), function()
local apis, err = dao.apis:find_by_keys { request_host = sanitized_host }
local apis, err = dao.apis:find_by_keys {request_host = sanitized_host}
if err then
return nil, err
elseif apis and #apis == 1 then
Expand All @@ -23,14 +23,16 @@ local function find_api(hosts)
end
end

function _M.execute(conf)
function _M.execute()
local ssl = require "ngx.ssl"
local server_name = ssl.server_name()
if server_name then -- Only support SNI requests
local api, err = find_api({server_name})
if not err and api then
ngx.ctx.api = api
if err then
ngx.log(ngx.ERR, err)
end

return api
end
end

Expand Down
121 changes: 121 additions & 0 deletions kong/core/handler.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
-- Kong core
--
-- This consists of events than need to
-- be ran at the very beginning and very end of the lua-nginx-module contexts.
-- It mainly carries information related to a request from one context to the next one,
-- through the `ngx.ctx` table.
--
-- In the `access_by_lua` phase, it is responsible for retrieving the API being proxied by
-- a Consumer. Then it is responsible for loading the plugins to execute on this request.
--
-- In other phases, we create different variables and timers.
-- Variables:
-- `plugins_to_execute`: an array of plugin to be executed for this request.
-- Timers:
-- `KONG_<CONTEXT_NAME>_STARTED_AT`: time at which a given context is started to be executed by all Kong plugins.
-- `KONG_<CONTEXT_NAME>_ENDED_AT`: time at which all plugins have been executed by Kong for this context.
-- `KONG_<CONTEXT_NAME>_TIME`: time taken by Kong to execute all the plugins for this context
--
-- @see https://github.com/openresty/lua-nginx-module#ngxctx

local utils = require "kong.tools.utils"
local reports = require "kong.core.reports"
local stringy = require "stringy"
local resolver = require "kong.core.resolver"
local constants = require "kong.constants"
local certificate = require "kong.core.certificate"

local table_insert = table.insert
local math_floor = math.floor

local MULT = 10^3
local function round(num)
return math_floor(num * MULT + 0.5) / MULT
end

return {
init_worker = function()
reports.init_worker()
end,
certificate = function()
ngx.ctx.api = certificate.execute()
end,
access = {
before = function()
ngx.ctx.KONG_ACCESS_START = ngx.now()
ngx.ctx.api, ngx.ctx.upstream_url = resolver.execute()
end,
-- Only executed if the `resolver` module found an API and allows nginx to proxy it.
after = function()
local now = ngx.now()
ngx.ctx.KONG_ACCESS_TIME = now - ngx.ctx.KONG_ACCESS_START
ngx.ctx.KONG_ACCESS_ENDED_AT = now
ngx.ctx.KONG_PROXIED = true

-- Append any querystring parameters modified during plugins execution
local upstream_url = unpack(stringy.split(ngx.ctx.upstream_url, "?"))
if utils.table_size(ngx.req.get_uri_args()) > 0 then
upstream_url = upstream_url.."?"..ngx.encode_args(ngx.req.get_uri_args())
end

-- Set the `$upstream_url` variable for the `proxy_pass` nginx's directive.
ngx.var.upstream_url = upstream_url
end
},
header_filter = {
before = function()
if ngx.ctx.KONG_PROXIED then
ngx.ctx.KONG_HEADER_FILTER_STARTED_AT = ngx.now()
end
end,
after = function()
if ngx.ctx.KONG_PROXIED then
local now = ngx.now()
local proxy_started_at = ngx.ctx.KONG_ACCESS_ENDED_AT
local proxy_ended_at = ngx.ctx.KONG_HEADER_FILTER_STARTED_AT
local upstream_response_time = round(proxy_ended_at - proxy_started_at)
local proxy_time = round(now - ngx.req.start_time() - upstream_response_time)

ngx.ctx.KONG_HEADER_FILTER_TIME = now - ngx.ctx.KONG_HEADER_FILTER_STARTED_AT
ngx.header[constants.HEADERS.UPSTREAM_LATENCY] = upstream_response_time * 1000 -- ms
ngx.header[constants.HEADERS.PROXY_LATENCY] = proxy_time * 1000 -- ms
ngx.header["Via"] = constants.NAME.."/"..constants.VERSION
else
ngx.header["Server"] = constants.NAME.."/"..constants.VERSION
end
end
},
-- `body_filter_by_lua` can be executed mutiple times depending on the size of the
-- response body.
-- To compute the time spent in Kong, we keep an array of size n,
-- n being the number of times the directive ran:
-- starts = {4312, 5423, 4532}
-- ends = {4320, 5430, 4550}
-- time = 8 + 7 + 18 = 33 = total time spent in `body_filter` in all plugins
body_filter = {
before = function()
if ngx.ctx.KONG_BODY_FILTER_STARTS == nil then
ngx.ctx.KONG_BODY_FILTER_STARTS = {}
ngx.ctx.KONG_BODY_FILTER_EDINGS = {}
end
table_insert(ngx.ctx.KONG_BODY_FILTER_STARTS, ngx.now())
end,
after = function()
table_insert(ngx.ctx.KONG_BODY_FILTER_EDINGS, ngx.now())

if ngx.arg[2] then
-- compute time spent in Kong's body_filters
local total_time = 0
for i in ipairs(ngx.ctx.KONG_BODY_FILTER_EDINGS) do
total_time = total_time + (ngx.ctx.KONG_BODY_FILTER_EDINGS[i] - ngx.ctx.KONG_BODY_FILTER_STARTS[i])
end
ngx.ctx.KONG_BODY_FILTER_TIME = total_time
ngx.ctx.KONG_BODY_FILTER_STARTS = nil
ngx.ctx.KONG_BODY_FILTER_EDINGS = nil
end
end
},
log = function()
reports.log()
end
}
118 changes: 118 additions & 0 deletions kong/core/plugins_iterator.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
local cache = require "kong.tools.database_cache"
local constants = require "kong.constants"
local responses = require "kong.tools.responses"

local table_remove = table.remove
local table_insert = table.insert
local ipairs = ipairs

--- Load the configuration for a plugin entry in the DB.
-- Given an API, a Consumer and a plugin name, retrieve the plugin's configuration if it exists.
-- Results are cached in ngx.dict
-- @param[type=string] api_id ID of the API being proxied.
-- @param[type=string] consumer_id ID of the Consumer making the request (if any).
-- @param[type=stirng] plugin_name Name of the plugin being tested for.
-- @treturn table Plugin retrieved from the cache or database.
local function load_plugin_configuration(api_id, consumer_id, plugin_name)
local cache_key = cache.plugin_key(plugin_name, api_id, consumer_id)

local plugin = cache.get_or_set(cache_key, function()
local rows, err = dao.plugins:find_by_keys {
api_id = api_id,
consumer_id = consumer_id ~= nil and consumer_id or constants.DATABASE_NULL_ID,
name = plugin_name
}
if err then
return responses.send_HTTP_INTERNAL_SERVER_ERROR(err)
end

if #rows > 0 then
return table_remove(rows, 1)
else
-- insert a cached value to not trigger too many DB queries.
-- for now, this will lock the cache for the expiraiton duration.
return {null = true}
end
end)

if plugin ~= nil and plugin.enabled then
return plugin.config or {}
end
end

local function load_plugins_for_req(loaded_plugins)
if ngx.ctx.plugins_for_request == nil then
local t = {}
-- Build an array of plugins that must be executed for this particular request.
-- A plugin is considered to be executed if there is a row in the DB which contains:
-- 1. the API id (contained in ngx.ctx.api.id, retrived by the core resolver)
-- 2. a Consumer id, in which case it overrides any previous plugin found in 1.
-- this use case will be treated once the authentication plugins have run (access phase).
-- Such a row will contain a `config` value, which is a table.
if ngx.ctx.api ~= nil then
for _, plugin in ipairs(loaded_plugins) do
local plugin_configuration = load_plugin_configuration(ngx.ctx.api.id, nil, plugin.name)
if plugin_configuration ~= nil then
table_insert(t, {plugin, plugin_configuration})
end
end
end

ngx.ctx.plugins_for_request = t
end
end

--- Plugins for request iterator.
-- Iterate over the plugin loaded for a request, stored in `ngx.ctx.plugins_for_request`.
-- @param[type=string] context_name Name of the current nginx context. We don't use `ngx.get_phase()` simply because we can avoid it.
-- @treturn function iterator
local function iter_plugins_for_req(loaded_plugins, context_name)
-- In case previous contexts did not run, we need to handle
-- the case when plugins have not been fetched for a given request.
-- This will simply make it so the look gets skipped if no API is set in the context
load_plugins_for_req(loaded_plugins)

local i = 0

-- Iterate on plugins to execute for this request until
-- a plugin with a handler for the given context is found.
local function get_next()
i = i + 1
local p = ngx.ctx.plugins_for_request[i]
if p == nil then
return
end

local plugin, plugin_configuration = p[1], p[2]
if plugin.handler[context_name] == nil then
ngx.log(ngx.DEBUG, "No handler for "..context_name.." phase on "..plugin.name.." plugin")
return get_next()
end

return plugin, plugin_configuration
end

return function()
local plugin, plugin_configuration = get_next()

-- Check if any Consumer was authenticated during the access phase.
-- If so, retrieve the configuration for this Consumer which overrides
-- the API-wide configuration.
if plugin ~= nil and context_name == "access" then
local consumer_id = ngx.ctx.authenticated_credential and ngx.ctx.authenticated_credential.consumer_id or nil
if consumer_id ~= nil then
local consumer_plugin_configuration = load_plugin_configuration(ngx.ctx.api.id, consumer_id, plugin.name)
if consumer_plugin_configuration ~= nil then
-- This Consumer has a special configuration when this plugin gets executed.
-- Override this plugin's configuration for this request.
plugin_configuration = consumer_plugin_configuration
ngx.ctx.plugins_for_request[i][2] = consumer_plugin_configuration
end
end
end

return plugin, plugin_configuration
end
end

return iter_plugins_for_req
21 changes: 11 additions & 10 deletions kong/reports/init_worker.lua → kong/core/reports.lua
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
local syslog = require "kong.tools.syslog"
local lock = require "resty.lock"
local cache = require "kong.tools.database_cache"

local INTERVAL = 3600

local _M = {}

local function create_timer(at, cb)
local ok, err = ngx.timer.at(at, cb)
if not ok then
Expand All @@ -14,7 +11,8 @@ local function create_timer(at, cb)
end

local function send_ping(premature)
local lock = lock:new("locks", {
local resty_lock = require "resty.lock"
local lock = resty_lock:new("locks", {
exptime = INTERVAL - 0.001
})
local elapsed = lock:lock("ping")
Expand All @@ -27,9 +25,12 @@ local function send_ping(premature)
create_timer(INTERVAL, send_ping)
end

function _M.execute()
cache.rawset(cache.requests_key(), 0, 0) -- Initializing the counter
create_timer(INTERVAL, send_ping)
end

return _M
return {
init_worker = function()
cache.rawset(cache.requests_key(), 0, 0) -- Initializing the counter
create_timer(INTERVAL, send_ping)
end,
log = function()
cache.incr(cache.requests_key(), 1)
end
}
Loading

0 comments on commit 058c1c2

Please sign in to comment.