180 lines
4.1 KiB
Lua
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
|