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
- Call-Definition → Method Scheme Path TE [Authority] [Timeout] Content-Type [Message-Type] [Message-Encoding] [Message-Accept-Encoding] [User-Agent]
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
- Percent-Encoded → 1*(Percent-Byte-Unencoded / Percent-Byte-Encoded)
- 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
- protobuf 聲明的服務(wù)接口很容易通過 protoc 的代碼生成擴(kuò)展映射到 GRPC 上鳞贷。以下定義要使用的映射。
- Service-Name → ?( {proto package name} "." ) {service name}
- Message-Type → {fully qualified proto message name}
- Content-Type → "application/grpc+proto"