文章来源:https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md
本文用于详细描述gRPC承载HTTP2帧的实现。
Outline
以下是grpc请求和响应流中消息原子的一般顺序:
- Request → Request-Headers *Length-Prefixed-Message EOS
- Response → (Response-Headers *Length-Prefixed-Message Trailers) / Trailers-Only
- Request → Request-Headers *Length-Prefixed-Message EOS
Request-Headers 在 HEADERS + CONTINUATION 帧中被发送给HTTP2的headers。 - Request-Headers → Call-Definition *Custom-Metadata
- Call-Definition → Method Scheme Path TE [Authority] [Timeout] Content-Type [Message-Type] [Message-Encoding] [Message-Accept-Encoding] [User-Agent]
- Method → “:method POST”
- Scheme → ":scheme " (“http” / “https”)
- Path → “:path” “/” Service-Name “/” {method name} # But see note below.
- Service-Name → {IDL-specific service name}
- Authority → “:authority” {virtual host name of authority}
- TE → “te” “trailers” # Used to detect incompatible proxies
- Timeout → “grpc-timeout” TimeoutValue TimeoutUnit
- TimeoutValue → {positive integer as ASCII string of at most 8 digits}
- 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” / {custom})]
- Content-Coding → “identity” / “gzip” / “deflate” / “snappy” / {custom}
- Message-Encoding → “grpc-encoding” Content-Coding
- Message-Accept-Encoding → “grpc-accept-encoding” Content-Coding *("," Content-Coding)
- User-Agent → “user-agent” {structured user-agent string}
- Message-Type → “grpc-message-type” {type name for message schema}
- Custom-Metadata → Binary-Header / ASCII-Header
- Binary-Header → {Header-Name “-bin” } {base64 encoded value}
- ASCII-Header → Header-Name ASCII-Value
- Header-Name → 1*( %x30-39 / %x61-7A / “_” / “-” / “.”) ; 0-9 a-z _ - .
- ASCII-Value → 1*( %x20-%x7E ) ; space and printable ASCII
某些gRPC实现可能允许覆盖上面显示的Path格式,但强烈建议不要使用此功能。gRPC不会破坏使用这种覆盖用户,但是我们不主动支持它,并且当路径不是上面显示的形式时,某些功能(如,服务配置支持)将不起作用。
如果省略Timeout,服务器应该假定无限超时。客户端实现可以根据其部署要求自由发送默认的最小超时。
Custom-Metadata 是由应用程序层定义的一组任意键值对。以“grpc-”开头但未在此处列出的标题名称保留,用于将来的grpc使用,不应被应用程序用作Custom-Metadata。
请注意,HTTP2不允许任意八位字节序列用于标头值,因此必须使用Base64对二进制标头值进行编码。实现必须接收填充和未填充的值,并应发出未填充的值。应用程序通过使其名称以“-bin”结尾来定义二进制标头。运行时库使用此后缀来检测二进制标头,并在发送和接收标头时正确应用base64编码和解码。
除了具有重复标题名称的值之外,不保证自定义元数据标题顺序。重复的标题名称可以将其值与“,”作为分隔符连接,并在语义上等效。在解码Base64编码值之前,实现必须在“,”拆分Binary-Headers。
ASCII值不应具有前导或尾随空格。如果它包含前导或尾随空格,则可能会将其删除。定义的ASCII-Value字符范围比HTTP更严格。由于接收到HTTP中有效字段值的无效ASCII值,实现不得出错,但不严格定义精确行为:它们可能抛弃值或接收该值。如果接收,必须注意确保允许应用程序将值作为元数据回显。如,如果原数据作为请求中的列表提供给应用程序,则应用程序不应通过提供与响应中的元数据相同的列表来触发错误。
服务器可能会限制Request-Headers的大小,建议默认值为8 KiB。鼓励应用实现中计算总标头大小,如HttP/2的
SETTINGS_MAX_HEADER_LIST_SIZE
:所有头字段的总和,对于每个字段,未压缩字段名称和值长度加上32的总和,二进制值得长度为post-Base64.Length-Prefixed-Message的重复序列在DATA帧中传递
- Length-Prefixed-Message → 消息长度压缩标志信息
- Compressed-Flag → 0 / 1 # 1表示无符号整数
- Message-Length → {length of Message} # 4字节无符号整数(大端)
- Message → *{binary octet}
对于请求,EOS(流结束)由最后接收的DATA帧上存在 END_STREAM 标志指示。在需要关闭请求流但没有数据要发送的情况下,实现必须发送一个空数据帧,并设置此标志。
Responses
- 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] *Custom-Metadata
- HTTP-Status → “:status 200”
- Status → “grpc-status” 1*DIGIT ; 0-9
- Status-Message → “grpc-message” Percent-Encoded
- Percent-Encoded → 1*(Percent-Byte-Unencoded / Percent-Byte-Encoded)
- Percent-Byte-Unencoded → 1*( %x20-%x24 / %x26-%x7E ) ; space and VCHAR, except %
- Percent-Byte-Encoded → “%” 2HEXDIGIT ; 0-9 A-F
对于响应,在最后接收到的携带Trailers的HEADERS帧上存在END_STREAM标志表示流末尾。
实现应该预期破坏的部署在响应中发送非200HTTP状态码,及各种非grpc内容类型并且省略Status & Status-Message。实现时必须合成 Status & Status-Message 以传播到应用程序层。
clients可能会限制Response-Headers,Trailers和Trailers-Only的大小,每个建议的默认值为8KiB。
Status的值部分是十进制编码的整数,作为ASCII字符串,没有任何前导零。
Status-Message的值部分在概念上是错误的Unicode字符串描述,物理编码为UTF-8,后跟百分号编码(url编码)。在 RFC 3986 §2.1 中指定了百分号编码,尽管此处使用的表单具有不同的受限字符。解码为无效值时,实现不能报错或者丢弃message。在最坏的情况下,实现可以完全终止对状态消息的解码,使得用户将接收原始的百分号编码形式。或者,实现可以解码有效部分,同时保留损坏的%-编码,或者用替换字符(如,’?’ 或Unicode替换字符)替换它们。
Example 简单的一元调用展示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.wef315yfh138vh31hv93hv8h3vDATA (flags = END_STREAM)
Response
HEADERS (flags = END_HEADERS)
:status = 200
grpc-encoding = gzip
content-type = application/grpc+protoDATA
HEADERS (flags = END_STREAM, END_HEADERS)
grpc-status = 0 # OK
trace-proto-bin = jher831yy13JHy3hc
User Agents 虽然该协议不需要用户代理来运行,但建议客户端提供一个结构化的用户处理字符串,该字符串提供调用库、版本&平台的基本描述,以促进异构环境中的问题诊断。建议库开发人员使用以下结构
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)
Idempotency and Retries 除非明确定义,否则gRPC调用不被认为是幂等的。特别:
- 无法验证已启动的调用将不会重试。
- 没有重复抑制机制,因为没有必要。
- 标记为幂等的调用可多次发送。
所有GRPC调用都需要指定内部ID。我们将在此方案中使用HTTP2 stream-id作为呼叫标识符。注意:这些ID是开放式HTTP2会话的上下文,并且在处理多个HTTP2会话的给定进程中不是唯一的,也不能用作GUID。
Data Frames
DATA帧边界与Length-Prefixed-Message边界无关,并且实现不应对其边界对齐做出任何假设。
Errors
在RPC期间发生应用程序或运行时错误时,将在Trailers中传递Status和Status-Message。
在某些情况下,消息流的成帧可能已损坏,RPC运行时将选择使用RST_STREAM帧向其peer指示此状态。RPC运行时实现应该将RST_STREAM解释为立即完全关闭的流,并且应该将错误传播到调用应用程序层。
采用如下的RST_STREAM错误代码映射到GRPC错误代码。
HTTP2 Code | GRPC Code |
---|---|
NO_ERROR(0) | INTERNAL - An explicit GRPC status of OK should have been sent but this might be used to aggressively lameduck in some scenarios. |
PROTOCOL_ERROR(1) | INTERNAL |
INTERNAL_ERROR(2) | INTERNAL |
FLOW_CONTROL_ERROR(3) | INTERNAL |
SETTINGS_TIMEOUT(4) | INTERNAL |
STREAM_CLOSED | No mapping as there is no open stream to propagate to. Implementations should log. |
FRAME_SIZE_ERROR | INTERNAL |
REFUSED_STREAM | UNAVAILABLE - Indicates that no processing occurred and the request can be retried, possibly elsewhere. |
CANCEL(8) | Mapped to call cancellation when sent by a client.Mapped to CANCELLED when sent by a server. Note that servers should only use this mechanism when they need to cancel a call but the payload byte sequence is incomplete. |
COMPRESSION_ERROR | INTERNAL |
CONNECT_ERROR | INTERNAL |
ENHANCE_YOUR_CALM | RESOURCE_EXHAUSTED …with additional error detail provided by runtime to indicate that the exhausted resource is bandwidth. |
INADEQUATE_SECURITY | PERMISSION_DENIED … with additional detail indicating that permission was denied as protocol is not secure enough for call. |
当TLS与HTTP2一起使用时,HTTP2规范要求使用TLS 1.2或更高版本。它还对部署中允许的密码施加了一些额外的限制,以避免已知问题以及需要SNI支持。期望HTTP2将与专有传输安全机制结合使用,规范不能对其提出任何有意义的建议。
Connection Management
GOAWAY Frame
由服务器发送到客户端以指示它们将不再接受关联连接上的任何新流。该帧包括服务器最后成功接受的流的id。客户端应该将上次成功接受的流后启动的任何流视为UNAVAILABLE,并在其他位置重试该调用。客户端可以继续使用已接受的流,直到它们完成或连接终止。
服务器应在终止连接之前发送GOAWAY,以便可靠地通知客户端哪些工作已被服务器接受并正在执行。
PING Frame
客户端和服务器都可以发送一个PING帧,其peer必须应答,来精确回应其接受到的是什么。这用于断言连接仍然存在,以及提供估算端到端延迟的方法。如果服务器启动PING未在运行时期望的截止期限内收到响应,则服务器上的所有未完成调用将以CANCELED状态关闭。过期的客户端启动PING将导致所有呼叫以UNAVAILABLE状态关闭。注意,PING的频率非常依赖于网络环境,实现可以根据网络和应用要求自由调整PING频率。
Connection failure
如果客户端上发生可检测的连接故障,则将以UNAVAILABLE状态关闭所有呼叫。对于服务器,将以CANCELED状态关闭打开的呼叫。
附录A - GRPC for Protobuf
【gRPC over HTTP2】protobuf声明的服务接口很容易通过protoc的代码生成扩展到GRPC上。以下定义要使用的映射。
- Service-Name → ?( {proto package name} “.” ) {service name}
- Message-Type → {fully qualified proto message name}
- Content-Type → “application/grpc+proto”