Skip to content

Commit

Permalink
feat(wasm): add proxy-wasm dynamic getters/setters
Browse files Browse the repository at this point in the history
Co-Authored-By: Hisham Muhammad <[email protected]>
  • Loading branch information
2 people authored and locao committed Dec 5, 2023
1 parent 81845c8 commit a0a0be5
Show file tree
Hide file tree
Showing 6 changed files with 771 additions and 14 deletions.
5 changes: 5 additions & 0 deletions changelog/unreleased/kong/wasm-dynamic-properties.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
message: >
Extend support for getting and setting Gateway values via proxy-wasm
properties in the `kong.*` namespace.
type: feature
scope: Core
1 change: 1 addition & 0 deletions kong-3.6.0-0.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ build = {
["kong.runloop.plugin_servers.mp_rpc"] = "kong/runloop/plugin_servers/mp_rpc.lua",
["kong.runloop.plugin_servers.pb_rpc"] = "kong/runloop/plugin_servers/pb_rpc.lua",
["kong.runloop.wasm"] = "kong/runloop/wasm.lua",
["kong.runloop.wasm.properties"] = "kong/runloop/wasm/properties.lua",

["kong.workspaces"] = "kong/workspaces/init.lua",

Expand Down
172 changes: 158 additions & 14 deletions kong/runloop/wasm.lua
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ local json_schema = require "kong.db.schema.json"
local pl_file = require "pl.file"
local pl_path = require "pl.path"
local constants = require "kong.constants"
local properties = require "kong.runloop.wasm.properties"


---@module 'resty.wasmx.proxy_wasm'
Expand Down Expand Up @@ -682,6 +683,155 @@ local function disable(reason)
end


local function register_property_handlers()
properties.reset()

properties.add_getter("kong.client.protocol", function(kong)
return true, kong.client.get_protocol(), true
end)

properties.add_getter("kong.nginx.subsystem", function(kong)
return true, kong.nginx.get_subsystem(), true
end)

properties.add_getter("kong.node.id", function(kong)
return true, kong.node.get_id(), true
end)

properties.add_getter("kong.node.memory_stats", function(kong)
local stats = kong.node.get_memory_stats()
if not stats then
return false
end
return true, cjson_encode(stats), false
end)

properties.add_getter("kong.request.forwarded_host", function(kong)
return true, kong.request.get_forwarded_host(), true
end)

properties.add_getter("kong.request.forwarded_port", function(kong)
return true, kong.request.get_forwarded_port(), true
end)

properties.add_getter("kong.request.forwarded_scheme", function(kong)
return true, kong.request.get_forwarded_scheme(), true
end)

properties.add_getter("kong.request.port", function(kong)
return true, kong.request.get_port(), true
end)

properties.add_getter("kong.response.source", function(kong)
return true, kong.request.get_source(), false
end)

properties.add_setter("kong.response.status", function(kong, _, _, status)
return true, kong.response.set_status(tonumber(status)), false
end)

properties.add_getter("kong.router.route", function(kong)
local route = kong.router.get_route()
if not route then
return true, nil, true
end
return true, cjson_encode(route), true
end)

properties.add_getter("kong.router.service", function(kong)
local service = kong.router.get_service()
if not service then
return true, nil, true
end
return true, cjson_encode(service), true
end)

properties.add_setter("kong.service.target", function(kong, _, _, target)
local host, port = target:match("^(.*):([0-9]+)$")
port = tonumber(port)
if not (host and port) then
return false
end

kong.service.set_target(host, port)
return true, target, false
end)

properties.add_setter("kong.service.upstream", function(kong, _, _, upstream)
local ok, err = kong.service.set_upstream(upstream)
if not ok then
kong.log.err(err)
return false
end

return true, upstream, false
end)

properties.add_setter("kong.service.request.scheme", function(kong, _, _, scheme)
kong.service.request.set_scheme(scheme)
return true, scheme, false
end)

properties.add_getter("kong.route_id", function(_, _, ctx)
local value = ctx.route and ctx.route.id
local ok = value ~= nil
local const = ok
return ok, value, const
end)

properties.add_getter("kong.service.response.status", function(kong)
return true, kong.service.response.get_status(), false
end)

properties.add_getter("kong.service_id", function(_, _, ctx)
local value = ctx.service and ctx.service.id
local ok = value ~= nil
local const = ok
return ok, value, const
end)

properties.add_getter("kong.version", function(kong)
return true, kong.version, true
end)

properties.add_namespace_handlers("kong.ctx.shared",
function(kong, _, _, key)
local value = kong.ctx.shared[key]
local ok = value ~= nil
value = ok and tostring(value) or nil
return ok, value, false
end,

function(kong, _, _, key, value)
kong.ctx.shared[key] = value
return true
end
)

properties.add_namespace_handlers("kong.configuration",
function(kong, _, _, key)
local value = kong.configuration[key]
if value ~= nil then
if type(value) == "table" then
value = cjson_decode(value)
else
value = tostring(value)
end

return true, value, true
end

return false
end,

function()
-- kong.configuration is read-only: setter rejects all
return false
end
)
end


local function enable(kong_config)
set_available_filters(kong_config.wasm_modules_parsed)

Expand All @@ -690,6 +840,8 @@ local function enable(kong_config)

proxy_wasm = proxy_wasm or require "resty.wasmx.proxy_wasm"

register_property_handlers()

ENABLED = true
STATUS = STATUS_ENABLED
end
Expand Down Expand Up @@ -746,18 +898,6 @@ function _M.init_worker()
end


local function set_proxy_wasm_property(property, value)
if not value then
return
end

local ok, err = proxy_wasm.set_property(property, value)
if not ok then
log(ERR, "failed to set proxy-wasm '", property, "' property: ", err)
end
end


---
-- Lookup and execute the filter chain that applies to the current request
-- (if any).
Expand Down Expand Up @@ -788,8 +928,12 @@ function _M.attach(ctx)
return kong.response.error(500)
end

set_proxy_wasm_property("kong.route_id", ctx.route and ctx.route.id)
set_proxy_wasm_property("kong.service_id", ctx.service and ctx.service.id)
ok, err = proxy_wasm.set_host_properties_handlers(properties.get,
properties.set)
if not ok then
log(CRIT, "failed setting host property handlers: ", err)
return kong.response.error(500)
end

ok, err = proxy_wasm.start()
if not ok then
Expand Down
129 changes: 129 additions & 0 deletions kong/runloop/wasm/properties.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
local _M = {}

local clear_tab = require "table.clear"

local kong = kong
local ngx = ngx


local simple_getters = {}
local simple_setters = {}
local namespace_handlers = {}

local get_namespace, rebuild_namespaces
do
local patterns = {}
local handlers = {}
local namespaces_len = 0

function rebuild_namespaces()
clear_tab(patterns)
clear_tab(handlers)

for ns, handler in pairs(namespace_handlers) do
table.insert(patterns, ns .. ".")
table.insert(handlers, handler)
end

namespaces_len = #patterns
end

local find = string.find
local sub = string.sub

---@param property string
---@return table? namespace
---@return string? key
function get_namespace(property)
for i = 1, namespaces_len do
local from, to = find(property, patterns[i], nil, true)
if from == 1 then
local key = sub(property, to + 1)
return handlers[i], key
end
end
end
end


function _M.reset()
clear_tab(simple_getters)
clear_tab(simple_setters)
clear_tab(namespace_handlers)
rebuild_namespaces()
end


function _M.add_getter(name, handler)
assert(type(name) == "string")
assert(type(handler) == "function")

simple_getters[name] = handler
end


function _M.add_setter(name, handler)
assert(type(name) == "string")
assert(type(handler) == "function")

simple_setters[name] = handler
end


function _M.add_namespace_handlers(name, get, set)
assert(type(name) == "string")
assert(type(get) == "function")
assert(type(set) == "function")

namespace_handlers[name] = { get = get, set = set }
rebuild_namespaces()
end


---@param name string
---@return boolean? ok
---@return string? value_or_error
---@return boolean? is_const
function _M.get(name)
local ok, value, const = false, nil, nil

local getter = simple_getters[name]
if getter then
ok, value, const = getter(kong, ngx, ngx.ctx)

else
local ns, key = get_namespace(name)

if ns then
ok, value, const = ns.get(kong, ngx, ngx.ctx, key)
end
end

return ok, value, const
end


---@param name string
---@param value string|nil
---@return boolean? ok
---@return string? cached_value
---@return boolean? is_const
function _M.set(name, value)
local ok, cached_value, const = false, nil, nil

local setter = simple_setters[name]
if setter then
ok, cached_value, const = setter(kong, ngx, ngx.ctx, value)

else
local ns, key = get_namespace(name)
if ns then
ok, cached_value, const = ns.set(kong, ngx, ngx.ctx, key, value)
end
end

return ok, cached_value, const
end


return _M
Loading

0 comments on commit a0a0be5

Please sign in to comment.