ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

EMQX 消息转换表达式中的非 ASCII Unicode 崩溃修复:`iolist_to_binary` 与 `unicode:characters_to_binary` 的正确取舍

EMQX 消息转换表达式中的非 ASCII Unicode 崩溃修复:`iolist_to_binary` 与 `unicode:characters_to_binary` 的正确取舍 后端物联网消息队列通信【免费下载链接】emqxThe most scalable and reliable MQTT broker for AI, IoT, IIoT and connected vehicles项目地址https://gitcode.com/gh_mirrors/em/emqx点击查看免费下载导读本文聚焦 EMQX 开源仓库中一项针对消息转换Message Transformation功能的缺陷修复当用户在转换表达式中使用中文、日文、emoji 等非 ASCII Unicode 字符串时模块的错误处理路径可能触发进程崩溃。文章将结合变更记录 fix-16847.en.md、核心实现与回归测试从 Erlang 字节列表与 Unicode 码点列表的本质差异出发剖析崩溃根因、修复方案及其验证方式帮助开发者在编写转换表达式或扩展类似功能时规避同类问题。一、变更记录与问题概述仓库 changes/ee/fix-16847.en.md 用一句话记录了本次修复的核心内容Fix a crash when non-ASCII unicode string is used in message transformation expression.即在消息转换Message Transformation表达式中使用了非 ASCII 的 Unicode 字符串时会导致崩溃crash——而不是简单地返回转换失败。这意味着一个本该优雅降级按failure_action丢弃消息或断开客户端的场景演变成了运行时异常这是需要被修复的。二、功能背景消息转换与 variform 表达式在展开根因前先明确这个崩溃发生在哪个功能链路中。EMQX 的emqx_message_transformation应用封装了发布消息转换能力对入站或内部触发的发布消息负载payload按用户配置的转换规则进行改写失败时可以丢弃消息或断开违规客户端应用 README。从 emqx_message_transformation.erl 的源码结构可以还原核心链路应用通过emqx_hooks:put(message.publish, ...)注册发布钩子将on_message_publish/1挂到message.publish钩子上register_hooks/0消息发布时按 topic 在注册表中匹配命中的转换规则on_message_publish/1每条转换规则由若干operation组成每个 operation 包含key如payload.msg与value一段variform 表达式依次对消息上下文求值run_transformation/2。其中表达式使用的是一门名为variform的模板语言由 emqx_variform.erl 实现支持concat([你好世界])这类字符串拼接、模板插值等能力。问题恰恰出在表达式含非 ASCII 字符串时。三、根因分析Unicode 码点列表不是 iolist3.1 编译期表达式被存为 Unicode 码点列表variform 的compile/1把用户提交的二进制表达式转换为内部编译结构。关键在 compile/1compile(Expression) when is_binary(Expression) - compile(unicode:characters_to_list(Expression));unicode:characters_to_list/1会把 UTF-8 二进制按字符拆解成Unicode 码点codepoint列表。例如你好世界/utf8被编译后expr字段里存放的是[20320, 22909, 19990, 30028]这样一个整数列表——这些整数的值都远大于 255。而decompile/1只是原样返回该列表decompile/1不做任何转换。3.2 错误处理期prettify_operation 触雷崩溃发生在转换执行失败后的美化输出环节。在 eval_operation/2 中表达式求值被try...catch包裹一旦抛出异常会把operation prettify_operation(Operation)写入失败上下文用于日志与跟踪catch Class:Error:Stacktrace - FailureContext1 #trace_failure_context{ ... context #{ ... operation prettify_operation(Operation) } },而修复前的prettify_operation/1当前版本见 prettify_operation/1对value字段的处理方式是maps:update_with( value, fun(V) - unicode:characters_to_binary(emqx_variform:decompile(V)) end, Operation0 ),关键点在于这里必须使用unicode:characters_to_binary/1。原因如下emqx_variform:decompile(V)返回的是码点列表可能含大于 255 的整数iolist_to_binary/1只接受0255 范围内的字节组成的深层列表iolist遇到大于 255 的码点会直接抛出badarg异常如果错误处理路径中再次抛出badarg且该异常未被捕获就会演变成进程崩溃——这正是本次修复要消灭的二次崩溃一次表达式求值失败本应按规则优雅处理结果却在记录失败原因时又崩了一次。修复前若写成iolist_to_binary(emqx_variform:decompile(V))那么当表达式含你好世界、emoji、日文假名等任意码点大于 255 的字符时prettify_operation/1必然崩溃。修复后改用unicode:characters_to_binary/1它会将码点列表正确编码回 UTF-8 二进制从而稳定输出如concat([你好世界])/utf8这样的可读文本。补充说明该函数不仅服务于错误上下文还负责把 operation 的key码点路径列表通过lists:join(., Path)与iolist_to_binary/1拼接成payload.msg形式的字符串。key路径本身只由 ASCII 的字段名组成因此key一侧使用iolist_to_binary是安全的风险仅在value表达式本体一侧。四、回归测试用中文表达式验证修复仓库为该修复配套了专门的回归测试位于 emqx_message_transformation_tests.erl%% Variform expressions containing non-ASCII unicode characters should not %% crash prettify_operation/1. emqx_variform:compile/1 stores the expression %% as a unicode codepoint list, and emqx_variform:decompile/1 returns it as-is. %% iolist_to_binary/1 cannot handle codepoints 255, so prettify_operation %% must use unicode:characters_to_binary/1 instead. prettify_unicode_operation_test() - Expr concat([你好世界])/utf8, {ok, Compiled} emqx_variform:compile(Expr), Operation #{key [payload, msg], value Compiled}, Result emqx_message_transformation:prettify_operation(Operation), ?assertMatch(#{key : payload.msg, value : concat([你好世界])/utf8}, Result).该测试用例自身就注释了完整的故障模型可以拆解为三层验证构造复现场景用 UTF-8 中文表达式concat([你好世界])走emqx_variform:compile/1得到内部编译结构——其中表达式已被存储为 Unicode 码点列表断言输出稳定调用prettify_operation/1后value必须被还原为concat([你好世界])/utf8且key被拼接为payload.msg隐含验证不崩溃若实现误用iolist_to_binary/1该用例会在prettify_operation/1内部抛出badarg测试直接失败从而把回归牢牢锁死。测试还覆盖了转换名称等字段的 Unicode 校验场景如nãme/utf8、test_哈哈/utf8属于非法名称见同文件invalid_names_test_/0说明该模块对 Unicode 输入的处理是系统性关注点而本次修复补齐了表达式求值失败路径上的最后一环。五、修复影响与同类隐患排查建议5.1 对用户行为的影响修复后使用中文等非 ASCII 字符编写转换表达式成为被显式支持的行为表达式含中文、日文、emoji 时concat([温度传感器])、${payload.name}等写法可正常求值即便求值失败例如字段不存在、类型不匹配失败上下文中的operation字段也能以可读的 UTF-8 文本形式展示供日志与 trace 分析使用失败后的处理仍然遵循规则的failure_actionignore/drop/disconnect见 run_transformation/2 与 run_transformations/2不会再出现失败处理自身崩溃的连锁反应。5.2 给 Erlang 开发者的排查建议本次修复背后是一条通用 Erlang 经验iolist_to_binary/1与unicode:characters_to_binary/1的输入域并不相同。凡是由unicode:characters_to_list/1、unicode:characters_to_binary/1或字符串字面量产生的字符列表其元素可能是任意 Unicode 码点含大于 255 的值一律不能直接交给iolist_to_binary/1反之由binary_to_list/1产生的字节列表元素恒为 0255才可以。排查同类型崩溃时可以优先搜索代码中先decompile/characters_to_list得到列表、再iolist_to_binary收尾的模式并确认是否需要对 Unicode 输入做转码。六、小结本次修复通过将prettify_operation/1中的列表转二进制操作从iolist_to_binary/1替换为unicode:characters_to_binary/1解决了消息转换表达式含非 ASCII Unicode 字符串时的崩溃问题并配套中文表达式的回归测试防止复发。变更虽小但它同时涉及 variform 编译期的数据表示码点列表、求值失败期的错误上下文构建以及 Erlang Unicode 处理的基础语义是理解 EMQX 消息转换模块内部实现与 Erlang 字符处理边界的一个极佳切入点。相关代码均可继续在 emqx_message_transformation.erl、emqx_variform.erl 与 emqx_message_transformation_tests.erl 中查阅。赞分享后端物联网消息队列通信【免费下载链接】emqxThe most scalable and reliable MQTT broker for AI, IoT, IIoT and connected vehicles项目地址https://gitcode.com/gh_mirrors/em/emqx点击查看免费下载相关推荐EMQX Logger 格式化器崩溃修复解析深度嵌套非 ASCII Term 引发的 FORMATTER CRASHEMQX Logger 格式化器崩溃修复解析深度嵌套非 ASCII Term 引发的 FORMATTER CRASH 导读 EMQX 在 debug 级别日志后端物联网消息队列通信linuxdeployqt与CMake集成非qmake项目的完整部署方案linuxdeployqt与CMake集成非qmake项目的完整部署方案 linuxdeployqt是一款强大的Linux应用部署工具能够将应用程序及其依赖开发工具CLI构建工具JavaScript 正则表达式中的 Unicode 处理修饰符 u 和 \p{...} 类JavaScript 正则表达式中的 Unicode 处理修饰符 u 和 \p{...} 类 在 JavaScript 中处理 Unicode 字符时正文档教程前端创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表