首页
/ gRPC over HTTP/2 协议深度解析:报文格式、流语义与 chttp2 传输层实现

gRPC over HTTP/2 协议深度解析:报文格式、流语义与 chttp2 传输层实现

2026-09-05 10:51:25作者:咎岭娴Homer

本文基于 gRPC 仓库中的协议规范文档 doc/PROTOCOL-HTTP2.md,系统讲解 gRPC 如何承载于 HTTP/2 帧之上:请求与响应的 ABNF 报文文法、grpc-timeout 等核心头字段、Length-Prefixed-Message 分帧格式、trailers 中的错误状态语义,以及 GOAWAY/PING/RST_STREAM 等连接管理帧的处理规则。读完本文,你可以准确理解一次 gRPC 调用在 HTTP/2 线路上的完整字节级流转,并能对照当前仓库的 chttp2 传输层源码验证上述规则的真实落地方式。

协议概述:一次调用的报文原子序列

gRPC 的通信建立在 HTTP/2 分帧(HTTP/2 framing)之上,规范假设读者已熟悉 HTTP/2 协议本身。协议的生产规则(production rules)采用 ABNF 语法(RFC 5234 定义的标准)描述。

一次完整的 gRPC 请求/响应消息流由以下报文原子(message atoms)构成:

Request  → Request-Headers *Length-Prefixed-Message EOS
Response → (Response-Headers *Length-Prefixed-Message Trailers) / Trailers-Only
  • 请求(客户端 → 服务端):先发送 Request-Headers(HEADERS 帧),随后是零个或多个 Length-Prefixed-Message(DATA 帧),最后以 EOS(END_STREAM 标志)结束流。
  • 响应(服务端 → 客户端):正常路径为 Response-Headers + 零或多个 Length-Prefixed-Message + Trailers;对于立即报错的调用,允许只发送 Trailers-Only(单帧)。

在 gRPC 源码中,这套协议由 chttp2 传输层插件实现,其定位在 src/core/ext/transport/chttp2/README.md 中明确写道:"chttp2 transport plugin - implements grpc over http2",即该传输层承担了"把 gRPC 调用翻译成 HTTP/2 帧"的全部职责。

Path 规则与 Content-Type 校验

ABNF 中 :path 的格式为 "/" Service-Name "/" {方法名},例如 /helloworld.Greeter/SayHello。规范特别强调 Path 大小写敏感,且允许部分实现覆盖该 Path 格式的行为——但这种做法被强烈不建议(strongly discouraged)。gRPC 官方不会主动破坏这类自定义行为,但也不提供活跃支持;当 Path 不是标准格式时,某些功能(如 service config 支持)将不生效。

Content-Type 必须以 application/grpc 开头(可附加 +proto / +json / 自定义后缀)。如果请求头中的 Content-Type 不满足该前缀,gRPC 服务端 SHOULD 返回 HTTP 415 (Unsupported Media Type)。这样做的目的,是防止其他 HTTP/2 客户端把 gRPC 的错误响应(其 HTTP 状态恒为 200 OK)误判为成功。

从源码结构看,这一前缀校验在元数据层就被编码为强类型约束:src/core/call/metadata_batch.h#L124-L151ContentTypeMetadata 的取值仅区分三种状态(kApplicationGrpc / kEmpty / kInvalid),注释明确写着 "gRPC says that content-type can be application/grpc[;something]. Core has only ever verified the prefix"——即核心只校验 application/grpc 前缀,与规范描述一致。

请求头(Request-Headers)完整文法

Request-Headers 通过 HTTP/2 的 HEADERS + CONTINUATION 帧块下发,其完整 ABNF 定义如下:

Request-Headers    → Call-Definition *Custom-Metadata
Call-Definition    → Method Scheme Path [Authority] TE [Timeout]
                     Content-Type [Message-Type] [Message-Encoding]
                     [Message-Accept-Encoding] [User-Agent]
Method             → ":method" "POST"
Scheme             → ":scheme" ("http" / "https")
Path               → ":path" "/" Service-Name "/" {方法名}
Service-Name       → {IDL 特定的服务名}
Authority          → ":authority" {权威虚拟主机名}
TE                 → "te" "trailers"        ; 用于探测不兼容的中间代理
Timeout            → "grpc-timeout" TimeoutValue TimeoutUnit
TimeoutValue       → {最多 8 位的十进制正整数字符串}
TimeoutUnit        → Hour / Minute / Second / Millisecond / Microsecond / Nanosecond
  Hour             → "H"
  Minute           → "M"
  Second           → "S"
  Millisecond      → "m"
  Microsecond      → "u"
  Nanosecond       → "n"
Content-Type       → "content-type" "application/grpc" [("+proto" / "+json" / {自定义})]
Content-Coding     → "identity" / "gzip" / "deflate" / "snappy" / {自定义}
Message-Encoding   → "grpc-encoding" Content-Coding
Message-Accept-Encoding → "grpc-accept-encoding" Content-Coding *( "," Content-Coding )
User-Agent         → "user-agent" {结构化 UA 字符串}
Message-Type       → "grpc-message-type" {消息 schema 类型名}
Custom-Metadata    → Binary-Header / ASCII-Header
Binary-Header      → {Header-Name "-bin"} {base64 编码值}
ASCII-Header       → Header-Name ASCII-Value
Header-Name        → 1*( %x30-39 / %x61-7A / "_" / "-" / "." )  ; 即 0-9 a-z _ - .
ASCII-Value        → 1*( %x20-%x7E )  ; 空格与可打印 ASCII

几个关键约束逐一展开:

1. 头字段排序要求

HTTP/2 要求保留头(以 : 开头的伪头,如 :method:scheme:path:authority)必须出现在所有其他头之前。gRPC 规范在此之上补充了两条推荐排序:Timeout 头应紧跟在保留头之后发送;Call-Definition 头应在 Custom-Metadata 之前发送

2. grpc-timeout 的解析与编码

超时值的单位覆盖了从纳秒到小时的全部粒度,且值部分最多 8 位数字(因此超时上限约 9999 小时)。如果请求省略 Timeout,服务端应视为无限超时;客户端实现可以根据部署需求自由发送一个默认的最小超时。

在源码中,grpc-timeout 被建模为一个独立的元数据 trait:src/core/call/metadata_batch.h#L75-L90GrpcTimeoutMetadataValueTypeTimestamp(绝对截止时间戳),注释说明它在传输层发送前会被转换为时长(Duration);kTransferOnTrailersOnly = false 表明该头不会在 Trailers-Only 响应中被透传。这与规范中"Timeout 只在请求头中出现"的语义吻合。

类似地,TE 头在 src/core/call/metadata_batch.h#L93-L121TeMetadata 中只允许 trailers 一个合法值(空表示未设置,非法值会被记录为 kInvalid),印证了规范中 "TE → te trailers ; Used to detect incompatible proxies" 的定位——它本质上是 gRPC 客户端探测链路上代理是否支持 trailers 的探针。

3. 自定义元数据(Custom-Metadata)与二进制头

Custom-Metadata 是应用层自定义的任意 key-value 头集合,有若干必须遵守的规则:

  • grpc- 开头但未被协议列出的头名,保留给 gRPC 未来使用,应用不应将其用作自定义元数据。
  • HTTP/2 不允许头值携带任意八位字节序列,因此二进制头值必须按 RFC 4648 §4 进行 Base64 编码。实现必须同时接受带填充(padded)与不带填充(un-padded)的 Base64 值,且发送时应输出不带填充的形式。应用通过让头名以 -bin 结尾来声明这是一个二进制头;运行时库依据该后缀检测并自动施加 Base64 编解码。
  • Custom-Metadata 的头顺序不保证被保留,唯一例外是同名重复头的值顺序。同名头可能以 , 作为分隔符合并,且应被视为语义等价。实现必须在解码 Base64 之前,先对 Binary-Header 按 , 切分
  • ASCII-Value 不应带首尾空白;如果带了,可能被剥离。注意 gRPC 定义的 ASCII-Value 字符集比 HTTP 更严格:实现不应因收到一个"对 HTTP 合法的 field-value 但对 gRPC 非法的 ASCII-Value"而报错,其行为未被严格限定——可以丢弃该值,也可以接受。若接受,必须注意允许应用安全地回显(echo)该值:例如请求中作为列表提供的元数据,不应在作为响应元数据回传时触发错误。

4. 请求头大小限制

服务端可以限制 Request-Headers 的总大小,建议默认值为 8 KiB。规范鼓励实现按 HTTP/2 SETTINGS_MAX_HEADER_LIST_SIZE 的方式计算总头大小:对所有头字段求和,每个字段贡献为"未压缩头名长度 + 头值长度 + 32",且二进制头的值长度按 Base64 编码之后计算

Length-Prefixed-Message:消息分帧格式

请求中重复出现的 Length-Prefixed-Message 序列通过 DATA 帧下发,其格式为:

Length-Prefixed-Message → Compressed-Flag Message-Length Message
Compressed-Flag   → 0 / 1                     ; 1 字节无符号整数
Message-Length    → {Message 的长度}           ; 4 字节无符号整数(大端序)
Message           → *{binary octet}

即每条 gRPC 消息在线路上都是"1 字节压缩标志 + 4 字节大端长度 + 消息体"的封装。关于压缩标志的语义:

  • Compressed-Flag = 1 表示消息体已按 Message-Encoding 头声明的机制压缩;0 表示未做任何编码。
  • 压缩上下文不跨消息边界维护——实现必须为流中的每条消息创建全新的压缩上下文。
  • 如果请求未携带 Message-Encoding 头,Compressed-Flag 必须为 0

源码层面,DATA 帧的接收与消息重组由 src/core/ext/transport/chttp2/transport/frame_data.ccsrc/core/ext/transport/chttp2/transport/message_assembler.h 等文件负责。例如 frame_data.cc#L128-L133 在解析出 Length-Prefixed-MessageMessage-Length 字段后,会立即检查该长度是否超过调用方通过调用上下文(call context)设定的 max_recv_message_length,超过即报"message larger than maximum"错误——这正是"4 字节长度字段先于消息体被校验"这一分帧设计在实现中的体现。

EOS:请求流的结束语义

对请求而言,EOS(end-of-stream)由最后一个 DATA 帧上的 END_STREAM 标志指示。特殊场景:当请求流需要关闭但已无数据可发送时(例如客户端 unary 调用只发了 HEADERS 就要关闭发送方向),实现必须发送一个空的 DATA 帧并带上 END_STREAM 标志。这一点在消息组装器中有直接对应:src/core/ext/transport/chttp2/transport/message_assembler.h#L160-L175 中的 GenerateNextFrame 负责把消息切分为若干 DATA 帧,而 GenerateEmptyEndFrame 正是为"无数据仍需 END_STREAM"的场景生成的空帧。

响应(Responses):Trailers 与错误状态

响应的完整文法:

Response        → (Response-Headers *Length-Prefixed-Message Trailers) / Trailers-Only
Response-Headers → HTTP-Status [Message-Encoding] [Message-Accept-Encoding]
                   Content-Type *Custom-Metadata
Trailers-Only   → HTTP-Status Content-Type Trailers
Trailers        → Status [Status-Message] [Status-Details] *Custom-Metadata
HTTP-Status     → ":status" "200"
Status          → "grpc-status" 1*DIGIT        ; 0-9(十进制 ASCII,无前导零)
Status-Message  → "grpc-message" Percent-Encoded
Status-Details  → "grpc-status-details-bin" {base64 编码值}
Percent-Encoded  → 1*(Percent-Byte-Unencoded / Percent-Byte-Encoded)
Percent-Byte-Unencoded → 1*( %x20-%x24 / %x26-%x7E )  ; 空格与 VCHAR(不含 %)
Percent-Byte-Encoded   → "%" 2HEXDIGIT

关键语义逐条解析

  1. 帧块结构Response-HeadersTrailers-Only 各自在单个 HEADERS 帧块中下发。大多数响应同时携带 headers 与 trailers,但产生立即错误的调用允许只发 Trailers-OnlyStatus 必须始终出现在 Trailers 中,即使状态码是 OK(0)——这是 gRPC 客户端判定调用成功与否的唯一依据,HTTP 层的 200 只说明传输正常。
  2. EOS:响应的结束由携带 Trailers 的最后一个 HEADERS 帧上的 END_STREAM 标志指示。
  3. 对破损部署的容错:实现应预期真实网络中存在发送非 200 HTTP 状态、发送非 gRPC content-type、甚至完全省略 StatusStatus-Message 的"破损部署"(broken deployments)。当出现这些情况时,实现必须合成(synthesize)一个 Status 与 Status-Message 上报给应用层,而不是把错误吞掉或原样暴露 HTTP 细节。
  4. 响应头/trailers 大小:客户端可以限制 Response-HeadersTrailersTrailers-Only 的大小,建议默认各 8 KiB
  5. Status 值格式:十进制 ASCII 整数字符串,不允许前导零
  6. Status-Message 的百分号编码:其值在概念上是一段 Unicode 错误描述,物理编码为"先 UTF-8,再 percent-encoding"。percent-encoding 参照 RFC 3986 §2.1,但此处允许未编码的字符集与标准 URL 不同(空格与 VCHAR 除外 % 本身,见上文 ABNF)。解码到非法值时,实现不得报错或丢弃消息,最宽松的处理是放弃解码、让用户收到原始的 percent-encoded 形式;也可以解码合法部分、保留损坏的 % 编码原样、或用替换字符(如 ? 或 Unicode 替换字符)替代。
  7. Status-Details:仅当 Status 不为 OK 时允许携带,内容是关于该 RPC 错误的附加信息。如果其中包含 status code 字段,不得与 Status 头矛盾,消费方必须验证这一要求。在 Protobuf 场景下(见附录 A),该字段的值就是 google.rpc.Status 消息的 Base64 编码。

示例:一次一元调用的完整 HTTP/2 帧序列

以下是规范给出的 unary call 示例,展示了帧级别的报文序列:

请求(Request)

HEADERS (flags = END_HEADERS)
:method = POST
:scheme = http
:path = /google.pubsub.v2.PublisherService/CreateTopic
:authority = pubsub.googleapis.com
grpc-timeout = 1S
content-type = application/grpc+proto
grpc-encoding = gzip
authorization = Bearer y235.wef315yfh138vh31hv93hv8h3v

DATA (flags = END_STREAM)
<Length-Prefixed Message>

响应(Response)

HEADERS (flags = END_HEADERS)
:status = 200
grpc-encoding = gzip
content-type = application/grpc+proto

DATA
<Length-Prefixed Message>

HEADERS (flags = END_STREAM, END_HEADERS)
grpc-status = 0 # OK
trace-proto-bin = jher831yy13JHy3hc

注意示例中请求侧 DATA 帧直接带 END_STREAM(一元请求只有一条消息),响应侧的 Trailers HEADERS 帧同时携带 END_STREAMEND_HEADERS,且 grpc-status = 0 表明调用成功——即便成功,trailers 也必须携带 Status,与该帧即流结束帧的语义完全一致。响应中的 trace-proto-bin 则是一个应用层自定义二进制头(-bin 后缀触发 Base64 编解码)。

User-Agent 约定

协议功能上不要求携带 user-agent,但推荐客户端提供结构化的 UA 字符串,描述调用库、版本与平台,以方便在异构环境中定位问题。推荐的构造文法:

User-Agent → "grpc-" Language ?("-" Variant) "/" Version
             ?( " (" *(AdditionalProperty ";") ")" )

例如:

grpc-java/1.2.3
grpc-ruby/1.2.3
grpc-ruby-jruby/1.3.4
grpc-java-android/0.9.1 (gingerbread/1.2.4; nexus5; tmobile)

幂等性与重试

除非明确定义,gRPC 调用默认不假定为幂等(not idempotent),具体规则:

  • 无法证明已经开始(cannot be proven to have started)的调用,不会被重试;
  • 不存在重复抑制(duplicate suppression)机制——因为不需要;
  • 被标记为幂等的调用,可能被发送多次。

这条语义是 gRPC 上层重试策略(retry)与负载均衡失败转移设计的底线:传输层只有在能确信"对端从未开始处理"(如 REFUSED_STREAM)时,才允许透明重试。

HTTP/2 传输映射

流标识(Stream Identification)

所有 gRPC 调用需要一个内部 ID,该方案直接复用 HTTP/2 stream-id 作为调用标识。注意:这些 id 只在单个 HTTP/2 会话(session)上下文内有意义,在处理多个 HTTP/2 会话的进程中不具有全局唯一性,也不能当作 GUID 使用。

DATA 帧与消息边界

DATA 帧的边界与 Length-Prefixed-Message 的边界没有任何对应关系,实现不得假设两者对齐。一条消息可能被切分进多个 DATA 帧,一个 DATA 帧也可能包含多条消息的片段——这正是需要 4 字节长度字段做帧内重组的原因。

错误处理与 RST_STREAM 映射

当 RPC 过程中发生应用或运行时错误时,StatusStatus-Message 通过 Trailers 下发。但在某些情况下,消息流的帧格式可能已经损坏,此时 RPC 运行时会选择用 RST_STREAM 帧告知对端。gRPC 运行时实现应把 RST_STREAM 解释为流的立即完全关闭,并把错误向上传播到调用方应用层。

规范定义了 RST_STREAM 错误码到 gRPC 错误码的映射表:

HTTP/2 Code gRPC Code
NO_ERROR (0) INTERNAL —— 本应显式发送 OK 状态;此情形也可能用于某些场景下激进的 lameduck(摘除慢节点)
PROTOCOL_ERROR (1) INTERNAL
INTERNAL_ERROR (2) INTERNAL
FLOW_CONTROL_ERROR (3) INTERNAL
SETTINGS_TIMEOUT (4) INTERNAL
STREAM_CLOSED 无映射——此时已无打开的流可供传播。实现应记录日志
FRAME_SIZE_ERROR INTERNAL
REFUSED_STREAM UNAVAILABLE —— 表示未发生任何处理,该请求可以被重试,甚至可在其他节点重试
CANCEL (8) 客户端发出时映射为"调用被取消";服务端发出时映射为 CANCELLED。注意:服务端只应在"需要取消调用但消息字节序列不完整"的场景使用此机制
COMPRESSION_ERROR INTERNAL
CONNECT_ERROR INTERNAL
ENHANCE_YOUR_CALM RESOURCE_EXHAUSTED —— 由运行时附加错误详情,指明耗尽的资源是带宽
INADEQUATE_SECURITY PERMISSION_DENIED —— 附加详情说明权限被拒绝,原因是所用协议的安全性不足以支撑该调用

这套映射在当前仓库源码中有精确对应。src/core/ext/transport/chttp2/transport/http2_status.h 定义了与 RFC 9113 一致的 Http2ErrorCode 枚举(kNoError=0x0kInadequateSecurity=0xc 等),而 ErrorCodeToAbslStatusCodehttp2_status.h#L58-L77)实现了从 HTTP/2 错误码到 gRPC 状态码的转换:

  • kEnhanceYourCalmkResourceExhausted(对应表中 ENHANCE_YOUR_CALM → RESOURCE_EXHAUSTED)
  • kInadequateSecuritykPermissionDenied(对应 INADEQUATE_SECURITY → PERMISSION_DENIED)
  • kRefusedStreamkUnavailable(对应 REFUSED_STREAM → UNAVAILABLE,即"可重试"状态)
  • kCancel → 以当前时间是否已超过 deadline 区分 kDeadlineExceeded / kCancelled
  • 其余错误码默认落到 kInternal,与表中 NO_ERROR/PROTOCOL_ERROR/INTERNAL_ERROR/FLOW_CONTROL_ERROR/SETTINGS_TIMEOUT/COMPRESSION_ERROR/CONNECT_ERROR 均映射为 INTERNAL 的规定一致

反向转换 AbslStatusCodeToErrorCodehttp2_status.h#L83-L100)则用于本地 gRPC 状态需要编码进 RST_STREAM/GOAWAY 帧时的选型。此外该文件中的 Http2Status 类明确区分 kConnectionError(用于 GOAWAY 帧的错误码)与 kStreamError(用于 RST_STREAM 帧的错误码)两种错误类型,与规范中"流错误 vs 连接错误"的边界划分相呼应。

安全(Security)

HTTP/2 规范强制:使用 TLS 时必须为 TLS 1.2 或更高版本;同时规范对允许的密码套件施加了额外约束以规避已知问题,并要求支持 SNI。此外可以预期 HTTP/2 会与私有传输安全机制配合使用,对此规范不做具体建议。对应地,上文 INADEQUATE_SECURITY → PERMISSION_DENIED 的映射正是当协议安全性不足以支撑调用时的拒绝路径。

连接管理

GOAWAY 帧

GOAWAY 由服务端发给客户端,表示该连接不再接受新的流,帧中携带服务端最后成功接受的流 id。客户端应把在"最后成功接受的流"之后发起的所有流视为 UNAVAILABLE,并将调用重试到别处;对于已被接受的流,客户端可以继续使用直到其完成或连接终止。规范建议:服务端在终止连接之前应先发送 GOAWAY,以便可靠告知客户端哪些工作已被接受并正在执行。

源码中 GOAWAY 帧的收发由 src/core/ext/transport/chttp2/transport/frame_goaway.ccsrc/core/ext/transport/chttp2/transport/goaway.cc 等文件实现,http2_status.hGetConnectionErrorCode 的注释也明确写道:其用途之一就是"确定应写入 HTTP2 GOAWAY 帧的错误码"。

PING 帧

客户端与服务端都可以发送 PING 帧,对端必须精确回显收到的内容。用途有二:断言连接仍然存活(liveness),以及提供估算端到端延迟的手段。超时语义如下:

  • 服务端发起的 PING 若在运行时预期的期限之内未收到响应,该服务端上所有未决调用将以 CANCELLED 状态关闭;
  • 客户端发起的 PING 超期,会导致所有调用以 UNAVAILABLE 状态关闭。

PING 的频率高度依赖网络环境,实现可以自由地依据网络与应用需求调整 PING 频率。仓库中相关的实现文件包括 src/core/ext/transport/chttp2/transport/keepalive.ccsrc/core/ext/transport/chttp2/transport/frame_ping.cc,以及防止对端被 PING 洪泛的 ping_abuse_policy.ccping_rate_policy.cc——从文件结构可以推断,传输层对 PING 的发送频率与滥用防护做了独立策略化管理。

连接失败(Connection failure)

如果客户端检测到可识别的连接失败,所有调用以 UNAVAILABLE 状态关闭;对服务端而言,未决调用以 CANCELLED 状态关闭。这与 PING 超时路径的语义(客户端侧 UNAVAILABLE / 服务端侧 CANCELLED)保持一致:客户端视角把"连接死了"解释为"服务暂不可用,可换路重试",服务端视角把"流没了"解释为"取消"。

附录 A:GRPC for Protobuf

由 protobuf 声明的服务接口,可以通过 protoc 的代码生成扩展轻松映射到 gRPC。规范定义的映射关系:

Service-Name   → ?( {proto 包名} "." ) {服务名}
Message-Type   → {完全限定的 proto 消息名}
Content-Type   → "application/grpc+proto"
Status-Details → {google.rpc.Status proto 消息}

即::path 中的 Service-Name 采用"proto 包名(可选)+ 点号 + 服务名";grpc-message-type 头携带完全限定的消息类型名;content-type 固定为 application/grpc+proto;而错误详情头 grpc-status-details-bin 中 Base64 编码的正是 google.rpc.Status 消息本体,其内部 status code 字段不得与 grpc-status 头矛盾(消费方必须校验)。仓库中 src/compiler/ 目录下的 cpp_generator.ccpython_generator.cc 等各语言代码生成器,以及 examples/protos/helloworld.proto 等示例 proto,就是这套"proto → 各语言 gRPC stub"映射关系的具体落地。

小结:从规范到实现的验证路径

本文沿 doc/PROTOCOL-HTTP2.md 的完整脉络,梳理了 gRPC over HTTP/2 的三层要点:

  1. 报文层:Request-Headers / Length-Prefixed-Message / Trailers 三段式结构,以及 grpc-timeout(最多 8 位数字 + H/M/S/m/u/n 单位)、te: trailers 代理探测、application/grpc 前缀校验(否则 415)、-bin 二进制头的 Base64 编解码与 , 切分规则、8 KiB 头大小建议;
  2. 语义层:Status 必须恒在 Trailers 中出现、grpc-message 的 UTF-8 + percent-encoding 容错解码、grpc-status-details-bin 仅对非 OK 状态携带且 status code 不得矛盾、UNAVAILABLE vs CANCELLED 在客户端/服务端视角的对称性;
  3. 传输层:stream-id 作为调用 id、DATA 帧与消息边界解耦、RST_STREAM 到 gRPC 错误码的 12 项映射(在 src/core/ext/transport/chttp2/transport/http2_status.h 中可逐条核对)、GOAWAY 与 PING 的连接管理职责。

读者可以按"规范 ABNF → 元数据 trait(src/core/call/metadata_batch.h)→ chttp2 帧处理(src/core/ext/transport/chttp2/transport/)"的路径在当前仓库中自行验证上述每一条规则,测试侧 test/core/transport/ 下还有大量 .headers 帧级测试数据与端到端用例可供进一步深入。

登录后查看全文
热门项目推荐
相关项目推荐