[譯]gRPC over HTTP2

gRPC over HTTP2

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

介紹

本文主要描述 grpc 基于 http2 framing 的實(shí)現(xiàn)

大綱

下述是在一次grpc請(qǐng)求和應(yīng)答里通用的消息原子組成:

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

Requests

  • Request → Request-Headers *Length-Prefixed-Message EOS

Request-Headers

Request-Headers 被當(dāng)作用 http2 headers,通過 HEADERS + 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 協(xié)議要求 reserved headers (以 ":" 開頭的 header) 必須出現(xiàn)在其他 headers 之前闺鲸。此外,還要求 Timeout 頭必須緊跟在 reserved headers 之后被發(fā)送换帜。還有就是戏自,Call-Definition 需要在發(fā)送 Custom-Metadata 之前發(fā)送。

一些 gRPC 的實(shí)現(xiàn)可能會(huì)允許上面的 Path 格式被覆蓋,但是這樣的功能是強(qiáng)烈不推薦的响驴。gRPC 不會(huì)特意的去阻止用戶使用 Path 格式覆蓋這個(gè)功能, 但我們絕不會(huì)主動(dòng)的支持它,另外就是當(dāng) PATH 的格式不是上述那樣的話, 一些功能(如:service config support)可能會(huì)不能正常工作撕蔼。

在 Timeout 被缺省時(shí), 服務(wù)端應(yīng)當(dāng)假定 Timeout 是無窮大的豁鲤。客戶端實(shí)現(xiàn)可以根據(jù)其部署要求自由發(fā)送默認(rèn)的最小超時(shí)時(shí)間鲸沮。

如果 Content-Type 不是以 "application/grpc" 開頭時(shí), gRPC 服務(wù)端應(yīng)當(dāng)以 HTTP 狀態(tài)碼 415 (Unsupported Media Type) 進(jìn)行應(yīng)答琳骡。這樣, 能阻斷 HTTP/2 的客戶端(以狀態(tài)碼200標(biāo)識(shí)成功)解析一個(gè) gRPC 錯(cuò)誤。

Custom-Metadata 是一系列由應(yīng)用層定義的 key-value 鍵值對(duì)組成的集合讼溺。應(yīng)用程序不能使用以 "grpc-" 開頭的 Header 名作為 Custom-Metadata 的 key, 因?yàn)橐?"grpc-" 開頭的 Header 名被用于預(yù)留的在未來給 GRPC 使用楣号。

注意,因?yàn)?HTTP2 不允許二進(jìn)制序列作為 header 的值,所以二進(jìn)制的 header 值必須使用 Base64 編碼炫狱。實(shí)現(xiàn)時(shí)必須可以接收 padded 和 un-padded 的值藻懒,發(fā)送 un-padded 的值。應(yīng)用程序定義的二進(jìn)制 header 名需要以 "-bin" 為后綴视译。運(yùn)行庫可以使用這個(gè)后綴檢測(cè)出二進(jìn)制的 headers嬉荆,然后在發(fā)送和接收時(shí)使用 base64 進(jìn)行加解密。

Custom-Metadata 不能保證 header 的順序除非有重復(fù)的 header 名憎亚。當(dāng)有重復(fù)的 header 名時(shí)员寇,在語義上等價(jià)于將他們的值以 "," 作為分隔符拼接起來。所以第美,實(shí)現(xiàn)時(shí)需要注意蝶锋,對(duì)于 二進(jìn)制類型的 header,需要先用 "," 進(jìn)行分割什往,然后再用 base64 進(jìn)行解密處理扳缕。

Custom-Metadata 的 ASCII 類型的值,不能有空格做前后綴别威,如果有的話躯舔,會(huì)被剝離掉。另外省古,這里可以使用的 ASCII 字符范圍比 HTTP 更加嚴(yán)格粥庄。實(shí)現(xiàn)時(shí),不能因?yàn)榻邮盏皆?HTTP 有效但在 Custom-Metadata 中無效的 ASCII 字符而出錯(cuò)豺妓,但是卻沒有嚴(yán)格定義具體的行為:可以拋棄也可以接受這個(gè)值惜互。如果被接受,必須注意確保允許應(yīng)用程序?qū)⒅底鳛樵獢?shù)據(jù)回顯琳拭。例如训堆,如果這個(gè)元數(shù)據(jù)被當(dāng)作一個(gè)列表在一個(gè)請(qǐng)求中傳遞給應(yīng)用程序,那么如果應(yīng)用程序把這個(gè)元數(shù)據(jù)放在應(yīng)答中時(shí)不應(yīng)引發(fā)一個(gè)錯(cuò)誤白嘁。

服務(wù)端可以限制 Request-Headers 的大小坑鱼,建議默認(rèn) 8kb。建議實(shí)現(xiàn)時(shí)計(jì)算 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

一個(gè) Request 中可以有多個(gè) Length-Prefixed-Message 數(shù)據(jù)鲁沥,它們通過 http2 的 DATA 幀進(jìn)行傳輸。

  • 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}

當(dāng) Compressed-Flag 值為1時(shí)耕魄,標(biāo)識(shí)著二進(jìn)制的 Message 是使用 Message-Encoding 頭中聲明的方式壓縮過的黍析。值為0時(shí),意味著沒有被壓縮。
如果 Message-Encoding 頭缺省的情況下屎开,Compressed-Flag 值必須為0阐枣。壓縮的上下文信息不會(huì)在跨越 message 邊界時(shí)依然被維護(hù)马靠,所以,實(shí)現(xiàn)時(shí)必須為流中的每一個(gè) message 創(chuàng)建一個(gè)新的上下文蔼两。

EOS(end-of-stream)

對(duì)于請(qǐng)求來說甩鳄,EOS (end-of-stream) 通過接收到的最后一個(gè) DATA 幀中的 END_STREAM 標(biāo)記來標(biāo)識(shí)的。在 Request 需要被關(guān)閉但是又沒有數(shù)據(jù)需要發(fā)送的情況下额划,gRPC的實(shí)現(xiàn)必須發(fā)送一個(gè)包含 END_STREAM 標(biāo)記的空 DATA 幀妙啃。

Responses

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

Response-Headers

  • 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
    • 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
    • HTTP-Status → ":status 200"

Response-Headers 和 Trailers-Only 都是通過一個(gè)獨(dú)立的 HTTP2 HEADERS 幀塊進(jìn)行傳輸。大多數(shù)的 responses 都應(yīng)該既有 headers 又有 trailers俊戳,但是 Trailers-Only 當(dāng)在需要產(chǎn)生一個(gè)即時(shí)錯(cuò)誤時(shí)是被允許的揖赴。另外,Status 信息必須在 Trailers 中抑胎,即使 status code 是 OK.

對(duì)于 responses 來說燥滑,end-of-stream 是通過最后一個(gè)傳輸 Trailers 的 HEADERS 幀的 END_STREAM 標(biāo)記來標(biāo)示的。

實(shí)現(xiàn)時(shí)應(yīng)當(dāng)期望 broken deployments 在響應(yīng)中發(fā)送 非200 的 HTTP狀態(tài)代碼以及各種非GRPC內(nèi)容類型并且省略狀態(tài)和狀態(tài)消息阿逃。當(dāng)這種情況發(fā)生時(shí)铭拧,實(shí)現(xiàn)時(shí)必須合成一個(gè) Status & Status-Message 以傳播給應(yīng)用層

客戶端應(yīng)當(dāng)限制 Response-Headers, Trailers, 或 Trailers-Only 的大小,一般推薦上述對(duì)象的大小限制都是 8Kb

Status 的值部分是十進(jìn)制編碼的整數(shù)恃锉,作為ASCII字符串搀菩,沒有任何前導(dǎo)零

Status-Message 的值部分理論上應(yīng)當(dāng)是一個(gè)描述錯(cuò)誤的 Unicode 字符串,實(shí)際上多用 UTF-8 跟著是 url 編碼(percent-encoding)破托。當(dāng)解碼無效值時(shí)肪跋,該實(shí)現(xiàn)一定不能拋出錯(cuò)誤 或者 丟棄這個(gè) message。最壞情況土砂,就是終止解碼這個(gè) Status-Message澎嚣,這樣用戶可以接收到原始的 url 編碼格式數(shù)據(jù)∥林ィ或者,該實(shí)現(xiàn)可以解碼有效部分褥琐,同時(shí)保留損壞的% - 編碼锌俱,或者用替換字符(例如,'敌呈?'或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

雖然協(xié)議不要求用戶代理來運(yùn)行,但建議客戶端提供結(jié)構(gòu)化的用戶代理字符串磕洪,該字符串提供了調(diào)用庫吭练、版本和平臺(tái)的基本描述,以便在異構(gòu)環(huán)境中進(jìn)行問題診斷析显。建議庫開發(fā)人員使用以下結(jié)構(gòu):
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 調(diào)用不應(yīng)被假設(shè)為冪等的. 特別地:

  • 無法驗(yàn)證已啟動(dòng)的調(diào)用將不會(huì)重試
  • 沒有重復(fù)抑制機(jī)制鲫咽,因?yàn)闆]有必要
  • 標(biāo)記為冪等的呼叫可以多次發(fā)送

HTTP2 Transport Mapping

Stream Identification

所有 GRPC 調(diào)用都需要指定一個(gè)內(nèi)部ID。我們將在此方案中使用 HTTP2 stream-id 作為調(diào)用標(biāo)識(shí)符。注意:這些 ID 是已打開 HTTP2 會(huì)話的上下文分尸,并且锦聊,在處理多個(gè) HTTP2 會(huì)話的給定進(jìn)程中不是唯一的,也不能用作GUID箩绍。

Data Frames

DATA 幀邊界與 Length-Prefixed-Message 邊界無關(guān)孔庭,并且實(shí)現(xiàn)不應(yīng)對(duì)其對(duì)齊做出任何假設(shè)。

Errors

在 RPC 期間發(fā)生應(yīng)用程序或運(yùn)行時(shí)錯(cuò)誤時(shí)材蛛,將在 Trailers 中傳遞 Status 和 Status-Message圆到。

在某些情況下,消息流的幀可能已損壞卑吭,RPC 運(yùn)行時(shí)將選擇使用 RST_STREAM 幀向其對(duì)等方指示此狀態(tài)芽淡。RPC運(yùn)行時(shí)實(shí)現(xiàn)應(yīng)該將 RST_STREAM 解釋為流的立即 full-closure,并且應(yīng)該將錯(cuò)誤傳播到調(diào)用應(yīng)用程序?qū)印?/p>

Security

當(dāng) TLS 與 HTTP2 一起使用時(shí)陨簇,HTTP2 規(guī)范要求使用 TLS 1.2 或更高版本吐绵。它還對(duì)部署中允許的密碼施加了一些額外的限制,以避免已知問題以及需要SNI支持河绽。另外己单,預(yù)計(jì) HTTP2 將與專有傳輸安全機(jī)制結(jié)合使用,此時(shí)規(guī)范不能對(duì)其提出任何有意義的建議

Connection Management

GOAWAY Frame

由 servers 發(fā)送給 clients 用于標(biāo)識(shí) servers 不再在相關(guān)的連接上接收任何新的 stream耙饰。這個(gè)幀包含了 server 成功接收的最后一個(gè) stream id纹笼。clients 應(yīng)該認(rèn)為最后一個(gè)被 server 成功接收的 stream 之后的任何 stream 都是 UNAVAILABLE,應(yīng)當(dāng)在其他地方進(jìn)行重試苟跪。clients 可以繼續(xù)處理已接受的流廷痘,直到它們完成或連接終止。

servers 應(yīng)當(dāng)在終止連接前發(fā)送 GOAWAY 幀件已,以可靠地通知 clients 哪些工作已被服務(wù)器接受并正在執(zhí)行笋额。

PING Frame

clients 和 servers 都可以發(fā)送 PING 幀,對(duì)端必須以它接收到的內(nèi)容進(jìn)行回復(fù)篷扩。它被用來斷定連接依然存活兄猩,并提供了一種評(píng)估 end-to-end 延遲的方法。如果 servers 啟動(dòng)的 PING 未在運(yùn)行時(shí)期望的截止期限內(nèi)收到響應(yīng)鉴未,則服務(wù)器上的所有未完成調(diào)用將以 CANCELED 狀態(tài)關(guān)閉枢冤。如果 clients 啟動(dòng)的 PING 超時(shí)未收到響應(yīng)時(shí),將導(dǎo)致所有調(diào)用以 UNAVAILABLE 狀態(tài)關(guān)閉铜秆。請(qǐng)注意淹真,PING 的頻率高度依賴于網(wǎng)絡(luò)環(huán)境,實(shí)現(xiàn)可以根據(jù)網(wǎng)絡(luò)和應(yīng)用要求自由調(diào)整 PING 頻率连茧。

Connection failure

如果客戶端上發(fā)生可檢測(cè)的連接故障核蘸,則將以 UNAVAILABLE 狀態(tài)關(guān)閉所有調(diào)用巍糯。對(duì)于服務(wù)器,將以 CANCELED 狀態(tài)關(guān)閉打開的調(diào)用值纱。

Appendix A - GRPC for Protobuf

  1. protobuf 聲明的服務(wù)接口很容易通過 protoc 的代碼生成擴(kuò)展映射到 GRPC 上鳞贷。以下定義要使用的映射。
    • Service-Name → ?( {proto package name} "." ) {service name}
    • Message-Type → {fully qualified proto message name}
    • Content-Type → "application/grpc+proto"
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末虐唠,一起剝皮案震驚了整個(gè)濱河市搀愧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌疆偿,老刑警劉巖咱筛,帶你破解...
    沈念sama閱讀 212,816評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異杆故,居然都是意外死亡迅箩,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門处铛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來饲趋,“玉大人,你說我怎么就攤上這事撤蟆∞人埽” “怎么了?”我有些...
    開封第一講書人閱讀 158,300評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵家肯,是天一觀的道長(zhǎng)龄砰。 經(jīng)常有香客問我,道長(zhǎng)讨衣,這世上最難降的妖魔是什么换棚? 我笑而不...
    開封第一講書人閱讀 56,780評(píng)論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮反镇,結(jié)果婚禮上固蚤,老公的妹妹穿的比我還像新娘。我一直安慰自己歹茶,他們只是感情好夕玩,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評(píng)論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著辆亏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鳖目。 梳的紋絲不亂的頭發(fā)上扮叨,一...
    開封第一講書人閱讀 50,084評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音领迈,去河邊找鬼彻磁。 笑死碍沐,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的衷蜓。 我是一名探鬼主播累提,決...
    沈念sama閱讀 39,151評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼磁浇!你這毒婦竟也來了斋陪?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,912評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤置吓,失蹤者是張志新(化名)和其女友劉穎无虚,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體衍锚,經(jīng)...
    沈念sama閱讀 44,355評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡友题,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了戴质。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片度宦。...
    茶點(diǎn)故事閱讀 38,809評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖告匠,靈堂內(nèi)的尸體忽然破棺而出戈抄,到底是詐尸還是另有隱情,我是刑警寧澤凫海,帶...
    沈念sama閱讀 34,504評(píng)論 4 334
  • 正文 年R本政府宣布呛凶,位于F島的核電站,受9級(jí)特大地震影響行贪,放射性物質(zhì)發(fā)生泄漏漾稀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評(píng)論 3 317
  • 文/蒙蒙 一建瘫、第九天 我趴在偏房一處隱蔽的房頂上張望崭捍。 院中可真熱鬧,春花似錦啰脚、人聲如沸殷蛇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽粒梦。三九已至,卻和暖如春荸实,著一層夾襖步出監(jiān)牢的瞬間匀们,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評(píng)論 1 267
  • 我被黑心中介騙來泰國打工准给, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留泄朴,地道東北人重抖。 一個(gè)月前我還...
    沈念sama閱讀 46,628評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像祖灰,于是被迫代替她去往敵國和親钟沛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評(píng)論 2 351