From d89343e30781883672d97fabfb586b83382aea67 Mon Sep 17 00:00:00 2001 From: thefosk Date: Thu, 6 Apr 2017 14:44:16 -0700 Subject: [PATCH] feat(core) new rewrite_by_lua handler for plugins Signed-off-by: Thibault Charbonnier This handler exposes the Nginx rewrite phase. This handler is called for all loaded plugins (since the rewrite phase is executed prior to API matching). From #2354 --- CHANGELOG.md | 7 + kong/core/handler.lua | 9 ++ kong/core/plugins_iterator.lua | 11 +- kong/kong.lua | 13 ++ kong/plugins/base_plugin.lua | 19 ++- kong/plugins/log-serializers/basic.lua | 3 +- kong/templates/nginx_kong.lua | 4 + .../05-proxy/10-handler_spec.lua | 151 ++++++++++++++++++ .../kong/plugins/rewriter/handler.lua | 19 +++ .../kong/plugins/rewriter/schema.lua | 5 + 10 files changed, 231 insertions(+), 10 deletions(-) create mode 100644 spec/02-integration/05-proxy/10-handler_spec.lua create mode 100644 spec/fixtures/custom_plugins/kong/plugins/rewriter/handler.lua create mode 100644 spec/fixtures/custom_plugins/kong/plugins/rewriter/schema.lua diff --git a/CHANGELOG.md b/CHANGELOG.md index 2160ba58f40d..f27ffb10175c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,13 @@ ### Added + :fireworks: Plugins can implement a new `rewrite_by_lua` handler to execute + code in the Nginx rewrite phase. This phase is executed prior to matching a + registered Kong API, and prior to any authentication plugin. As such, plugins + implementing this phase don't have to be configured via the Admin API to be + executed. Enabled plugins (loaded via the `custom_plugins` Kong configuration + value) will execute their `rewrite_by_lua` handler for each request. + [#2354](https://github.com/Mashape/kong/pull/2354) - Ability for the client to chose whether the upstream request (Kong <-> upstream) should contain a trailing slash in its URI. Prior to this change, Kong 0.10 would unconditionally append a trailing slash to all upstream diff --git a/kong/core/handler.lua b/kong/core/handler.lua index 01a2a2188538..ab44b8921f71 100644 --- a/kong/core/handler.lua +++ b/kong/core/handler.lua @@ -66,6 +66,15 @@ return { certificate.execute() end }, + rewrite = { + before = function() + ngx.ctx.KONG_REWRITE_START = get_now() + end, + after = function () + local ctx = ngx.ctx + ctx.KONG_REWRITE_TIME = get_now() - ctx.KONG_REWRITE_START -- time spent in Kong's rewrite_by_lua + end + }, access = { before = function() if not router then diff --git a/kong/core/plugins_iterator.lua b/kong/core/plugins_iterator.lua index 5c10df0002d9..8cbfb7c998f9 100644 --- a/kong/core/plugins_iterator.lua +++ b/kong/core/plugins_iterator.lua @@ -66,7 +66,8 @@ local function iter_plugins_for_req(loaded_plugins, access_or_cert_ctx) local function get_next() i = i + 1 local plugin = loaded_plugins[i] - if plugin and ctx.api then + local api = ctx.api + if plugin then -- load the plugin configuration in early phases if access_or_cert_ctx then @@ -75,7 +76,9 @@ local function iter_plugins_for_req(loaded_plugins, access_or_cert_ctx) -- Search API and Consumer specific, or consumer specific local consumer_id = (ctx.authenticated_consumer or empty).id if consumer_id and plugin.schema and not plugin.schema.no_consumer then - plugin_configuration = load_plugin_configuration(ctx.api.id, consumer_id, plugin.name) + if api then + plugin_configuration = load_plugin_configuration(api.id, consumer_id, plugin.name) + end if not plugin_configuration then plugin_configuration = load_plugin_configuration(nil, consumer_id, plugin.name) end @@ -83,7 +86,9 @@ local function iter_plugins_for_req(loaded_plugins, access_or_cert_ctx) if not plugin_configuration then -- Search API specific, or global - plugin_configuration = load_plugin_configuration(ctx.api.id, nil, plugin.name) + if api then + plugin_configuration = load_plugin_configuration(api.id, nil, plugin.name) + end if not plugin_configuration then plugin_configuration = load_plugin_configuration(nil, nil, plugin.name) end diff --git a/kong/kong.lua b/kong/kong.lua index 9689789f4c57..d38d54b718c7 100644 --- a/kong/kong.lua +++ b/kong/kong.lua @@ -259,6 +259,19 @@ function Kong.balancer() end end +function Kong.rewrite() + core.rewrite.before() + + -- we're just using the iterator, as in this rewrite phase no consumer nor + -- api will have been identified, hence we'll just be executing the global + -- plugins + for plugin, plugin_conf in plugins_iterator(singletons.loaded_plugins, true) do + plugin.handler:rewrite(plugin_conf) + end + + core.rewrite.after() +end + function Kong.access() core.access.before() diff --git a/kong/plugins/base_plugin.lua b/kong/plugins/base_plugin.lua index 2732922129ef..d3b7e0ab340d 100644 --- a/kong/plugins/base_plugin.lua +++ b/kong/plugins/base_plugin.lua @@ -1,32 +1,39 @@ local Object = require "kong.vendor.classic" local BasePlugin = Object:extend() +local ngx_log = ngx.log +local DEBUG = ngx.DEBUG + function BasePlugin:new(name) self._name = name end function BasePlugin:init_worker() - ngx.log(ngx.DEBUG, " executing plugin \""..self._name.."\": init_worker") + ngx_log(DEBUG, "executing plugin \"", self._name, "\": init_worker") end function BasePlugin:certificate() - ngx.log(ngx.DEBUG, " executing plugin \""..self._name.."\": certificate") + ngx_log(DEBUG, "executing plugin \"", self._name, "\": certificate") +end + +function BasePlugin:rewrite() + ngx_log(DEBUG, "executing plugin \"", self._name, "\": rewrite") end function BasePlugin:access() - ngx.log(ngx.DEBUG, " executing plugin \""..self._name.."\": access") + ngx_log(DEBUG, "executing plugin \"", self._name, "\": access") end function BasePlugin:header_filter() - ngx.log(ngx.DEBUG, " executing plugin \""..self._name.."\": header_filter") + ngx_log(DEBUG, "executing plugin \"", self._name, "\": header_filter") end function BasePlugin:body_filter() - ngx.log(ngx.DEBUG, " executing plugin \""..self._name.."\": body_filter") + ngx_log(DEBUG, "executing plugin \"", self._name, "\": body_filter") end function BasePlugin:log() - ngx.log(ngx.DEBUG, " executing plugin \""..self._name.."\": log") + ngx_log(DEBUG, "executing plugin \"", self._name, "\": log") end return BasePlugin diff --git a/kong/plugins/log-serializers/basic.lua b/kong/plugins/log-serializers/basic.lua index a2847be44488..4404c6ea2eed 100644 --- a/kong/plugins/log-serializers/basic.lua +++ b/kong/plugins/log-serializers/basic.lua @@ -28,7 +28,8 @@ function _M.serialize(ngx) tries = addr.tries, latencies = { kong = (ngx.ctx.KONG_ACCESS_TIME or 0) + - (ngx.ctx.KONG_RECEIVE_TIME or 0), + (ngx.ctx.KONG_RECEIVE_TIME or 0) + + (ngx.ctx.KONG_REWRITE_TIME or 0), proxy = ngx.ctx.KONG_WAITING_TIME or -1, request = ngx.var.request_time * 1000 }, diff --git a/kong/templates/nginx_kong.lua b/kong/templates/nginx_kong.lua index cdad3158d5b6..536dad9a7540 100644 --- a/kong/templates/nginx_kong.lua +++ b/kong/templates/nginx_kong.lua @@ -96,6 +96,10 @@ server { set $upstream_host nil; set $upstream_scheme nil; + rewrite_by_lua_block { + kong.rewrite() + } + access_by_lua_block { kong.access() } diff --git a/spec/02-integration/05-proxy/10-handler_spec.lua b/spec/02-integration/05-proxy/10-handler_spec.lua new file mode 100644 index 000000000000..375424c1d490 --- /dev/null +++ b/spec/02-integration/05-proxy/10-handler_spec.lua @@ -0,0 +1,151 @@ +local helpers = require "spec.helpers" + +describe("OpenResty phases", function() + describe("rewrite_by_lua", function() + describe("enabled on all APIs", function() + local api_client, proxy_client + + setup(function() + -- insert plugin-less api and a global plugin + assert(helpers.dao.apis:insert { + name = "rewrite1", + hosts = { "rewriter1.com" }, + upstream_url = "http://mockbin.org" + }) + assert(helpers.dao.plugins:insert { + name = "rewriter", + config = { + value = "global plugin", + }, + }) + + assert(helpers.start_kong({ + custom_plugins = "rewriter", + })) + + api_client = helpers.admin_client() + proxy_client = helpers.proxy_client() + end) + + teardown(function() + if api_client then api_client:close() end + helpers.stop_kong() + end) + + it("runs", function() + local res = assert(proxy_client:send { + method = "GET", + path = "/request", + headers = { + host = "rewriter1.com", + }, + }) + assert.response(res).has.status(200) + local value = assert.request(res).has.header("rewriter") + assert.equal("global plugin", value) + end) + end) + + describe("enabled on a specific APIs", function() + local api_client, proxy_client + + setup(function() + -- api specific plugin + local api2 = assert(helpers.dao.apis:insert { + name = "rewrite2", + hosts = { "rewriter2.com" }, + upstream_url = "http://mockbin.org" + }) + assert(helpers.dao.plugins:insert { + api_id = api2.id, + name = "rewriter", + config = { + value = "api-specific plugin", + }, + }) + + assert(helpers.start_kong({ + custom_plugins = "rewriter", + })) + + api_client = helpers.admin_client() + proxy_client = helpers.proxy_client() + end) + + teardown(function() + if api_client then api_client:close() end + helpers.stop_kong() + end) + + it("doesn't run", function() + local res = assert(proxy_client:send { + method = "GET", + path = "/request", + headers = { + host = "rewriter2.com", + }, + }) + assert.response(res).has.status(200) + assert.request(res).has.no.header("rewriter") + end) + end) + + describe("enabled on a specific Consumers", function() + local api_client, proxy_client + + setup(function() + -- consumer specific plugin + local api3 = assert(helpers.dao.apis:insert { + name = "rewrite3", + hosts = { "rewriter3.com" }, + upstream_url = "http://mockbin.org" + }) + assert(helpers.dao.plugins:insert { + api_id = api3.id, + name = "key-auth", + }) + local consumer3 = assert(helpers.dao.consumers:insert { + username = "test-consumer", + }) + assert(helpers.dao.keyauth_credentials:insert { + key = "kong", + consumer_id = consumer3.id + }) + assert(helpers.dao.plugins:insert { + consumer_id = consumer3.id, + name = "rewriter", + config = { + value = "consumer-specific plugin", + }, + }) + + assert(helpers.start_kong({ + custom_plugins = "rewriter", + })) + + api_client = helpers.admin_client() + proxy_client = helpers.proxy_client() + end) + + teardown(function() + if api_client then api_client:close() end + helpers.stop_kong() + end) + + it("doesn't run", function() + local res = assert(proxy_client:send { + method = "GET", + path = "/request", + headers = { + host = "rewriter3.com", + apikey = "kong" + }, + }) + assert.response(res).has.status(200) + local value = assert.request(res).has.header("x-consumer-username") + assert.equal("test-consumer", value) + assert.request(res).has.no.header("rewriter") + end) + end) + end) +end) diff --git a/spec/fixtures/custom_plugins/kong/plugins/rewriter/handler.lua b/spec/fixtures/custom_plugins/kong/plugins/rewriter/handler.lua new file mode 100644 index 000000000000..5906b421f8c4 --- /dev/null +++ b/spec/fixtures/custom_plugins/kong/plugins/rewriter/handler.lua @@ -0,0 +1,19 @@ +-- a plugin fixture to test running of the rewrite phase handler. + +local BasePlugin = require "kong.plugins.base_plugin" + +local Rewriter = BasePlugin:extend() + +Rewriter.PRIORITY = 1000 + +function Rewriter:new() + Rewriter.super.new(self, "rewriter") +end + +function Rewriter:rewrite(conf) + Rewriter.super.access(self) + + ngx.req.set_header("rewriter", conf.value) +end + +return Rewriter \ No newline at end of file diff --git a/spec/fixtures/custom_plugins/kong/plugins/rewriter/schema.lua b/spec/fixtures/custom_plugins/kong/plugins/rewriter/schema.lua new file mode 100644 index 000000000000..b1c9db36b1c9 --- /dev/null +++ b/spec/fixtures/custom_plugins/kong/plugins/rewriter/schema.lua @@ -0,0 +1,5 @@ +return { + fields = { + value = { typ = "string" } + } +} \ No newline at end of file