Skip to content

Commit

Permalink
CORS fixes and status endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
subnetmarco committed Jul 15, 2015
1 parent a9ff1bd commit 417c137
Show file tree
Hide file tree
Showing 14 changed files with 177 additions and 41 deletions.
1 change: 1 addition & 0 deletions kong-0.4.0-1.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ build = {

["kong.api.app"] = "kong/api/app.lua",
["kong.api.crud_helpers"] = "kong/api/crud_helpers.lua",
["kong.api.route_helpers"] = "kong/api/route_helpers.lua",
["kong.api.routes.kong"] = "kong/api/routes/kong.lua",
["kong.api.routes.apis"] = "kong/api/routes/apis.lua",
["kong.api.routes.consumers"] = "kong/api/routes/consumers.lua",
Expand Down
5 changes: 5 additions & 0 deletions kong.yml
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,11 @@ nginx: |
';
}
location /nginx_status {
internal;
stub_status;
}
location /robots.txt {
return 200 'User-agent: *\nDisallow: /';
}
Expand Down
37 changes: 37 additions & 0 deletions kong/api/route_helpers.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
local stringy = require "stringy"

local _M = {}

function _M.get_hostname()
local f = io.popen ("/bin/hostname")
local hostname = f:read("*a") or ""
f:close()
hostname = string.gsub(hostname, "\n$", "")
return hostname
end

function _M.parse_status(value)
local result = {}
local parts = stringy.split(value, "\n")
for i, v in ipairs(parts) do
local part = stringy.strip(v)
if i == 1 then
result["connections_active"] = tonumber(string.sub(part, string.find(part, "%d+")))
elseif i == 3 then
local counter = 1
local stat_names = { "connections_accepted", "connections_handled", "total_requests"}
for stat in string.gmatch(part, "%S+") do
result[stat_names[counter]] = tonumber(stat)
counter = counter + 1
end
elseif i == 4 then
local reading, writing, waiting = string.match(part, "%a+:%s*(%d+)%s*%a+:%s*(%d+)%s*%a+:%s*(%d+)")
result["connections_reading"] = tonumber(reading)
result["connections_writing"] = tonumber(writing)
result["connections_waiting"] = tonumber(waiting)
end
end
return result
end

return _M
21 changes: 12 additions & 9 deletions kong/api/routes/kong.lua
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
local constants = require "kong.constants"

local function get_hostname()
local f = io.popen ("/bin/hostname")
local hostname = f:read("*a") or ""
f:close()
hostname = string.gsub(hostname, "\n$", "")
return hostname
end
local route_helpers = require "kong.api.route_helpers"

return {
["/"] = {
Expand All @@ -19,13 +12,23 @@ return {
return helpers.responses.send_HTTP_OK({
tagline = "Welcome to Kong",
version = constants.VERSION,
hostname = get_hostname(),
hostname = route_helpers.get_hostname(),
plugins = {
available_on_server = configuration.plugins_available,
enabled_in_cluster = db_plugins
},
lua_version = jit and jit.version or _VERSION
})
end
},
["/status"] = {
GET = function(self, dao, helpers)
local res = ngx.location.capture("/nginx_status")
if res.status == 200 then
return helpers.responses.send_HTTP_OK(route_helpers.parse_status(res.body))
else
return helpers.responses.send_HTTP_INTERNAL_SERVER_ERROR(res.body)
end
end
}
}
18 changes: 12 additions & 6 deletions kong/dao/schemas_validation.lua
Original file line number Diff line number Diff line change
Expand Up @@ -102,16 +102,22 @@ function _M.validate_entity(t, schema, options)

-- [ENUM] Check if the value is allowed in the enum.
if t[column] ~= nil and v.enum then
local found = false
for _, allowed in ipairs(v.enum) do
if allowed == t[column] then
found = true
break
local found = true
local wrong_value = t[column]
if v.type == "array" then
for _, array_value in ipairs(t[column]) do
if not utils.table_contains(v.enum, array_value) then
found = false
wrong_value = array_value
break
end
end
else
found = utils.table_contains(v.enum, t[column])
end

if not found then
errors = utils.add_error(errors, column, string.format("\"%s\" is not allowed. Allowed values are: \"%s\"", t[column], table.concat(v.enum, "\", \"")))
errors = utils.add_error(errors, column, string.format("\"%s\" is not allowed. Allowed values are: \"%s\"", wrong_value, table.concat(v.enum, "\", \"")))
end
end

Expand Down
24 changes: 10 additions & 14 deletions kong/plugins/cors/access.lua
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,23 @@ end

local function configure_headers(ngx, conf, headers)
if conf.headers == nil then
ngx.header["Access-Control-Allow-Headers"] = headers['access-control-request-headers'] or ""
ngx.header["Access-Control-Allow-Headers"] = headers["access-control-request-headers"] or ""
else
ngx.header["Access-Control-Allow-Headers"] = conf.headers
ngx.header["Access-Control-Allow-Headers"] = table.concat(conf.headers, ",")
end
end

local function configure_exposed_headers(ngx, conf)
if conf.exposed_headers ~= nil then
ngx.header["Access-Control-Expose-Headers"] = conf.exposed_headers
ngx.header["Access-Control-Expose-Headers"] = table.concat(conf.exposed_headers, ",")
end
end

local function configure_methods(ngx, conf)
if conf.methods ~= nil then
ngx.header["Access-Control-Allow-Methods"] = conf.methods
else
if conf.methods == nil then
ngx.header["Access-Control-Allow-Methods"] = "GET,HEAD,PUT,PATCH,POST,DELETE"
else
ngx.header["Access-Control-Allow-Methods"] = table.concat(conf.methods, ",")
end
end

Expand All @@ -46,22 +46,18 @@ local function configure_max_age(ngx, conf)
end

function _M.execute(conf)
local request = ngx.req
local method = request.get_method()
local headers = request.get_headers()

configure_origin(ngx, conf)
configure_credentials(ngx, conf)

if method == "OPTIONS" then
-- Preflight
configure_headers(ngx, conf, headers)
if ngx.req.get_method() == "OPTIONS" then -- Preflight request
configure_headers(ngx, conf, ngx.req.get_headers())
configure_methods(ngx, conf)
configure_max_age(ngx, conf)

if not conf.preflight_continue then
if not conf.preflight_continue then -- Check if the preflight request should end here, or be proxied
return responses.send_HTTP_NO_CONTENT()
end

else
configure_exposed_headers(ngx, conf)
end
Expand Down
8 changes: 4 additions & 4 deletions kong/plugins/cors/schema.lua
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
return {
fields = {
origin = { type = "string" },
headers = { type = "string" },
exposed_headers = { type = "string" },
methods = { type = "string" },
headers = { type = "array" },
exposed_headers = { type = "array" },
methods = { type = "array", enum = { "HEAD", "GET", "POST", "PUT", "PATCH", "DELETE" } },
max_age = { type = "number" },
credentials = { type = "boolean", default = false },
preflight_continue = { type = "boolean", default = false }
}
}
}
3 changes: 1 addition & 2 deletions kong/plugins/ip_restriction/init_worker.lua
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
local iputils = require "resty.iputils"

local _M = {}

function _M.execute()
local iputils = require "resty.iputils"
iputils.enable_lrucache()
end

Expand Down
3 changes: 1 addition & 2 deletions kong/plugins/ip_restriction/schema.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
local iputils = require "resty.iputils"

local function validate_ips(v, t, column)
local iputils = require "resty.iputils"
local new_fields
if v and type(v) == "table" then
for _, ip in ipairs(v) do
Expand Down
19 changes: 19 additions & 0 deletions spec/integration/admin_api/kong_routes_spec.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
local json = require "cjson"
local http_client = require "kong.tools.http_client"
local spec_helper = require "spec.spec_helpers"
local utils = require "kong.tools.utils"

describe("Admin API", function()

Expand Down Expand Up @@ -52,4 +53,22 @@ describe("Admin API", function()
end)
end)
end)

describe("/status", function()
it("should return status information", function()
local response, status = http_client.get(spec_helper.API_URL.."/status")
assert.are.equal(200, status)
local body = json.decode(response)
assert.truthy(body)

assert.are.equal(7, utils.table_size(body))
assert.truthy(body.connections_accepted)
assert.truthy(body.connections_active)
assert.truthy(body.connections_handled)
assert.truthy(body.connections_reading)
assert.truthy(body.connections_writing)
assert.truthy(body.connections_waiting)
assert.truthy(body.total_requests)
end)
end)
end)
21 changes: 21 additions & 0 deletions spec/integration/admin_api/route_helpers_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
local route_helpers = require "kong.api.route_helpers"

describe("Route Helpers", function()

it("should return the hostname", function()
assert.truthy(route_helpers.get_hostname())
end)

it("should return parse the nginx status", function()
local status = "Active connections: 33 \nserver accepts handled requests\n 3 5 7 \nReading: 314 Writing: 1 Waiting: 2 \n"
local res = route_helpers.parse_status(status)

assert.are.equal(33, res.connections_active)
assert.are.equal(3, res.connections_accepted)
assert.are.equal(5, res.connections_handled)
assert.are.equal(7, res.total_requests)
assert.are.equal(314, res.connections_reading)
assert.are.equal(1, res.connections_writing)
assert.are.equal(2, res.connections_waiting)
end)
end)
8 changes: 4 additions & 4 deletions spec/plugins/cors_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ describe("CORS Plugin", function()
plugin_configuration = {
{ name = "cors", value = {}, __api = 1 },
{ name = "cors", value = { origin = "example.com",
methods = "GET",
headers = "origin, type, accepts",
exposed_headers = "x-auth-token",
methods = { "GET" },
headers = { "origin", "type", "accepts" },
exposed_headers = { "x-auth-token" },
max_age = 23,
credentials = true }, __api = 2 }
}
Expand Down Expand Up @@ -52,7 +52,7 @@ describe("CORS Plugin", function()
-- assertions
assert.are.equal(204, status)
assert.are.equal("example.com", headers["access-control-allow-origin"])
assert.are.equal("origin, type, accepts", headers["access-control-allow-headers"])
assert.are.equal("origin,type,accepts", headers["access-control-allow-headers"])
assert.are.equal(nil, headers["access-control-expose-headers"])
assert.are.equal("GET", headers["access-control-allow-methods"])
assert.are.equal(tostring(23), headers["access-control-max-age"])
Expand Down
45 changes: 45 additions & 0 deletions spec/unit/schemas_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ describe("Schemas", function()
allowed = {enum = {"hello", "world"}},
boolean_val = {type = "boolean"},
endpoint = { type = "url" },
enum_array = { type = "array", enum = { "hello", "world" }},
default = {default = function(t)
assert.truthy(t)
return "default"
Expand Down Expand Up @@ -313,6 +314,50 @@ describe("Schemas", function()
assert.truthy(err)
assert.are.same("\"hello123\" is not allowed. Allowed values are: \"hello\", \"world\"", err.allowed)
end)

it("should validate an enum into an array", function()
-- Failure
local values = { string = "somestring", enum_array = "hello1" }

local valid, err = validate_entity(values, schema)
assert.truthy(err)
assert.are.same("\"hello1\" is not allowed. Allowed values are: \"hello\", \"world\"", err.enum_array)

-- Failure
local values = { string = "somestring", enum_array = { "hello1" } }

local valid, err = validate_entity(values, schema)
assert.truthy(err)
assert.are.same("\"hello1\" is not allowed. Allowed values are: \"hello\", \"world\"", err.enum_array)

-- Success
local values = { string = "somestring", enum_array = { "hello" } }

local valid, err = validate_entity(values, schema)
assert.falsy(err)
assert.truthy(valid)

-- Success
local values = { string = "somestring", enum_array = { "hello", "world" } }

local valid, err = validate_entity(values, schema)
assert.falsy(err)
assert.truthy(valid)

-- Failure
local values = { string = "somestring", enum_array = { "hello", "world", "another" } }

local valid, err = validate_entity(values, schema)
assert.truthy(err)
assert.are.same("\"another\" is not allowed. Allowed values are: \"hello\", \"world\"", err.enum_array)

-- Success
local values = { string = "somestring", enum_array = { } }

local valid, err = validate_entity(values, schema)
assert.falsy(err)
assert.truthy(valid)
end)
end)

describe("[func]", function()
Expand Down
5 changes: 5 additions & 0 deletions spec/unit/statics_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,11 @@ nginx: |
';
}
location /nginx_status {
internal;
stub_status;
}
location /robots.txt {
return 200 'User-agent: *\nDisallow: /';
}
Expand Down

1 comment on commit 417c137

@subnetmarco
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This commit also exposes a /status endpoint on the admin API for basic monitoring purposes.

Please sign in to comment.