grpc HTTP2使用

简介

本文档详细描述grpc是如何借助HTTP2实现的。您需要熟悉HTTP2规范。

Protocal

生产规则使用ABNF语法。

Outline

以下是GRPC请求和响应消息流中的一般顺序

  • Request → Request-Headers *Length-Prefixed-Message EOS
  • Response → (Response-Headers *Length-Prefixed-Message Trailers) / Trailers-Only

Request

  • Request → Request-Headers *Length-Prefixed-Message EOS
    Request-Headers会作为HTTP2的HEADERS + CONTINUATION 帧来传输.(注:CONTINUATION帧是分片传输时使用)
  • 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

HTTP2要求reserved headers(以“:”开头的header)出现在所有其他标头之前。此外,其他的实现中应该在reserved headers之后立即发送Timeout,并且应该将Call-Definition头早于Custom-Metadata发送。

某些gRPC实现可能允许覆盖上面列表中的Path格式,但是强烈建议不要使用此功能。gRPC不会打破使用这种方式的用户,但是我们不乐意支持它,因为当路径不是上面显示的形式时,某些功能(例如,service config support)将不起作用。

如果省略了Timeout,则服务器应假定无限超时。客户端实现可根据其部署要求自由发送默认的最小超时。

如果Content-Type并非以“ application / grpc”开头,则gRPC服务器会返回415(不支持的媒体类型)。这可以防止其他HTTP/2客户机将状态为200 (OK)的gRPC错误响应解释为成功。(注:通过application / grpc来区分grpc请求和普通HTTP2请求)

Custom-Metadata是由应用程序层定义的任意键/值对集合。header名称以“ grpc-”开头,但未在此处列出保留以供将来的GRPC使用,并且这些保留header不应被应用程序用作Custom-Metadata。

请注意,HTTP2并不是使用随意的八字节序列来作为headers,二进制header值必须按照https://tools.ietf.org/html/rfc4648#section-4使用Base64进行编码。具体实现会接受填充的和未填充的值,并且发出未填充的值(???)。应用程序以“-bin”结尾来定义二进制headers。Runtime库会使用此后缀来检测二进制headers,并在发送和接收headers时使用base64编码和解码。

除了名称相同的header外,不保证Custom-Metadata header的顺序。重复的headers名称可以将其值以“,”作为分隔符,并在语义上等效。在Base64解码之前,这些Binary-Header是通过“,”来分隔的。

ASCII-Value不应在前或尾有空格。如果它包含前或尾空格,则可能会删除它。ASCII-Value定义的字符范围比HTTP更严格。具体的实现中要做到不会因接收到无效的ASCII-Value但在HTTP中为有效的字段值而抛错,但没有严格定义确切的行为:它们可能会丢弃该值或接受该值。如果接受了该值,则必须注意确保允许应用程序将值作为元数据回传也能够接受(注:就是双边都要能够接受)。例如,如果将元数据作为请求中的list提供给应用程序,则应用程序不会因为响应有相同的元数据列表而抛出错误。

服务器可能会限制请求header的大小,建议默认值为8 KiB。提倡在具体的实现中计算总header大小,例如HTTP / 2的SETTINGS_MAX_HEADER_LIST_SIZE:所有标头字段的总和 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项放在DATA帧中传递

  • Length-Prefixed-Message → Compressed-Flag Message-Length Message
  • Compressed-Flag → 0 / 1 # encoded as 1 byte unsigned integer
  • Message-Length → {length of Message} # encoded as 4 byte unsigned integer (big endian)
  • Message → *{binary octet}

Compressed-Flag值为1表示二进制八字节序列的Message是使用Message-Encoding声明的方式压缩了。值为0表示未发生消息字节的编码。Compression contexts 在消息处理后就不保留了,在具体实现里必须为流中的每个消息创建一个新的上下文。如果省略了Message-Encoding标头,则Compressed-Flag必须为0。

对于请求,EOS(end-of-stream)是通过最后接收到的DATA帧上存在END_STREAM标志来判断的。在需要关闭请求流而且也没有数据要发送的情况下,具体的实现中会发送一个设置了该标志(END_STREAM)的空数据帧。

Response

  • 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

Response-Headers 和 Trailers-Only都是在一个HTTP2 HEADERS帧中传递。一般多数响应都具有headers和trailers的,但是允许使用Trailers-Only发送一个即时错误。即使状态代码是OK,也必须在Trailers中发送状态。

对于响应的EOS则是通过最后接收到的带有Trailer的HEADERS帧上END_STREAM标志的存在来指示的。

具体实现应该预料到有问题的部署会响应中发送非200个HTTP状态码,以及各种非grpc内容的类型,具体实现则必须生成Status和Status-Message,以便在发生这种情况时传播到应用层。

客户端可以限制Response-HeaderTrailerTrailer-Only的大小,建议每个默认值为8 KiB。

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

Status-Message的值在是Unicode字符串描述的error信息,物理编码为UTF-8,后跟着 percent-encoding。 percent-encoding在RFC 3986§2.1中指定,尽管此处使用的形式具有不同的restricted characters。当解码无效值时,具体实现不得出错或丢弃该消息。在最坏的情况下,具体实现可能完全中止对status message的解码,以使用户会收到原始的百分比编码形式。或者,具体实现可以解码有效部分,同时保持损坏的%-encodings不变,或者用“?”或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.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来干啥,但是建议客户端提供结构化的user-agent string 来提供调用库的描述、版本和平台描述信息,这样方便在异构环境中进行问题诊断。建议库开发人员使用以下结构

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调用不是幂等的。特别的:

  • 不会重试一个不确定是否开始了的call
  • 由于没有必要,因此没有duplicate suppression的机制 (???)
  • 标记为幂等的call可能会多次发送

HTTP2 Transport Mapping

Stream Identification

所有GRPC调用都需要指定一个内部ID。我们将使用HTTP2 stream-ids作为call标识符。注意:这些ID与打开的HTTP2会话相关,并且在处理多个HTTP2会话的进程中也不是唯一的,也不能用作GUID。

Data Frames

DATA帧边界与Length-Prefixed-Message边界没有关系,具体实现不应对其对齐方式进行任何假设。

Error

在应用或者runtime运行RPC期间发生错误时, Status和Status-Message会包含在Trailers中

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

RST_STREAM错误代码到GRPC错误代码的映射关系如下:
在这里插入图片描述

Security

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

Connection Management

GOAWAY Frame
由服务器发送给客户端,用来告诉客户端,服务端将不再接受相关连接上的任何新流。该帧包含服务器上次成功接受的流的ID。客户端应将上次成功接受的流之后启动的任何流视为UNAVAILABLE ,并在其他地方重试call。客户端可以自由地继续使用已经接受的流,直到它们完成或连接终止。服务器应在终止连接之前发送GOAWAY,以可靠地通知客户端服务器已经接受并且正在执行的工作。

PING Frame
客户端和服务器都可以发送PING帧,对等方必须通过准确地响应它们收到的内容。这用于断言该连接是否有效,并提供一种估算端到端延迟的方法。如果服务器发送的PING在运行时deadline内未收到响应,则服务器上所有未完成的call将以CANCELED状态关闭。客户端发送的PING过期将导致所有call以UNAVAILABLE状态关闭。请注意,PING的频率高度依赖于网络环境,具体实现可以根据网络和应用程序的要求自由调整PING频率。

Connection failure
如果在客户端上发生可检测到的连接失败,则所有呼叫将以UNAVAILABLE状态关闭。对于服务器,打开的呼叫将以CANCELLED 状态关闭。

Appendix A - GRPC for Protobuf

通过protoc的代码生成工具,可以轻松的将protobuf声明的服务接口映射到GRPC。以下定义了要使用的映射。

  • Service-Name → ?( {proto package name} “.” ) {service name}
  • Message-Type → {fully qualified proto message name}
  • Content-Type → “application/grpc+proto”

参考:
https://stackoverflow.com/questions/56711666/why-does-grpc-uses-length-prefixed-messages
https://www.cnblogs.com/ghj1976/p/4581426.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章