HTTP 升級

Envoy 升級支援主要用於 WebSocket 和 CONNECT 支援,但也可用於任意升級。

升級會將 HTTP 標頭和升級酬載都傳遞到 HTTP 過濾器鏈。

可以設定 upgrade_configs,無論是否使用自訂過濾器鏈。

如果僅指定了 upgrade_type,則升級標頭、任何請求和回應主體,以及 HTTP 資料酬載都將通過預設的 HTTP 過濾器鏈。

為了避免為升級酬載使用僅限 HTTP 的過濾器,可以為給定的升級類型設定自訂 filters,包括僅使用路由器過濾器將 HTTP 資料發送到上游。

提示

緩衝通常與升級不相容,因此如果在預設 HTTP 過濾器鏈中設定了 Buffer 過濾器,則應使用 升級過濾器 並將緩衝過濾器排除在該清單之外,來排除升級。

可以在 每個路由 的基礎上啟用或停用升級。

任何每個路由的啟用/停用都會自動覆寫如下所述的 HttpConnectionManager 設定,但自訂過濾器鏈只能在每個 HttpConnectionManager 的基礎上設定。

HCM 升級已啟用

路由升級已啟用

升級已啟用

真 (預設)

真 (預設)

真 (預設)

真 (預設)

提示

升級的統計資訊全部捆綁在一起,因此 WebSocket 和其他升級的 統計資訊 會通過諸如 downstream_cx_upgrades_totaldownstream_cx_upgrades_active 等統計資訊進行追蹤。

HTTP/2 或 HTTP/3 跳躍上的 WebSocket

雖然預設情況下會關閉 HTTP/2 和 HTTP/3 對 WebSockets 的支援,但 Envoy 確實支援通過 HTTP/2 及以上協定通道傳輸 WebSockets,以便在整個過程中首選統一的 HTTP/2+ 網格部署;例如,這會啟用以下形式的部署

[用戶端] —-> HTTP/1.1 >—- [前端 Envoy] —-> HTTP/2 >—- [Sidecar Envoy —-> HTTP/1 >—- 應用程式]

在這種情況下,如果用戶端正在使用 WebSocket,我們希望 WebSocket 功能完整地到達上游伺服器,這意味著它需要遍歷 HTTP/2+ 跳躍。

對於 HTTP/2,這是通過 Extended CONNECT (RFC 8441) 支援來實現的,通過在第二層 Envoy 上將 allow_connect 設定為 true 來啟用。

對於 HTTP/3,通過 alpha 選項 allow_extended_connect 設定了平行支援,因為目前還沒有正式的 RFC。

WebSocket 請求將轉換為 HTTP/2+ CONNECT 資料流,其中 :protocol 標頭指示原始升級,遍歷 HTTP/2+ 跳躍,並降級回 HTTP/1 WebSocket 升級。

相同的升級-CONNECT-升級轉換將在任何 HTTP/2+ 跳躍上執行,但有文檔中記錄的缺陷,即 HTTP/1.1 方法始終假定為 GET

非 WebSocket 升級允許使用任何有效的 HTTP 方法(即 POST),並且當前的升級/降級機制將捨棄原始方法,並將升級請求轉換為最終 Envoy-上游跳躍上的 GET 方法。

注意

HTTP/2+ 升級路徑具有非常嚴格的 HTTP/1.1 相容性,因此不會代理帶有主體的 WebSocket 升級請求或回應。

CONNECT 支援

預設情況下,Envoy CONNECT 支援已關閉(Envoy 會回應 CONNECT 請求而內部產生 403 錯誤)。

可以通過上述升級選項啟用 CONNECT 支援,將升級值設定為特殊關鍵字 CONNECT

雖然對於 HTTP/2 及以上版本,CONNECT 請求可能具有路徑,但通常對於 HTTP/1.1 CONNECT 請求沒有路徑,只能使用 connect_matcher 來匹配。

注意

在為 CONNECT 請求執行非萬用字元網域匹配時,將匹配 CONNECT 目標,而不是 Host/Authority 標頭。您可能需要包含埠(例如,hostname:port)才能成功匹配。

Envoy 可以通過兩種方式處理 CONNECT,一種是像對待任何其他請求一樣代理 CONNECT 標頭,並讓上游終止 CONNECT 請求,另一種是終止 CONNECT 請求,並將酬載作為原始 TCP 資料轉發。

設定 CONNECT 升級設定時,預設行為是代理 CONNECT 請求,使用升級路徑像對待任何其他請求一樣對待它。

如果需要終止,可以通過設定 connect_config 來完成。

如果此訊息存在於 CONNECT 請求中,則路由器過濾器將剝離請求標頭,並將 HTTP 酬載轉發到上游。收到來自上游的初始 TCP 資料後,路由器將合成 200 回應標頭,然後將 TCP 資料作為 HTTP 回應主體轉發。

警告

如果未正確設定,此 CONNECT 支援模式可能會產生重大安全漏洞,因為如果上游位於主體酬載中,則會轉發未經清理的標頭

請謹慎使用!

提示

有關代理 connect 的範例,請參閱 configs/proxy_connect.yaml

有關終止 connect 的範例,請參閱 configs/terminate_http1_connect.yamlconfigs/terminate_http2_connect.yaml

注意

對於 CONNECT-over-TLS,目前無法將 Envoy 設定為在一個跳躍中以明文方式執行 CONNECT 請求並加密先前未加密的酬載。

要以純文字傳送 CONNECT 並加密酬載,必須先通過「上游」TLS 回送連線轉發 HTTP 酬載以對其進行加密,然後讓 TCP 監聽器接收加密的酬載並將 CONNECT 上傳送上游。

通過 HTTP 通道傳輸 TCP

Envoy 還支援通過 HTTP CONNECT 或 HTTP POST 請求通道傳輸原始 TCP。請在下面找到一些使用案例。

HTTP/2+ CONNECT 可用於通過預先預熱的安全連線代理多路復用的 TCP,並攤銷任何 TLS 交握的成本。

代理 SMTP 的設定範例如下所示

[SMTP 上游] —> 原始 SMTP >— [L2 Envoy] —> 透過 HTTP/2 CONNECT 通道傳輸的 SMTP >— [L1 Envoy] —> 原始 SMTP >— [用戶端]

HTTP/1.1 CONNECT 可用於讓 TCP 用戶端連線到自己的目的地,並通過 HTTP 代理伺服器(例如:不支援 HTTP/2 的公司代理)傳輸。

[HTTP 伺服器] —> 原始 HTTP >— [L2 Envoy] —> 透過 HTTP/1.1 CONNECT 通道傳輸的 HTTP >— [L1 Envoy] —> 原始 HTTP >— [HTTP 用戶端]

注意

當使用 HTTP/1 CONNECT 時,您最終會為每個 TCP 用戶端連線在 L1 和 L2 Envoy 之間建立一個 TCP 連線,當您可以選擇時,最好使用 HTTP/2 或更高版本。

當中間代理不支援 CONNECT 時,HTTP POST 也可用於代理多工 TCP。

一個代理 HTTP 的範例設定如下所示:

[TCP 伺服器] —> 原始 TCP >— [L2 Envoy] —> 透過 HTTP/2 HTTP/1.1 POST 通道傳輸的 TCP >— [中間代理] —> HTTP/2 HTTP/1.1 POST >— [L1 Envoy] —> 原始 TCP >— [TCP 用戶端]

提示

此類設定的範例可以在 Envoy 範例組態目錄中找到。

對於 HTTP/1.1 CONNECT,請嘗試以下任一方法:

$ envoy -c configs/encapsulate_in_http1_connect.yaml --base-id 1
$ envoy -c configs/terminate_http1_connect.yaml --base-id 1

對於 HTTP/2 CONNECT,請嘗試以下任一方法:

$ envoy -c configs/encapsulate_in_http2_connect.yaml --base-id 1
$ envoy -c configs/terminate_http2_connect.yaml --base-id 1

對於 HTTP/2 POST,請嘗試以下任一方法:

$ envoy -c configs/encapsulate_in_http2_post.yaml --base-id 1
$ envoy -c configs/terminate_http2_post.yaml --base-id 1

在所有情況下,您都將執行第一個 Envoy,監聽 10000 埠上的 TCP 流量,並將其封裝在 HTTP CONNECT 或 HTTP POST 請求中,以及第二個監聽 10001 埠的 Envoy,該 Envoy 會剝離 CONNECT 標頭(對於 POST 請求則不需要),並將原始 TCP 轉發到上游,在此案例中為 google.com。

Envoy 會等待 HTTP 通道建立(即收到 CONNECT 請求的成功回應),然後才開始將下游 TCP 資料串流至上游。

如果您想要解封裝 CONNECT 請求,並對解封裝的酬載執行 HTTP 處理,最簡單的方法是使用內部監聽器

CONNECT-UDP 支援

注意

CONNECT-UDP 處於 alpha 狀態,可能不夠穩定,不適合在生產環境中使用。我們建議謹慎使用此功能。

CONNECT-UDP (RFC 9298) 允許 HTTP 用戶端通過 HTTP 代理伺服器建立 UDP 通道。與僅限於通道傳輸 TCP 的 CONNECT 不同,CONNECT-UDP 可用於代理基於 UDP 的協定,例如 HTTP/3。

Envoy 中預設停用 CONNECT-UDP 支援。與 CONNECT 類似,可以透過將值設定為特殊關鍵字 CONNECT-UDP,透過upgrade_configs啟用它。與 CONNECT 類似,CONNECT-UDP 請求預設會轉發到上游。connect_config必須設定為終止請求,並將酬載作為 UDP 資料包轉發到目標。

範例設定

以下範例設定會讓 Envoy 將 CONNECT-UDP 請求轉發到上游。請注意,upgrade_configs 設定為 CONNECT-UDP

27      filters:
28      - name: envoy.filters.network.http_connection_manager
29        typed_config:
30          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
31          codec_type: HTTP3
32          stat_prefix: ingress_http
33          route_config:
34            name: local_route
35            virtual_hosts:
36            - name: local_service
37              domains:
38              - "*"
39              routes:
40              - match:
41                  connect_matcher:
42                    {}
43                route:
44                  cluster: cluster_0
45          http_filters:
46          - name: envoy.filters.http.router
47            typed_config:
48              "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
49          http3_protocol_options:
50            allow_extended_connect: true
51          upgrade_configs:
52          - upgrade_type: CONNECT-UDP
53  clusters:
54  - name: cluster_0

以下範例設定會讓 Envoy 終止 CONNECT-UDP 請求,並將 UDP 酬載傳送到目標。如此範例中所示,connect_config 必須設定為終止 CONNECT-UDP 請求。

26      filters:
27      - name: envoy.filters.network.http_connection_manager
28        typed_config:
29          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
30          codec_type: HTTP3
31          stat_prefix: ingress_http
32          route_config:
33            name: local_route
34            virtual_hosts:
35            - name: local_service
36              domains:
37              - "*"
38              routes:
39              - match:
40                  connect_matcher:
41                    {}
42                route:
43                  cluster: service_google
44                  upgrade_configs:
45                  - upgrade_type: CONNECT-UDP
46                    connect_config:
47                      {}
48          http_filters:
49          - name: envoy.filters.http.router
50            typed_config:
51              "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
52          http3_protocol_options:
53            allow_extended_connect: true
54          upgrade_configs:
55          - upgrade_type: CONNECT-UDP
56  clusters:
57  - name: service_google

透過 HTTP 建立 UDP 通道

注意

原始 UDP 通道傳輸處於 alpha 狀態,可能不夠穩定,不適合在生產環境中使用。我們建議謹慎使用此功能。

除了上述章節中說明的 CONNECT-UDP 終止之外,Envoy 也支援透過 HTTP CONNECT 或 HTTP POST 請求建立原始 UDP 通道,方法是利用 UDP Proxy 監聽器篩選器。預設情況下,會停用 UDP 通道傳輸,並且可以透過設定 tunneling_config 的組態來啟用。

注意

目前,Envoy 僅支援透過 HTTP/2 串流建立 UDP 通道。

預設情況下,tunneling_config 會根據HTTP RFC 中的 Proxying UDP,升級連線以針對每個 UDP 會話(UDP 會話由資料包 5 元組識別)建立 HTTP/2 串流。由於此升級協定需要一個封裝機制來保留原始資料包的界限,因此需要套用HTTP Capsule 會話篩選器。HTTP/2 串流將在向上游連線上多工。

與 TCP 通道傳輸不同,後者可透過交替停用從連線插槽讀取來套用下游流量控制,對於 UDP 資料包,不支援此機制。因此,當建立 UDP 通道且從下游接收到新的資料包時,如果上游已就緒,則會將其串流傳輸到上游,或由 UDP Proxy 停止。如果上游未就緒(例如:在等待 HTTP 回應標頭時),則可以捨棄資料包或將其緩衝,直到上游就緒。在這種情況下,預設會捨棄下游資料包,除非 buffer_optionstunneling_config 設定。預設緩衝區限制很小,旨在嘗試防止大量不需要的緩衝記憶體,但應根據所需的使用案例進行調整。當上游準備就緒時,UDP Proxy 將首先清除所有先前緩衝的資料包。

注意

如果設定了 POST,則上游串流不符合 connect-udp RFC,而是 POST 請求。標頭中使用的路徑將從 post_path 欄位設定,並且標頭將不包含目標主機和目標埠,這是 connect-udp 協定所要求的。應謹慎使用此選項。

範例設定

以下範例設定讓 Envoy 將原始 UDP 資料包透過升級後的 CONNECT-UDP 請求,隧道傳輸至上游。

32        session_filters:
33        - name: envoy.filters.udp.session.http_capsule
34          typed_config:
35            '@type': type.googleapis.com/envoy.extensions.filters.udp.udp_proxy.session.http_capsule.v3.FilterConfig
36        tunneling_config:
37          # note: proxy_host supports string substitution, for example setting "%FILTER_STATE(proxy.host.key:PLAIN)%"
38          # will take the target host value from the session's filter state.
39          proxy_host: proxy.host.com
40          # note: target_host supports string substitution, for example setting "%FILTER_STATE(target.host.key:PLAIN)%"
41          # will take the target host value from the session's filter state.
42          target_host: target.host.com
43          # note: The target port value can be overridden per-session by setting the required port value for
44          # the filter state key ``udp.connect.target_port``.
45          default_target_port: 443
46          retry_options:
47            max_connect_attempts: 2
48          buffer_options:
49            max_buffered_datagrams: 1024
50            max_buffered_bytes: 16384
51          headers_to_add:
52          - header:
53              key: original_dst_port