
Envoy HTTP Header Formatters 配置指南HTTP/1.1 请求头大小写保留与格式化的完整实现解析【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy导读HTTP/1.1 协议本身对请求头字段名大小写不敏感但 Envoy 默认会将所有 header key 规范化为小写这在迁移依赖特定 header 大小写的存量系统时会造成兼容性问题。本篇基于 Envoy 官方 v3 API 文档api-v3/config/http/header_formatters.rst及header_casing.rst配置指南系统讲解 Envoy 的 Header Formatter 扩展体系包括 stateless无状态与 stateful有状态两类 formatter 的设计差异、header_key_format配置字段的完整语义、preserve_case 扩展的 YAML 实战配置并深入 preserve_case 的 proto 定义与 C 实现源码让你掌握在上下游两侧精确控制 HTTP/1.1 header 大小写输出的完整方案。一、为什么需要 Header FormatterHTTP/1.1 header 大小写问题的由来HTTP/1.1 规范RFC 7230规定 header 字段名是大小写不敏感的即Content-Type、content-type与CONTENT-TYPE在语义上完全等价。然而在真实生产环境中许多存量系统尤其是早期 Java 应用、遗留网关、签名校验逻辑或日志分析系统会对 header 的原始大小写形式产生隐式依赖。当 Envoy 处理 HTTP/1.1 流量时其默认行为是将所有 header key 规范化为全小写lowercase。虽然这完全符合 HTTP/1.1 规范但正如官方文档 HTTP/1.1 Header Casing 所指出While this is compliant with the HTTP/1.1 spec, in practice this can result in issues when migrating existing systems that might rely on specific header casing.也就是说小写化本身是合规的但在迁移存量系统例如后端服务用X-Custom-Header而非x-custom-header来匹配业务逻辑时会带来不必要的兼容性风险。为此Envoy 在Http1ProtocolOptions中提供了header_key_format配置字段让运维人员可以在序列化编码输出阶段对 header key 做定制化格式化。适用边界说明该能力针对 HTTP/1.1 编解码器codec生效。HTTP/2 与 HTTP/3 协议由于二进制分帧机制天然要求 header 名小写化不受此配置影响本文所述配置均以 HTTP/1.1 场景为前提。二、HeaderKeyFormat 配置字段解析Stateless 与 Stateful 两大体系header_key_format是Http1ProtocolOptions定义于 api/envoy/config/core/v3/protocol.proto下的嵌套消息HeaderKeyFormat。其 proto 定义通过一个互斥的 oneofoneof header_format且validate.required true来承载两类 formattermessage HeaderKeyFormat { message ProperCaseWords { ... } oneof header_format { // Formats the header by proper casing words... ProperCaseWords proper_case_words 1; // Configuration for stateful formatter extensions that allow using received headers to // affect the output of encoding headers. E.g., preserving case during proxying. // [#extension-category: envoy.http.stateful_header_formatters] TypedExtensionConfig stateful_formatter 8; } }从源码结构看oneof的存在意味着同一时刻只能启用一种 formatter二者互斥配置多个会触发 proto 校验错误。按官方文档划分Envoy 当前支持两类 header key formatter2.1 Stateless Formatters无状态格式化器核心特征仅在编码encoding阶段运行不依赖此前对 header 的任何记忆或状态每次对传入的 key 独立完成转换。典型代表proper_case_wordsProperCaseWords 形式。其行为规则是对每个单词的首字符、以及任何跟在特殊字符之后的字母字符进行大写化。proto 注释中给出了两个示例content-type→Content-Typefoo$b#$are→Foo$B#$Are同时官方也明确指出了该方案的一个已知局限某些 header 并不符合常规大小写习惯例如TE会被格式化成Te因为T是首字符大写E跟在E后面不满足特殊字符后跟字母的规则因此保持小写。适用场景从非 HTTP/1 协议转换到 HTTP/1 时例如 HTTP/2 上游 → HTTP/1 下游或在多跳代理间转换希望输出看起来规范的 header或者不希望为有状态格式化付出额外内存开销的场景。2.2 Stateful Formatters有状态格式化器核心特征在解码decoding阶段被实例化对每一个解码出来的 header 都会被调用并将状态附着到 header map 上随后在整个代理栈entire proxy stack中随请求流转最终在编码阶段利用记忆的原始信息来格式化输出。因此它能够横穿完整的代理处理链路。典型代表preserve_case大小写保留格式化器通过stateful_formatter字段以 TypedExtensionConfig 的形式进行扩展配置。适用场景在代理过程中完整保留 HTTP/1 header 的原始大小写适用于对 header 大小写敏感的存量系统迁移。重要注意官方明确说明当使用 Stateful Formatter 时由 Envoy 内部或过滤器例如 Lua filter新增的 header 仍然会被小写化。这一点在配置文档 header_casing.rst 中有专门的 note 说明——有状态格式化只能记住从客户端真实解码出来的 header 大小写对于 Envoy 自造如x-envoy-*系列或脚本过滤器注入的 header依然遵循默认小写规则。三、stateful_formatter 扩展点与 preserve_case proto 定义stateful_formatter字段是一个扩展点extension point其扩展类别为envoy.http.stateful_header_formatters。本文关联文档api-v3/config/http/header_formatters.rst的正文本体即指向该类别下所有扩展的 v3 API 文档树../../extensions/http/header_formatters/*/v3/*。当前仓库中该类别下的实现是preserve_case扩展其 proto 定义位于 api/envoy/extensions/http/header_formatters/preserve_case/v3/preserve_case.proto注册名为envoy.http.stateful_header_formatters.preserve_case。完整配置消息PreserveCaseFormatterConfig包含三个核心要素message PreserveCaseFormatterConfig { enum FormatterTypeOnEnvoyHeaders { // Use LowerCase on Envoy added headers. DEFAULT 0; // Use ProperCaseHeaderKeyFormatter on Envoy added headers that upper cases the first character // in each word. The first character as well as any alpha character following a special // character is upper cased. PROPER_CASE 1; } // Allows forwarding reason phrase text. // This is off by default, and a standard reason phrase is used for a corresponding HTTP response code. bool forward_reason_phrase 1; // Type of formatter to use on headers which are added by Envoy (which are lower case by default). // The default type is DEFAULT, use LowerCase on Envoy headers. FormatterTypeOnEnvoyHeaders formatter_type_on_envoy_headers 2 [(validate.rules).enum {defined_only: true}]; }各字段语义详解字段类型默认值含义forward_reason_phraseboolfalse是否透传 HTTP 响应的 reason phrase原因短语文本。默认关闭此时对相应响应码使用标准原因短语如200 OK中的OK。formatter_type_on_envoy_headers枚举DEFAULT全小写对Envoy 自身新增的 header使用哪种格式化策略。注意它只作用于 Envoy 添加的 header而客户端真实传入的 header 始终按原始大小写保留。formatter_type_on_envoy_headers PROPER_CASE枚举值—对 Envoy 新增 header 使用ProperCaseHeaderKeyFormatter每个单词首字符大写特殊字符后的字母字符也大写。该 proto 文件同时声明了package_version_status ACTIVE表明该 API 处于活跃发布状态可放心在生产配置中使用。此外 proto 注释中还标注了扩展名envoy.http.stateful_header_formatters.preserve_case与 C 实现中的注册名一致。3.1 为什么需要区分 Envoy 新增 header 与 客户端原始 header从实现上看preserve_case 的核心价值在于记住原始大小写但它只能记住从客户端解码出来的 header。对于 Envoy 在代理过程中自造的 header如各种x-envoy-*头并不存在原始大小写可供记忆因此需要一个单独的枚举来控制这些 header 的输出风格默认全小写DEFAULT或采用 proper case 风格PROPER_CASE。四、实战配置完整保留 HTTP/1.1 header 大小写可复制示例官方在 header_casing.rst 中给出了一个完整的端到端配置示例展示如何在下行downstream请求头与上行upstream响应头两侧同时启用 preserve_case。该示例的原型文件位于 preserve-case.yaml如下已补全注释说明static_resources: listeners: - address: socket_address: address: 0.0.0.0 port_value: 443 filter_chains: - filters: - name: envoy.filters.network.http_connection_manager typed_config: type: type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: ingress_http # 下行方向控制客户端请求头解码后→ 上游请求头编码时的大小写输出 http_protocol_options: header_key_format: stateful_formatter: name: preserve_case typed_config: type: type.googleapis.com/envoy.extensions.http.header_formatters.preserve_case.v3.PreserveCaseFormatterConfig http_filters: - name: envoy.filters.http.router typed_config: type: type.googleapis.com/envoy.extensions.filters.http.router.v3.Router route_config: virtual_hosts: - name: default domains: [*] routes: - match: {prefix: /} route: cluster: service_foo clusters: - name: service_foo # 上行方向控制上游响应头解码后→ 下游响应头编码时的大小写输出 typed_extension_protocol_options: envoy.extensions.upstreams.http.v3.HttpProtocolOptions: type: type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions explicit_http_config: http_protocol_options: header_key_format: stateful_formatter: name: preserve_case typed_config: type: type.googleapis.com/envoy.extensions.http.header_formatters.preserve_case.v3.PreserveCaseFormatterConfig load_assignment: cluster_name: some_service endpoints: - lb_endpoints: - endpoint: address: socket_address: address: 127.0.0.1 port_value: 80804.1 配置位置两个方向的控制点官方文档明确给出了两个独立的配置入口理解这一点是正确配置的关键下行请求头客户端 → Envoy → 上游配置在 HTTP Connection Manager 的http_protocol_options中。字段路径为HttpConnectionManager.http_protocol_options.header_key_format对应 proto 字段 envoy_v3_api_field_extensions.filters.network.http_connection_manager.v3.HttpConnectionManager.http_protocol_options。客户端以X-Custom-Header形式发送的请求头在 Envoy 转发给上游时将以原始大小写输出。上行响应头上游 → Envoy → 客户端配置在 cluster 的typed_extension_protocol_options中key 为envoy.extensions.upstreams.http.v3.HttpProtocolOptions消息体为HttpProtocolOptions其中通过explicit_http_config.http_protocol_options.header_key_format指定 formatter对应 proto 字段 envoy_v3_api_msg_extensions.upstreams.http.v3.HttpProtocolOptions。上游返回的响应头大小写将在发送给客户端时被保留。实践建议若希望客户端看到的请求头与上游收到的请求头大小写一致两个方向都需要配置。只配置下行方向意味着客户端发送X-Custom-Header→ 上游收到X-Custom-Header但上游返回的X-Response-Header若不加处理下游客户端将收到小写化的x-response-header。4.2 更完整的 preserve_case 配置结合 proto 字段上述官方示例未显式设置PreserveCaseFormatterConfig的字段全部使用默认值。若需要透传 reason phrase 并对 Envoy 自造 header 采用 proper case 风格可在typed_config中补充typed_config: type: type.googleapis.com/envoy.extensions.http.header_formatters.preserve_case.v3.PreserveCaseFormatterConfig forward_reason_phrase: true formatter_type_on_envoy_headers: PROPER_CASE其中forward_reason_phrase: true会启用 reason phrase 透传源码中由setReasonPhrase/getReasonPhrase成对实现见下文formatter_type_on_envoy_headers: PROPER_CASE会让 Envoy 新增的 header 以ProperCaseHeaderKeyFormatter风格输出例如 Envoy 添加的x-envoy-upstream-service-time会输出为X-Envoy-Upstream-Service-Time。注意formatter_type_on_envoy_headers有defined_only: true校验规则必须显式填写枚举定义内的合法值DEFAULT或PROPER_CASE。五、源码级原理PreserveCaseHeaderFormatter 的实现细节理解配置后我们再深入 C 实现来印证上述行为。preserve_case 扩展的源码位于 source/extensions/http/header_formatters/preserve_case/由三个文件组成preserve_case_formatter.h/preserve_case_formatter.cc核心格式化器实现config.h/config.cc扩展工厂factory实现完成 proto 配置到 formatter 实例的转换。5.1 工厂注册与配置转换在 config.cc 中PreserveCaseFormatterFactoryConfig::createFactoryFromProto负责将 proto 配置转换为工厂实例PreserveCaseFormatterFactoryConfig::createFactoryFromProto(...) { ... std::make_sharedPreserveCaseFormatterFactory(config.forward_reason_phrase(), ...); } LEGACY_REGISTER_FACTORY(PreserveCaseFormatterFactoryConfig, Envoy::Http::StatefulHeaderKeyFormatterFactoryConfig, preserve_case);注册名为preserve_case与 YAML 配置中stateful_formatter.name: preserve_case完全对应而 config.h 中name()方法返回的完整扩展标识符为envoy.http.stateful_header_formatters.preserve_case对应扩展类别envoy.http.stateful_header_formatters。5.2 核心格式化器记忆原始大小写 双策略输出preserve_case_formatter.cc 中的PreserveCaseHeaderFormatter实现了有状态格式化的完整生命周期// 解码阶段每当解码出一个 header key将其原始形式记录下来 void PreserveCaseHeaderFormatter::processKey(absl::string_view key) { // Note: This implementation will only remember the first instance of a particular header key. // So for example Foo followed by foo will both be serialized as Foo on the way out. original_header_keys_.emplace(key); } // 编码阶段优先使用记忆的原始大小写否则回退到 Envoy header 策略 std::string PreserveCaseHeaderFormatter::format(absl::string_view key) const { const auto remembered_key_itr original_header_keys_.find(key); if (remembered_key_itr ! original_header_keys_.end()) { return *remembered_key_itr; // ① 记忆命中返回原始大小写 } else if (formatterOnEnvoyHeaders().has_value()) { return formatterOnEnvoyHeaders()-format(key); // ② Envoy 新增 header按策略格式化 } else { return std::string(key); // ③ 默认返回小写 } }三个关键实现细节值得注意大小写记忆是有损去重的original_header_keys_内部存储了原始 key且只记住同一 header key 的第一个实例。源码注释明确说明例如先出现Foo再出现foo二者在编码输出时都会统一序列化为Foo。作者在注释中表示可以做更好但不太值得等有人反馈再说——这意味着同一请求内重复出现的大小写变体 key将以第一个实例为准。构造阶段根据枚举选择 Envoy header 策略对应 proto 的formatter_type_on_envoy_headersswitch (formatter_type_on_envoy_headers_) { case ...PreserveCaseFormatterConfig::DEFAULT: header_key_formatter_on_enovy_headers_ Envoy::Http::HeaderKeyFormatterConstPtr(); // 无策略 → 保持小写 break; case ...PreserveCaseFormatterConfig::PROPER_CASE: header_key_formatter_on_enovy_headers_ std::make_uniqueEnvoy::Http::Http1::ProperCaseHeaderKeyFormatter(); // 采用 proper case break; }即DEFAULT模式下 Envoy 新增 header 输出小写PROPER_CASE模式下复用ProperCaseHeaderKeyFormatter与无状态proper_case_words使用相同的底层格式化逻辑。reason phrase 透传setReasonPhrase仅在forward_reason_phrase_为 true 时才保存传入的 reason phrasegetReasonPhrase再将其取出用于响应编码——这就是 proto 中forward_reason_phrase字段的实现落点。5.3 有状态与无状态的工程本质区别从StatefulHeaderKeyFormatter接口envoy/http/header_formatter.h与上述实现可以总结出工程本质差异无状态statelessformat(key)一次调用即完成转换纯函数式无额外内存开销适合跨协议转换场景有状态stateful在解码期通过processKey累积原始大小写记忆以内存换保真随 header map 贯穿整个代理栈编码期用记忆做输出——这正是官方文档所述traverse the entire proxy stack的实现基础。六、总结与选用建议围绕 header_formatters.rst 所指向的扩展体系可总结出以下工程决策路径需求场景推荐方案配置位置存量系统依赖客户端请求头原始大小写preserve_casestatefulHCM 的http_protocol_options.header_key_format.stateful_formatter存量系统依赖上游响应头原始大小写preserve_casestatefulcluster 的typed_extension_protocol_options内HttpProtocolOptions只需规范化美观的 header 输出如跨协议转换proper_case_wordsstateless同上两个位置改用proper_case_words: {}透传自定义 reason phrasepreserve_caseforward_reason_phrase: true在 preserve_case 的typed_config中设置Envoy 自造 header 也想用 proper case 风格preserve_caseformatter_type_on_envoy_headers: PROPER_CASE同上核心要点回顾两类 formatter 互斥oneof header_format校验同一 header 输出路径只能二选一有状态格式化只能保留客户端真实解码出的 header 大小写Envoy 自造及过滤器新增的 header 仍默认小写可用formatter_type_on_envoy_headers微调大小写记忆按首个实例为准同一 key 的后续大小写变体不会各自保留上行与下行是两个独立配置点需要保留双向大小写时必须两处都配置。如需在真实环境中验证可直接参考仓库中的完整配置示例 preserve-case.yaml 与 API 定义 protocol.proto、preserve_case.proto以及核心实现 preserve_case_formatter.cc对照理解配置字段与运行时行为的一一对应关系。【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考