Files
kong--kong/kong/runloop/plugin_servers/rpc/mp_rpc.lua
T
2026-07-13 12:32:21 +08:00

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,
}