Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[refactor/resolver] lighter kong.lua #708

Merged
merged 5 commits into from
Nov 10, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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