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_total
和 downstream_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.yaml 和 configs/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_options 由 tunneling_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