# rebar3_gpb_rpc_plugin **Repository Path**: gelomen/rebar3_gpb_rpc_plugin ## Basic Information - **Project Name**: rebar3_gpb_rpc_plugin - **Description**: No description available - **Primary Language**: Erlang - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-07-01 - **Last Updated**: 2024-07-01 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README Rebar3 gpb rpc plugin ===== 原来 [gpb](https://github.com/tomas-abrahamsson/gpb) 编译出来的文件并没有实现`service`,但是项目需要使用到这个功能做为协议的路由定义,因此有了这个插件 ## 应用场景 插件最最要的功能还是通过`gpb`分析协议,然后映射到自定义模板从而生成目标文件 ## 例子 [例子](/example) 比较简单,只有一个心跳协议 ## 协议解析 [msg.proto](example/proto/msg.proto): ```protobuf enum cmd{ msg_base = 0; } ``` 这个文件可以称为`路由模块`,这个文件只定义了一个名为`cmd`的`枚举(enum)`,其意思为`一级协议号(cmd)`,里面的每一个`Key`必须跟`协议的文件名`一致,其值表示`一级协议号(cmd)` 如上: `msg_base` => `msg_base.proto` [msg_base.proto](example/proto/msg_base.proto): ```protobuf syntax = "proto3"; import "google/protobuf/empty.proto"; enum c_cmd{ heartbeat_req = 0; heartbeat_resp = 1; } service msg_base_service{ rpc heartbeat(heartbeat_req) returns (heartbeat_resp); rpc heartbeat_1(heartbeat_req) returns (Empty); rpc heartbeat_2(Empty) returns (heartbeat_resp); } message heartbeat_req{} message heartbeat_resp{} ``` 从`路由模块`索引到这个这个协议文件,这个文件可以称为`二级路由模块`,里面定义了一个名为`c_cmd`的`枚举(enum)`,其意思为`二级协议号(c_cmd)`,里面的每一个`Key`必须跟`消息名(message)`一致,其值表示`二级协议号(c_cmd)` 另外还需要定义一个`service`,`service`名前缀必须与文件名一直并以`_service`结尾 ### proto3版本 里面定义的每一个`rpc`表示: * 同步协议 ```protobuf rpc heartbeat(heartbeat_req) returns (heartbeat_resp); ## 格式如: rpc func(req_message) returns (resp_message); ## 意思是: 当服务器收到req_message消息时,会调用func方法,然后发送返回resp_message消息给客户端 ``` * 异步协议 ```protobuf rpc heartbeat_1(heartbeat_req) returns (Empty); ## 格式如: rpc func(req_message) returns (Empty); ## 意思是: 当服务器收到req_message消息时,会调用func方法,没有消息返回给客户端 rpc heartbeat_2(Empty) returns (heartbeat_resp); ## 格式如: rpc func(Empty) returns (resp_message); ## 意思是: 服务器会主动发送resp_message消息给客户端 ``` ### proto2版本 里面定义的每一个`rpc`表示: > proto2版本没有`import "google/protobuf/empty.proto";` 因此需要我们自己定义一个 `undefined`的消息,然后引入到每个协议文件 * 同步协议 ```protobuf rpc heartbeat(heartbeat_req) returns (heartbeat_resp); ## 格式如: rpc func(req_message) returns (resp_message); ## 意思是: 当服务器收到req_message消息时,会调用func方法,然后发送返回resp_message消息给客户端 ``` * 异步协议 ```protobuf rpc heartbeat_1(heartbeat_req) returns (undefined); ## 格式如: rpc func(req_message) returns (undefined); ## 意思是: 当服务器收到req_message消息时,会调用func方法,没有消息返回给客户端 rpc heartbeat_2(undefined) returns (heartbeat_resp); ## 格式如: rpc func(undefined) returns (resp_message); ## 意思是: 服务器会主动发送resp_message消息给客户端 ``` ### 使用配置 [rebar.config](example/rebar.config) ```erlang %% for make *.erl file {erl_opts, [ warnings_as_errors, {i, "_build/default/plugins/gpb/include"} ]}. %% 增加插件 {plugins, [ {rebar3_gpb_rpc_plugin, "0.2.0"}, {rebar3_gpb_plugin, "1.11.6"} ]}. %% 增加钩子 %% for make *.proto file {provider_hooks, [ {pre, [ %% for gpb {compile, {protobuf, compile}}, {clean, {protobuf, clean}}, %% for gpb rpc {compile, {gpb_rpc, compile}}, {clean, {gpb_rpc, clean}} ]} ]}. %% gpb的配置 {gpb_opts, [ {i, "proto"}, strings_as_binaries, %% use_packages %% {nif, true}, {verify, always}, {o_erl, "src/proto"}, {o_hrl, "include/proto"}, {module_name_suffix, "_pb"} ]}. %% gpb_rpc的配置 {gpb_rpc_opts, [ {msg_prefix, "msg_"}, %% 设置协议前缀 {mod_prefix, "mod_"}, %% 设置生成的erl文件名前缀 {o_erl, "src/msg"}, %% 输出的erl文件目录 {o_hrl, "include/msg"}, %% 输出的hrl文件目录 {erl_tpl, "templates/gpb_rpc/gpb_rpc.erl.tpl"}, %% erl模板地址 {hrl_tpl, "templates/gpb_rpc/gpb_rpc.hrl.tpl"} %% hrl模板地址 ]}. ``` ## 编译结果 ```shell $ make co ``` 进入example目录执行上面命令,之后看一下效果 先看[erl模板文件](example/templates/gpb_rpc/gpb_rpc.erl.tpl): ```erlang -module({{proto_name}}). -compile(inline). -include("msg.hrl"). -ifdef(CMD_{{proto_name_upper}}). -include("{{proto_name}}.hrl"). -include("{{gpb_proto}}.hrl"). -define(THIS_CMD, ?CMD_{{proto_name_upper}}). -export([ handle_msg/3, decode_input_msg/1, decode_output_msg/1, decode_input/2, decode_output/2, encode_msg/2, send_msg/3, handle_msg/4 ]). {{#callback_list}} -callback {{handle_func}}(ex_msg:msg(), ex_msg:state()) -> ex_msg:func_reply(). {{/callback_list}} {{#rpc_list}} handle_msg(?C_CMD_{{input_upper}}, Binary, State) -> Msg = {{gpb_proto}}:decode_msg(Binary, {{input}}), Maps = {{handle_mod}}:{{handle_func}}(Msg, State), case maps:find(msg, Maps) of {ok, RespMsg} -> PbBinary = {{gpb_proto}}:encode_msg(RespMsg), RespBinary = <>, case maps:find(resp_binary, Maps) of {ok, RespBinaryList} when is_list(RespBinaryList) -> Maps#{resp_binary => [RespBinary | RespBinaryList]}; {ok, OldRespBinary} -> Maps#{resp_binary => [RespBinary, OldRespBinary]}; error -> Maps#{resp_binary => RespBinary} end; error -> Maps end; {{/rpc_list}} {{#input_list}} handle_msg(?C_CMD_{{input_upper}}, Binary, State) -> Msg = {{gpb_proto}}:decode_msg(Binary, {{input}}), {{handle_mod}}:{{handle_func}}(Msg, State); {{/input_list}} handle_msg(CCmd, _Binary, _State) -> #{error => {not_defined_c_cmd, CCmd}}. decode_input_msg(<>) -> decode_input(CCmd, MsgBinary). decode_output_msg(<>) -> decode_output(CCmd, MsgBinary). {{#rpc_list}} handle_msg(?C_CMD_{{input_upper}}, MsgType, Binary, State) -> Msg = {{gpb_proto}}:decode_msg(Binary, {{input}}), {{handle_mod}}:{{handle_func}}({MsgType, Msg}, State); {{/rpc_list}} {{#input_list}} handle_msg(?C_CMD_{{input_upper}}, MsgType, Binary, State) -> Msg = {{gpb_proto}}:decode_msg(Binary, {{input}}), {{handle_mod}}:{{handle_func}}({MsgType, Msg}, State); {{/input_list}} handle_msg(CCmd, _Msg, _Binary, _State) -> {error, {not_defined_c_cmd, CCmd}}. {{#rpc_list}} decode_input(?C_CMD_{{input_upper}}, MsgBinary) -> {{{handle_mod}}, {{handle_func}}, {{gpb_proto}}:decode_msg(MsgBinary, {{input}})}; {{/rpc_list}} {{#input_list}} decode_input(?C_CMD_{{input_upper}}, MsgBinary) -> {{{handle_mod}}, {{handle_func}}, {{gpb_proto}}:decode_msg(MsgBinary, {{input}})}; {{/input_list}} decode_input(CCmd, _MsgBinary) -> {error, {not_defined_c_cmd, CCmd}}. {{#output_list}} decode_output(?C_CMD_{{output_upper}}, MsgBinary) -> {{gpb_proto}}:decode_msg(MsgBinary, {{output}}); {{/output_list}} decode_output(CCmd, _MsgBinary) -> {error, {not_defined_c_cmd, CCmd}}. encode_msg(CCmd, RespMsg) -> RespBinary = {{gpb_proto}}:encode_msg(RespMsg), <>. send_msg(undefined, _CCmd, _Msg) -> undefined; send_msg(Pid, CCmd, Msg) -> Binary = encode_msg(CCmd, Msg), Pid ! {send, Binary}, ok. -endif. ``` 再来看看生成的文件`src/msg/msg_base.erl`: ```erlang %% -*- coding: utf-8 -*- %% Automatically generated, do not edit %% Generated by gpb_rpc_compile -module(msg_base). -compile(inline). -include("msg.hrl"). -ifdef(CMD_MSG_BASE). -include("msg_base.hrl"). -include("msg_base_pb.hrl"). -define(THIS_CMD, ?CMD_MSG_BASE). -export([ handle_msg/3, decode_input_msg/1, decode_output_msg/1, decode_input/2, decode_output/2, encode_msg/2, send_msg/3, handle_msg/4 ]). -callback heartbeat(ex_msg:msg(), ex_msg:state()) -> ex_msg:func_reply(). -callback heartbeat_1(ex_msg:msg(), ex_msg:state()) -> ex_msg:func_reply(). handle_msg(?C_CMD_HEARTBEAT_REQ, Binary, State) -> Msg = msg_base_pb:decode_msg(Binary, heartbeat_req), Maps = mod_base:heartbeat(Msg, State), case maps:find(msg, Maps) of {ok, RespMsg} -> PbBinary = msg_base_pb:encode_msg(RespMsg), RespBinary = <>, case maps:find(resp_binary, Maps) of {ok, RespBinaryList} when is_list(RespBinaryList) -> Maps#{resp_binary => [RespBinary | RespBinaryList]}; {ok, OldRespBinary} -> Maps#{resp_binary => [RespBinary, OldRespBinary]}; error -> Maps#{resp_binary => RespBinary} end; error -> Maps end; handle_msg(?C_CMD_HEARTBEAT_REQ, Binary, State) -> Msg = msg_base_pb:decode_msg(Binary, heartbeat_req), mod_base:heartbeat_1(Msg, State); handle_msg(CCmd, _Binary, _State) -> #{error => {not_defined_c_cmd, CCmd}}. decode_input_msg(<>) -> decode_input(CCmd, MsgBinary). decode_output_msg(<>) -> decode_output(CCmd, MsgBinary). handle_msg(?C_CMD_HEARTBEAT_REQ, MsgType, Binary, State) -> Msg = msg_base_pb:decode_msg(Binary, heartbeat_req), mod_base:heartbeat({MsgType, Msg}, State); handle_msg(?C_CMD_HEARTBEAT_REQ, MsgType, Binary, State) -> Msg = msg_base_pb:decode_msg(Binary, heartbeat_req), mod_base:heartbeat_1({MsgType, Msg}, State); handle_msg(CCmd, _Msg, _Binary, _State) -> {error, {not_defined_c_cmd, CCmd}}. decode_input(?C_CMD_HEARTBEAT_REQ, MsgBinary) -> {mod_base, heartbeat, msg_base_pb:decode_msg(MsgBinary, heartbeat_req)}; decode_input(?C_CMD_HEARTBEAT_REQ, MsgBinary) -> {mod_base, heartbeat_1, msg_base_pb:decode_msg(MsgBinary, heartbeat_req)}; decode_input(CCmd, _MsgBinary) -> {error, {not_defined_c_cmd, CCmd}}. decode_output(?C_CMD_HEARTBEAT_RESP, MsgBinary) -> msg_base_pb:decode_msg(MsgBinary, heartbeat_resp); decode_output(CCmd, _MsgBinary) -> {error, {not_defined_c_cmd, CCmd}}. encode_msg(CCmd, RespMsg) -> RespBinary = msg_base_pb:encode_msg(RespMsg), <>. send_msg(undefined, _CCmd, _Msg) -> undefined; send_msg(Pid, CCmd, Msg) -> Binary = encode_msg(CCmd, Msg), Pid ! {send, Binary}, ok. ``` ----- 再看看[hrl模板文件](example/templates/gpb_rpc/gpb_rpc.hrl.tpl): ```erl -ifndef({{proto_name_upper}}_H). -define({{proto_name_upper}}_H, true). {{#enums_list}} %% {{enum_name}} {{#enum_list}} -define({{enum_name_upper}}_{{enum_key_upper}}, {{enum_value}}). %% {{enum_key}} -define({{enum_name_upper}}_{{enum_key_upper}}_KEY, '{{enum_key}}'). %% {{enum_value}} {{/enum_list}} {{/enums_list}} -endif. ``` 看看生成的文件: `msg.hrl`: ```erlang %% -*- coding: utf-8 -*- %% Automatically generated, do not edit %% Generated by gpb_rpc_compile -ifndef(MSG_H). -define(MSG_H, true). %% cmd -define(CMD_MSG_BASE, 0). %% msg_base -define(CMD_MSG_BASE_KEY, 'msg_base'). %% 0 ``` `msg_base.hrl`: ```erlang %% -*- coding: utf-8 -*- %% Automatically generated, do not edit %% Generated by gpb_rpc_compile -ifndef(MSG_BASE_H). -define(MSG_BASE_H, true). %% c_cmd -define(C_CMD_HEARTBEAT_REQ, 0). %% heartbeat_req -define(C_CMD_HEARTBEAT_REQ_KEY, 'heartbeat_req'). %% 0 -define(C_CMD_HEARTBEAT_RESP, 1). %% heartbeat_resp -define(C_CMD_HEARTBEAT_RESP_KEY, 'heartbeat_resp'). %% 1 ``` ## 使用 `example.erl`: ```erlang -module(example). %% API exports -export([ handle_msg/2, mk_resp/0 ]). -include("msg_base.hrl"). -include("msg_base_pb.hrl"). %%==================================================================== %% API functions %%==================================================================== handle_msg(<>, State) -> MsgMod = msg_pb:enum_symbol_by_value_cmd(Cmd), MsgMod:handle_msg(CCmd, Binary, State). mk_resp() -> msg_base:encode_msg(?C_CMD_HEARTBEAT_RESP, #heartbeat_resp{}). ``` 当外部需要处理消息是,只需要调用`example:handle_msg(Binary, State)`之后会根据之前定义好的协议头和方法去分发消息处理,以上面心跳的例子来说,当收到`<<0:7, 0:9, Binary/binary>>`的协议时,`Cmd=0`, `CCmd=0`,转换成枚举就是`Cmd=msg_base`,`CCmd=heartbeat_req`,然后会分发到处理函数`mod_base:heartbeat(#heartbeat_req{}, State)` `mod_base.erl`: ```erlang -module(mod_base). -behavior(msg_base). %% API -export([ heartbeat/2, heartbeat_1/2 ]). -include("msg_base.hrl"). -include("msg_base_pb.hrl"). heartbeat(#heartbeat_req{}, _State) -> #{msg => #heartbeat_resp{}}. heartbeat_1(#heartbeat_req{}, _State) -> #{}. ```