Files
2026-07-13 12:32:21 +08:00

404 lines
9.4 KiB
Lua

local kong_global = require "kong.global"
local cjson = require "cjson.safe"
local grpc_tools = require "kong.tools.grpc"
local pb = require "pb"
local lpack = require "lua_pack"
local rpc_util = require "kong.runloop.plugin_servers.rpc.util"
local ngx = ngx
local kong = kong
local cjson_encode = cjson.encode
local t_unpack = table.unpack -- luacheck: ignore table
local st_pack = lpack.pack
local st_unpack = lpack.unpack
local str_find = string.find
local proto_fname = "kong/pluginsocket.proto"
local Rpc = {}
Rpc.__index = Rpc
local pb_unwrap
do
local structpb_value, structpb_list, structpb_struct
function structpb_value(v)
if type(v) ~= "table" then
return v
end
if v.list_value then
return structpb_list(v.list_value)
end
if v.struct_value then
return structpb_struct(v.struct_value)
end
return v.bool_value or v.string_value or v.number_value or v.null_value
end
function structpb_list(l)
local out = {}
if type(l) == "table" then
for i, v in ipairs(l.values or l) do
out[i] = structpb_value(v)
end
end
return out
end
function structpb_struct(struct)
if type(struct) ~= "table" then
return struct
end
local out = {}
for k, v in pairs(struct.fields or struct) do
out[k] = structpb_value(v)
end
return out
end
local function unwrap_val(d) return d.v end
pb_unwrap = {
[".google.protobuf.Empty"] = function() end,
[".google.protobuf.Value"] = structpb_value,
[".google.protobuf.ListValue"] = function(v)
return t_unpack(structpb_list(v))
end,
[".google.protobuf.Struct"] = structpb_struct,
[".kong_plugin_protocol.Bool"] = unwrap_val,
[".kong_plugin_protocol.Number"] = unwrap_val,
[".kong_plugin_protocol.Int"] = unwrap_val,
[".kong_plugin_protocol.String"] = unwrap_val,
[".kong_plugin_protocol.ByteString"] = unwrap_val,
[".kong_plugin_protocol.KV"] = function(d)
return d.k, structpb_value(d.v)
end,
[".kong_plugin_protocol.Target"] = function(d)
return d.host, d.port
end,
[".kong_plugin_protocol.ExitArgs"] = function (d)
return d.status, d.body, structpb_struct(d.headers)
end,
[".kong_plugin_protocol.ConsumerSpec"] = function (d)
return d.id, d.by_username
end,
[".kong_plugin_protocol.AuthenticateArgs"] = function (d)
return d.consumer, d.credential
end,
}
end
local pb_wrap
do
local structpb_value, structpb_list, structpb_struct
function structpb_value(v)
local t = type(v)
local bool_v = nil
if t == "boolean" then
bool_v = v
end
local list_v = nil
local struct_v = nil
if t == "table" then
if v[1] ~= nil then
list_v = structpb_list(v)
else
struct_v = structpb_struct(v)
end
end
return {
null_value = t == "nil" and 1 or nil,
bool_value = bool_v,
number_value = t == "number" and v or nil,
string_value = t == "string" and v or nil,
list_value = list_v,
struct_value = struct_v,
}
end
function structpb_list(l)
local out = {}
for i, v in ipairs(l) do
out[i] = structpb_value(v)
end
return { values = out }
end
function structpb_struct(d)
local out = {}
for k, v in pairs(d) do
out[k] = structpb_value(v)
end
return { fields = out }
end
local function wrap_val(v) return { v = v } end
pb_wrap = {
[".google.protobuf.Value"] = structpb_value,
[".google.protobuf.ListValue"] = structpb_list,
[".google.protobuf.Struct"] = structpb_struct,
[".kong_plugin_protocol.Bool"] = wrap_val,
[".kong_plugin_protocol.Number"] = wrap_val,
[".kong_plugin_protocol.Int"] = wrap_val,
[".kong_plugin_protocol.String"] = wrap_val,
[".kong_plugin_protocol.ByteString"] = wrap_val,
[".kong_plugin_protocol.RawBodyResult"] = function(v, err)
if type(v) == "string" then
return { content = v }
end
local path = ngx.req.get_body_file()
if path then
return { body_filepath = path }
end
return { error = err or "Can't read request body" }
end,
--[".kong_plugin_protocol.MemoryStats"] = - function(v)
-- return {
-- lua_shared_dicts = {
--
-- }
-- }
--end
}
end
local function load_service(pdk)
local p = grpc_tools.new()
local protoc_instance = p.protoc_instance
protoc_instance:loadfile(proto_fname)
local parsed = protoc_instance:parsefile(proto_fname)
local service = {}
for i, s in ipairs(parsed.service) do
for j, m in ipairs(s.method) do
local method_name = s.name .. '.' .. m.name
local lower_name = m.options and m.options.MethodName
or method_name
:gsub('_', '.')
:gsub('([a-z]%d*)([A-Z])', '%1_%2')
:lower()
service[lower_name] = {
method_name = method_name,
method = rpc_util.index_table(pdk, lower_name),
input_type = m.input_type,
output_type = m.output_type,
}
--print(("service[%q] = %s"):format(lower_name, pp(service[lower_name])))
end
end
return service
end
local rpc_service
local function identity_function(x)
return x
end
local function call_pdk(pdk, method_name, arg)
local method = rpc_service[method_name]
if not method then
return nil, ("method %q not found"):format(method_name)
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
arg = assert(pb.decode(method.input_type, arg))
local unwrap = pb_unwrap[method.input_type] or identity_function
local wrap = pb_wrap[method.output_type] or identity_function
local reply = wrap(method.method(unwrap(arg)))
if reply == nil then
--kong.log.debug("no reply")
return ""
end
reply = assert(pb.encode(method.output_type, reply))
return reply
end
local function read_frame(c)
--kong.log.debug("reading frame...")
local msg, err = c:receive(4) -- uint32
if not msg then
return nil, err
end
local _, msg_len = st_unpack(msg, "I")
--kong.log.debug("len: ", msg_len)
msg, err = c:receive(msg_len)
if not msg then
return nil, err
end
--kong.log.debug(("data: %q"):format(msg))
return msg, nil
end
local function write_frame(c, msg)
assert (c:send(st_pack("I", #msg)))
assert (c:send(msg))
end
function Rpc:call(method, data, do_bridge_loop)
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
msg_id = msg_id + 1
--kong.log.debug("will encode: ", pp{sequence = msg_id, [method] = data})
local msg, err = assert(pb.encode(".kong_plugin_protocol.RpcCall", { -- luacheck: ignore err
sequence = msg_id,
[method] = data,
}))
--kong.log.debug("encoded len: ", #msg)
assert (c:send(st_pack("I", #msg)))
assert (c:send(msg))
while do_bridge_loop do
local method_name
method_name, err = read_frame(c)
if not method_name then
return nil, err
end
if method_name == "" then
break
end
--kong.log.debug(("pdk method: %q (%d)"):format(method_name, #method_name))
local args
args, err = read_frame(c)
if not args then
return nil, err
end
local reply
reply, err = call_pdk(self.plugin.exposed_api, method_name, args)
if not reply then
return nil, err
end
err = write_frame(c, reply)
if err then
return nil, err
end
end
msg, err = read_frame(c)
if not msg then
return nil, err
end
c:setkeepalive()
msg = assert(pb.decode(".kong_plugin_protocol.RpcReturn", msg))
--kong.log.debug("decoded: "..pp(msg))
assert(msg.sequence == msg_id)
return msg
end
function Rpc:call_start_instance(plugin_name, conf)
local status, err = self:call("cmd_start_instance", {
name = plugin_name,
config = cjson_encode(conf)
})
if status == nil then
return nil, err
end
kong.log.debug("started plugin server: seq ", conf.__seq__, ", worker ", ngx.worker.id(), ", instance id ",
status.instance_status.instance_id)
return {
id = status.instance_status.instance_id,
conf = conf,
seq = conf.__seq__,
Config = status.instance_status.config,
rpc = self,
}
end
function Rpc:call_close_instance(instance_id)
return self:call("cmd_close_instance", {
instance_id = instance_id,
})
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)
local res
if not err then
res, err = self:call("cmd_handle_event", {
instance_id = instance_id,
event_name = phase,
}, true)
end
if not res or res == "" then
local err_lowered = err and err:lower() or "unknown error"
if str_find(err_lowered, "no plugin instance", nil, true)
or str_find(err_lowered, "closed", nil, true) then
self.plugin.instance_callbacks.reset_instance(plugin_name, conf)
kong.log.warn(err)
return self:handle_event(conf, phase)
else
kong.log.err("pluginserver error: ", err or "unknown error")
kong.response.error(500)
end
end
end
local function new(plugin)
if not rpc_service then
rpc_service = load_service(plugin.exposed_api)
end
local self = setmetatable({
msg_id = 0,
plugin = plugin,
}, Rpc)
return self
end
return {
new = new,
}