HTTP 升级

Envoy 升级支持主要用于 WebSocket 和 CONNECT 支持,但也可能用于任意升级。

升级会将 HTTP 标头和升级有效负载都通过 HTTP 过滤器链传递。

可以配置 upgrade_configs,可以选择使用或不使用自定义过滤器链。

如果只指定了 upgrade_type,升级标头、任何请求和响应主体以及 HTTP 数据有效负载都将通过默认的 HTTP 过滤器链。

为了避免对升级有效负载使用 HTTP 专用过滤器,可以为给定的升级类型设置自定义 filters,直到并且包括仅使用路由器过滤器将 HTTP 数据发送到上游。

提示

缓冲通常与升级不兼容,因此,如果在默认的 HTTP 过滤器链中配置了 Buffer 过滤器,则应通过使用 upgrade filters 并在该列表中不包括缓冲过滤器来将其排除在升级之外。

可以在 每个路由 基础上启用或禁用升级。

任何每个路由的启用/禁用都会自动覆盖下面列出的 HttpConnectionManager 配置,但自定义过滤器链只能在每个 HttpConnectionManager 基础上配置。

HCM 升级启用

路由升级启用

升级启用

T(默认)

T(默认)

T

T(默认)

F

F

F

T(默认)

T

F

F

F

提示

升级的统计信息都捆绑在一起,因此 WebSocket 和其他升级 统计信息 通过 downstream_cx_upgrades_totaldownstream_cx_upgrades_active 等统计信息进行跟踪。

通过 HTTP/2 或 HTTP/3 跳跃的 Websocket

虽然 WebSockets 的 HTTP/2 和 HTTP/3 支持默认情况下是禁用的,但 Envoy 确实支持在更喜欢在整个部署中使用统一 HTTP/2+ 网格的部署中,通过 HTTP/2 及更高版本进行 WebSocket 隧道;这使得例如以下形式的部署成为可能

[Client] —-> HTTP/1.1 >—- [Front Envoy] —-> HTTP/2 >—- [Sidecar Envoy —-> HTTP/1 >—- App]

在这种情况下,如果客户端例如使用 WebSocket,我们希望 WebSocket 在功能上完好无损地到达上游服务器,这意味着它需要遍历 HTTP/2+ 跳跃。

这是通过 扩展 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-Upstream 跳跃上的 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 支持模式会导致重大安全漏洞,因为上游将被转发 **未经清理的标头**(如果它们在主体有效负载中)。

请谨慎使用!

提示

有关代理连接的示例,请参见 configs/proxy_connect.yaml

有关终止连接的示例,请参见 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 Upstream] —> raw SMTP >— [L2 Envoy] —> SMTP tunneled over HTTP/2 CONNECT >— [L1 Envoy] —> raw SMTP >— [Client]

HTTP/1.1 CONNECT 可用于让 TCP 客户端通过 HTTP 代理服务器(例如不支持 HTTP/2 的公司代理)连接到其自己的目标。

[HTTP Server] —> raw HTTP >— [L2 Envoy] —> HTTP tunneled over HTTP/1.1 CONNECT >— [L1 Envoy] —> raw HTTP >— [HTTP Client]

注意

当使用 HTTP/1 CONNECT 时,最终会在 L1 和 L2 Envoy 之间为每个 TCP 客户端连接建立一个 TCP 连接,如果可以选择,建议使用 HTTP/2 或更高版本。

HTTP POST 也可用于在不支持 CONNECT 的中间代理中代理多路复用的 TCP。

代理 HTTP 的示例设置如下所示。

[TCP Server] —> raw TCP >— [L2 Envoy] —> TCP tunneled over HTTP/2 or HTTP/1.1 POST >— [Intermediate Proxies] —> HTTP/2 or HTTP/1.1 POST >— [L1 Envoy] —> raw TCP >— [TCP Client]

提示

这种设置的示例可以在 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 请求中,第二个 Envoy 将在 10001 上侦听,剥离 CONNECT 标头(对于 POST 请求不需要),并转发原始 TCP 上游流量,在本例中是到 google.com。

Envoy 在开始将下游 TCP 数据流式传输到上游之前,等待 HTTP 隧道建立(即收到对 CONNECT 请求的成功响应)。

如果您想解封装 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,可以通过 upgrade_configs 将其值设置为特殊关键字 CONNECT-UDP 来启用。与 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 还支持通过利用 UDP 代理侦听器过滤器将原始 UDP 通过 HTTP CONNECT 或 HTTP POST 请求进行隧道传输。默认情况下,UDP 隧道传输处于禁用状态,可以通过为 tunneling_config 设置配置来启用。

注意

目前,Envoy 仅支持通过 HTTP/2 流进行 UDP 隧道传输。

默认情况下,tunneling_config 将升级连接以根据 HTTP 中的 UDP 代理 RFC 为每个 UDP 会话创建 HTTP/2 流(UDP 会话由数据报的 5 元组标识)。由于此升级协议需要封装机制来保留原始数据报的边界,因此需要应用 HTTP 胶囊 会话过滤器。HTTP/2 流将通过上游连接进行多路复用。

与 TCP 隧道传输不同,TCP 隧道传输可以通过交替禁用连接套接字的读取来应用下游流控制,对于 UDP 数据报,不支持此机制。因此,当对 UDP 进行隧道传输并且从下游接收到新数据报时,如果上游已准备好,则会将其流式传输到上游,或者由 UDP 代理停止。如果上游未准备好(例如,正在等待 HTTP 响应标头),则数据报可能会被丢弃或缓冲,直到上游准备好。在这种情况下,默认情况下,下游数据报将被丢弃,除非 buffer_optionstunneling_config 设置。默认缓冲区限制很小,以尝试防止大量不需要的缓冲内存,但可以且应该根据所需用例进行调整。当上游准备好时,UDP 代理将首先刷新所有以前缓冲的数据报。

注意

如果设置了 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