[译]gRPC over HTTP2

2020-08-24  本文已影响0人  joyousx

gRPC over HTTP2

原文:https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md

介绍

本文主要描述 grpc 基于 http2 framing 的实现

大纲

下述是在一次grpc请求和应答里通用的消息原子组成:

Requests

Request-Headers

Request-Headers 被当作用 http2 headers,通过 HEADERS + CONTINUATION 帧来传输

HTTP2 协议要求 reserved headers (以 ":" 开头的 header) 必须出现在其他 headers 之前。此外,还要求 Timeout 头必须紧跟在 reserved headers 之后被发送。还有就是,Call-Definition 需要在发送 Custom-Metadata 之前发送。

一些 gRPC 的实现可能会允许上面的 Path 格式被覆盖,但是这样的功能是强烈不推荐的。gRPC 不会特意的去阻止用户使用 Path 格式覆盖这个功能, 但我们绝不会主动的支持它,另外就是当 PATH 的格式不是上述那样的话, 一些功能(如:service config support)可能会不能正常工作。

在 Timeout 被缺省时, 服务端应当假定 Timeout 是无穷大的。客户端实现可以根据其部署要求自由发送默认的最小超时时间。

如果 Content-Type 不是以 "application/grpc" 开头时, gRPC 服务端应当以 HTTP 状态码 415 (Unsupported Media Type) 进行应答。这样, 能阻断 HTTP/2 的客户端(以状态码200标识成功)解析一个 gRPC 错误。

Custom-Metadata 是一系列由应用层定义的 key-value 键值对组成的集合。应用程序不能使用以 "grpc-" 开头的 Header 名作为 Custom-Metadata 的 key, 因为以 "grpc-" 开头的 Header 名被用于预留的在未来给 GRPC 使用。

注意,因为 HTTP2 不允许二进制序列作为 header 的值,所以二进制的 header 值必须使用 Base64 编码。实现时必须可以接收 padded 和 un-padded 的值,发送 un-padded 的值。应用程序定义的二进制 header 名需要以 "-bin" 为后缀。运行库可以使用这个后缀检测出二进制的 headers,然后在发送和接收时使用 base64 进行加解密。

Custom-Metadata 不能保证 header 的顺序除非有重复的 header 名。当有重复的 header 名时,在语义上等价于将他们的值以 "," 作为分隔符拼接起来。所以,实现时需要注意,对于 二进制类型的 header,需要先用 "," 进行分割,然后再用 base64 进行解密处理。

Custom-Metadata 的 ASCII 类型的值,不能有空格做前后缀,如果有的话,会被剥离掉。另外,这里可以使用的 ASCII 字符范围比 HTTP 更加严格。实现时,不能因为接收到在 HTTP 有效但在 Custom-Metadata 中无效的 ASCII 字符而出错,但是却没有严格定义具体的行为:可以抛弃也可以接受这个值。如果被接受,必须注意确保允许应用程序将值作为元数据回显。例如,如果这个元数据被当作一个列表在一个请求中传递给应用程序,那么如果应用程序把这个元数据放在应答中时不应引发一个错误。

服务端可以限制 Request-Headers 的大小,建议默认 8kb。建议实现时计算 header 的总大小,像 HTTP/2 的 SETTINGS_MAX_HEADER_LIST_SIZE 那样:the sum of all header fields, for each field the sum of the uncompressed field name and value lengths plus 32, with binary values' lengths being post-Base64.

Length-Prefixed-Message

一个 Request 中可以有多个 Length-Prefixed-Message 数据,它们通过 http2 的 DATA 帧进行传输。

当 Compressed-Flag 值为1时,标识着二进制的 Message 是使用 Message-Encoding 头中声明的方式压缩过的。值为0时,意味着没有被压缩。
如果 Message-Encoding 头缺省的情况下,Compressed-Flag 值必须为0。压缩的上下文信息不会在跨越 message 边界时依然被维护,所以,实现时必须为流中的每一个 message 创建一个新的上下文。

EOS(end-of-stream)

对于请求来说,EOS (end-of-stream) 通过接收到的最后一个 DATA 帧中的 END_STREAM 标记来标识的。在 Request 需要被关闭但是又没有数据需要发送的情况下,gRPC的实现必须发送一个包含 END_STREAM 标记的空 DATA 帧。

Responses

Response-Headers

Response-Headers 和 Trailers-Only 都是通过一个独立的 HTTP2 HEADERS 帧块进行传输。大多数的 responses 都应该既有 headers 又有 trailers,但是 Trailers-Only 当在需要产生一个即时错误时是被允许的。另外,Status 信息必须在 Trailers 中,即使 status code 是 OK.

对于 responses 来说,end-of-stream 是通过最后一个传输 Trailers 的 HEADERS 帧的 END_STREAM 标记来标示的。

实现时应当期望 broken deployments 在响应中发送 非200 的 HTTP状态代码以及各种非GRPC内容类型并且省略状态和状态消息。当这种情况发生时,实现时必须合成一个 Status & Status-Message 以传播给应用层

客户端应当限制 Response-Headers, Trailers, 或 Trailers-Only 的大小,一般推荐上述对象的大小限制都是 8Kb

Status 的值部分是十进制编码的整数,作为ASCII字符串,没有任何前导零

Status-Message 的值部分理论上应当是一个描述错误的 Unicode 字符串,实际上多用 UTF-8 跟着是 url 编码(percent-encoding)。当解码无效值时,该实现一定不能抛出错误 或者 丢弃这个 message。最坏情况,就是终止解码这个 Status-Message,这样用户可以接收到原始的 url 编码格式数据。或者,该实现可以解码有效部分,同时保留损坏的% - 编码,或者用替换字符(例如,'?'或Unicode替换字符)替换它们。

Example

以下以 unary-call 为例展示 HTTP2 的帧序列

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

User Agents

虽然协议不要求用户代理来运行,但建议客户端提供结构化的用户代理字符串,该字符串提供了调用库、版本和平台的基本描述,以便在异构环境中进行问题诊断。建议库开发人员使用以下结构:
User-Agent → "grpc-" Language ?("-" Variant) "/" Version ?( " (" *(AdditionalProperty ";") ")" )
E.g.

    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 调用不应被假设为幂等的. 特别地:

HTTP2 Transport Mapping

Stream Identification

所有 GRPC 调用都需要指定一个内部ID。我们将在此方案中使用 HTTP2 stream-id 作为调用标识符。注意:这些 ID 是已打开 HTTP2 会话的上下文,并且,在处理多个 HTTP2 会话的给定进程中不是唯一的,也不能用作GUID。

Data Frames

DATA 帧边界与 Length-Prefixed-Message 边界无关,并且实现不应对其对齐做出任何假设。

Errors

在 RPC 期间发生应用程序或运行时错误时,将在 Trailers 中传递 Status 和 Status-Message。

在某些情况下,消息流的帧可能已损坏,RPC 运行时将选择使用 RST_STREAM 帧向其对等方指示此状态。RPC运行时实现应该将 RST_STREAM 解释为流的立即 full-closure,并且应该将错误传播到调用应用程序层。

Security

当 TLS 与 HTTP2 一起使用时,HTTP2 规范要求使用 TLS 1.2 或更高版本。它还对部署中允许的密码施加了一些额外的限制,以避免已知问题以及需要SNI支持。另外,预计 HTTP2 将与专有传输安全机制结合使用,此时规范不能对其提出任何有意义的建议

Connection Management

GOAWAY Frame

由 servers 发送给 clients 用于标识 servers 不再在相关的连接上接收任何新的 stream。这个帧包含了 server 成功接收的最后一个 stream id。clients 应该认为最后一个被 server 成功接收的 stream 之后的任何 stream 都是 UNAVAILABLE,应当在其他地方进行重试。clients 可以继续处理已接受的流,直到它们完成或连接终止。

servers 应当在终止连接前发送 GOAWAY 帧,以可靠地通知 clients 哪些工作已被服务器接受并正在执行。

PING Frame

clients 和 servers 都可以发送 PING 帧,对端必须以它接收到的内容进行回复。它被用来断定连接依然存活,并提供了一种评估 end-to-end 延迟的方法。如果 servers 启动的 PING 未在运行时期望的截止期限内收到响应,则服务器上的所有未完成调用将以 CANCELED 状态关闭。如果 clients 启动的 PING 超时未收到响应时,将导致所有调用以 UNAVAILABLE 状态关闭。请注意,PING 的频率高度依赖于网络环境,实现可以根据网络和应用要求自由调整 PING 频率。

Connection failure

如果客户端上发生可检测的连接故障,则将以 UNAVAILABLE 状态关闭所有调用。对于服务器,将以 CANCELED 状态关闭打开的调用。

Appendix A - GRPC for Protobuf

  1. protobuf 声明的服务接口很容易通过 protoc 的代码生成扩展映射到 GRPC 上。以下定义要使用的映射。
    • Service-Name → ?( {proto package name} "." ) {service name}
    • Message-Type → {fully qualified proto message name}
    • Content-Type → "application/grpc+proto"
上一篇下一篇

猜你喜欢

热点阅读