Envoy Header Transforms API 深度解析:在过滤器链中提前获取路由级请求/响应头转换
Envoy Header Transforms API 深度解析在过滤器链中提前获取路由级请求/响应头转换【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy导读本文聚焦 Envoy 路由器模块中的一对核心扩展 APIRouteEntry::requestHeaderTransforms与ResponseEntry::responseHeaderTransforms。它们允许 HTTP 过滤器在请求处理早期甚至在收到响应之前就提前取得路由条目本会在finalizeRequestHeaders/finalizeResponseHeaders阶段执行的请求头与响应头转换集合从而支持请求阶段预取、后续阶段延迟应用的过滤器实现模式。读完本文你将掌握这两个 API 的接口语义、与finalize*Headers的关系、底层Http::HeaderTransforms数据结构以及使用它们时必须注意的路由修改时序约束。一、API 概述为何需要提前获取头转换在 Envoy 的路由处理流程中路由条目RouteEntry会在请求被转发给上游之前执行一系列具有潜在破坏性的头部变换例如应用request_headers_to_add/response_headers_to_add配置的增删改执行 Host 重写host rewrite与路径重写path rewrite为 CONNECT 请求恢复原始端口根据most_specific_header_mutations_wins语义合并多级路由配置的头部修改。这些变换统一由两个方法封装接口定义见 envoy/router/router.hRouteEntry::finalizeRequestHeaders(...)接口声明——在转发前对请求头做最终处理ResponseEntry::finalizeResponseHeaders(...)接口声明——在获得初始响应头后立即对响应头做处理。这两个方法都会原地修改传入的HeaderMap因此被设计为仅在转发前调用一次。而requestHeaderTransforms与responseHeaderTransforms则提供了非破坏性的替代路径Returns the header transforms that would be applied if finalize*Headers were called now. This is useful if you want to obtain header transforms at request time and process them later.即在不实际修改任何头部的前提下返回当前时刻本会被finalize*Headers应用的完整转换集合。这使过滤器可以在decodeHeaders阶段就预览路由最终会施加的头部修改将其序列化后传给下游客户端或后续处理环节。二、接口签名与返回值语义两个 API 的完整声明如下envoy/router/router.h 与 envoy/router/router.h// ResponseEntry路由条目共同基类侧 virtual Http::HeaderTransforms responseHeaderTransforms(const StreamInfo::StreamInfo stream_info, bool do_formatting true) const PURE; // RouteEntry 侧 virtual Http::HeaderTransforms requestHeaderTransforms(const StreamInfo::StreamInfo stream_info, bool do_formatting true) const PURE;参数语义参数含义stream_info当前请求的StreamInfo用于在转换求值时读取请求头、动态元数据metadata等上下文信息do_formatting是否对配置的转换执行格式化求值传false时返回原始未格式化的值例如未展开的%DYNAMIC_METADATA(...)%格式串原始文本默认true返回值Http::HeaderTransforms两个 API 的返回值类型为Envoy::Http::HeaderTransforms其结构定义在 envoy/http/header_map.h/** * Wraps a set of header modifications. */ struct HeaderTransforms { std::vectorstd::pairHttp::LowerCaseString, std::string headers_to_append_or_add; std::vectorstd::pairHttp::LowerCaseString, std::string headers_to_overwrite_or_add; std::vectorstd::pairHttp::LowerCaseString, std::string headers_to_add_if_absent; std::vectorHttp::LowerCaseString headers_to_remove; };四个成员分别对应四种头部操作语义headers_to_append_or_add向头部追加值已存在则追加不存在则新增headers_to_overwrite_or_add覆盖已存在的值或新增headers_to_add_if_absent仅当头部不存在时才添加headers_to_remove需要删除的头部名称列表。从源码结构可以看出该结构将转换建模为可独立于实际HeaderMap存在的一组操作指令这正是过滤器可以取走并在别处延迟应用的前提。三、示例用法在请求阶段获取响应头转换关联文档给出了一个典型场景——过滤器在decodeHeaders请求头解码阶段就获取响应头的转换集合随后将其作为本地响应内容回传给客户端由客户端在稍后处理这些转换。Http::FilterHeadersStatus MyFilter::decodeHeaders( Http::RequestHeaderMap headers, bool) { auto transforms decoder_callbacks_-route()-routeEntry()-responseHeaderTransforms( decoder_callbacks_-streamInfo()); // Send the response headers back to the client; they will process them later. decoder_callbacks_-sendLocalReply( Envoy::Http::Code::OK, MySerializedTransforms(transforms), nullptr, Envoy::Grpc::Status::Ok, local_reply); }代码要点拆解获取路由条目通过decoder_callbacks_-route()-routeEntry()拿到当前请求匹配的RouteEntry。responseHeaderTransforms定义在ResponseEntry上DirectResponseEntry与RouteEntry的共同基类因此该调用是合法的传入 StreamInfodecoder_callbacks_-streamInfo()为转换求值提供请求上下文序列化并回传MySerializedTransforms(transforms)是业务自定义的序列化逻辑将HeaderTransforms编码为可传输格式随后通过sendLocalReply构造200 OK本地响应返回给客户端。客户端拿到后可以复现与服务端finalizeResponseHeaders一致的头转换效果。若需要获取原始未格式化的值例如保留配置中的格式串原文交由下游自行求值则将do_formatting显式置为falseauto raw_transforms decoder_callbacks_-route()-routeEntry()-responseHeaderTransforms( decoder_callbacks_-streamInfo(), /*do_formatting*/false);四、底层实现从finalize*Headers到getHeaderTransforms要理解这两个 API 为何能等价于finalize*Headers需要深入 source/common/router/config_impl.cc 中RouteEntryImplBase的实现。4.1finalizeRequestHeaders的完整流程finalizeRequestHeaders 的实现揭示了请求头最终化包含的四个步骤void RouteEntryImplBase::finalizeRequestHeaders(Http::RequestHeaderMap headers, const Formatter::Context context, const StreamInfo::StreamInfo stream_info, bool keep_original_host_or_path) const { // 1) 先应用 request_headers_to_add 配置的头部变换 for (const HeaderParser* header_parser : getRequestHeaderParsers( /*specificity_ascend*/vhost_-globalRouteConfig().mostSpecificHeaderMutationsWins())) { header_parser-evaluateHeaders(headers, context, stream_info); } // 2) 若为 CONNECT 请求恢复原始端口 if (Http::HeaderUtility::getPortStart(headers.getHostValue()) absl::string_view::npos) { if (auto typed_state stream_info.filterState().getDataReadOnlyOriginalConnectPort( OriginalConnectPort::key()); typed_state ! nullptr) { headers.setHost(absl::StrCat(headers.getHostValue(), :, typed_state-value())); } } // 3) 处理 Host 重写 finalizeHostHeader(headers, context, stream_info, keep_original_host_or_path); // 4) 处理路径重写 finalizePathHeader(headers, context, stream_info, keep_original_host_or_path); }值得注意的设计顺序request_headers_to_add的头部变换最先执行因为 Host/路径重写可能依赖这些新加入的头部例如重写目标值中引用了新增头的值。4.2finalizeResponseHeaders的流程响应侧更简单finalizeResponseHeaders 仅遍历响应头解析器并求值void RouteEntryImplBase::finalizeResponseHeaders(Http::ResponseHeaderMap headers, const Formatter::Context context, const StreamInfo::StreamInfo stream_info) const { for (const HeaderParser* header_parser : getResponseHeaderParsers( /*specificity_ascend*/vhost_-globalRouteConfig().mostSpecificHeaderMutationsWins())) { header_parser-evaluateHeaders(headers, context, stream_info); } }4.3 非破坏性版本requestHeaderTransforms/responseHeaderTransforms对应的非破坏性实现在 config_impl.ccHttp::HeaderTransforms RouteEntryImplBase::responseHeaderTransforms(const StreamInfo::StreamInfo stream_info, bool do_formatting) const { Http::HeaderTransforms transforms; for (const HeaderParser* header_parser : getResponseHeaderParsers( /*specificity_ascend*/vhost_-globalRouteConfig().mostSpecificHeaderMutationsWins())) { // Later evaluated header parser wins. mergeTransforms(transforms, header_parser-getHeaderTransforms(stream_info, do_formatting)); } return transforms; } Http::HeaderTransforms RouteEntryImplBase::requestHeaderTransforms(const StreamInfo::StreamInfo stream_info, bool do_formatting) const { Http::HeaderTransforms transforms; for (const HeaderParser* header_parser : getRequestHeaderParsers( /*specificity_ascend*/vhost_-globalRouteConfig().mostSpecificHeaderMutationsWins())) { // Later evaluated header parser wins. mergeTransforms(transforms, header_parser-getHeaderTransforms(stream_info, do_formatting)); } return transforms; }关键实现要点同一组解析器两个 API 复用了getRequestHeaderParsers/getResponseHeaderParsers与finalize*Headers完全相同的解析器遍历顺序因此返回的转换集合与finalize*Headers实际执行的效果一致specificity_ascend合并语义遍历顺序由mostSpecificHeaderMutationsWins配置决定后求值的解析器优先级更高Later evaluated header parser wins这与文档中对most_specific_header_mutations_wins的既有约定保持一致mergeTransforms合并多个解析器虚拟主机级、路由级、加权集群级等产生的转换通过mergeTransforms按优先级归并进同一个HeaderTransforms最终返回的是一个已按路由优先级拍平的完整操作集。4.4getHeaderTransforms从执行到描述HeaderParser::getHeaderTransforms的声明位于 source/common/router/header_parser.h其注释明确了它与evaluateHeaders的分工Same as evaluateHeaders, but returns the modifications that would have been made rather than modifying an existing HeaderMap.即evaluateHeaders是执行修改而getHeaderTransforms是描述修改。这一对方法共同支撑了 Envoy 路由系统中命令式执行与声明式获取两种头部处理模式命令式evaluateHeaders原地修改HeaderMap用于finalizeRequestHeaders/finalizeResponseHeaders的实际转发路径声明式getHeaderTransforms产出Http::HeaderTransforms操作集合供过滤器在请求早期捕获、序列化、延迟应用。五、使用约束与风险提示必读关联文档给出了一个至关重要的警告接口注释envoy/router/router.h也反复强调Note: do not use unless you are sure that there will be no route modifications later in the filter chain.如果你无法确认过滤器链后续不会再发生路由修改就不应使用这些 API。具体风险包括时序敏感性requestHeaderTransforms/responseHeaderTransforms返回的是当前时刻的转换快照。若后续过滤器通过request_headers_to_remove、路由重选如WeightedCluster动态切换、或改变stream_info中影响格式化求值的状态那么已获取的转换集合将与最终finalize*Headers实际应用的转换不一致格式化求值滞后do_formattingtrue时转换中的格式串如%DYNAMIC_METADATA(...)%、%REQ(...)%等在获取时即基于当前stream_info求值。请求后续阶段若这些数据源发生变化已求值的结果不会自动更新与finalizeRequestHeaders的其他步骤不完全重叠如 4.1 所示finalizeRequestHeaders除头部解析器外还包含 CONNECT 端口恢复、Host/路径重写等步骤而requestHeaderTransforms仅覆盖头部解析器产生的转换。如果你的过滤器需要完整复现转发前的所有头部变化应结合RouteEntry::currentUrlPathAfterRewriteenvoy/router/router.h返回finalizeRequestHeaders计算出的重写后路径等信息综合判断。六、典型应用场景从接口设计与实现机制出发这类提前获取头转换的 API 适用于以下过滤器场景以下均为由源码接口语义推断的合理用法具体业务实现需自行设计响应头转换的异步/延迟处理如文档示例所示在请求阶段捕获响应头转换并随本地响应下发由客户端或后续异步链路在真正构造响应时应用避免在响应路径上再次依赖路由上下文头部转换的观测与审计过滤器可以在不干扰实际转发即不调用finalize*Headers的前提下记录当前路由将施加的头部修改用于调试、审计或遥测协议桥接与转换透传当 Envoy 需要将头部修改语义传递给下游代理或网关时可将其序列化为显式指令header 或扩展协议字段由下游复现等价转换。无论哪种场景都必须遵守第五节的约束仅在确信过滤器链后续不会再有路由修改时才使用。七、小结RouteEntry::requestHeaderTransforms与ResponseEntry::responseHeaderTransforms提供了对路由级头部转换的非破坏性预览能力返回类型为Http::HeaderTransformsenvoy/http/header_map.h涵盖追加/覆盖/条件添加/删除四类操作它们与finalizeRequestHeaders/finalizeResponseHeaders共享同一组头部解析器与specificity_ascend合并语义因此结果与最终执行效果一致实现见 source/common/router/config_impl.ccdo_formatting参数控制返回格式化后的值还是原始配置值满足本地求值与原样透传两种需求使用前提是确认过滤器链后续不存在路由修改否则获取到的转换快照可能与实际应用结果产生偏差如需完整复现转发前效果还需结合currentUrlPathAfterRewrite等接口综合处理。【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考