http://doc.oschina.net/grpc?t=58011
HTTP2 協議上的 gRPC
本文檔作為 gRPC 在 HTTP2 草案17框架上的實現的詳細描述,假設你已經熟悉 HTTP2 的規范。產品規則采用的是ABNF 語法
大綱
以下是 gRPC 請求和應答消息流中一般的消息順序:
- 請求 → 請求報頭 *有定界符的消息 EOS
- 應答 → 應答報頭 *有定界符的消息 EOS
- 應答 → (應答報頭 *有定界符的消息 跟蹤信息) / 僅僅跟蹤時
請求
-
請求 → 請求報頭 *界定的消息 EOS 請求報頭是通過報頭+聯系幀方式以 HTTP2 報頭來發送的。
-
請求報頭 → 調用定義 *自定義元數據
-
調用定義 → 方法模式路徑TE [授權] [超時] [內容類型] [消息類型] [消息編碼] [接受消息類型] [用戶代理]
-
方法 → “:method POST”
-
模式 → “:scheme ” (“http” / “https”)
-
路徑 → “:path” {開放的 API 對應的方法路徑}
-
Authority → “:authority” {授權的對應的虛擬主機域名}
-
TE → “te” “trailers” # 用來檢測不兼容的代理
-
超時 → “grpc-timeout” 超時時間值 超時時間單位
-
超時時間值 → {至少8位數字正整數的 ASCII 碼字符串}
-
超時時間單位 → 時 / 分 / 秒 / 毫秒 / 微秒 / 納秒
-
時 → “H”
-
分 → “M”
-
秒 → “S”
-
毫秒 → “m”
-
微秒 → “u”
-
納秒 → “n”
-
內容類型 → “content-type” “application/grpc” [(“+proto” / “+json” / {自定義})]
-
內容編碼 → “gzip” / “deflate” / “snappy” / {自定義}
-
消息編碼 → “grpc-encoding” Content-Coding
-
接受消息編碼 → “grpc-accept-encoding” Content-Coding *("," Content-Coding)
-
用戶代理 → “user-agent” {結構化的用戶代理字符串}
-
消息類型 → “grpc-message-type” {消息模式的類型名}
-
自定義數據 → 二進制報頭 / ASCII 碼報頭
-
二進制報頭 → {以“-bin”結尾小寫的報頭名稱的 ASCII 碼 } {以 base64 進行編碼的值}
-
ASCII 碼報頭 → {小寫報頭名稱的 ASCII 碼} {值}
HTTP2 需要一個在其他報頭之前以“:”開始的保留報頭。額外的實現應該在保留報頭后面馬上傳送超時信息,並且應該在發送自定義元數據前發送調用定義報頭。 如果超時信息被遺漏,服務端會認為是無限時長的超時。客戶端實現可以根據發布需要自由地發送一個默認最小超時時間。 自定義元數據是應用層定義的任意的鍵值對集合。除了 HTTP2 報頭部總長度的傳輸限制外,唯一的約束就是以“grpc-”開始的報頭名稱是為將來使用保留的。
注意 HTTP2 並不允許隨意使用字節序列來作為報頭值,所以二進制的報頭值必須使用 Base64 來編碼,參見https://tools.ietf.org/html/rfc4648#section-4。 實現必須接受填充的和非填充的值,並且發出非填充的值。應用以“-bin”結尾的名稱來定義二進制報頭。運行時庫在報頭被發送和接收時,用這個后綴來檢測二進制報頭並且正確地在報頭被發送和接收時進行 Base64 編碼和解碼。
界定的消息的重復序列通過數據幀來進行傳輸。
-
界定的消息 → 壓縮標志 消息長度 消息
-
壓縮標志 → 0 / 1 # 編碼為 1 byte 的無符號整數
-
消息長度 → {消息長度} # 編碼為 4 byte 的無符號整數
-
消息 → *{二進制字節}
壓縮標志 值為1 表示消息的二進制序列通過消息編碼報頭聲明的機制進行壓縮,為0表示消息的字節碼沒有進行編碼。壓縮上下文不在消息編輯間維護,聲明必須為流中的每個消息創建一個新的上下文。假如 壓縮標志 被遺漏了,那么壓縮標志 必須為0。
對請求來講,EOS (end-of-stream)以最后接收到的數據幀出現 END_STREAM 標志為准。 在請求流需要關閉但是沒有數據繼續發送的情況下,代碼必須發送包含這個標志的空數據幀。
應答
-
應答 → (應答報頭 界定的消息 跟蹤信息) / 僅僅跟蹤
-
應答報頭 → HTTP 狀態 [消息編碼] [消息接受編碼] 內容類型 *自定義元數據
-
僅僅跟蹤 → HTTP 狀態 內容類型 跟蹤消息
-
跟蹤消息 → 狀態 [狀態消息] *自定義元數據
-
HTTP狀態 → “:status 200”
-
狀態 → “grpc-status” <狀態碼的 ASCII 字符串>
-
狀態消息 → “grpc-message” <狀態描述文本對應的 ASCII 字符串>
應答報頭 和 僅僅跟蹤 分別在一個HTTP2報頭幀塊里發送。大多數應答期望既有報頭又有跟蹤消息,但是調用允許僅僅跟蹤生成一個立即的錯誤。假如狀態碼是 OK 的話,則必須在跟蹤消息里發送狀態。 對於應答來講,通過在最后一個接收的包含跟蹤信息的報頭幀里提供一個 END_STREAM 標志來表明流結束。
實現應當會讓中斷的部署在應答里發送一個非200的HTTP狀態碼和一系列非GRPC內容類型並且省略狀態和狀態消息。 當發生這種情況時實現應當合成狀態和狀態消息來擴散到應用層。
例子
單項調用HTTP2幀序列例子
請求
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) <Delimited Message>
應答
HEADERS (flags = END_HEADERS) :status = 200 grpc-encoding = gzip DATA <Delimited Message> HEADERS (flags = END_STREAM, END_HEADERS) grpc-status = 0 # OK trace-proto-bin = jher831yy13JHy3hc
用戶代理
當協議不需要一個用戶代理時,建議客戶端提供一個結構化的用戶代理字符串來對要調用的庫、版本和平台提供一個基本的描述來幫助在異質的環境里進行問題診斷。庫開發者建議使用以下結構:
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)
HTTP2 傳輸映射
流識別
所有的 GRPC 調用需要定義指定一個內部 ID。我們將在這個模式里使用 HTTP2 流 ID 來作為調用標識。注意:這些 ID 在一個打開的 HTTP2 會話里是前后關聯的,在一個處理多個 HTTP2 會話的進程里不是唯一的,也不能被用作 GUID。
數據幀
數據幀邊界與界定消息的邊界無關,實現時不應假定它們有一致性。
錯誤
當應用錯誤或運行時錯誤在 PRC 調用過程中出現時,狀態和狀態消息應當通過跟蹤消息發送。
在有些情況下可能消息流的幀已經中斷,RPC 運行時會選擇使用 RST_STREAM 幀來給對方表示這種狀態。RPC 運行時聲明應當將 RST_STREAM 解釋為流的完全關閉,並且將錯誤傳播到應用層。 以下為從 RST_STREAM 錯誤碼到 GRPC 的錯誤碼的映射:
HTTP2 編碼 | GRPC 編碼 |
---|---|
NO_ERROR(0) | INTERNAL -一個顯式的GRPC OK狀態應當被發出,但是這個也許在某些場景里會被侵略性地使用 |
PROTOCOL_ERROR(1) | INTERNAL |
INTERNAL_ERROR(2) | INTERNAL |
FLOW_CONTROL_ERROR(3) | INTERNAL |
SETTINGS_TIMEOUT(4) | INTERNAL |
STREAM_CLOSED | 無映射,因為沒有打開的流來傳播。實現應記錄。 |
FRAME_SIZE_ERROR | INTERNAL |
REFUSED_STREAM | UNAVAILABLE-表示請求未作處理且可以重試,可能在他處重試。 |
CANCEL(8) | 當是由客戶端發出時映射為調用取消,當是由服務端發出時映射為 CANCELLED。注意服務端在需要取消調用時應僅僅使用這個機制,但是有效荷載字節順序是不完整的 |
COMPRESSION_ERROR | INTERNAL |
CONNECT_ERROR | INTERNAL |
ENHANCE_YOUR_CALM | RESOURCE_EXHAUSTED...並且運行時提供有額外的錯誤詳情,表示耗盡資源是帶寬 |
INADEQUATE_SECURITY | PERMISSION_DENIED... 並且有額外的信息表明許可被拒絕,因為對調用來說協議不夠安全 |
安全
HTTP2 規范當使用 TLS 時強制使用 TLS 1.2 及以上的版本,並且在部署上對允許的密碼施加一些額外的限制以避免已知的比如需要 SNI 支持的問題。並且期待 HTTP2 與專有的傳輸安全機制相結合,這些傳輸機制的規格說明不能提供有意義的建議。
連接管理
GOAWAY 幀
服務端發出這種幀給客戶端表示服務端在相關的連接上不再接受任何新流。這種幀包含服務端最后成功接受的流的ID。客戶端應該認為任何在最后成功的流后面初始化的任意流為 UNAVAILABLE,並且在別處重試這些調用。客戶端可以自由地在已經接受的流上繼續工作直到它們完成或者連接中斷。 服務端應該在終止連接前發送 GOAWAY 幀,以可靠地通知客戶端哪些工作已經被服務端接受並執行。
PING 幀
客戶端和服務端均可以發送一個 PING 幀,對方必須精確回顯它們所接收到的信息。這可以被用來確認連接仍然是活動的,並且能夠提供估計端對端延遲估計的方法。假如服務端初始的 PING 在最后期限仍然沒有收到運行時所期待的應答的話,所有未完成的調用將會被以取消狀態關閉。一個客戶端期滿的初始的PING則會導致所有的調用被以用不可用狀態關閉。注意PING的頻率高度依賴於網絡環境,實現可以根據網絡和應用需要,自由地調整PING頻率。
連接失敗
假如客戶端檢測到連接失敗,所有的調用都會被以不可用狀態關閉。而服務端側則所有已經打開的調用都會被以取消狀態關閉。
附錄 A - Protobuf 上的 GRPC
用 protobuf 定義的服務接口可以通過 protoc 的代碼生成擴展簡單地映射成 GRPC ,以下定義了所用的映射:
-
路徑 → / 服務名 / {方法名}
-
服務名 → ?( {proto 包名} "." ) {服務名}
-
消息類型 → {全路徑 proto 消息名}
-
內容類型 → "application/grpc+proto"