From f3cd113776691c9ab35e16a1b872fc4477e9fd8f Mon Sep 17 00:00:00 2001 From: Paul Austin Date: Fri, 3 Feb 2017 19:27:23 -0800 Subject: [PATCH 01/20] feat(schema): regex for numbers Modified the schema validation to allow regex on numbers. For example \\d{3} to only accept 3 digits in the number. --- kong/dao/schemas_validation.lua | 2 +- spec/01-unit/07-schema_validation_spec.lua | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/kong/dao/schemas_validation.lua b/kong/dao/schemas_validation.lua index 6efa5eec48f3..c9b587adc2af 100644 --- a/kong/dao/schemas_validation.lua +++ b/kong/dao/schemas_validation.lua @@ -147,7 +147,7 @@ function _M.validate_entity(tbl, schema, options) end -- [REGEX] Check field against a regex if specified - if type(t[column]) == "string" and v.regex then + if (type(t[column]) == "string" or type(t[column]) == "number") and v.regex then if not ngx.re.find(t[column], v.regex) then errors = utils.add_error(errors, error_prefix..column, column.." has an invalid value") end diff --git a/spec/01-unit/07-schema_validation_spec.lua b/spec/01-unit/07-schema_validation_spec.lua index 2a88fb235389..29523197a40a 100644 --- a/spec/01-unit/07-schema_validation_spec.lua +++ b/spec/01-unit/07-schema_validation_spec.lua @@ -13,6 +13,7 @@ describe("Schemas", function() string = { type = "string", required = true, immutable = true}, table = {type = "table"}, number = {type = "number"}, + numberRegex = {type = "number", regex = "\\d{3}"}, url = {regex = "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])$"}, date = {default = 123456, immutable = true}, allowed = {enum = {"hello", "world"}}, @@ -296,6 +297,14 @@ describe("Schemas", function() assert.truthy(err) assert.are.same("url has an invalid value", err.url) end) + it("should validate a field against a regex", function() + local values = {string = "value", numberRegex = "22"} + + local valid, err = validate_entity(values, schema) + assert.falsy(valid) + assert.truthy(err) + assert.are.same("numberRegex has an invalid value", err.numberRegex) + end) end) describe("[enum]", function() From fb608ba31c1f4aa0240e48021cc46b19764d4840 Mon Sep 17 00:00:00 2001 From: Paul Austin Date: Thu, 26 Jan 2017 18:30:40 +0000 Subject: [PATCH 02/20] feat(tools): HTTP Response 503 Service unavailable Add HTTP response for 503 Service unavailable. This will be used for a new api-unavailable plugin that will send a 503 error. See #1279. --- kong/tools/responses.lua | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/kong/tools/responses.lua b/kong/tools/responses.lua index aaea4183dcbd..8ae096ced3c3 100644 --- a/kong/tools/responses.lua +++ b/kong/tools/responses.lua @@ -39,6 +39,7 @@ local server_header = meta._NAME.."/"..meta._VERSION -- @field HTTP_CONFLICT 409 Conflict -- @field HTTP_UNSUPPORTED_MEDIA_TYPE 415 Unsupported Media Type -- @field HTTP_INTERNAL_SERVER_ERROR Internal Server Error +-- @field HTTP_SERVICE_UNAVAILABLE 503 Service Unavailable -- @usage return responses.send_HTTP_OK() -- @usage return responses.HTTP_CREATED("Entity created") -- @usage return responses.HTTP_INTERNAL_SERVER_ERROR() @@ -55,7 +56,8 @@ local _M = { HTTP_METHOD_NOT_ALLOWED = 405, HTTP_CONFLICT = 409, HTTP_UNSUPPORTED_MEDIA_TYPE = 415, - HTTP_INTERNAL_SERVER_ERROR = 500 + HTTP_INTERNAL_SERVER_ERROR = 500, + HTTP_SERVICE_UNAVAILABLE = 503 } } @@ -68,6 +70,7 @@ local _M = { -- @field status_codes.HTTP_UNAUTHORIZED Default: Unauthorized -- @field status_codes.HTTP_INTERNAL_SERVER_ERROR Always "Internal Server Error" -- @field status_codes.HTTP_METHOD_NOT_ALLOWED Always "Method not allowed" +-- @field status_codes.HTTP_SERVICE_UNAVAILABLE Default: "Service Unavailable" local response_default_content = { [_M.status_codes.HTTP_UNAUTHORIZED] = function(content) return content or "Unauthorized" @@ -83,6 +86,9 @@ local response_default_content = { end, [_M.status_codes.HTTP_METHOD_NOT_ALLOWED] = function(content) return "Method not allowed" + end, + [_M.status_codes.HTTP_SERVICE_UNAVAILABLE] = function(content) + return content or "Service Unavailable" end } @@ -96,7 +102,7 @@ local function send_response(status_code) -- @param content (Optional) The content to send as a response. -- @return ngx.exit (Exit current context) return function(content, headers) - if status_code >= _M.status_codes.HTTP_INTERNAL_SERVER_ERROR then + if status_code >= _M.status_codes.HTTP_INTERNAL_SERVER_ERROR and status_code ~= _M.status_codes.HTTP_SERVICE_UNAVAILABLE then if content then ngx.log(ngx.ERR, tostring(content)) end From ff4e2b8fdaaf09674f9a2184be405ce520c8b22c Mon Sep 17 00:00:00 2001 From: Paul Austin Date: Thu, 26 Jan 2017 22:07:58 +0000 Subject: [PATCH 03/20] Code style changes Added , to end of list Changed to only log 500 errors --- kong/tools/responses.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kong/tools/responses.lua b/kong/tools/responses.lua index 8ae096ced3c3..f28688b3d812 100644 --- a/kong/tools/responses.lua +++ b/kong/tools/responses.lua @@ -57,7 +57,7 @@ local _M = { HTTP_CONFLICT = 409, HTTP_UNSUPPORTED_MEDIA_TYPE = 415, HTTP_INTERNAL_SERVER_ERROR = 500, - HTTP_SERVICE_UNAVAILABLE = 503 + HTTP_SERVICE_UNAVAILABLE = 503, } } @@ -102,7 +102,7 @@ local function send_response(status_code) -- @param content (Optional) The content to send as a response. -- @return ngx.exit (Exit current context) return function(content, headers) - if status_code >= _M.status_codes.HTTP_INTERNAL_SERVER_ERROR and status_code ~= _M.status_codes.HTTP_SERVICE_UNAVAILABLE then + if status_code == _M.status_codes.HTTP_INTERNAL_SERVER_ERROR then if content then ngx.log(ngx.ERR, tostring(content)) end From 97a24d5268a7114f07f7fd583e3ce476dd37353c Mon Sep 17 00:00:00 2001 From: Paul Austin Date: Fri, 27 Jan 2017 11:59:15 +0000 Subject: [PATCH 04/20] Implemented tests and updated comments --- kong/tools/responses.lua | 10 +++++----- spec/01-unit/10-responses_spec.lua | 19 ++++++++++++++++++- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/kong/tools/responses.lua b/kong/tools/responses.lua index f28688b3d812..c602bbed0e5a 100644 --- a/kong/tools/responses.lua +++ b/kong/tools/responses.lua @@ -70,7 +70,7 @@ local _M = { -- @field status_codes.HTTP_UNAUTHORIZED Default: Unauthorized -- @field status_codes.HTTP_INTERNAL_SERVER_ERROR Always "Internal Server Error" -- @field status_codes.HTTP_METHOD_NOT_ALLOWED Always "Method not allowed" --- @field status_codes.HTTP_SERVICE_UNAVAILABLE Default: "Service Unavailable" +-- @field status_codes.HTTP_SERVICE_UNAVAILABLE Default: "Service unavailable" local response_default_content = { [_M.status_codes.HTTP_UNAUTHORIZED] = function(content) return content or "Unauthorized" @@ -88,8 +88,8 @@ local response_default_content = { return "Method not allowed" end, [_M.status_codes.HTTP_SERVICE_UNAVAILABLE] = function(content) - return content or "Service Unavailable" - end + return content or "Service unavailable" + end, } -- Return a closure which will be usable to respond with a certain status code. @@ -97,7 +97,7 @@ local response_default_content = { -- @param[type=number] status_code The status for which to define a function local function send_response(status_code) -- Send a JSON response for the closure's status code with the given content. - -- If the content happens to be an error (>500), it will be logged by ngx.log as an ERR. + -- If the content happens to be an error (500), it will be logged by ngx.log as an ERR. -- @see https://github.com/openresty/lua-nginx-module -- @param content (Optional) The content to send as a response. -- @return ngx.exit (Exit current context) @@ -147,7 +147,7 @@ local closure_cache = {} --- Send a response with any status code or body, -- Not all status codes are available as sugar methods, this function can be -- used to send any response. --- If the `status_code` parameter is in the 5xx range, it is expectde that the `content` parameter be the error encountered. It will be logged and the response body will be empty. The user will just receive a 500 status code. +-- If the `status_code` parameter is in the 5xx range, it is expected that the `content` parameter be the error encountered. For 500 errors it will be logged. The response body will be empty. The user will just receive a 500 status code. -- Will call `ngx.say` and `ngx.exit`, terminating the current context. -- @see ngx.say -- @see ngx.exit diff --git a/spec/01-unit/10-responses_spec.lua b/spec/01-unit/10-responses_spec.lua index 77b0b89c3f87..1b7bad3fb60d 100644 --- a/spec/01-unit/10-responses_spec.lua +++ b/spec/01-unit/10-responses_spec.lua @@ -58,9 +58,12 @@ describe("Response helpers", function() end) end end) - it("calls `ngx.log` if and only if a 500 status code range was given", function() + it("calls `ngx.log` if and only if a 500 status code was given", function() responses.send_HTTP_BAD_REQUEST() assert.stub(ngx.log).was_not_called() + + responses.send_HTTP_BAD_REQUEST("error") + assert.stub(ngx.log).was_not_called() responses.send_HTTP_INTERNAL_SERVER_ERROR() assert.stub(ngx.log).was_not_called() @@ -69,6 +72,14 @@ describe("Response helpers", function() assert.stub(ngx.log).was_called() end) + it("don't call `ngx.log` if a 503 status code was given", function() + responses.send_HTTP_SERVICE_UNAVAILABLE() + assert.stub(ngx.log).was_not_called() + + responses.send_HTTP_SERVICE_UNAVAILABLE() + assert.stub(ngx.log).was_not_called("error") + end) + describe("default content rules for some status codes", function() it("should apply default content rules for some status codes", function() responses.send_HTTP_NOT_FOUND() @@ -86,6 +97,12 @@ describe("Response helpers", function() responses.send_HTTP_INTERNAL_SERVER_ERROR("override") assert.stub(ngx.say).was.called_with("{\"message\":\"An unexpected error occurred\"}") end) + it("should apply default content rules for some status codes", function() + responses.send_HTTP_SERVICE_UNAVAILABLE() + assert.stub(ngx.say).was.called_with("{\"message\":\"Service unavailable\"}") + responses.send_HTTP_SERVICE_UNAVAILABLE("override") + assert.stub(ngx.say).was.called_with("{\"message\":\"override\"}") + end) end) describe("send()", function() From 330ab87ab2bab87f203f12c28751daff92e1d565 Mon Sep 17 00:00:00 2001 From: Paul Austin Date: Tue, 7 Feb 2017 06:28:38 -0800 Subject: [PATCH 05/20] Revert "feat(schema): regex for numbers" This reverts commit f3cd113776691c9ab35e16a1b872fc4477e9fd8f. --- kong/dao/schemas_validation.lua | 2 +- spec/01-unit/07-schema_validation_spec.lua | 9 --------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/kong/dao/schemas_validation.lua b/kong/dao/schemas_validation.lua index c9b587adc2af..6efa5eec48f3 100644 --- a/kong/dao/schemas_validation.lua +++ b/kong/dao/schemas_validation.lua @@ -147,7 +147,7 @@ function _M.validate_entity(tbl, schema, options) end -- [REGEX] Check field against a regex if specified - if (type(t[column]) == "string" or type(t[column]) == "number") and v.regex then + if type(t[column]) == "string" and v.regex then if not ngx.re.find(t[column], v.regex) then errors = utils.add_error(errors, error_prefix..column, column.." has an invalid value") end diff --git a/spec/01-unit/07-schema_validation_spec.lua b/spec/01-unit/07-schema_validation_spec.lua index 29523197a40a..2a88fb235389 100644 --- a/spec/01-unit/07-schema_validation_spec.lua +++ b/spec/01-unit/07-schema_validation_spec.lua @@ -13,7 +13,6 @@ describe("Schemas", function() string = { type = "string", required = true, immutable = true}, table = {type = "table"}, number = {type = "number"}, - numberRegex = {type = "number", regex = "\\d{3}"}, url = {regex = "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])$"}, date = {default = 123456, immutable = true}, allowed = {enum = {"hello", "world"}}, @@ -297,14 +296,6 @@ describe("Schemas", function() assert.truthy(err) assert.are.same("url has an invalid value", err.url) end) - it("should validate a field against a regex", function() - local values = {string = "value", numberRegex = "22"} - - local valid, err = validate_entity(values, schema) - assert.falsy(valid) - assert.truthy(err) - assert.are.same("numberRegex has an invalid value", err.numberRegex) - end) end) describe("[enum]", function() From 1dc346798d5cef68f1ca83cfc44f80590852e32d Mon Sep 17 00:00:00 2001 From: Paul Austin Date: Tue, 7 Feb 2017 08:38:46 -0800 Subject: [PATCH 06/20] feat(plugin): request-termination A new plug-in that allows a request to be terminated and a specified HTTP status code and body returned. This is useful to temporarily return a status page for a service. For example if the service is unavailable due to scheduled maintenance. --- kong-0.9.8-0.rockspec | 5 + kong/constants.lua | 3 +- kong/plugins/request-termination/daos.lua | 3 + kong/plugins/request-termination/handler.lua | 42 +++++ .../migrations/cassandra.lua | 23 +++ .../migrations/postgres.lua | 27 +++ kong/plugins/request-termination/schema.lua | 33 ++++ .../18-request-termination/01-schema_spec.lua | 57 ++++++ .../18-request-termination/02-access_spec.lua | 177 ++++++++++++++++++ 9 files changed, 369 insertions(+), 1 deletion(-) create mode 100644 kong/plugins/request-termination/daos.lua create mode 100644 kong/plugins/request-termination/handler.lua create mode 100644 kong/plugins/request-termination/migrations/cassandra.lua create mode 100644 kong/plugins/request-termination/migrations/postgres.lua create mode 100644 kong/plugins/request-termination/schema.lua create mode 100644 spec/03-plugins/18-request-termination/01-schema_spec.lua create mode 100644 spec/03-plugins/18-request-termination/02-access_spec.lua diff --git a/kong-0.9.8-0.rockspec b/kong-0.9.8-0.rockspec index 34d38f6344e4..9fb3ff7b6cbc 100644 --- a/kong-0.9.8-0.rockspec +++ b/kong-0.9.8-0.rockspec @@ -272,5 +272,10 @@ build = { ["kong.plugins.aws-lambda.handler"] = "kong/plugins/aws-lambda/handler.lua", ["kong.plugins.aws-lambda.schema"] = "kong/plugins/aws-lambda/schema.lua", ["kong.plugins.aws-lambda.v4"] = "kong/plugins/aws-lambda/v4.lua", + + ["kong.plugins.request-termination.migrations.cassandra"] = "kong/plugins/request-termination/migrations/cassandra.lua", + ["kong.plugins.request-termination.daos"] = "kong/plugins/request-termination/daos.lua", + ["kong.plugins.request-termination.handler"] = "kong/plugins/request-termination/handler.lua", + ["kong.plugins.request-termination.schema"] = "kong/plugins/request-termination/schema.lua", } } diff --git a/kong/constants.lua b/kong/constants.lua index ab05501b5389..0602983aca2b 100644 --- a/kong/constants.lua +++ b/kong/constants.lua @@ -4,7 +4,8 @@ local plugins = { "galileo", "request-transformer", "response-transformer", "request-size-limiting", "rate-limiting", "response-ratelimiting", "syslog", "loggly", "datadog", "runscope", "ldap-auth", "statsd", "bot-detection", - "aws-lambda" + "aws-lambda", + "request-termination", } local plugin_map = {} diff --git a/kong/plugins/request-termination/daos.lua b/kong/plugins/request-termination/daos.lua new file mode 100644 index 000000000000..956046c98083 --- /dev/null +++ b/kong/plugins/request-termination/daos.lua @@ -0,0 +1,3 @@ +return { + tables = {"request_termination"} +} diff --git a/kong/plugins/request-termination/handler.lua b/kong/plugins/request-termination/handler.lua new file mode 100644 index 000000000000..e59fe804e51e --- /dev/null +++ b/kong/plugins/request-termination/handler.lua @@ -0,0 +1,42 @@ +local BasePlugin = require "kong.plugins.base_plugin" +local responses = require "kong.tools.responses" +local meta = require "kong.meta" + +local server_header = meta._NAME.."/"..meta._VERSION + +local RequestTerminationHandler = BasePlugin:extend() + +RequestTerminationHandler.PRIORITY = 1 + +function RequestTerminationHandler:new() + RequestTerminationHandler.super.new(self, "request-termination") +end + +function RequestTerminationHandler:access(conf) + RequestTerminationHandler.super.access(self) + + local status_code = conf.status_code + local content_type = conf.content_type + local body = conf.body + local message = conf.message + if not status_code then + status_code = 503 + end + if body then + ngx.status = status_code + + if not content_type then + content_type = "application/json; charset=utf-8"; + end + ngx.header["Content-Type"] = content_type + ngx.header["Server"] = server_header + + ngx.say(body) + + return ngx.exit(status_code) + else + return responses.send(status_code, message) + end +end + +return RequestTerminationHandler diff --git a/kong/plugins/request-termination/migrations/cassandra.lua b/kong/plugins/request-termination/migrations/cassandra.lua new file mode 100644 index 000000000000..81fd20a750df --- /dev/null +++ b/kong/plugins/request-termination/migrations/cassandra.lua @@ -0,0 +1,23 @@ +return { + { + name = "2017-01-28-000001_init_request_termination", + up = [[ + CREATE TABLE IF NOT EXISTS request_terminations( + id uuid, + api_id uuid, + status_code smallint, + message text, + content_type text, + body text, + created_at timestamp, + PRIMARY KEY (id) + ); + + CREATE INDEX IF NOT EXISTS ON request_terminations(group); + CREATE INDEX IF NOT EXISTS request_terminations_api_id ON request_terminations(api_id); + ]], + down = [[ + DROP TABLE request_terminations; + ]] + } +} diff --git a/kong/plugins/request-termination/migrations/postgres.lua b/kong/plugins/request-termination/migrations/postgres.lua new file mode 100644 index 000000000000..65da175c3a97 --- /dev/null +++ b/kong/plugins/request-termination/migrations/postgres.lua @@ -0,0 +1,27 @@ +return { + { + name = "2017-01-28-000001_init_request_termination", + up = [[ + CREATE TABLE IF NOT EXISTS request_terminations( + id uuid, + api_id uuid REFERENCES apis (id) ON DELETE CASCADE, + status_code smallint, + message text, + content_type text, + body text, + created_at timestamp without time zone default (CURRENT_TIMESTAMP(0) at time zone 'utc'), + PRIMARY KEY (id) + ); + + DO $$ + BEGIN + IF (SELECT to_regclass('request_terminations_api_id')) IS NULL THEN + CREATE INDEX request_terminations_api_id ON request_terminations(api_id); + END IF; + END$$; + ]], + down = [[ + DROP TABLE request_terminations; + ]] + } +} diff --git a/kong/plugins/request-termination/schema.lua b/kong/plugins/request-termination/schema.lua new file mode 100644 index 000000000000..4caa59ce3eb2 --- /dev/null +++ b/kong/plugins/request-termination/schema.lua @@ -0,0 +1,33 @@ +local Errors = require "kong.dao.errors" +local utils = require "kong.tools.utils" + +return { + no_consumer = true, + fields = { + status_code = { type = "number" }, + message = { type = "string" }, + content_type = { type = "string" }, + body = { type = "string" }, + }, + self_check = function(schema, plugin_t, dao, is_updating) + local errors + + if plugin_t.status_code then + if plugin_t.status_code < 100 or plugin_t.status_code > 599 then + return false, "status_code must be between 100..599" + end + end + + if plugin_t.message then + if plugin_t.content_type or plugin_t.body then + return false, "message cannot be used with content_type or body" + end + else + if plugin_t.content_type and not plugin_t.body then + return false, "content_type requires a body" + end + end + + return true + end +} diff --git a/spec/03-plugins/18-request-termination/01-schema_spec.lua b/spec/03-plugins/18-request-termination/01-schema_spec.lua new file mode 100644 index 000000000000..0ea2a5f47679 --- /dev/null +++ b/spec/03-plugins/18-request-termination/01-schema_spec.lua @@ -0,0 +1,57 @@ +local schemas_validation = require "kong.dao.schemas_validation" +local schema = require "kong.plugins.request-termination.schema" + +local v = schemas_validation.validate_entity + +describe("Plugin: request-termination (schema)", function() + it("should accept a valid status_code", function() + assert(v({status_code = 404}, schema)) + end) + it("should accept a valid message", function() + assert(v({message = "Not found"}, schema)) + end) + it("should accept a valid content_type", function() + assert(v({content_type = "text/html",body = "

Not found

"}, schema)) + end) + it("should accept a valid body", function() + assert(v({body = "

Not found

"}, schema)) + end) + + describe("errors", function() + it("status_code should only accept numbers", function() + local ok, err = v({status_code = "abcd"}, schema) + assert.same({status_code = "status_code is not a number"}, err) + assert.False(ok) + end) + it("status_code < 100", function() + local ok, err, message = v({status_code = "99"}, schema) + assert.False(ok) + assert.same("status_code must be between 100..599", message) + end) + it("status_code > 599", function() + local ok, err, message = v({status_code = "600"}, schema) + assert.False(ok) + assert.same("status_code must be between 100..599", message) + end) + it("message with body", function() + local ok, err, message = v({message = "error", body = "test"}, schema) + assert.False(ok) + assert.same("message cannot be used with content_type or body", message) + end) + it("message with body and content_type", function() + local ok, err, message = v({message = "error", content_type="text/html", body = "test"}, schema) + assert.False(ok) + assert.same("message cannot be used with content_type or body", message) + end) + it("message with content_type", function() + local ok, err, message = v({message = "error", content_type="text/html"}, schema) + assert.False(ok) + assert.same("message cannot be used with content_type or body", message) + end) + it("content_type without body", function() + local ok, err, message = v({content_type="text/html"}, schema) + assert.False(ok) + assert.same("content_type requires a body", message) + end) + end) +end) diff --git a/spec/03-plugins/18-request-termination/02-access_spec.lua b/spec/03-plugins/18-request-termination/02-access_spec.lua new file mode 100644 index 000000000000..1c2704776a07 --- /dev/null +++ b/spec/03-plugins/18-request-termination/02-access_spec.lua @@ -0,0 +1,177 @@ +local helpers = require "spec.helpers" +local cache = require "kong.tools.database_cache" +local cjson = require "cjson" + +describe("Plugin: request-termination (access)", function() + local plugin_config + local client, admin_client + + setup(function() + local api1 = assert(helpers.dao.apis:insert { + name = "api-1", + hosts = { "api1.request-termination.com" }, + upstream_url = "http://mockbin.com" + }) + assert(helpers.dao.plugins:insert { + name = "request-termination", + api_id = api1.id, + config = { + } + }) + local api2 = assert(helpers.dao.apis:insert { + name = "api-2", + hosts = { "api2.request-termination.com" }, + upstream_url = "http://mockbin.com" + }) + assert(helpers.dao.plugins:insert { + name = "request-termination", + api_id = api2.id, + config = { + status_code=404 + } + }) + local api3 = assert(helpers.dao.apis:insert { + name = "api-3", + hosts = { "api3.request-termination.com" }, + upstream_url = "http://mockbin.com" + }) + assert(helpers.dao.plugins:insert { + name = "request-termination", + api_id = api3.id, + config = { + status_code=406, + message="Invalid" + } + }) + local api4 = assert(helpers.dao.apis:insert { + name = "api-4", + hosts = { "api4.request-termination.com" }, + upstream_url = "http://mockbin.com" + }) + assert(helpers.dao.plugins:insert { + name = "request-termination", + api_id = api4.id, + config = { + body="

Service is down for maintenance

" + } + }) + local api5 = assert(helpers.dao.apis:insert { + name = "api-5", + hosts = { "api5.request-termination.com" }, + upstream_url = "http://mockbin.com" + }) + assert(helpers.dao.plugins:insert { + name = "request-termination", + api_id = api5.id, + config = { + status_code=451, + content_type="text/html", + body="

Service is down due to content infringement

" + } + }) + local api6 = assert(helpers.dao.apis:insert { + name = "api-6", + hosts = { "api6.request-termination.com" }, + upstream_url = "http://mockbin.com" + }) + assert(helpers.dao.plugins:insert { + name = "request-termination", + api_id = api6.id, + config = { + status_code=503, + body='{"code": 1, "message": "Service unavailable}' + } + }) + + + assert(helpers.start_kong()) + client = helpers.proxy_client() + admin_client = helpers.admin_client() + end) + + teardown(function() + if client and admin_client then + client:close() + admin_client:close() + end + helpers.stop_kong() + end) + + describe("status code and message", function() + it("default status code and message", function() + local res = assert(client:send { + method = "GET", + path = "/status/200", + headers = { + ["Host"] = "api1.request-termination.com" + } + }) + local body = assert.res_status(503, res) + assert.equal([[{"message":"Service unavailable"}]], body) + end) + + it("status code with default message", function() + local res = assert(client:send { + method = "GET", + path = "/status/200", + headers = { + ["Host"] = "api2.request-termination.com" + } + }) + local body = assert.res_status(404, res) + assert.equal([[{"message":"Not found"}]], body) + end) + + it("status code with custom message", function() + local res = assert(client:send { + method = "GET", + path = "/status/200", + headers = { + ["Host"] = "api3.request-termination.com" + } + }) + local body = assert.res_status(406, res) + assert.equal([[{"message":"Invalid"}]], body) + end) + + end) + + describe("status code and body", function() + it("default status code and body", function() + local res = assert(client:send { + method = "GET", + path = "/status/200", + headers = { + ["Host"] = "api4.request-termination.com" + } + }) + local body = assert.res_status(503, res) + assert.equal([[

Service is down for maintenance

]], body) + end) + + it("status code with default message", function() + local res = assert(client:send { + method = "GET", + path = "/status/200", + headers = { + ["Host"] = "api5.request-termination.com" + } + }) + local body = assert.res_status(451, res) + assert.equal([[

Service is down due to content infringement

]], body) + end) + + it("status code with custom message", function() + local res = assert(client:send { + method = "GET", + path = "/status/200", + headers = { + ["Host"] = "api6.request-termination.com" + } + }) + local body = assert.res_status(503, res) + assert.equal([[{"code": 1, "message": "Service unavailable}]], body) + end) + + end) +end) From 55d74bead17700ea950b3e584654deff1a431ee9 Mon Sep 17 00:00:00 2001 From: Paul Austin Date: Fri, 3 Feb 2017 19:27:23 -0800 Subject: [PATCH 07/20] feat(schema): regex for numbers Modified the schema validation to allow regex on numbers. For example \\d{3} to only accept 3 digits in the number. --- kong/dao/schemas_validation.lua | 2 +- spec/01-unit/07-schema_validation_spec.lua | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/kong/dao/schemas_validation.lua b/kong/dao/schemas_validation.lua index 6efa5eec48f3..c9b587adc2af 100644 --- a/kong/dao/schemas_validation.lua +++ b/kong/dao/schemas_validation.lua @@ -147,7 +147,7 @@ function _M.validate_entity(tbl, schema, options) end -- [REGEX] Check field against a regex if specified - if type(t[column]) == "string" and v.regex then + if (type(t[column]) == "string" or type(t[column]) == "number") and v.regex then if not ngx.re.find(t[column], v.regex) then errors = utils.add_error(errors, error_prefix..column, column.." has an invalid value") end diff --git a/spec/01-unit/07-schema_validation_spec.lua b/spec/01-unit/07-schema_validation_spec.lua index 2a88fb235389..29523197a40a 100644 --- a/spec/01-unit/07-schema_validation_spec.lua +++ b/spec/01-unit/07-schema_validation_spec.lua @@ -13,6 +13,7 @@ describe("Schemas", function() string = { type = "string", required = true, immutable = true}, table = {type = "table"}, number = {type = "number"}, + numberRegex = {type = "number", regex = "\\d{3}"}, url = {regex = "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])$"}, date = {default = 123456, immutable = true}, allowed = {enum = {"hello", "world"}}, @@ -296,6 +297,14 @@ describe("Schemas", function() assert.truthy(err) assert.are.same("url has an invalid value", err.url) end) + it("should validate a field against a regex", function() + local values = {string = "value", numberRegex = "22"} + + local valid, err = validate_entity(values, schema) + assert.falsy(valid) + assert.truthy(err) + assert.are.same("numberRegex has an invalid value", err.numberRegex) + end) end) describe("[enum]", function() From c2bfe7e41a552463d75171f1af3a3c2ec1e5f58a Mon Sep 17 00:00:00 2001 From: Paul Austin Date: Thu, 26 Jan 2017 18:30:40 +0000 Subject: [PATCH 08/20] feat(tools): HTTP Response 503 Service unavailable Add HTTP response for 503 Service unavailable. This will be used for a new api-unavailable plugin that will send a 503 error. See #1279. --- kong/tools/responses.lua | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/kong/tools/responses.lua b/kong/tools/responses.lua index aaea4183dcbd..8ae096ced3c3 100644 --- a/kong/tools/responses.lua +++ b/kong/tools/responses.lua @@ -39,6 +39,7 @@ local server_header = meta._NAME.."/"..meta._VERSION -- @field HTTP_CONFLICT 409 Conflict -- @field HTTP_UNSUPPORTED_MEDIA_TYPE 415 Unsupported Media Type -- @field HTTP_INTERNAL_SERVER_ERROR Internal Server Error +-- @field HTTP_SERVICE_UNAVAILABLE 503 Service Unavailable -- @usage return responses.send_HTTP_OK() -- @usage return responses.HTTP_CREATED("Entity created") -- @usage return responses.HTTP_INTERNAL_SERVER_ERROR() @@ -55,7 +56,8 @@ local _M = { HTTP_METHOD_NOT_ALLOWED = 405, HTTP_CONFLICT = 409, HTTP_UNSUPPORTED_MEDIA_TYPE = 415, - HTTP_INTERNAL_SERVER_ERROR = 500 + HTTP_INTERNAL_SERVER_ERROR = 500, + HTTP_SERVICE_UNAVAILABLE = 503 } } @@ -68,6 +70,7 @@ local _M = { -- @field status_codes.HTTP_UNAUTHORIZED Default: Unauthorized -- @field status_codes.HTTP_INTERNAL_SERVER_ERROR Always "Internal Server Error" -- @field status_codes.HTTP_METHOD_NOT_ALLOWED Always "Method not allowed" +-- @field status_codes.HTTP_SERVICE_UNAVAILABLE Default: "Service Unavailable" local response_default_content = { [_M.status_codes.HTTP_UNAUTHORIZED] = function(content) return content or "Unauthorized" @@ -83,6 +86,9 @@ local response_default_content = { end, [_M.status_codes.HTTP_METHOD_NOT_ALLOWED] = function(content) return "Method not allowed" + end, + [_M.status_codes.HTTP_SERVICE_UNAVAILABLE] = function(content) + return content or "Service Unavailable" end } @@ -96,7 +102,7 @@ local function send_response(status_code) -- @param content (Optional) The content to send as a response. -- @return ngx.exit (Exit current context) return function(content, headers) - if status_code >= _M.status_codes.HTTP_INTERNAL_SERVER_ERROR then + if status_code >= _M.status_codes.HTTP_INTERNAL_SERVER_ERROR and status_code ~= _M.status_codes.HTTP_SERVICE_UNAVAILABLE then if content then ngx.log(ngx.ERR, tostring(content)) end From 4d0802c987ae378a89014fc57aceb54e5c110700 Mon Sep 17 00:00:00 2001 From: Paul Austin Date: Thu, 26 Jan 2017 22:07:58 +0000 Subject: [PATCH 09/20] Code style changes Added , to end of list Changed to only log 500 errors --- kong/tools/responses.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kong/tools/responses.lua b/kong/tools/responses.lua index 8ae096ced3c3..f28688b3d812 100644 --- a/kong/tools/responses.lua +++ b/kong/tools/responses.lua @@ -57,7 +57,7 @@ local _M = { HTTP_CONFLICT = 409, HTTP_UNSUPPORTED_MEDIA_TYPE = 415, HTTP_INTERNAL_SERVER_ERROR = 500, - HTTP_SERVICE_UNAVAILABLE = 503 + HTTP_SERVICE_UNAVAILABLE = 503, } } @@ -102,7 +102,7 @@ local function send_response(status_code) -- @param content (Optional) The content to send as a response. -- @return ngx.exit (Exit current context) return function(content, headers) - if status_code >= _M.status_codes.HTTP_INTERNAL_SERVER_ERROR and status_code ~= _M.status_codes.HTTP_SERVICE_UNAVAILABLE then + if status_code == _M.status_codes.HTTP_INTERNAL_SERVER_ERROR then if content then ngx.log(ngx.ERR, tostring(content)) end From 1ea22c522bcaa522748dd4f2d3e65e7f4e43afcd Mon Sep 17 00:00:00 2001 From: Paul Austin Date: Fri, 27 Jan 2017 11:59:15 +0000 Subject: [PATCH 10/20] Implemented tests and updated comments --- kong/tools/responses.lua | 10 +++++----- spec/01-unit/10-responses_spec.lua | 19 ++++++++++++++++++- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/kong/tools/responses.lua b/kong/tools/responses.lua index f28688b3d812..c602bbed0e5a 100644 --- a/kong/tools/responses.lua +++ b/kong/tools/responses.lua @@ -70,7 +70,7 @@ local _M = { -- @field status_codes.HTTP_UNAUTHORIZED Default: Unauthorized -- @field status_codes.HTTP_INTERNAL_SERVER_ERROR Always "Internal Server Error" -- @field status_codes.HTTP_METHOD_NOT_ALLOWED Always "Method not allowed" --- @field status_codes.HTTP_SERVICE_UNAVAILABLE Default: "Service Unavailable" +-- @field status_codes.HTTP_SERVICE_UNAVAILABLE Default: "Service unavailable" local response_default_content = { [_M.status_codes.HTTP_UNAUTHORIZED] = function(content) return content or "Unauthorized" @@ -88,8 +88,8 @@ local response_default_content = { return "Method not allowed" end, [_M.status_codes.HTTP_SERVICE_UNAVAILABLE] = function(content) - return content or "Service Unavailable" - end + return content or "Service unavailable" + end, } -- Return a closure which will be usable to respond with a certain status code. @@ -97,7 +97,7 @@ local response_default_content = { -- @param[type=number] status_code The status for which to define a function local function send_response(status_code) -- Send a JSON response for the closure's status code with the given content. - -- If the content happens to be an error (>500), it will be logged by ngx.log as an ERR. + -- If the content happens to be an error (500), it will be logged by ngx.log as an ERR. -- @see https://github.com/openresty/lua-nginx-module -- @param content (Optional) The content to send as a response. -- @return ngx.exit (Exit current context) @@ -147,7 +147,7 @@ local closure_cache = {} --- Send a response with any status code or body, -- Not all status codes are available as sugar methods, this function can be -- used to send any response. --- If the `status_code` parameter is in the 5xx range, it is expectde that the `content` parameter be the error encountered. It will be logged and the response body will be empty. The user will just receive a 500 status code. +-- If the `status_code` parameter is in the 5xx range, it is expected that the `content` parameter be the error encountered. For 500 errors it will be logged. The response body will be empty. The user will just receive a 500 status code. -- Will call `ngx.say` and `ngx.exit`, terminating the current context. -- @see ngx.say -- @see ngx.exit diff --git a/spec/01-unit/10-responses_spec.lua b/spec/01-unit/10-responses_spec.lua index 77b0b89c3f87..1b7bad3fb60d 100644 --- a/spec/01-unit/10-responses_spec.lua +++ b/spec/01-unit/10-responses_spec.lua @@ -58,9 +58,12 @@ describe("Response helpers", function() end) end end) - it("calls `ngx.log` if and only if a 500 status code range was given", function() + it("calls `ngx.log` if and only if a 500 status code was given", function() responses.send_HTTP_BAD_REQUEST() assert.stub(ngx.log).was_not_called() + + responses.send_HTTP_BAD_REQUEST("error") + assert.stub(ngx.log).was_not_called() responses.send_HTTP_INTERNAL_SERVER_ERROR() assert.stub(ngx.log).was_not_called() @@ -69,6 +72,14 @@ describe("Response helpers", function() assert.stub(ngx.log).was_called() end) + it("don't call `ngx.log` if a 503 status code was given", function() + responses.send_HTTP_SERVICE_UNAVAILABLE() + assert.stub(ngx.log).was_not_called() + + responses.send_HTTP_SERVICE_UNAVAILABLE() + assert.stub(ngx.log).was_not_called("error") + end) + describe("default content rules for some status codes", function() it("should apply default content rules for some status codes", function() responses.send_HTTP_NOT_FOUND() @@ -86,6 +97,12 @@ describe("Response helpers", function() responses.send_HTTP_INTERNAL_SERVER_ERROR("override") assert.stub(ngx.say).was.called_with("{\"message\":\"An unexpected error occurred\"}") end) + it("should apply default content rules for some status codes", function() + responses.send_HTTP_SERVICE_UNAVAILABLE() + assert.stub(ngx.say).was.called_with("{\"message\":\"Service unavailable\"}") + responses.send_HTTP_SERVICE_UNAVAILABLE("override") + assert.stub(ngx.say).was.called_with("{\"message\":\"override\"}") + end) end) describe("send()", function() From 6ee52b50e25e3544356ef3ce4d28c309ceac7212 Mon Sep 17 00:00:00 2001 From: Paul Austin Date: Tue, 7 Feb 2017 06:28:38 -0800 Subject: [PATCH 11/20] Revert "feat(schema): regex for numbers" This reverts commit f3cd113776691c9ab35e16a1b872fc4477e9fd8f. --- kong/dao/schemas_validation.lua | 2 +- spec/01-unit/07-schema_validation_spec.lua | 9 --------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/kong/dao/schemas_validation.lua b/kong/dao/schemas_validation.lua index c9b587adc2af..6efa5eec48f3 100644 --- a/kong/dao/schemas_validation.lua +++ b/kong/dao/schemas_validation.lua @@ -147,7 +147,7 @@ function _M.validate_entity(tbl, schema, options) end -- [REGEX] Check field against a regex if specified - if (type(t[column]) == "string" or type(t[column]) == "number") and v.regex then + if type(t[column]) == "string" and v.regex then if not ngx.re.find(t[column], v.regex) then errors = utils.add_error(errors, error_prefix..column, column.." has an invalid value") end diff --git a/spec/01-unit/07-schema_validation_spec.lua b/spec/01-unit/07-schema_validation_spec.lua index 29523197a40a..2a88fb235389 100644 --- a/spec/01-unit/07-schema_validation_spec.lua +++ b/spec/01-unit/07-schema_validation_spec.lua @@ -13,7 +13,6 @@ describe("Schemas", function() string = { type = "string", required = true, immutable = true}, table = {type = "table"}, number = {type = "number"}, - numberRegex = {type = "number", regex = "\\d{3}"}, url = {regex = "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])$"}, date = {default = 123456, immutable = true}, allowed = {enum = {"hello", "world"}}, @@ -297,14 +296,6 @@ describe("Schemas", function() assert.truthy(err) assert.are.same("url has an invalid value", err.url) end) - it("should validate a field against a regex", function() - local values = {string = "value", numberRegex = "22"} - - local valid, err = validate_entity(values, schema) - assert.falsy(valid) - assert.truthy(err) - assert.are.same("numberRegex has an invalid value", err.numberRegex) - end) end) describe("[enum]", function() From a641fd31cb798f2dd1838b386d56afa3258a0481 Mon Sep 17 00:00:00 2001 From: Paul Austin Date: Tue, 7 Feb 2017 08:38:46 -0800 Subject: [PATCH 12/20] feat(plugin): request-termination A new plug-in that allows a request to be terminated and a specified HTTP status code and body returned. This is useful to temporarily return a status page for a service. For example if the service is unavailable due to scheduled maintenance. --- kong-0.9.9-0.rockspec | 5 + kong/constants.lua | 3 +- kong/plugins/request-termination/daos.lua | 3 + kong/plugins/request-termination/handler.lua | 42 +++++ .../migrations/cassandra.lua | 23 +++ .../migrations/postgres.lua | 27 +++ kong/plugins/request-termination/schema.lua | 33 ++++ .../18-request-termination/01-schema_spec.lua | 57 ++++++ .../18-request-termination/02-access_spec.lua | 177 ++++++++++++++++++ 9 files changed, 369 insertions(+), 1 deletion(-) create mode 100644 kong/plugins/request-termination/daos.lua create mode 100644 kong/plugins/request-termination/handler.lua create mode 100644 kong/plugins/request-termination/migrations/cassandra.lua create mode 100644 kong/plugins/request-termination/migrations/postgres.lua create mode 100644 kong/plugins/request-termination/schema.lua create mode 100644 spec/03-plugins/18-request-termination/01-schema_spec.lua create mode 100644 spec/03-plugins/18-request-termination/02-access_spec.lua diff --git a/kong-0.9.9-0.rockspec b/kong-0.9.9-0.rockspec index 5aff1401e497..9accb4a8f124 100644 --- a/kong-0.9.9-0.rockspec +++ b/kong-0.9.9-0.rockspec @@ -272,5 +272,10 @@ build = { ["kong.plugins.aws-lambda.handler"] = "kong/plugins/aws-lambda/handler.lua", ["kong.plugins.aws-lambda.schema"] = "kong/plugins/aws-lambda/schema.lua", ["kong.plugins.aws-lambda.v4"] = "kong/plugins/aws-lambda/v4.lua", + + ["kong.plugins.request-termination.migrations.cassandra"] = "kong/plugins/request-termination/migrations/cassandra.lua", + ["kong.plugins.request-termination.daos"] = "kong/plugins/request-termination/daos.lua", + ["kong.plugins.request-termination.handler"] = "kong/plugins/request-termination/handler.lua", + ["kong.plugins.request-termination.schema"] = "kong/plugins/request-termination/schema.lua", } } diff --git a/kong/constants.lua b/kong/constants.lua index ab05501b5389..0602983aca2b 100644 --- a/kong/constants.lua +++ b/kong/constants.lua @@ -4,7 +4,8 @@ local plugins = { "galileo", "request-transformer", "response-transformer", "request-size-limiting", "rate-limiting", "response-ratelimiting", "syslog", "loggly", "datadog", "runscope", "ldap-auth", "statsd", "bot-detection", - "aws-lambda" + "aws-lambda", + "request-termination", } local plugin_map = {} diff --git a/kong/plugins/request-termination/daos.lua b/kong/plugins/request-termination/daos.lua new file mode 100644 index 000000000000..956046c98083 --- /dev/null +++ b/kong/plugins/request-termination/daos.lua @@ -0,0 +1,3 @@ +return { + tables = {"request_termination"} +} diff --git a/kong/plugins/request-termination/handler.lua b/kong/plugins/request-termination/handler.lua new file mode 100644 index 000000000000..e59fe804e51e --- /dev/null +++ b/kong/plugins/request-termination/handler.lua @@ -0,0 +1,42 @@ +local BasePlugin = require "kong.plugins.base_plugin" +local responses = require "kong.tools.responses" +local meta = require "kong.meta" + +local server_header = meta._NAME.."/"..meta._VERSION + +local RequestTerminationHandler = BasePlugin:extend() + +RequestTerminationHandler.PRIORITY = 1 + +function RequestTerminationHandler:new() + RequestTerminationHandler.super.new(self, "request-termination") +end + +function RequestTerminationHandler:access(conf) + RequestTerminationHandler.super.access(self) + + local status_code = conf.status_code + local content_type = conf.content_type + local body = conf.body + local message = conf.message + if not status_code then + status_code = 503 + end + if body then + ngx.status = status_code + + if not content_type then + content_type = "application/json; charset=utf-8"; + end + ngx.header["Content-Type"] = content_type + ngx.header["Server"] = server_header + + ngx.say(body) + + return ngx.exit(status_code) + else + return responses.send(status_code, message) + end +end + +return RequestTerminationHandler diff --git a/kong/plugins/request-termination/migrations/cassandra.lua b/kong/plugins/request-termination/migrations/cassandra.lua new file mode 100644 index 000000000000..81fd20a750df --- /dev/null +++ b/kong/plugins/request-termination/migrations/cassandra.lua @@ -0,0 +1,23 @@ +return { + { + name = "2017-01-28-000001_init_request_termination", + up = [[ + CREATE TABLE IF NOT EXISTS request_terminations( + id uuid, + api_id uuid, + status_code smallint, + message text, + content_type text, + body text, + created_at timestamp, + PRIMARY KEY (id) + ); + + CREATE INDEX IF NOT EXISTS ON request_terminations(group); + CREATE INDEX IF NOT EXISTS request_terminations_api_id ON request_terminations(api_id); + ]], + down = [[ + DROP TABLE request_terminations; + ]] + } +} diff --git a/kong/plugins/request-termination/migrations/postgres.lua b/kong/plugins/request-termination/migrations/postgres.lua new file mode 100644 index 000000000000..65da175c3a97 --- /dev/null +++ b/kong/plugins/request-termination/migrations/postgres.lua @@ -0,0 +1,27 @@ +return { + { + name = "2017-01-28-000001_init_request_termination", + up = [[ + CREATE TABLE IF NOT EXISTS request_terminations( + id uuid, + api_id uuid REFERENCES apis (id) ON DELETE CASCADE, + status_code smallint, + message text, + content_type text, + body text, + created_at timestamp without time zone default (CURRENT_TIMESTAMP(0) at time zone 'utc'), + PRIMARY KEY (id) + ); + + DO $$ + BEGIN + IF (SELECT to_regclass('request_terminations_api_id')) IS NULL THEN + CREATE INDEX request_terminations_api_id ON request_terminations(api_id); + END IF; + END$$; + ]], + down = [[ + DROP TABLE request_terminations; + ]] + } +} diff --git a/kong/plugins/request-termination/schema.lua b/kong/plugins/request-termination/schema.lua new file mode 100644 index 000000000000..4caa59ce3eb2 --- /dev/null +++ b/kong/plugins/request-termination/schema.lua @@ -0,0 +1,33 @@ +local Errors = require "kong.dao.errors" +local utils = require "kong.tools.utils" + +return { + no_consumer = true, + fields = { + status_code = { type = "number" }, + message = { type = "string" }, + content_type = { type = "string" }, + body = { type = "string" }, + }, + self_check = function(schema, plugin_t, dao, is_updating) + local errors + + if plugin_t.status_code then + if plugin_t.status_code < 100 or plugin_t.status_code > 599 then + return false, "status_code must be between 100..599" + end + end + + if plugin_t.message then + if plugin_t.content_type or plugin_t.body then + return false, "message cannot be used with content_type or body" + end + else + if plugin_t.content_type and not plugin_t.body then + return false, "content_type requires a body" + end + end + + return true + end +} diff --git a/spec/03-plugins/18-request-termination/01-schema_spec.lua b/spec/03-plugins/18-request-termination/01-schema_spec.lua new file mode 100644 index 000000000000..0ea2a5f47679 --- /dev/null +++ b/spec/03-plugins/18-request-termination/01-schema_spec.lua @@ -0,0 +1,57 @@ +local schemas_validation = require "kong.dao.schemas_validation" +local schema = require "kong.plugins.request-termination.schema" + +local v = schemas_validation.validate_entity + +describe("Plugin: request-termination (schema)", function() + it("should accept a valid status_code", function() + assert(v({status_code = 404}, schema)) + end) + it("should accept a valid message", function() + assert(v({message = "Not found"}, schema)) + end) + it("should accept a valid content_type", function() + assert(v({content_type = "text/html",body = "

Not found

"}, schema)) + end) + it("should accept a valid body", function() + assert(v({body = "

Not found

"}, schema)) + end) + + describe("errors", function() + it("status_code should only accept numbers", function() + local ok, err = v({status_code = "abcd"}, schema) + assert.same({status_code = "status_code is not a number"}, err) + assert.False(ok) + end) + it("status_code < 100", function() + local ok, err, message = v({status_code = "99"}, schema) + assert.False(ok) + assert.same("status_code must be between 100..599", message) + end) + it("status_code > 599", function() + local ok, err, message = v({status_code = "600"}, schema) + assert.False(ok) + assert.same("status_code must be between 100..599", message) + end) + it("message with body", function() + local ok, err, message = v({message = "error", body = "test"}, schema) + assert.False(ok) + assert.same("message cannot be used with content_type or body", message) + end) + it("message with body and content_type", function() + local ok, err, message = v({message = "error", content_type="text/html", body = "test"}, schema) + assert.False(ok) + assert.same("message cannot be used with content_type or body", message) + end) + it("message with content_type", function() + local ok, err, message = v({message = "error", content_type="text/html"}, schema) + assert.False(ok) + assert.same("message cannot be used with content_type or body", message) + end) + it("content_type without body", function() + local ok, err, message = v({content_type="text/html"}, schema) + assert.False(ok) + assert.same("content_type requires a body", message) + end) + end) +end) diff --git a/spec/03-plugins/18-request-termination/02-access_spec.lua b/spec/03-plugins/18-request-termination/02-access_spec.lua new file mode 100644 index 000000000000..1c2704776a07 --- /dev/null +++ b/spec/03-plugins/18-request-termination/02-access_spec.lua @@ -0,0 +1,177 @@ +local helpers = require "spec.helpers" +local cache = require "kong.tools.database_cache" +local cjson = require "cjson" + +describe("Plugin: request-termination (access)", function() + local plugin_config + local client, admin_client + + setup(function() + local api1 = assert(helpers.dao.apis:insert { + name = "api-1", + hosts = { "api1.request-termination.com" }, + upstream_url = "http://mockbin.com" + }) + assert(helpers.dao.plugins:insert { + name = "request-termination", + api_id = api1.id, + config = { + } + }) + local api2 = assert(helpers.dao.apis:insert { + name = "api-2", + hosts = { "api2.request-termination.com" }, + upstream_url = "http://mockbin.com" + }) + assert(helpers.dao.plugins:insert { + name = "request-termination", + api_id = api2.id, + config = { + status_code=404 + } + }) + local api3 = assert(helpers.dao.apis:insert { + name = "api-3", + hosts = { "api3.request-termination.com" }, + upstream_url = "http://mockbin.com" + }) + assert(helpers.dao.plugins:insert { + name = "request-termination", + api_id = api3.id, + config = { + status_code=406, + message="Invalid" + } + }) + local api4 = assert(helpers.dao.apis:insert { + name = "api-4", + hosts = { "api4.request-termination.com" }, + upstream_url = "http://mockbin.com" + }) + assert(helpers.dao.plugins:insert { + name = "request-termination", + api_id = api4.id, + config = { + body="

Service is down for maintenance

" + } + }) + local api5 = assert(helpers.dao.apis:insert { + name = "api-5", + hosts = { "api5.request-termination.com" }, + upstream_url = "http://mockbin.com" + }) + assert(helpers.dao.plugins:insert { + name = "request-termination", + api_id = api5.id, + config = { + status_code=451, + content_type="text/html", + body="

Service is down due to content infringement

" + } + }) + local api6 = assert(helpers.dao.apis:insert { + name = "api-6", + hosts = { "api6.request-termination.com" }, + upstream_url = "http://mockbin.com" + }) + assert(helpers.dao.plugins:insert { + name = "request-termination", + api_id = api6.id, + config = { + status_code=503, + body='{"code": 1, "message": "Service unavailable}' + } + }) + + + assert(helpers.start_kong()) + client = helpers.proxy_client() + admin_client = helpers.admin_client() + end) + + teardown(function() + if client and admin_client then + client:close() + admin_client:close() + end + helpers.stop_kong() + end) + + describe("status code and message", function() + it("default status code and message", function() + local res = assert(client:send { + method = "GET", + path = "/status/200", + headers = { + ["Host"] = "api1.request-termination.com" + } + }) + local body = assert.res_status(503, res) + assert.equal([[{"message":"Service unavailable"}]], body) + end) + + it("status code with default message", function() + local res = assert(client:send { + method = "GET", + path = "/status/200", + headers = { + ["Host"] = "api2.request-termination.com" + } + }) + local body = assert.res_status(404, res) + assert.equal([[{"message":"Not found"}]], body) + end) + + it("status code with custom message", function() + local res = assert(client:send { + method = "GET", + path = "/status/200", + headers = { + ["Host"] = "api3.request-termination.com" + } + }) + local body = assert.res_status(406, res) + assert.equal([[{"message":"Invalid"}]], body) + end) + + end) + + describe("status code and body", function() + it("default status code and body", function() + local res = assert(client:send { + method = "GET", + path = "/status/200", + headers = { + ["Host"] = "api4.request-termination.com" + } + }) + local body = assert.res_status(503, res) + assert.equal([[

Service is down for maintenance

]], body) + end) + + it("status code with default message", function() + local res = assert(client:send { + method = "GET", + path = "/status/200", + headers = { + ["Host"] = "api5.request-termination.com" + } + }) + local body = assert.res_status(451, res) + assert.equal([[

Service is down due to content infringement

]], body) + end) + + it("status code with custom message", function() + local res = assert(client:send { + method = "GET", + path = "/status/200", + headers = { + ["Host"] = "api6.request-termination.com" + } + }) + local body = assert.res_status(503, res) + assert.equal([[{"code": 1, "message": "Service unavailable}]], body) + end) + + end) +end) From 8ada13f47d1da4d8ab93c11a0ebe7d939fd6db25 Mon Sep 17 00:00:00 2001 From: Paul Austin Date: Tue, 7 Feb 2017 11:58:03 -0800 Subject: [PATCH 13/20] Removed custom table definitions as the config is stored directly in kong's plugins table --- kong/plugins/request-termination/daos.lua | 3 --- .../migrations/cassandra.lua | 23 ---------------- .../migrations/postgres.lua | 27 ------------------- 3 files changed, 53 deletions(-) delete mode 100644 kong/plugins/request-termination/daos.lua delete mode 100644 kong/plugins/request-termination/migrations/cassandra.lua delete mode 100644 kong/plugins/request-termination/migrations/postgres.lua diff --git a/kong/plugins/request-termination/daos.lua b/kong/plugins/request-termination/daos.lua deleted file mode 100644 index 956046c98083..000000000000 --- a/kong/plugins/request-termination/daos.lua +++ /dev/null @@ -1,3 +0,0 @@ -return { - tables = {"request_termination"} -} diff --git a/kong/plugins/request-termination/migrations/cassandra.lua b/kong/plugins/request-termination/migrations/cassandra.lua deleted file mode 100644 index 81fd20a750df..000000000000 --- a/kong/plugins/request-termination/migrations/cassandra.lua +++ /dev/null @@ -1,23 +0,0 @@ -return { - { - name = "2017-01-28-000001_init_request_termination", - up = [[ - CREATE TABLE IF NOT EXISTS request_terminations( - id uuid, - api_id uuid, - status_code smallint, - message text, - content_type text, - body text, - created_at timestamp, - PRIMARY KEY (id) - ); - - CREATE INDEX IF NOT EXISTS ON request_terminations(group); - CREATE INDEX IF NOT EXISTS request_terminations_api_id ON request_terminations(api_id); - ]], - down = [[ - DROP TABLE request_terminations; - ]] - } -} diff --git a/kong/plugins/request-termination/migrations/postgres.lua b/kong/plugins/request-termination/migrations/postgres.lua deleted file mode 100644 index 65da175c3a97..000000000000 --- a/kong/plugins/request-termination/migrations/postgres.lua +++ /dev/null @@ -1,27 +0,0 @@ -return { - { - name = "2017-01-28-000001_init_request_termination", - up = [[ - CREATE TABLE IF NOT EXISTS request_terminations( - id uuid, - api_id uuid REFERENCES apis (id) ON DELETE CASCADE, - status_code smallint, - message text, - content_type text, - body text, - created_at timestamp without time zone default (CURRENT_TIMESTAMP(0) at time zone 'utc'), - PRIMARY KEY (id) - ); - - DO $$ - BEGIN - IF (SELECT to_regclass('request_terminations_api_id')) IS NULL THEN - CREATE INDEX request_terminations_api_id ON request_terminations(api_id); - END IF; - END$$; - ]], - down = [[ - DROP TABLE request_terminations; - ]] - } -} From 4b5f437c77a3dbcff5412d05fcef0238a24587d2 Mon Sep 17 00:00:00 2001 From: Paul Austin Date: Tue, 7 Feb 2017 11:59:42 -0800 Subject: [PATCH 14/20] in-line default vakue for status_code --- kong/plugins/request-termination/handler.lua | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/kong/plugins/request-termination/handler.lua b/kong/plugins/request-termination/handler.lua index e59fe804e51e..7b032784f56b 100644 --- a/kong/plugins/request-termination/handler.lua +++ b/kong/plugins/request-termination/handler.lua @@ -15,13 +15,10 @@ end function RequestTerminationHandler:access(conf) RequestTerminationHandler.super.access(self) - local status_code = conf.status_code + local status_code = conf.status_code or 503 local content_type = conf.content_type local body = conf.body local message = conf.message - if not status_code then - status_code = 503 - end if body then ngx.status = status_code From c2f91334c5843c08a0cc3fd42551a238f974dd25 Mon Sep 17 00:00:00 2001 From: Paul Austin Date: Mon, 13 Feb 2017 19:53:27 -0800 Subject: [PATCH 15/20] removed links to request-termination lua files that were no longer required --- kong-0.9.9-0.rockspec | 2 -- 1 file changed, 2 deletions(-) diff --git a/kong-0.9.9-0.rockspec b/kong-0.9.9-0.rockspec index a0442a1b2c90..8c469ad654b2 100644 --- a/kong-0.9.9-0.rockspec +++ b/kong-0.9.9-0.rockspec @@ -278,8 +278,6 @@ build = { ["kong.plugins.request-termination.handler"] = "kong/plugins/request-termination/handler.lua", ["kong.plugins.request-termination.schema"] = "kong/plugins/request-termination/schema.lua", - ["kong.plugins.request-termination.migrations.cassandra"] = "kong/plugins/request-termination/migrations/cassandra.lua", - ["kong.plugins.request-termination.daos"] = "kong/plugins/request-termination/daos.lua", ["kong.plugins.request-termination.handler"] = "kong/plugins/request-termination/handler.lua", ["kong.plugins.request-termination.schema"] = "kong/plugins/request-termination/schema.lua", } From cf8ad0b68df3c33bfa678f7d23de1ad12642b63c Mon Sep 17 00:00:00 2001 From: Paul Austin Date: Mon, 13 Feb 2017 20:01:51 -0800 Subject: [PATCH 16/20] Duplicate imports --- kong-0.9.9-0.rockspec | 5 ----- 1 file changed, 5 deletions(-) diff --git a/kong-0.9.9-0.rockspec b/kong-0.9.9-0.rockspec index 8c469ad654b2..c2f645c0c1b4 100644 --- a/kong-0.9.9-0.rockspec +++ b/kong-0.9.9-0.rockspec @@ -273,11 +273,6 @@ build = { ["kong.plugins.aws-lambda.schema"] = "kong/plugins/aws-lambda/schema.lua", ["kong.plugins.aws-lambda.v4"] = "kong/plugins/aws-lambda/v4.lua", - ["kong.plugins.request-termination.migrations.cassandra"] = "kong/plugins/request-termination/migrations/cassandra.lua", - ["kong.plugins.request-termination.daos"] = "kong/plugins/request-termination/daos.lua", - ["kong.plugins.request-termination.handler"] = "kong/plugins/request-termination/handler.lua", - ["kong.plugins.request-termination.schema"] = "kong/plugins/request-termination/schema.lua", - ["kong.plugins.request-termination.handler"] = "kong/plugins/request-termination/handler.lua", ["kong.plugins.request-termination.schema"] = "kong/plugins/request-termination/schema.lua", } From 174e9ce96b5c88e95878289d0f667d9d1055274a Mon Sep 17 00:00:00 2001 From: Paul Austin Date: Mon, 6 Mar 2017 10:06:10 -0800 Subject: [PATCH 17/20] Changed the list of built in plugins to be one per line. This makes it easier to merge changes --- kong/constants.lua | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/kong/constants.lua b/kong/constants.lua index 0602983aca2b..3c7375452436 100644 --- a/kong/constants.lua +++ b/kong/constants.lua @@ -1,9 +1,30 @@ local plugins = { - "jwt", "acl", "correlation-id", "cors", "oauth2", "tcp-log", "udp-log", - "file-log", "http-log", "key-auth", "hmac-auth", "basic-auth", "ip-restriction", - "galileo", "request-transformer", "response-transformer", - "request-size-limiting", "rate-limiting", "response-ratelimiting", "syslog", - "loggly", "datadog", "runscope", "ldap-auth", "statsd", "bot-detection", + "jwt", + "acl", + "correlation-id", + "cors", + "oauth2", + "tcp-log", + "udp-log", + "file-log", + "http-log", + "key-auth", + "hmac-auth", + "basic-auth", + "ip-restriction", + "galileo", + "request-transformer", + "response-transformer", + "request-size-limiting", + "rate-limiting", + "response-ratelimiting", + "syslog", + "loggly", + "datadog", + "runscope", + "ldap-auth", + "statsd", + "bot-detection", "aws-lambda", "request-termination", } From 26476d71347ac823ccdefe82ae9740dc0d091d87 Mon Sep 17 00:00:00 2001 From: Paul Austin Date: Mon, 6 Mar 2017 10:13:30 -0800 Subject: [PATCH 18/20] Implemented changes as requested in pull request --- kong.conf | 316 ++++++++++++++++++ kong/plugins/request-termination/handler.lua | 2 +- kong/plugins/request-termination/schema.lua | 10 +- kong/tools/responses.lua | 3 +- .../18-request-termination/01-schema_spec.lua | 28 +- .../18-request-termination/02-access_spec.lua | 4 +- 6 files changed, 339 insertions(+), 24 deletions(-) create mode 100644 kong.conf diff --git a/kong.conf b/kong.conf new file mode 100644 index 000000000000..201e370cb74f --- /dev/null +++ b/kong.conf @@ -0,0 +1,316 @@ +# ----------------------- +# Kong configuration file +# ----------------------- +# +# The commented-out settings shown in this file represent the default values. +# +# This file is read when `kong start` or `kong compile` are used. Kong +# generates the Nginx configuration with the settings specified in this file. +# +# All environment variables prefixed with `KONG_` and capitalized will override +# the settings specified in this file. +# Example: +# `log_level` setting -> `KONG_LOG_LEVEL` env variable +# +# Boolean values can be specified as `on`/`off` or `true`/`false`. +# Lists must be specified as comma-separated strings. +# +# All comments in this file can be removed safely, including the +# commented-out properties. +# You can verify the integrity of your settings with `kong check `. + +#------------------------------------------------------------------------------ +# GENERAL +#------------------------------------------------------------------------------ + +#prefix = /usr/local/kong/ # Working directory. Equivalent to Nginx's + # prefix path, containing temporary files + # and logs. + # Each Kong process must have a separate + # working directory. + +#log_level = notice # Log level of the Nginx server. Logs are + # found at /logs/error.log +# Note: See http://nginx.org/en/docs/ngx_core_module.html#error_log for a list +# of accepted values. + +#custom_plugins = # Comma-separated list of additional plugins + # this node should load. + # Use this property to load custom plugins + # that are not bundled with Kong. + # Plugins will be loaded from the + # `kong.plugins.{name}.*` namespace. + +#anonymous_reports = on # Send anonymous usage data such as error + # stack traces to help improve Kong. + +#------------------------------------------------------------------------------ +# NGINX +#------------------------------------------------------------------------------ + +#proxy_listen = 0.0.0.0:8000 # Address and port on which Kong will accept + # HTTP requests. + # This is the public-facing entrypoint of + # Kong, to which your consumers will make + # requests to. +# Note: See http://nginx.org/en/docs/http/ngx_http_core_module.html#listen for +# a description of the accepted formats for this and other *_listen values. + +#proxy_listen_ssl = 0.0.0.0:8443 # Address and port on which Kong will accept + # HTTPS requests if `ssl` is enabled. + +#admin_listen = 0.0.0.0:8001 # Address and port on which Kong will expose + # an entrypoint to the Admin API. + # This API lets you configure and manage Kong, + # and should be kept private and secured. + +#admin_listen_ssl = 0.0.0.0:8444 # Address and port on which Kong will accept + # HTTPS requests to the admin API, if + # `admin_ssl` is enabled. + +#nginx_worker_processes = auto # Determines the number of worker processes + # spawned by Nginx. + +#nginx_daemon = on # Determines wether Nginx will run as a daemon + # or as a foreground process. Mainly useful + # for development or when running Kong inside + # a Docker environment. + +#mem_cache_size = 128m # Size of the in-memory cache for database + # entities. The accepted units are `k` and + # `m`, with a minimum recommended value of + # a few MBs. + +#ssl = on # Determines if Nginx should be listening for + # HTTPS traffic on the `proxy_listen_ssl` + # address. If disabled, Nginx will only bind + # itself on `proxy_listen`, and all SSL + # settings will be ignored. + +#ssl_cert = # If `ssl` is enabled, the absolute path to + # the SSL certificate for the + # `proxy_listen_ssl` address. + +#ssl_cert_key = # If `ssl` is enabled, the absolute path to + # the SSL key for the `proxy_listen_ssl` + # address. + +#admin_ssl = on # Determines if Nginx should be listening for + # HTTPS traffic on the `admin_listen_ssl` + # address. If disabled, Nginx will only bind + # itself on `admin_listen`, and all SSL + # settings will be ignored. + +#admin_ssl_cert = # If `admin_ssl` is enabled, the absolute path + # to the SSL certificate for the + # `admin_listen_ssl` address. + +#admin_ssl_cert_key = # If `admin_ssl` is enabled, the absolute path + # to the SSL key for the `admin_listen_ssl` + # address. + +#upstream_keepalive = 60 # Sets the maximum number of idle keepalive + # connections to upstream servers that are + # preserved in the cache of each worker + # process. When this number is exceeded, the + # least recently used connections are closed. + +#------------------------------------------------------------------------------ +# DATASTORE +#------------------------------------------------------------------------------ + +# Kong will store all of its data (such as APIs, consumers and plugins) in +# either Cassandra or PostgreSQL. +# +# All Kong nodes belonging to the same cluster must connect themselves to the +# same database. + +#database = postgres # Determines which of PostgreSQL or Cassandra + # this node will use as its datastore. + # Accepted values are `postgres` and + # `cassandra`. + +#pg_host = 127.0.0.1 # The PostgreSQL host to connect to. +#pg_port = 5432 # The port to connect to. +#pg_user = kong # The username to authenticate if required. +#pg_password = kong # The password to authenticate if required. +#pg_database = kong # The database name to connect to. + +#pg_ssl = off # Toggles client-server TLS connections + # between Kong and PostgreSQL. + +#pg_ssl_verify = off # Toggles server certificate verification if + # `pg_ssl` is enabled. + # See the `lua_ssl_trusted_certificate` + # setting to specify a certificate authority. + +#cassandra_contact_points = 127.0.0.1 # A comma-separated list of contact + # points to your cluster. + +#cassandra_port = 9042 # The port on which your nodes are listening + # on. All your nodes and contact points must + # listen on the same port. + +#cassandra_keyspace = kong # The keyspace to use in your cluster. + +#cassandra_timeout = 5000 # Defines the timeout (in ms), for reading + # and writing. + +#cassandra_ssl = off # Toggles client-to-node TLS connections + # between Kong and Cassandra. + +#cassandra_ssl_verify = off # Toggles server certificate verification if + # `cassandra_ssl` is enabled. + # See the `lua_ssl_trusted_certificate` + # setting to specify a certificate authority. + +#cassandra_username = kong # Username when using the + # `PasswordAuthenticator` scheme. + +#cassandra_password = kong # Password when using the + # `PasswordAuthenticator` scheme. + +#cassandra_consistency = ONE # Consistency setting to use when reading/ + # writing to the Cassandra cluster. + +#cassandra_lb_policy = RoundRobin # Load balancing policy to use when + # distributing queries across your Cassandra + # cluster. + # Accepted values are `RoundRobin` and + # `DCAwareRoundRobin`. + # Prefer the later if and only if you are + # using a multi-datacenter cluster. + +#cassandra_local_datacenter = # When using the `DCAwareRoundRobin` load + # balancing policy, you must specify the name + # of the local (closest) datacenter for this + # Kong node. + +#cassandra_repl_strategy = SimpleStrategy # When migrating for the first time, + # Kong will use this setting to + # create your keyspace. + # Accepted values are + # `SimpleStrategy` and + # `NetworkTopologyStrategy`. + +#cassandra_repl_factor = 1 # When migrating for the first time, Kong + # will create the keyspace with this + # replication factor when using the + # `SimpleStrategy`. + +#cassandra_data_centers = dc1:2,dc2:3 # When migrating for the first time, + # will use this setting when using the + # `NetworkTopologyStrategy`. + # The format is a comma-separated list + # made of :. + +#------------------------------------------------------------------------------ +# CLUSTERING +#------------------------------------------------------------------------------ + +# In addition to pointing to the same database, each Kong node must be +# reachable by the other nodes in the cluster. +# +# Kong's clustering works on the IP layer (hostnames are not supported, only +# IPs) and expects a flat network topology without any NAT between the +# datacenters. +# +# A common pattern is to create a VPN between the two datacenters such that +# the flat network assumption is not violated. +# +# See the clustering reference for more informations: +# https://getkong.org/docs/latest/clustering/ + +#cluster_listen = 0.0.0.0:7946 # Address and port used to communicate with + # other nodes in the cluster. + # All other Kong nodes in the same cluster + # must be able to communicate over both + # TCP and UDP on this address. + # Only IPv4 addresses are supported. + +#cluster_listen_rpc = 127.0.0.1:7373 # Address and port used to communicate + # with the cluster through the agent + # running on this node. Only contains + # TCP traffic local to this node. + +#cluster_advertise = # By default, the `cluster_listen` address + # is advertised over the cluster. + # If the `cluster_listen` host is '0.0.0.0', + # then the first local, non-loopback IPv4 + # address will be advertised to other nodes. + # However, in some cases (specifically NAT + # traversal), there may be a routable address + # that cannot be bound to. This flag enables + # advertising a different address to support + # this. + +#cluster_encrypt_key = # base64-encoded 16-bytes key to encrypt + # cluster traffic with. + +#cluster_ttl_on_failure = 3600 # Time to live (in seconds) of a node in the + # cluster when it stops sending healthcheck + # pings, possibly caused by a node or network + # failure. + # If a node is not able to send a new + # healthcheck ping before the expiration, + # other nodes in the cluster will stop + # attempting to connect to it. + # Recommended to be at least `60`. + +#cluster_profile = wan # The timing profile for inter-cluster pings + # and timeouts. If a `lan` or `local` profile + # is used over the Internet, a high rate of + # failures is risked as the timing contraints + # would be too tight. + # Accepted values are `local`, `lan`, `wan`. + +#------------------------------------------------------------------------------ +# DNS RESOLVER +#------------------------------------------------------------------------------ + +#dns_resolver = # Comma separated list of name servers, each + # entry in `ipv4[:port]` format to be used by + # Kong. If not specified the nameservers in + # the local `resolv.conf` file will be used. + # Port defaults to 53 if omitted. + +#dns_hostsfile = /etc/hosts # The `hosts` file to use. + +#------------------------------------------------------------------------------ +# DEVELOPMENT & MISCELLANEOUS +#------------------------------------------------------------------------------ + +# Additional settings inherited from lua-nginx-module allowing for more +# flexibility and advanced usage. +# +# See the lua-nginx-module documentation for more informations: +# https://github.com/openresty/lua-nginx-module + +#lua_ssl_trusted_certificate = # Absolute path to the certificate + # authority file for Lua cosockets in PEM + # format. This certificate will be the one + # used for verifying Kong's database + # connections, when `pg_ssl_verify` or + # `cassandra_ssl_verify` are enabled. + +#lua_ssl_verify_depth = 1 # Sets the verification depth in the server + # certificates chain used by Lua cosockets, + # set by `lua_ssl_trusted_certificate`. + # This includes the certificates configured + # for Kong's database connections. + +#lua_code_cache = on # When disabled, every request will run in a + # separate Lua VM instance: all Lua modules + # will be loaded from scratch. Useful for + # adopting an edit-and-refresh approach while + # developing a plugin. + # Turning this directive off has a severe + # impact on performance. + +#lua_package_path = # Sets the Lua module search path (LUA_PATH). + # Useful when developing or using custom + # plugins not stored in the default search + # path. + +#lua_package_cpath = # Sets the Lua C module search path + # (LUA_CPATH). diff --git a/kong/plugins/request-termination/handler.lua b/kong/plugins/request-termination/handler.lua index 7b032784f56b..31237b95c398 100644 --- a/kong/plugins/request-termination/handler.lua +++ b/kong/plugins/request-termination/handler.lua @@ -15,7 +15,7 @@ end function RequestTerminationHandler:access(conf) RequestTerminationHandler.super.access(self) - local status_code = conf.status_code or 503 + local status_code = conf.status_code local content_type = conf.content_type local body = conf.body local message = conf.message diff --git a/kong/plugins/request-termination/schema.lua b/kong/plugins/request-termination/schema.lua index 4caa59ce3eb2..3a08100d4d4a 100644 --- a/kong/plugins/request-termination/schema.lua +++ b/kong/plugins/request-termination/schema.lua @@ -4,27 +4,25 @@ local utils = require "kong.tools.utils" return { no_consumer = true, fields = { - status_code = { type = "number" }, + status_code = { type = "number", default = 503 }, message = { type = "string" }, content_type = { type = "string" }, body = { type = "string" }, }, self_check = function(schema, plugin_t, dao, is_updating) - local errors - if plugin_t.status_code then if plugin_t.status_code < 100 or plugin_t.status_code > 599 then - return false, "status_code must be between 100..599" + return false, Errors.schema("status_code must be between 100..599") end end if plugin_t.message then if plugin_t.content_type or plugin_t.body then - return false, "message cannot be used with content_type or body" + return false, Errors.schema("message cannot be used with content_type or body") end else if plugin_t.content_type and not plugin_t.body then - return false, "content_type requires a body" + return false, Errors.schema("content_type requires a body") end end diff --git a/kong/tools/responses.lua b/kong/tools/responses.lua index ee812252158c..1cdee585d485 100644 --- a/kong/tools/responses.lua +++ b/kong/tools/responses.lua @@ -147,7 +147,8 @@ local closure_cache = {} --- Send a response with any status code or body, -- Not all status codes are available as sugar methods, this function can be -- used to send any response. --- If the `status_code` parameter is in the 5xx range, it is expected that the `content` parameter be the error encountered. For 500 errors it will be logged. The response body will be empty. The user will just receive a 500 status code. +-- For `status_code=5xx` the `content` parameter should be the description of the error that occurred. +-- For `status_code=500` the content will be logged by ngx.log as an ERR. -- Will call `ngx.say` and `ngx.exit`, terminating the current context. -- @see ngx.say -- @see ngx.exit diff --git a/spec/03-plugins/18-request-termination/01-schema_spec.lua b/spec/03-plugins/18-request-termination/01-schema_spec.lua index 0ea2a5f47679..a728a426d920 100644 --- a/spec/03-plugins/18-request-termination/01-schema_spec.lua +++ b/spec/03-plugins/18-request-termination/01-schema_spec.lua @@ -19,39 +19,39 @@ describe("Plugin: request-termination (schema)", function() describe("errors", function() it("status_code should only accept numbers", function() - local ok, err = v({status_code = "abcd"}, schema) - assert.same({status_code = "status_code is not a number"}, err) + local ok, error = v({status_code = "abcd"}, schema) + assert.same({status_code = "status_code is not a number"}, error) assert.False(ok) end) it("status_code < 100", function() - local ok, err, message = v({status_code = "99"}, schema) + local ok, _, error = v({status_code = "99"}, schema) assert.False(ok) - assert.same("status_code must be between 100..599", message) + assert.same("status_code must be between 100..599", error.message) end) it("status_code > 599", function() - local ok, err, message = v({status_code = "600"}, schema) + local ok, _, error = v({status_code = "600"}, schema) assert.False(ok) - assert.same("status_code must be between 100..599", message) + assert.same("status_code must be between 100..599", error.message) end) it("message with body", function() - local ok, err, message = v({message = "error", body = "test"}, schema) + local ok, _, error = v({message = "error", body = "test"}, schema) assert.False(ok) - assert.same("message cannot be used with content_type or body", message) + assert.same("message cannot be used with content_type or body", error.message) end) it("message with body and content_type", function() - local ok, err, message = v({message = "error", content_type="text/html", body = "test"}, schema) + local ok, _, error = v({message = "error", content_type="text/html", body = "test"}, schema) assert.False(ok) - assert.same("message cannot be used with content_type or body", message) + assert.same("message cannot be used with content_type or body", error.message) end) it("message with content_type", function() - local ok, err, message = v({message = "error", content_type="text/html"}, schema) + local ok, _, error = v({message = "error", content_type="text/html"}, schema) assert.False(ok) - assert.same("message cannot be used with content_type or body", message) + assert.same("message cannot be used with content_type or body", error.message) end) it("content_type without body", function() - local ok, err, message = v({content_type="text/html"}, schema) + local ok, _, error = v({content_type="text/html"}, schema) assert.False(ok) - assert.same("content_type requires a body", message) + assert.same("content_type requires a body", error.message) end) end) end) diff --git a/spec/03-plugins/18-request-termination/02-access_spec.lua b/spec/03-plugins/18-request-termination/02-access_spec.lua index 1c2704776a07..0ee832805135 100644 --- a/spec/03-plugins/18-request-termination/02-access_spec.lua +++ b/spec/03-plugins/18-request-termination/02-access_spec.lua @@ -79,7 +79,7 @@ describe("Plugin: request-termination (access)", function() api_id = api6.id, config = { status_code=503, - body='{"code": 1, "message": "Service unavailable}' + body='{"code": 1, "message": "Service unavailable"}' } }) @@ -170,7 +170,7 @@ describe("Plugin: request-termination (access)", function() } }) local body = assert.res_status(503, res) - assert.equal([[{"code": 1, "message": "Service unavailable}]], body) + assert.equal([[{"code": 1, "message": "Service unavailable"}]], body) end) end) From 69c59f07c7aac7dd832d9a1fa193c9fe4f8ce8c6 Mon Sep 17 00:00:00 2001 From: Paul Austin Date: Mon, 6 Mar 2017 11:02:27 -0800 Subject: [PATCH 19/20] Removed accidental commit of kong.conf --- kong.conf | 316 ------------------------------------------------------ 1 file changed, 316 deletions(-) delete mode 100644 kong.conf diff --git a/kong.conf b/kong.conf deleted file mode 100644 index 201e370cb74f..000000000000 --- a/kong.conf +++ /dev/null @@ -1,316 +0,0 @@ -# ----------------------- -# Kong configuration file -# ----------------------- -# -# The commented-out settings shown in this file represent the default values. -# -# This file is read when `kong start` or `kong compile` are used. Kong -# generates the Nginx configuration with the settings specified in this file. -# -# All environment variables prefixed with `KONG_` and capitalized will override -# the settings specified in this file. -# Example: -# `log_level` setting -> `KONG_LOG_LEVEL` env variable -# -# Boolean values can be specified as `on`/`off` or `true`/`false`. -# Lists must be specified as comma-separated strings. -# -# All comments in this file can be removed safely, including the -# commented-out properties. -# You can verify the integrity of your settings with `kong check `. - -#------------------------------------------------------------------------------ -# GENERAL -#------------------------------------------------------------------------------ - -#prefix = /usr/local/kong/ # Working directory. Equivalent to Nginx's - # prefix path, containing temporary files - # and logs. - # Each Kong process must have a separate - # working directory. - -#log_level = notice # Log level of the Nginx server. Logs are - # found at /logs/error.log -# Note: See http://nginx.org/en/docs/ngx_core_module.html#error_log for a list -# of accepted values. - -#custom_plugins = # Comma-separated list of additional plugins - # this node should load. - # Use this property to load custom plugins - # that are not bundled with Kong. - # Plugins will be loaded from the - # `kong.plugins.{name}.*` namespace. - -#anonymous_reports = on # Send anonymous usage data such as error - # stack traces to help improve Kong. - -#------------------------------------------------------------------------------ -# NGINX -#------------------------------------------------------------------------------ - -#proxy_listen = 0.0.0.0:8000 # Address and port on which Kong will accept - # HTTP requests. - # This is the public-facing entrypoint of - # Kong, to which your consumers will make - # requests to. -# Note: See http://nginx.org/en/docs/http/ngx_http_core_module.html#listen for -# a description of the accepted formats for this and other *_listen values. - -#proxy_listen_ssl = 0.0.0.0:8443 # Address and port on which Kong will accept - # HTTPS requests if `ssl` is enabled. - -#admin_listen = 0.0.0.0:8001 # Address and port on which Kong will expose - # an entrypoint to the Admin API. - # This API lets you configure and manage Kong, - # and should be kept private and secured. - -#admin_listen_ssl = 0.0.0.0:8444 # Address and port on which Kong will accept - # HTTPS requests to the admin API, if - # `admin_ssl` is enabled. - -#nginx_worker_processes = auto # Determines the number of worker processes - # spawned by Nginx. - -#nginx_daemon = on # Determines wether Nginx will run as a daemon - # or as a foreground process. Mainly useful - # for development or when running Kong inside - # a Docker environment. - -#mem_cache_size = 128m # Size of the in-memory cache for database - # entities. The accepted units are `k` and - # `m`, with a minimum recommended value of - # a few MBs. - -#ssl = on # Determines if Nginx should be listening for - # HTTPS traffic on the `proxy_listen_ssl` - # address. If disabled, Nginx will only bind - # itself on `proxy_listen`, and all SSL - # settings will be ignored. - -#ssl_cert = # If `ssl` is enabled, the absolute path to - # the SSL certificate for the - # `proxy_listen_ssl` address. - -#ssl_cert_key = # If `ssl` is enabled, the absolute path to - # the SSL key for the `proxy_listen_ssl` - # address. - -#admin_ssl = on # Determines if Nginx should be listening for - # HTTPS traffic on the `admin_listen_ssl` - # address. If disabled, Nginx will only bind - # itself on `admin_listen`, and all SSL - # settings will be ignored. - -#admin_ssl_cert = # If `admin_ssl` is enabled, the absolute path - # to the SSL certificate for the - # `admin_listen_ssl` address. - -#admin_ssl_cert_key = # If `admin_ssl` is enabled, the absolute path - # to the SSL key for the `admin_listen_ssl` - # address. - -#upstream_keepalive = 60 # Sets the maximum number of idle keepalive - # connections to upstream servers that are - # preserved in the cache of each worker - # process. When this number is exceeded, the - # least recently used connections are closed. - -#------------------------------------------------------------------------------ -# DATASTORE -#------------------------------------------------------------------------------ - -# Kong will store all of its data (such as APIs, consumers and plugins) in -# either Cassandra or PostgreSQL. -# -# All Kong nodes belonging to the same cluster must connect themselves to the -# same database. - -#database = postgres # Determines which of PostgreSQL or Cassandra - # this node will use as its datastore. - # Accepted values are `postgres` and - # `cassandra`. - -#pg_host = 127.0.0.1 # The PostgreSQL host to connect to. -#pg_port = 5432 # The port to connect to. -#pg_user = kong # The username to authenticate if required. -#pg_password = kong # The password to authenticate if required. -#pg_database = kong # The database name to connect to. - -#pg_ssl = off # Toggles client-server TLS connections - # between Kong and PostgreSQL. - -#pg_ssl_verify = off # Toggles server certificate verification if - # `pg_ssl` is enabled. - # See the `lua_ssl_trusted_certificate` - # setting to specify a certificate authority. - -#cassandra_contact_points = 127.0.0.1 # A comma-separated list of contact - # points to your cluster. - -#cassandra_port = 9042 # The port on which your nodes are listening - # on. All your nodes and contact points must - # listen on the same port. - -#cassandra_keyspace = kong # The keyspace to use in your cluster. - -#cassandra_timeout = 5000 # Defines the timeout (in ms), for reading - # and writing. - -#cassandra_ssl = off # Toggles client-to-node TLS connections - # between Kong and Cassandra. - -#cassandra_ssl_verify = off # Toggles server certificate verification if - # `cassandra_ssl` is enabled. - # See the `lua_ssl_trusted_certificate` - # setting to specify a certificate authority. - -#cassandra_username = kong # Username when using the - # `PasswordAuthenticator` scheme. - -#cassandra_password = kong # Password when using the - # `PasswordAuthenticator` scheme. - -#cassandra_consistency = ONE # Consistency setting to use when reading/ - # writing to the Cassandra cluster. - -#cassandra_lb_policy = RoundRobin # Load balancing policy to use when - # distributing queries across your Cassandra - # cluster. - # Accepted values are `RoundRobin` and - # `DCAwareRoundRobin`. - # Prefer the later if and only if you are - # using a multi-datacenter cluster. - -#cassandra_local_datacenter = # When using the `DCAwareRoundRobin` load - # balancing policy, you must specify the name - # of the local (closest) datacenter for this - # Kong node. - -#cassandra_repl_strategy = SimpleStrategy # When migrating for the first time, - # Kong will use this setting to - # create your keyspace. - # Accepted values are - # `SimpleStrategy` and - # `NetworkTopologyStrategy`. - -#cassandra_repl_factor = 1 # When migrating for the first time, Kong - # will create the keyspace with this - # replication factor when using the - # `SimpleStrategy`. - -#cassandra_data_centers = dc1:2,dc2:3 # When migrating for the first time, - # will use this setting when using the - # `NetworkTopologyStrategy`. - # The format is a comma-separated list - # made of :. - -#------------------------------------------------------------------------------ -# CLUSTERING -#------------------------------------------------------------------------------ - -# In addition to pointing to the same database, each Kong node must be -# reachable by the other nodes in the cluster. -# -# Kong's clustering works on the IP layer (hostnames are not supported, only -# IPs) and expects a flat network topology without any NAT between the -# datacenters. -# -# A common pattern is to create a VPN between the two datacenters such that -# the flat network assumption is not violated. -# -# See the clustering reference for more informations: -# https://getkong.org/docs/latest/clustering/ - -#cluster_listen = 0.0.0.0:7946 # Address and port used to communicate with - # other nodes in the cluster. - # All other Kong nodes in the same cluster - # must be able to communicate over both - # TCP and UDP on this address. - # Only IPv4 addresses are supported. - -#cluster_listen_rpc = 127.0.0.1:7373 # Address and port used to communicate - # with the cluster through the agent - # running on this node. Only contains - # TCP traffic local to this node. - -#cluster_advertise = # By default, the `cluster_listen` address - # is advertised over the cluster. - # If the `cluster_listen` host is '0.0.0.0', - # then the first local, non-loopback IPv4 - # address will be advertised to other nodes. - # However, in some cases (specifically NAT - # traversal), there may be a routable address - # that cannot be bound to. This flag enables - # advertising a different address to support - # this. - -#cluster_encrypt_key = # base64-encoded 16-bytes key to encrypt - # cluster traffic with. - -#cluster_ttl_on_failure = 3600 # Time to live (in seconds) of a node in the - # cluster when it stops sending healthcheck - # pings, possibly caused by a node or network - # failure. - # If a node is not able to send a new - # healthcheck ping before the expiration, - # other nodes in the cluster will stop - # attempting to connect to it. - # Recommended to be at least `60`. - -#cluster_profile = wan # The timing profile for inter-cluster pings - # and timeouts. If a `lan` or `local` profile - # is used over the Internet, a high rate of - # failures is risked as the timing contraints - # would be too tight. - # Accepted values are `local`, `lan`, `wan`. - -#------------------------------------------------------------------------------ -# DNS RESOLVER -#------------------------------------------------------------------------------ - -#dns_resolver = # Comma separated list of name servers, each - # entry in `ipv4[:port]` format to be used by - # Kong. If not specified the nameservers in - # the local `resolv.conf` file will be used. - # Port defaults to 53 if omitted. - -#dns_hostsfile = /etc/hosts # The `hosts` file to use. - -#------------------------------------------------------------------------------ -# DEVELOPMENT & MISCELLANEOUS -#------------------------------------------------------------------------------ - -# Additional settings inherited from lua-nginx-module allowing for more -# flexibility and advanced usage. -# -# See the lua-nginx-module documentation for more informations: -# https://github.com/openresty/lua-nginx-module - -#lua_ssl_trusted_certificate = # Absolute path to the certificate - # authority file for Lua cosockets in PEM - # format. This certificate will be the one - # used for verifying Kong's database - # connections, when `pg_ssl_verify` or - # `cassandra_ssl_verify` are enabled. - -#lua_ssl_verify_depth = 1 # Sets the verification depth in the server - # certificates chain used by Lua cosockets, - # set by `lua_ssl_trusted_certificate`. - # This includes the certificates configured - # for Kong's database connections. - -#lua_code_cache = on # When disabled, every request will run in a - # separate Lua VM instance: all Lua modules - # will be loaded from scratch. Useful for - # adopting an edit-and-refresh approach while - # developing a plugin. - # Turning this directive off has a severe - # impact on performance. - -#lua_package_path = # Sets the Lua module search path (LUA_PATH). - # Useful when developing or using custom - # plugins not stored in the default search - # path. - -#lua_package_cpath = # Sets the Lua C module search path - # (LUA_CPATH). From 759fcd87d441f75716263a134056ad4b01e7f1ed Mon Sep 17 00:00:00 2001 From: Paul Austin Date: Tue, 7 Mar 2017 06:06:04 -0800 Subject: [PATCH 20/20] Renamed variable error to err --- .../18-request-termination/01-schema_spec.lua | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/spec/03-plugins/18-request-termination/01-schema_spec.lua b/spec/03-plugins/18-request-termination/01-schema_spec.lua index a728a426d920..9b342141542f 100644 --- a/spec/03-plugins/18-request-termination/01-schema_spec.lua +++ b/spec/03-plugins/18-request-termination/01-schema_spec.lua @@ -19,39 +19,39 @@ describe("Plugin: request-termination (schema)", function() describe("errors", function() it("status_code should only accept numbers", function() - local ok, error = v({status_code = "abcd"}, schema) - assert.same({status_code = "status_code is not a number"}, error) + local ok, err = v({status_code = "abcd"}, schema) + assert.same({status_code = "status_code is not a number"}, err) assert.False(ok) end) it("status_code < 100", function() - local ok, _, error = v({status_code = "99"}, schema) + local ok, _, err = v({status_code = "99"}, schema) assert.False(ok) - assert.same("status_code must be between 100..599", error.message) + assert.same("status_code must be between 100..599", err.message) end) it("status_code > 599", function() - local ok, _, error = v({status_code = "600"}, schema) + local ok, _, err = v({status_code = "600"}, schema) assert.False(ok) - assert.same("status_code must be between 100..599", error.message) + assert.same("status_code must be between 100..599", err.message) end) it("message with body", function() - local ok, _, error = v({message = "error", body = "test"}, schema) + local ok, _, err = v({message = "error", body = "test"}, schema) assert.False(ok) - assert.same("message cannot be used with content_type or body", error.message) + assert.same("message cannot be used with content_type or body", err.message) end) it("message with body and content_type", function() - local ok, _, error = v({message = "error", content_type="text/html", body = "test"}, schema) + local ok, _, err = v({message = "error", content_type="text/html", body = "test"}, schema) assert.False(ok) - assert.same("message cannot be used with content_type or body", error.message) + assert.same("message cannot be used with content_type or body", err.message) end) it("message with content_type", function() - local ok, _, error = v({message = "error", content_type="text/html"}, schema) + local ok, _, err = v({message = "error", content_type="text/html"}, schema) assert.False(ok) - assert.same("message cannot be used with content_type or body", error.message) + assert.same("message cannot be used with content_type or body", err.message) end) it("content_type without body", function() - local ok, _, error = v({content_type="text/html"}, schema) + local ok, _, err = v({content_type="text/html"}, schema) assert.False(ok) - assert.same("content_type requires a body", error.message) + assert.same("content_type requires a body", err.message) end) end) end)