为 HTTP/2 代理实现 websocket 支持

Posted

技术标签:

【中文标题】为 HTTP/2 代理实现 websocket 支持【英文标题】:Implementing websocket support for HTTP/2 proxies 【发布时间】:2021-03-15 15:44:38 【问题描述】:

我正在开发一个 https http/2 代理服务器,如此处所述: https://chromium.googlesource.com/chromium/src/+/HEAD/net/docs/proxy.md#HTTPS-proxy-scheme 里面提到了

由于连接限制较高,使用 HTTP/2 的 HTTPS 代理在 Chrome 中可以提供比常规 HTTP 代理更好的性能(Chrome 中的 HTTP/1.1 代理被限制为跨所有域的 32 个同时连接)。

但是当用户尝试通过原始 http 连接浏览使用 websocket 的网站时,响应包含禁止在 http/2 中使用的“升级”http 标头,因为没有用于 HTTP/2 的 websocket。 https://daniel.haxx.se/blog/2016/06/15/no-websockets-over-http2/#:~:text=There%20is%20no%20websockets%20for%20HTTP%2F2.&text=That%20spec%20details%20how%20a,connection%20into%20a%20websockets%20connection. 问题来自这样一个事实,即当 http1.1 流量通过实现 http/2 的 https 代理时,标头必须从 http1.1 传输到 http/2。当然,当网页使用 TLS 时,不会出现所有流量都通过 http 'CONNECT' 方法建立的连接这样的问题。当网站不使用 TLS 时会出现问题。 如果没有解决这个问题,说明HTTPS代理不应该实现HTTP/2协议。

【问题讨论】:

【参考方案1】:

因为没有用于 HTTP/2 的 websockets

您发布的链接是旧的,从那时起,RFC 8441 指定了基于 HTTP/2 的 WebSocket。

HTTP/2 代理的行为在RFC 7540, section 8.3 中指定。

当客户端使用安全 HTTP/2 与代理通信时,每个 HTTP/2 流都是通往服务器的“隧道”。

客户端和代理使用安全的 HTTP/2 进行通信,并且“隧道”发生是因为每个 HTTP/2 流都成为具有“无限”响应的“无限”请求,即 HTTP/2 的“无限”系列 @987654324 @ 携带不透明字节数组有效负载的双向帧。

代理的工作是解包客户端接收到的DATA帧,并将字节数组有效负载转发给服务器,如果代理之间的通信可能会将其重新包装成HTTP/2 DATA帧并且服务器也是 HTTP/2(可以优化解包和重新包装,但可能会很棘手——例如流编号可能不同)。

当客户端尝试通过 HTTP/2 执行 WebSocket 时,浏览器将按照 RFC 8441 的规定执行操作,并且代理将收到带有 :protocol 伪标头的 CONNECT 请求,并且代理必须知道在这种情况下该怎么做,具体取决于它用于与服务器通信的协议。

上面描述了当与客户端的通信是 HTTP/2 时,您的代理需要支持什么。

如果您的代理需要支持使用 HTTP/1.1 的客户端,那么您需要实现代理支持 HTTP/1.1 和 WebSocket 代理所需的功能,这通常并不难:第一个只是 HTTP/1.1 转发或 HTTP CONNECT“隧道”,第二个是 WebSocket HTTP 升级请求转发,然后是“隧道”连接,甚至是成熟的 WebSocket 代理。

免责声明,我已经在Jetty Project 中实现了上述所有内容。 您可以使用 Jetty 10 或更高版本来实现 HTTP/1.1、HTTP/2 或 WebSocket,客户端和服务器(因此也是代理)。也支持基于 HTTP/2 的 WebSocket。

最后,回答您的最后一个问题,即使存在 WebSocket,也完全有可能让安全代理支持 HTTP/2。

例如,与服务器的明文 WebSocket 连接以 HTTP 升级请求开始;该请求将被加密发送到代理,代理将对其进行解密并将其转发到服务器(使用服务器支持的任何协议——甚至可以是基于安全 HTTP/2 的 WebSocket);服务器将回复 101 并将连接升级到 WebSocket;代理将从服务器接收 101 并升级到“隧道”或 WebSocket 代理;代理加密 101 响应并将其转发给客户端;客户端解密它并升级到 WebSocket 的连接。 此时,客户端通过与代理的安全连接向服务器发送明文 W​​ebSocket(代理看到明文 WebSocket 通信)。

Viceversa,与服务器的安全 WebSocket 连接从通过代理到服务器建立 HTTP CONNECT 隧道开始;然后客户端将与服务器建立安全通信;然后它将HTTP升级到WebSocket发送到服务器(通过代理隧道)。 此时,客户端通过与代理的安全连接(代理无法看到 WebSocket 通信)与服务器进行安全的 WebSocket 通信

HTTP/2 变体类似,只是考虑了 HTTP/2 特定的“隧道”和传输 WebSocket 的方式。

【讨论】:

使用 HTTP/2 代理时,仅当目标站点使用“https”时,浏览器才会使用 CONNECT 方法。但是当目标站点是“http”时,google chrome 和 firefox 不使用 CONNECT 方法,而是将一些 HTTP1 标头转换为 HTTP/2。当然,现在大多数网站都使用 HTTPS,使用 HTTP/2 代理浏览这些网站不会有任何问题。

以上是关于为 HTTP/2 代理实现 websocket 支持的主要内容,如果未能解决你的问题,请参考以下文章

Nginx实战之反向代理WebSocket的配置实例

第1882期基于Unix Socket的可靠Node.js HTTP代理实现(支持WebSocket协议)

Nginx实现HTTP和WebSocket的反向代理

使用 Apache 反向代理的 Ratchet websocket 没有响应

Nginx学习之反向代理WebSocket配置实例

基于 Unix Socket 的可靠 Node.js HTTP 代理实现(支持 WebSocket 协议)