C++ SDK 修复:流上出现 Local Reply 后响应回调被永久静默的问题)
Envoy 1.40 动态模块Dynamic ModulesC SDK 修复流上出现 Local Reply 后响应回调被永久静默的问题【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy本文围绕 Envoychangelogs/current中针对 dynamic modules 的一条 bug fix 记录展开C SDK 编写的动态模块过滤器一旦所在 HTTP 流上出现任何 local reply包括模块自己未发出的 local reply其onResponseHeaders、onResponseBody、onResponseTrailers三个响应回调会被永久跳过。下文完整还原该缺陷的现象、涉及的 local reply 触发场景、SDK 与宿主机之间的回调分发链路源码级并给出可用于验证修复的集成测试与direct_response路由示例。读完后你将理解 dynamic modules 的 ABI 回调桥接机制、local reply 在 Envoy 过滤器链中的传播语义以及如何在集成测试中复现并验证此类回调丢失问题。1. 缺陷记录原文与影响面该修复条目位于 changelogs/current/bug_fixes/dynamic_modules__cpp-sdk-response-callbacks-after-local-reply.rst全文内容如下dynamic modules: fixed a bug in the C SDK where a filter stopped receivingonResponseHeaders,onResponseBodyandonResponseTrailersafter any local reply on the stream, including local replies the module did not send. A module could not observe or modify the response for adirect_responseroute, a local reply from another filter, or an Envoy-generated error. The Rust and Go SDKs were unaffected.从中可以提取出四个关键事实受影响组件dynamic modules动态模块的C SDK路径。Rust SDK 和 Go SDK 不受影响失效的回调onResponseHeaders、onResponseBody、onResponseTrailers三个响应侧回调请求侧回调onRequestHeaders/onRequestBody/onRequestTrailers不受影响触发条件流上出现任何local reply——不限于模块自己通过sendLocalResponse()发出的也包括模块不知道的 local reply典型受害场景direct_response路由、其他过滤器发出的 local reply、Envoy 自身生成的错误响应如上游连接失败、路由超时、流重置等。当前仓库版本为 VERSION.txt 中的1.40.0-dev即该修复将随 1.40 开发周期发布属于尚未合入稳定版的 in-flight 变更。2. 背景知识Dynamic Modules 与三种 SDK 的架构分层Envoy 的 dynamic modules 机制允许以动态库.so/.dylib/.dll形式加载用 C、Rust、Go 编写的插件实现 HTTP 过滤器、网络过滤器、负载均衡策略等扩展而无需重新编译 Envoy。核心代码位于 source/extensions/dynamic_modules/包含abi/abi.h定义宿主与模块之间的 C ABIenvoy_dynamic_module_on_*回调族abi_impl.cc宿主侧 ABI 实现sdk/cpp/C SDKsdk.h为开发者接口sdk_internal.cc为 ABI 桥接实现sdk/rust/ 与 sdk/go/Rust / Go SDK。其中 C SDK 有一个重要的架构特征ABI 回调的入口函数直接实现在 Envoy 仓库内。以响应回调为例sdk_internal.cc 中定义了宿主机在响应各阶段调用的入口// source/extensions/dynamic_modules/sdk/cpp/sdk_internal.cc修复后的守卫逻辑 envoy_dynamic_module_type_on_http_filter_response_headers_status envoy_dynamic_module_on_http_filter_response_headers( envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr, envoy_dynamic_module_type_http_filter_module_ptr filter_module_ptr, bool end_of_stream) { auto* plugin_handle unwrapPointerHttpFilterHandleImpl(filter_module_ptr); if (plugin_handle nullptr || plugin_handle-local_reply_sent_) { return envoy_dynamic_module_type_on_http_filter_response_headers_status_Continue; } auto status plugin_handle-plugin_-onResponseHeaders(plugin_handle-response_headers_, end_of_stream); return static_castenvoy_dynamic_module_type_on_http_filter_response_headers_status(status); }envoy_dynamic_module_on_http_filter_response_body与envoy_dynamic_module_on_http_filter_response_trailers两个入口采用完全相同的守卫模式plugin_handle nullptr || plugin_handle-local_reply_sent_时直接返回Continue。与 C SDK 不同Rust/Go SDK 的回调桥接发生在模块一侧或由各自的宿主适配层独立完成状态管理因此本缺陷天然免疫——这正是 changelog 中 The Rust and Go SDKs were unaffected 的由来。3. Local Reply 的三类触发场景Local reply指不经过上游、由 Envoy 自身或过滤器链中某一级直接在编码侧生成的响应。文档点名的三类场景3.1direct_response路由路由配置中可直接声明由 Envoy 返回的响应例如routes: - name: local_ok match: prefix: /ok direct_response: status: 200 body: inline_string: ok这种路由在请求匹配后立即作为 local reply 走编码路径完全绕开上游集群。集成测试中正是用这种方式构造了模块未发出但模块应当能观察到的 local reply见 test/extensions/dynamic_modules/http/integration_test.cc 第 262284 行测试注释明确写道Adirect_responseroute is served as a local reply. The module did not send it, so its response callbacks must still fire.。3.2 其他过滤器发出的 local reply例如限流过滤器、鉴权过滤器在 decode 阶段判定拒绝后调用sendLocalReply。此时响应会穿过尚未处理过的下游过滤器的 encode 回调——对位于其后的 dynamic module 过滤器而言这是一个外部产生的 local reply。3.3 Envoy 生成的错误上游连接失败、no healthy upstream、路由阶段异常等由核心运行时直接生成的错误响应同样走 local reply 路径。这三类响应的共同点是它们都不是由该模块自己发起的。修复前的 bug 在于模块对这些响应失去了观察和修改的能力。4. 源码剖析回调丢失的根因4.1 宿主机侧的sent_local_reply_守卫dynamic modules 的宿主侧 HTTP 过滤器实现位于 source/extensions/filters/http/dynamic_modules/filter.cc。该过滤器通过sent_local_reply_标志跟踪本过滤器已经向编码侧直接注入过响应encode 路径全部带守卫// source/extensions/filters/http/dynamic_modules/filter.cc FilterHeadersStatus DynamicModuleHttpFilter::encodeHeaders(ResponseHeaderMap, bool end_of_stream) { if (sent_local_reply_) { // See the comment on the flag. return FilterHeadersStatus::Continue; } const envoy_dynamic_module_type_on_http_filter_response_headers_status status config_-on_http_filter_response_headers_(thisAsVoidPtr(), in_module_filter_, end_of_stream); encode_in_continue_ status envoy_dynamic_module_type_on_http_filter_response_headers_status_Continue; return static_castFilterHeadersStatus(status); }encodeData、encodeTrailers同理。而四个向编码侧直接注入响应的方法——sendLocalReply对应 SDK 的sendLocalResponse、sendResponseHeaders、sendResponseData、sendResponseTrailers——在入口处统一置位sent_local_reply_ truevoid DynamicModuleHttpFilter::sendLocalReply(Code code, ...) { sent_local_reply_ true; decoder_callbacks_-sendLocalReply(code, body, modify_headers, grpc_status, details); }也就是说当模块自己发出 local reply 时后续的 encode 回调会被宿主侧正确短路避免模块自己回看自己的响应。这部分语义是正确且必要的SDK 内部有同名的local_reply_sent_镜像标志在sendLocalResponse()中置位见 sdk_internal.cc 第 592601 行。4.2 缺陷所在SDK 桥接层的守卫条件过宽问题出在 SDK 桥接层sdk_internal.cc中三个响应回调入口的守卫。修复前守卫条件会把流上出现了任何 local reply与模块自己发出 local reply混为一谈导致direct_response路由、他过滤器 local reply、Envoy 生成错误这三类模块并未参与的响应同样被静默跳过——模块既收不到onResponseHeaders也无法改写状态码或响应头。修复后的守卫如第 2 节所示仅当plugin_handle无效或模块自身已发出 local replylocal_reply_sent_由模块自己的sendLocalResponse()等注入调用置位时才短路对模块无涉的 local replyonResponseHeaders/onResponseBody/onResponseTrailers照常触发模块得以观察并修改响应。HttpFilterHandleImpl中的状态字段定义位于 sdk_internal.cc 第 946947 行bool stream_complete_ false; bool local_reply_sent_ false;4.3 模块侧的 Local Reply 观察入口值得区分的是另一个钩子HttpFilter::onLocalReply。sdk.h 第 12291311 行定义了enum class LocalReplyStatus : uint32_t { Continue 0, ContinueAndResetStream 1, }; // HttpFilter 中默认实现直接返回 Continue virtual LocalReplyStatus onLocalReply(uint32_t response_code, std::string_view details, bool reset_imminent) { return LocalReplyStatus::Continue; }宿主侧在 filter.cc 中将其接入 Envoy 的 local reply 机制当配置中提供了on_http_filter_local_reply_钩子时把 local reply 的状态码、details 以及是否即将重置流传给模块返回ContinueAndResetStream时模块可以进一步要求重置流。SDK 桥接入口envoy_dynamic_module_on_http_filter_local_reply位于 sdk_internal.cc 第 15241538 行。因此修复后的完整语义是local reply 出现时模块既能通过onLocalReply钩子感知事件本身也能通过正常响应回调观察/改写实际发出的响应——后者正是本次 bug fix 恢复的能力。5. 验证路径集成测试与相关修复5.1 回归测试test/extensions/dynamic_modules/http/integration_test.cc 中的ResponseCallbacksOnLocalReply集成测试直接针对本缺陷配置一条direct_response: status: 200, body: ok的路由断言模块的响应回调在该 local reply 上仍然被调用。相关测试文件还包括 test/extensions/dynamic_modules/http/filter_test.cc、test/extensions/dynamic_modules/http/abi_impl_test.cc 以及模块测试数据 test/extensions/dynamic_modules/test_data/cpp/http_integration_test.cc。5.2 同批次的关联修复changelogs/current/bug_fixes/下还有一条相邻条目 dynamic_modules__http-filter-deferred-destroy.rst处理 HTTP 过滤器销毁钩子的延迟执行问题。从 filter.cc 的 destroy 逻辑可以印证该设计的动机// A module event hook can end the stream, which tears the filter chain down on the modules own // stack, so the in-module filter has to outlive that hook. if (dispatcher.has_value()) { if (DynamicModuleHttpFilterSharedPtr self weak_from_this().lock()) { Event::DeferredTaskUtil::deferredRun( *dispatcher, [self std::move(self), in_module_filter]() { self-config_-on_http_filter_destroy_(in_module_filter); }); return; } }由于模块事件钩子可能终结流并在模块自身栈上拆除过滤器链销毁钩子必须延迟到事件循环执行——这与本次local reply 后回调守卫修复共同保证了模块在流生命周期各阶段的回调一致性。6. 对模块开发者的实际影响与排查要点升级收益使用 C SDK 的模块升级后对direct_response路由、他过滤器 local reply、Envoy 生成错误的响应可以在onResponseHeaders中改写状态码/响应头、在onResponseBody中处理响应体。典型应用统一改写错误页文案、为 local reply 补充审计头、按 local reply 类型打点。状态隔离模块自身调用sendLocalResponse()之后响应回调仍会按模块自己发出的回复语义被短路local_reply_sent_这一行为修复前后保持一致是预期设计。语言 SDK 选择若此前因该缺陷在 Rust/Go SDK 与 C SDK 之间做过 workaround例如换用不受影响的 SDK可随 1.40 发布回归到 C SDK反之若依赖local reply 后回调被静默这一错误行为升级后需要显式在回调中处理新增的响应流。验证方式可参考 test/extensions/dynamic_modules/http/integration_test.cc 的ResponseCallbacksOnLocalReply测试结构在自己的集成测试中加入direct_response路由断言确认响应回调确实触发。7. 小结本条目修复的是 Envoy dynamic modules C SDK 中一个边界条件处理错误SDK 桥接层的响应回调守卫没有区分模块自己发出的 local reply与流上任意 local reply导致模块对direct_response路由、他过滤器 local reply 和 Envoy 生成错误完全失去观察与修改能力。修复后守卫收敛为仅短路模块自己注入的响应sdk_internal.cc 三个入口统一检查local_reply_sent_宿主机侧的sent_local_reply_语义保持不变filter.cc。Rust 与 Go SDK 不受影响。相关代码与测试分别位于 source/extensions/dynamic_modules/sdk/cpp/、source/extensions/filters/http/dynamic_modules/ 与 test/extensions/dynamic_modules/http/changelog 条目见 changelogs/current/bug_fixes/。【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考