news 2026/9/12 12:08:45

Envoy 1.40 动态模块(Dynamic Modules)C++ SDK 修复:流上出现 Local Reply 后响应回调被永久静默的问题

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Envoy 1.40 动态模块(Dynamic Modules)C++ SDK 修复:流上出现 Local Reply 后响应回调被永久静默的问题

Envoy 1.40 动态模块(Dynamic Modules)C++ 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),其onResponseHeadersonResponseBodyonResponseTrailers三个响应回调会被永久跳过。下文完整还原该缺陷的现象、涉及的 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.

从中可以提取出四个关键事实:

  1. 受影响组件:dynamic modules(动态模块)的C++ SDK路径。Rust SDK 和 Go SDK 不受影响;
  2. 失效的回调onResponseHeadersonResponseBodyonResponseTrailers三个响应侧回调(请求侧回调onRequestHeaders/onRequestBody/onRequestTrailers不受影响);
  3. 触发条件:流上出现任何local reply——不限于模块自己通过sendLocalResponse()发出的,也包括模块"不知道"的 local reply;
  4. 典型受害场景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 ABI(envoy_dynamic_module_on_*回调族);
  • abi_impl.cc:宿主侧 ABI 实现;
  • sdk/cpp/:C++ SDK(sdk.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 = unwrapPointer<HttpFilterHandleImpl>(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_cast<envoy_dynamic_module_type_on_http_filter_response_headers_status>(status); }

envoy_dynamic_module_on_http_filter_response_bodyenvoy_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 第 262~284 行,测试注释明确写道:"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_cast<FilterHeadersStatus>(status); }

encodeDataencodeTrailers同理。而四个"向编码侧直接注入响应"的方法——sendLocalReply(对应 SDK 的sendLocalResponse)、sendResponseHeaderssendResponseDatasendResponseTrailers——在入口处统一置位sent_local_reply_ = true

void 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 第 592~601 行)。

4.2 缺陷所在:SDK 桥接层的守卫条件过宽

问题出在 SDK 桥接层(sdk_internal.cc)中三个响应回调入口的守卫。修复前,守卫条件会把"流上出现了任何 local reply"与"模块自己发出 local reply"混为一谈,导致direct_response路由、他过滤器 local reply、Envoy 生成错误这三类模块并未参与的响应,同样被静默跳过——模块既收不到onResponseHeaders,也无法改写状态码或响应头。

修复后的守卫如第 2 节所示:仅当plugin_handle无效或模块自身已发出 local reply(local_reply_sent_由模块自己的sendLocalResponse()等注入调用置位)时才短路;对模块无涉的 local reply,onResponseHeaders/onResponseBody/onResponseTrailers照常触发,模块得以观察并修改响应。HttpFilterHandleImpl中的状态字段定义位于 sdk_internal.cc 第 946~947 行:

bool stream_complete_ = false; bool local_reply_sent_ = false;

4.3 模块侧的 Local Reply 观察入口

值得区分的是另一个钩子:HttpFilter::onLocalReply。sdk.h 第 1229~1311 行定义了:

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 第 1524~1538 行。

因此修复后的完整语义是: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 module's 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. 对模块开发者的实际影响与排查要点

  1. 升级收益:使用 C++ SDK 的模块升级后,对direct_response路由、他过滤器 local reply、Envoy 生成错误的响应,可以在onResponseHeaders中改写状态码/响应头、在onResponseBody中处理响应体。典型应用:统一改写错误页文案、为 local reply 补充审计头、按 local reply 类型打点。
  2. 状态隔离:模块自身调用sendLocalResponse()之后,响应回调仍会按"模块自己发出的回复"语义被短路(local_reply_sent_),这一行为修复前后保持一致,是预期设计。
  3. 语言 SDK 选择:若此前因该缺陷在 Rust/Go SDK 与 C++ SDK 之间做过 workaround(例如换用不受影响的 SDK),可随 1.40 发布回归到 C++ SDK;反之,若依赖"local reply 后回调被静默"这一错误行为,升级后需要显式在回调中处理新增的响应流。
  4. 验证方式:可参考 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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 12:02:24

TDD实战:用Jest和JUnit攻克秒杀系统核心链路测试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 11:56:53

地震声波正演中的MATLAB射线追踪:打靶法与弯曲法实现解析

简介&#xff1a;这套基于 MATLAB 的二维射线追踪与地震声波正演源码包&#xff0c;面向地球物理、地震勘探专业的初学者与研究者&#xff0c;用于模拟地震波在地层中的传播路径与接收信号。程序涵盖射线理论基础、几何扩散法、速度模型构建、源项与接收器设置、数值求解&#…

作者头像 李华
网站建设 2026/9/12 11:56:16

开始制作远程升级app模块

就是那种一发现版本升级了&#xff0c;然后就每次打开app提示升级的那种&#xff0c;否则就无法使用

作者头像 李华