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

180 lines
4.1 KiB
Lua

-- Copyright (c) Kong Inc. 2020
local cjson = require "cjson"
local buffer = require "string.buffer"
local pb = require "pb"
local grpc_tools = require "kong.tools.grpc"
local grpc_frame = grpc_tools.frame
local grpc_unframe = grpc_tools.unframe
local setmetatable = setmetatable
local ngx = ngx
local decode_base64 = ngx.decode_base64
local encode_base64 = ngx.encode_base64
local encode_json = cjson.encode
local decode_json = cjson.decode
local deco = {}
deco.__index = deco
local text_encoding_from_mime = {
["application/grpc-web"] = "plain",
["application/grpc-web-text"] = "base64",
["application/grpc-web+proto"] = "plain",
["application/grpc-web-text+proto"] = "base64",
["application/grpc-web+json"] = "plain",
["application/grpc-web-text+json"] = "base64",
["application/json"] = "plain",
}
local framing_form_mime = {
["application/grpc-web"] = "grpc",
["application/grpc-web-text"] = "grpc",
["application/grpc-web+proto"] = "grpc",
["application/grpc-web-text+proto"] = "grpc",
["application/grpc-web+json"] = "grpc",
["application/grpc-web-text+json"] = "grpc",
["application/json"] = "none",
}
local msg_encodign_from_mime = {
["application/grpc-web"] = "proto",
["application/grpc-web-text"] = "proto",
["application/grpc-web+proto"] = "proto",
["application/grpc-web-text+proto"] = "proto",
["application/grpc-web+json"] = "json",
["application/grpc-web-text+json"] = "json",
["application/json"] = "json",
}
-- parse, compile and load .proto file
-- returns a table mapping valid request URLs to input/output types
local _proto_info = {}
local function get_proto_info(fname)
local info = _proto_info[fname]
if info then
return info
end
info = {}
local grpc_tools_instance = grpc_tools.new()
grpc_tools_instance:each_method(fname, function(parsed, srvc, mthd)
info[("/%s.%s/%s"):format(parsed.package, srvc.name, mthd.name)] = {
mthd.input_type,
mthd.output_type,
}
end, true)
_proto_info[fname] = info
return info
end
-- return input and output names of the method specified by the url path
-- TODO: memoize
local function rpc_types(path, protofile)
if not protofile then
return nil
end
local info = get_proto_info(protofile)
local types = info[path]
if not types then
return nil, ("Unknown path %q"):format(path)
end
return types[1], types[2]
end
function deco.new(mimetype, path, protofile)
local text_encoding = text_encoding_from_mime[mimetype]
local framing = framing_form_mime[mimetype]
local msg_encoding = msg_encodign_from_mime[mimetype]
local input_type, output_type
if msg_encoding ~= "proto" then
if not protofile then
return nil, "transcoding requests require a .proto file defining the service"
end
input_type, output_type = rpc_types(path, protofile)
if not input_type then
return nil, output_type
end
end
return setmetatable({
mimetype = mimetype,
text_encoding = text_encoding,
framing = framing,
msg_encoding = msg_encoding,
input_type = input_type,
output_type = output_type,
}, deco)
end
function deco:upstream(body)
if self.text_encoding == "base64" then
body = decode_base64(body)
end
if self.msg_encoding == "json" then
local msg = body
if self.framing == "grpc" then
msg = grpc_unframe(body)
end
body = grpc_frame(0x0, pb.encode(self.input_type, decode_json(msg)))
end
return body
end
function deco:downstream(chunk)
if self.msg_encoding ~= "proto" then
local body = (self.downstream_body or "") .. chunk
local out = buffer.new()
local msg, body = grpc_unframe(body)
while msg do
msg = encode_json(pb.decode(self.output_type, msg))
if self.framing == "grpc" then
msg = grpc_frame(0x0, msg)
end
out:put(msg)
msg, body = grpc_unframe(body)
end
self.downstream_body = body
chunk = out:get()
end
if self.text_encoding == "base64" then
chunk = encode_base64(chunk)
end
return chunk
end
function deco:frame(ftype, msg)
local f = grpc_frame(ftype, msg)
if self.text_encoding == "base64" then
f = encode_base64(f)
end
return f
end
return deco