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

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