334 lines
6.9 KiB
Lua
334 lines
6.9 KiB
Lua
local kong_global = require "kong.global"
|
|
local cjson = require "cjson.safe"
|
|
local rpc_util = require "kong.runloop.plugin_servers.rpc.util"
|
|
local _
|
|
|
|
local msgpack do
|
|
msgpack = require "MessagePack"
|
|
local nil_pack = msgpack.packers["nil"]
|
|
-- let msgpack encode cjson.null
|
|
function msgpack.packers.userdata (buffer, userdata)
|
|
if userdata == cjson.null then
|
|
return nil_pack(buffer)
|
|
else
|
|
error "pack 'userdata' is unimplemented"
|
|
end
|
|
end
|
|
end
|
|
|
|
local ngx = ngx
|
|
local kong = kong
|
|
|
|
local cjson_encode = cjson.encode
|
|
local mp_pack = msgpack.pack
|
|
local mp_unpacker = msgpack.unpacker
|
|
local str_find = string.find
|
|
|
|
|
|
local Rpc = {}
|
|
Rpc.__index = Rpc
|
|
|
|
-- add MessagePack empty array/map
|
|
|
|
msgpack.packers['function'] = function (buffer, f)
|
|
f(buffer)
|
|
end
|
|
|
|
local function mp_empty_array(buffer)
|
|
msgpack.packers['array'](buffer, {}, 0)
|
|
end
|
|
|
|
local function mp_empty_map(buffer)
|
|
msgpack.packers['map'](buffer, {}, 0)
|
|
end
|
|
|
|
--- fix_mmap(t) : preprocess complex maps
|
|
local function fix_mmap(t)
|
|
local o, empty = {}, true
|
|
|
|
for k, v in pairs(t) do
|
|
empty = false
|
|
if v == true then
|
|
o[k] = mp_empty_array
|
|
|
|
elseif type(v) == "string" then
|
|
o[k] = { v }
|
|
|
|
else
|
|
o[k] = v
|
|
end
|
|
end
|
|
|
|
if empty then
|
|
return mp_empty_map
|
|
end
|
|
|
|
return o
|
|
end
|
|
|
|
local function fix_raw(bin)
|
|
local function mp_raw(buffer)
|
|
msgpack.packers['binary'](buffer, bin)
|
|
end
|
|
return mp_raw
|
|
end
|
|
|
|
local must_fix = {
|
|
["kong.request.get_query"] = fix_mmap,
|
|
["kong.request.get_headers"] = fix_mmap,
|
|
["kong.response.get_headers"] = fix_mmap,
|
|
["kong.service.response.get_headers"] = fix_mmap,
|
|
["kong.request.get_raw_body"] = fix_raw,
|
|
["kong.response.get_raw_body"] = fix_raw,
|
|
["kong.service.response.get_raw_body"] = fix_raw,
|
|
}
|
|
|
|
-- for unit-testing purposes only
|
|
Rpc.must_fix = must_fix
|
|
|
|
|
|
--[[
|
|
|
|
Kong API exposed to external plugins
|
|
|
|
--]]
|
|
|
|
|
|
local get_field
|
|
do
|
|
local method_cache = {}
|
|
|
|
function get_field(pdk, method)
|
|
if method_cache[method] then
|
|
return method_cache[method]
|
|
|
|
else
|
|
method_cache[method] = rpc_util.index_table(pdk, method)
|
|
return method_cache[method]
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
local function call_pdk_method(pdk, cmd, args)
|
|
local method = get_field(pdk, cmd)
|
|
if not method then
|
|
kong.log.err("could not find pdk method: ", cmd)
|
|
return
|
|
end
|
|
|
|
local saved = pdk.get_saved_req_data()
|
|
if saved and saved.plugin_name then
|
|
kong_global.set_namespaced_log(kong, saved.plugin_name)
|
|
end
|
|
|
|
local ret
|
|
if type(args) == "table" then
|
|
ret = method(unpack(args))
|
|
else
|
|
ret = method(args)
|
|
end
|
|
|
|
local fix = must_fix[cmd]
|
|
if fix then
|
|
ret = fix(ret)
|
|
end
|
|
|
|
return ret
|
|
end
|
|
|
|
|
|
-- return objects via the appropriately typed StepXXX method
|
|
local get_step_method
|
|
do
|
|
local by_pdk_method = {
|
|
["kong.client.get_credential"] = "plugin.StepCredential",
|
|
["kong.client.load_consumer"] = "plugin.StepConsumer",
|
|
["kong.client.get_consumer"] = "plugin.StepConsumer",
|
|
["kong.client.authenticate"] = "plugin.StepCredential",
|
|
["kong.node.get_memory_stats"] = "plugin.StepMemoryStats",
|
|
["kong.router.get_route"] = "plugin.StepRoute",
|
|
["kong.router.get_service"] = "plugin.StepService",
|
|
["kong.request.get_query"] = "plugin.StepMultiMap",
|
|
["kong.request.get_headers"] = "plugin.StepMultiMap",
|
|
["kong.response.get_headers"] = "plugin.StepMultiMap",
|
|
["kong.service.response.get_headers"] = "plugin.StepMultiMap",
|
|
}
|
|
|
|
function get_step_method(step_in, pdk_res, pdk_err)
|
|
if not pdk_res and pdk_err then
|
|
return "plugin.StepError", pdk_err
|
|
end
|
|
|
|
return ((type(pdk_res) == "table" and pdk_res._method)
|
|
or by_pdk_method[step_in.Data.Method]
|
|
or "plugin.Step"), pdk_res
|
|
end
|
|
end
|
|
|
|
|
|
|
|
|
|
function Rpc:call(method, ...)
|
|
self.msg_id = self.msg_id + 1
|
|
local msg_id = self.msg_id
|
|
|
|
local c, err = ngx.socket.connect("unix:" .. self.plugin.server_def.socket)
|
|
if not c then
|
|
kong.log.err("trying to connect: ", err)
|
|
return nil, err
|
|
end
|
|
|
|
-- request: [ 0, msg_id, method, args ]
|
|
local bytes, err = c:send(mp_pack({0, msg_id, method, {...}}))
|
|
if not bytes then
|
|
c:setkeepalive()
|
|
return nil, err
|
|
end
|
|
|
|
local reader = mp_unpacker(function()
|
|
return c:receiveany(4096)
|
|
end)
|
|
|
|
while true do
|
|
-- read an MP object
|
|
local ok, data = reader()
|
|
if not ok then
|
|
c:setkeepalive()
|
|
return nil, "no data"
|
|
end
|
|
|
|
if data[1] == 2 then
|
|
-- notification: [ 2, label, args ]
|
|
self:notification(data[2], data[3])
|
|
|
|
else
|
|
-- response: [ 1, msg_id, error, result ]
|
|
assert(data[1] == 1, "RPC response expected from Go plugin server")
|
|
assert(data[2] == msg_id,
|
|
"unexpected RPC response ID from Go plugin server")
|
|
|
|
-- it's our answer
|
|
c:setkeepalive()
|
|
|
|
if data[3] ~= nil then
|
|
return nil, data[3]
|
|
end
|
|
|
|
return data[4]
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
function Rpc:call_start_instance(plugin_name, conf)
|
|
local status, err = self:call("plugin.StartInstance", {
|
|
Name = plugin_name,
|
|
Config = cjson_encode(conf)
|
|
})
|
|
|
|
if status == nil then
|
|
return nil, err
|
|
end
|
|
|
|
return {
|
|
id = status.Id,
|
|
conf = conf,
|
|
seq = conf.__seq__,
|
|
Config = status.Config,
|
|
rpc = self,
|
|
}
|
|
end
|
|
|
|
|
|
function Rpc:call_close_instance(instance_id)
|
|
return self:call("plugin.CloseInstance", instance_id)
|
|
end
|
|
|
|
|
|
|
|
function Rpc:notification(label, args)
|
|
local f = self.plugin.rpc_notifications[label]
|
|
if f then
|
|
f(self, args)
|
|
end
|
|
end
|
|
|
|
|
|
--[[
|
|
|
|
--- Event loop -- instance reconnection
|
|
|
|
--]]
|
|
|
|
local function bridge_loop(instance_rpc, instance_id, phase)
|
|
if not instance_rpc then
|
|
kong.log.err("no instance_rpc: ", debug.traceback())
|
|
end
|
|
local step_in, err = instance_rpc:call("plugin.HandleEvent", {
|
|
InstanceId = instance_id,
|
|
EventName = phase,
|
|
})
|
|
if not step_in then
|
|
return step_in, err
|
|
end
|
|
|
|
local event_id = step_in.EventId
|
|
|
|
while true do
|
|
if step_in.Data == "ret" then
|
|
break
|
|
end
|
|
|
|
local pdk_res, pdk_err = call_pdk_method(
|
|
instance_rpc.plugin.exposed_api,
|
|
step_in.Data.Method,
|
|
step_in.Data.Args)
|
|
|
|
local step_method, step_res = get_step_method(step_in, pdk_res, pdk_err)
|
|
|
|
step_in, err = instance_rpc:call(step_method, {
|
|
EventId = event_id,
|
|
Data = step_res,
|
|
})
|
|
if not step_in then
|
|
return step_in, err
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
function Rpc:handle_event(conf, phase)
|
|
local plugin_name = self.plugin.name
|
|
|
|
local instance_id, err = self.plugin.instance_callbacks.get_instance_id(self.plugin, conf)
|
|
if not err then
|
|
_, err = bridge_loop(self, instance_id, phase)
|
|
end
|
|
|
|
if err then
|
|
local err_lowered = err:lower()
|
|
|
|
if str_find(err_lowered, "no plugin instance") then
|
|
self.plugin.instance_callbacks.reset_instance(plugin_name, conf)
|
|
kong.log.warn(err)
|
|
return self:handle_event(conf, phase)
|
|
end
|
|
|
|
kong.log.err(err)
|
|
end
|
|
end
|
|
|
|
local function new(plugin)
|
|
local self = setmetatable({
|
|
msg_id = 0,
|
|
plugin = plugin,
|
|
}, Rpc)
|
|
return self
|
|
end
|
|
|
|
|
|
return {
|
|
new = new,
|
|
}
|