
Apache Thrift Erlang 库实战指南客户端/服务端开发、异常追踪与命名兼容【免费下载链接】thriftApache Thrift项目地址: https://gitcode.com/GitHub_Trending/thr/thriftApache Thrift 为 Erlang/OTP 提供了完整的高质量绑定本文以 lib/erl/README.md 为骨架结合仓库内 Erlang 源码、EUnit 测试用例与编译器生成器实现系统讲解 Thrift Erlang 库的客户端调用方式、服务端部署、异常处理行为0.25.0 起的exceptions_include_traces变更以及 0.9.2 起的legacynames命名兼容选项。读完本文你将掌握thrift_client/thrift_client_util的完整调用链、thrift_socket_server的服务端配置、错误返回与异常语义并能根据应用场景正确取舍异常堆栈信息与新旧命名约定。Erlang 库模块全景Thrift 的 Erlang 实现位于仓库 lib/erl 目录下采用标准的 OTP 组织方式src/26 个 Erlang 源文件包含客户端thrift_client.erl、thrift_client_util.erl、thrift_reconnecting_client.erl、服务端thrift_socket_server.erl、thrift_processor.erl、协议层binary/compact/json、传输层socket、framed、buffered、http、file、disk_log、memory buffer、SSL以及多路复用支持thrift_multiplexed_protocol.erl。include/thrift_constants.hrl与thrift_protocol.hrl两个公共头文件。test/覆盖传输、协议、多路复用、命名兼容、异常追踪等场景的 EUnit 测试。thrift.app.srcOTP 应用描述文件版本为0.25.0定义了exceptions_include_traces应用环境变量。rebar.config与rebar.config.scriptrebar3 构建、xref、Dialyzer 与测试配置。客户端开发一个会话看懂thrift_clientREADME 给出了使用thrift_client的完整交互式会话示例这是理解 Erlang 客户端 API 的最佳入口1 {ok, C0} thrift_client_util:new(localhost, 9090, thrift_test_thrift, []), ok. ok 2 {C1, R1} thrift_client:call(C0, testVoid, []), R1. {ok,ok} 3 {C2, R2} thrift_client:call(C1, testVoid, [asdf]), R2. {error,{bad_args,testVoid,[asdf]}} 4 {C3, R3} thrift_client:call(C2, testI32, [123]), R3. {ok,123} 5 {C4, R4} thrift_client:call(C3, testOneway, [1]), R4. {ok,ok} 6 {C5, R5} thrift_client:call(C4, testXception, [foo]), R5. {error,{no_function,testXception}} 7 {C6, R6} thrift_client:call(C5, testException, [foo]), R6. {ok,ok} 8 {C7, R7} (catch thrift_client:call(C6, testException, [Xception])), R7. {exception,{xception,1001,Xception}}这个示例揭示了 Erlang 客户端 API 的三个核心设计1. 客户端是不变的状态值。thrift_client_util:new/4建立连接并返回{ok, Client}每一次thrift_client:call/3都会返回{ClientN, Result}二元组——ClientN是携带了最新协议状态含序列号的新客户端必须用它发起下一次调用。这符合 Erlang 函数式风格也避免了并发共享可变状态的问题。从源码看客户端状态是一个#tclient{service, protocol, seqid}记录thrift_client.erlseqid从 0 开始递增。2. 成功、失败与异常用不同通道表达。从 thrift_client.erl 的类型规约可见正常返回{ok, Value}其中void函数返回{ok, ok}调用参数错误{error, {bad_args, Function, Args}}——示例第 3 行向无参的testVoid传入[asdf]客户端在写消息前就通过length(PList) / length(Args)校验参数个数并拒绝发送thrift_client.erl函数不存在{error, {no_function, Function}}——示例第 6 行调用未定义函数testXception通过Service:function_info/2的function_clause捕获判定服务端声明的业务异常以 throw 形式抛出示例第 8 行用catch捕获到{exception, {xception, 1001, Xception}}。客户端在读取回复时若命中函数声明的 exceptions 字段会执行throw({NewClient, {exception, Exception}})thrift_client.erl因此调用方需要用try ... catch throw:{C, {exception, Ex}}或catch处理业务异常这与{ok, _}/{error, _}的普通返回路径不同。3. 底层协议自动处理。call/3内部依次完成查询function_info判断消息类型oneway_void走ONEWAY其余走CALL见 thrift_client.erl、写message_begin/参数/message_end、刷新传输层然后读取回复若回复的seqid与请求不一致会返回{error, {bad_seq_id, SeqId}}thrift_client.erl。建立连接thrift_client_util:new/4thrift_client_util:new(Host, Port, Service, Options)是常见的 socket 客户端构造器thrift_client_util.erl。它会将选项拆分为协议选项与传输选项split_options/1据此选择传输模块与协议模块最终调用thrift_client:new/2完成装配。支持的选项如下选项归属取值与默认值说明protocol协议binary默认/compact/json选择线上协议分别对应thrift_binary_protocol、thrift_compact_protocol、thrift_json_protocolstrict_read/strict_write协议布尔值传给协议层的严格读写开关framed传输布尔值默认false是否使用thrift_framed_transport帧传输connect_timeout/recv_timeout传输整数毫秒连接与接收超时sockopts传输列表透传给 socket 的选项ssltransport传输布尔值默认false为true时改用thrift_sslsocket_transportssloptions传输列表SSL/TLS 选项例如连接一个使用 compact 协议、framed 传输的服务端{ok, Client} thrift_client_util:new( localhost, 9090, my_service_thrift, [{protocol, compact}, {framed, true}] ).只发不收与优雅关闭thrift_client还导出了send_call/3与close/1thrift_client.erl。send_call/3只发送函数调用而不读取结果源码注释明确说明其典型用途向只写传输如thrift_disk_log_transport记录非 oneway 调用的日志thrift_client.erl。close/1则直接关闭底层传输。多路复用客户端thrift_client_util:new_multiplexed/3,4允许一个 TCP 连接承载多个 Thrift 服务thrift_client_util.erl传入[{ServiceName, ServiceModule}]服务映射返回每个服务名对应的客户端{ok, [{Calculator, CalcClient}, {Weather, WeatherClient}]} thrift_client_util:new_multiplexed( 127.0.0.1, 9090, [{Calculator, calculator_thrift}, {Weather, weather_thrift}], [] ).多路复用的服务名与函数名之间由thrift_constants.hrl中的MULTIPLEXED_SERVICE_SEPARATOR分隔服务端处理器按此解析并分发见下文。服务端开发thrift_socket_server与请求分发服务端以thrift_socket_server:start/1启动其选项解析逻辑集中在 thrift_socket_server.erl。常用选项选项取值与默认值说明nameatom 或字符串注册名{local, Name}用于stop/1port0–65535 整数监听端口ipany、元组或 IP 字符串绑定地址默认绑定所有接口serviceatom 或[{ServiceName, Module}]服务模块由 Thrift 编译器生成多路复用时必须是列表handleratom 或[{error_handler, M}, {ServiceName, M}, ...]业务回调模块多路复用时必须以error_handler开头max正整数默认2048最大并发 acceptor/连接数超过后不再接受新连接并记录错误日志protocolbinary默认/compact/json/{compact, Opts}/{json, Opts}/{binary, Opts}/{custom, Module, Opts}服务端协议framed布尔值默认false为true时用thrift_framed_transport否则用thrift_buffered_transportssltransport布尔值默认false是否启用 SSLssloptions列表默认[]SSL 服务端选项证书等socket_opts非空列表默认[{recv_timeout, 500}]透传的 socket 选项服务端底层在init/1中调用gen_tcp:listen默认打开binary、{reuseaddr, true}、{packet, 0}、{backlog, 4096}、{recbuf, 8192}、{active, false}thrift_socket_server.erl。典型的非多路复用启动方式{ok, Pid} thrift_socket_server:start([ {ip, 127.0.0.1}, {port, 9090}, {name, my_server}, {service, my_service_thrift}, {handler, my_service_handler}, {protocol, binary}, {framed, true} ]), % ... 需要停止时 thrift_socket_server:stop(my_server).handler 回调约定业务 handler 需要实现两个回调可对照 test/multiplexing_test.erl 的示例实现handle_function(Function, Params)按Functionatom 分发成功返回{reply, Value}对于void/oneway函数返回ok。如果函数声明了业务异常handler 可用throw({ExceptionRecord, ...})形式抛出处理器会将其序列化为声明的异常字段回给客户端见 thrift_processor.erl。handle_error(Function, Reason)处理调用出错、超时、连接关闭等情况thrift_processor.erl。服务端请求处理流程thrift_socket_server为每个连接 spawn 一个 acceptor 进程进入thrift_processor:init/1的消息循环thrift_processor.erl流程为读取message_begin→ 判断消息类型CALL/ONEWAY→ 若是多路复用则按服务名从 map 中取出服务模块与 handler → 解析函数名 → 读取参数 → 调用Handler:handle_function/2→ 回写REPLY消息。其中值得注意的实现细节未知方法防护服务端用list_to_existing_atom加function_info双重校验函数名避免恶意字符串导致进程崩溃并回写类型为UNKNOWN_METHOD的TApplicationException消息为Invalid method name: ...见 thrift_processor.erl同时调用handle_error/2上报。测试用例 test/unknown_method_test.erl 专门验证该行为。oneway 函数的异常被忽略handler 在 oneway 函数中抛错只会记 warning不回任何响应thrift_processor.erl。未声明异常handler 抛出的异常若不在该函数的 exceptions 列表中会被包装成exception_not_declared_as_thrown错误并走handle_error/5thrift_processor.erl。异常追踪开关0.25.0 的exceptions_include_traces变更README 的 Release Notes 记录了 0.25.0 的一项默认行为变更exceptions_include_traces应用变量默认值从开启改为false。这意味着当服务端 handler 崩溃时回传给调用方的TApplicationException不再携带崩溃项crash term和 Erlang 堆栈信息崩溃本身仍会通过error_logger在服务端本地完整记录。该默认值定义在 thrift.app.src注释说明了设计动机堆栈会暴露构建机上的内部模块名与源码绝对路径handle_unknown_exception路径上被抛出项还会随消息一起发送给对端因此默认关闭、按需开启是更安全的选择。底层实现在thrift_processor的handle_error/5thrift_processor.erl当application:get_env(thrift, exceptions_include_traces)为{ok, true}时TApplicationException的 message 为格式化后的{Error, Stack}否则为固定字符串An unknown handler error occurred.。如需恢复旧行为例如在可信内网或调试场景README 给出了两种方式% 运行时动态设置 application:set_env(thrift, exceptions_include_traces, true).或在sys.config中静态配置{thrift, [{exceptions_include_traces, true}]}测试 test/exception_traces_test.erl 通过真实 socket 调用固定了这一行为error_in_handler_does_not_reach_the_peer_test验证默认情况下对端既收不到 handler 内部植入的金丝雀词也收不到thrift_processor堆栈帧undeclared_exception_does_not_reach_the_peer_test验证未声明异常路径同样不泄露应用数据enabling_traces_still_sends_them_test则确认开启开关后堆栈确实随异常返回——即这是一次默认值调整而非功能移除。命名兼容0.9.2 的legacynames选项README 的 Release Notes 同时记录了 0.9.2 的命名约定变更struct 与函数的命名约定自该版本起发生改变。为保留旧命名向后兼容需要在生成代码时向编译器传入legacynames选项。在 Erlang 代码生成器 t_erl_generator.cc 中legacynames被解析为legacy_names_ true其帮助文本同文件 t_erl_generator.cc说明legacynames: Output files retain naming conventions of Thrift 0.9.1 and earlier。即开启后生成代码保留 Thrift 0.9.1 及更早版本的命名方式如保留 IDL 中的大写首字母 struct 名、不做snake_case化等。使用时通过 thrift 编译器的--gen erl:legacynames指定thrift --gen erl:legacynames your_service.thrift仓库中的测试对这块提供了佐证test/flags/LegacyNames.thrift 定义了CapitalizedStruct、ListCapitalizedStructs、Xception等大写首字母类型test/legacy_names_test.erl 断言开启legacynames后生成的 record 名为capitalizedStruct且legacyNames_types:struct_info_ext/1、legacyNames_thrift:function_info/2返回的扩展结构均保留该命名。除了legacynamesErlang 生成器还支持其他选项t_erl_generator.ccmaps使用 Erlang map 表示、delimiter自定义命名分隔符、app_prefix、stringstring|binary|both字符串映射方式、setv1|v2集合表示版本、typetype|nominal类型声明方式。同一目录下还有flags/Thrift3214.thrift与 test/test_thrift_3214.erl 等用例覆盖具体命名场景。构建、测试与质量保障Erlang 库使用 rebar3 构建rebar.config 中开启了debug_info及一系列编译警告启用了 XRef 检查deprecated_functions_calls、deprecated_functions和 Dialyzer 静态分析含unmatched_returns、error_handling、unknown警告PLT 额外引入ssl、inets、public_key应用并使用erlfmt宽度 100统一格式化。测试 profile 依赖meckEUnit 用例目录为test/与test/gen-erl。构建与运行测试的典型流程cd lib/erl rebar3 compile # 编译 rebar3 eunit # 运行 EUnit 测试 rebar3 dialyzer # 静态分析可选需要注意的是rebar.config.script 会根据 Erlang 运行时是否支持monotonic_time/0自动注入time_correction宏保证时间相关代码在新旧 OTP 版本上的行为一致。小结Apache Thrift 的 Erlang 库在 API 设计上完全遵循 Erlang 惯例客户端状态不可变、每次调用返回新客户端、业务异常以 throw 传播服务端由thrift_socket_server 生成的 service 模块 用户 handler 三部分协作。使用时要特别留意两个版本行为差异——0.25.0 起异常堆栈默认不上线按需用exceptions_include_traces开启0.9.2 起命名约定变更旧代码用legacynames生成。理解这些约定与底层 thrift_client.erl、thrift_processor.erl 的实现能帮助你在生产环境中快速定位调用链问题并为跨语言服务互通提供可靠的 Erlang 端实现。【免费下载链接】thriftApache Thrift项目地址: https://gitcode.com/GitHub_Trending/thr/thrift创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考