← 返回博客

通过代理选择适合您技术栈的协议:WebSocket、gRPC、HTTP/2

分析WebSocket、gRPC和HTTP/2的代理特性——选择哪个协议,如何配置代理服务器以及如何避免开发者常见错误。

📅2026年9月21日

在使用代理时,开发者常常会遇到经典的 HTTP 代理设置无法用于 WebSocket 连接,而 gRPC 则因 TLS 握手错误而中断。问题在于,WebSocket、HTTP/2 和 gRPC 并不仅仅是“HTTP 的变种”,而是具有不同隧道化、多路复用和头部处理要求的不同传输模型。本文将探讨如何代理每种协议,实践中遇到的潜在问题,以及如何根据具体任务选择合适的协议和代理类型——从通过 WebSocket 解析数据到高负载的 gRPC 微服务。

协议之间的区别及其对代理的重要性

HTTP/1.1 采用“请求-响应”模型:客户端打开连接,发送请求,接收响应,然后关闭连接或保持连接以便下一个请求(保持活动)。代理服务器在数十年里正是针对这种模型进行优化——解析头部、缓冲请求体、根据 Host 头进行简单路由。

WebSocket 打破了这种模型:在初始的 HTTP 握手(Upgrade: websocket)后,连接变成一个持久的双向通道,数据可以在任何时候双向传输。代理必须“放开”这个连接,简单地在两边转发字节,而不试图将其解释为 HTTP 请求。

HTTP/2 增加了多路复用——多个逻辑请求通过一个 TCP 连接并行进行,使用二进制帧而不是文本头部。对于代理来说,这意味着不能简单地逐行读取文本——需要在代理层支持 HTTP/2 的二进制协议或在 TLS 上进行透明的 TCP 隧道化。

gRPC 更进一步:它严格基于 HTTP/2,使用 Protocol Buffers 进行序列化,并要求从客户端到服务器的整个路径上都支持 HTTP/2 兼容的传输。如果代理在某个环节将连接“降级”到 HTTP/1.1,gRPC 请求将无法通过。

理解这些差异至关重要,因为选择错误的代理类型或错误的隧道化设置会导致连接中断、超时和难以解释的错误,这些错误在没有理解传输层的情况下很难诊断。

通过代理的 WebSocket: 设置和代码

通过代理的 WebSocket 有两种主要场景:使用 CONNECT 方法的 HTTP 代理(用于 WSS,即通过 TLS 的 WebSocket)和 SOCKS5 代理,它在不解析协议的情况下隧道化 TCP 连接。SOCKS5 通常问题更少,因为它最初被设计为任何 TCP 流量的“透明管道”。

使用 websockets 和 python-socks 库通过 SOCKS5 代理连接到 WebSocket 的示例:

import asyncio
from python_socks.sync import Proxy
import websockets
import websockets.sync.client as ws_client

def connect_ws_via_proxy():
    proxy = Proxy.from_url("socks5://user:pass@proxy_host:1080")
    sock = proxy.connect(dest_host="echo.websocket.events", dest_port=443)

    ws = ws_client.connect(
        "wss://echo.websocket.events",
        sock=sock,
        server_hostname="echo.websocket.events"
    )
    ws.send("Hello via proxy")
    print(ws.recv())
    ws.close()

connect_ws_via_proxy()

在 Node.js 中,类似的任务通过 ws 包与 socks-proxy-agent 结合解决:

const WebSocket = require('ws');
const { SocksProxyAgent } = require('socks-proxy-agent');

const agent = new SocksProxyAgent('socks5://user:pass@proxy_host:1080');
const socket = new WebSocket('wss://echo.websocket.events', { agent });

socket.on('open', () => socket.send('Hello via proxy'));
socket.on('message', (data) => console.log(data.toString()));

如果代理仅通过 HTTP(使用 CONNECT 方法)可用,方案类似——大多数现代 WebSocket 客户端能够通过 HTTP 隧道工作,只需确保代理不会因超时而中断长时间连接。这是便宜的数据中心代理常见的问题,它们对非活动连接的限制通常设定为 30-60 秒——对于流式 WebSocket 任务来说,这一点至关重要,最好向提供商确认保持活动参数。

通过代理的 HTTP/2: 多路复用和潜在问题

通过代理的 HTTP/2 的主要难点在于,许多代理服务器(尤其是旧的 Squid 或简单的转发代理)仅支持 HTTP/1.1,并会自动将连接“降级”。对于客户端来说,这通常不易察觉(请求仍然会执行),但多路复用的优势会丧失——延迟会增加,尤其是在对同一主机进行大量并行请求时。

可以通过带有 --http2 标志和详细输出的 cURL 检查连接是否支持通过代理的 HTTP/2:

curl -v --http2 -x http://user:pass@proxy_host:8080 https://example.com

# 在输出中查找以下行:
# * Using HTTP2, server supports multiplexing
# 如果没有这行——连接降级到 HTTP/1.1

在 Python 中,通过代理的 HTTP/2 需要 httpx 库,并启用 HTTP/2 支持:

import httpx

proxies = {
    "http://": "http://user:pass@proxy_host:8080",
    "https://": "http://user:pass@proxy_host:8080",
}

with httpx.Client(proxies=proxies, http2=True) as client:
    response = client.get("https://example.com")
    print(response.http_version)  # 期望 "HTTP/2"
    print(response.status_code)

重要提示:HTTP/2 需要带有 ALPN 扩展的 TLS 以协商协议,因此代理必须通过 CONNECT 透明地隧道化 TLS 流量,而不是自行终止 TLS(如果不是专用的 HTTP/2 兼容代理)。这就是为什么在处理 HTTP/2 的任务中,住宅代理和 数据中心代理 的表现有所不同——重要的是向提供商确认是否支持无中断的 TLS 隧道化。

通过代理的 gRPC: TLS, ALPN 和特殊要求

gRPC 是这组协议中要求最高的。它严格依赖于 HTTP/2,并且通常使用双向 TLS(mTLS)进行身份验证。普通的转发代理,如果不支持 HTTP/2 CONNECT 隧道化“如实”,将根本无法通过 gRPC 流量——连接将因 UNAVAILABLE: upstream connect error 错误而中断。

在 Python 中,通过代理代理 gRPC(使用 grpc 库)需要通过环境变量设置代理,因为在 channel options 中历史上没有内置的代理支持:

import os
import grpc

os.environ["grpc_proxy"] = "http://user:pass@proxy_host:8080"
os.environ["https_proxy"] = "http://user:pass@proxy_host:8080"

channel = grpc.secure_channel(
    "grpc.example.com:443",
    grpc.ssl_channel_credentials()
)

# 通过生成的 stub 调用示例
# stub = YourServiceStub(channel)
# response = stub.YourMethod(request)

在 Node.js 中,gRPC 的代理通过 grpc-js 的 channel 选项与 HTTP/2 兼容的代理代理结合设置:

const grpc = require('@grpc/grpc-js');
const { HttpsProxyAgent } = require('https-proxy-agent');

process.env.grpc_proxy = 'http://user:pass@proxy_host:8080';
process.env.https_proxy = 'http://user:pass@proxy_host:8080';

const client = new YourServiceClient(
  'grpc.example.com:443',
  grpc.credentials.createSsl()
);

为了通过代理稳定地运行 gRPC,低延迟和稳定的 TCP 连接至关重要,避免频繁中断——多路复用的 gRPC 流对网络故障的敏感程度高于单个 HTTP/1.1 请求。在这种情况下,住宅代理通常比便宜的数据中心池表现出更好的稳定性,因为到目标服务器的路径通过更可预测的网络基础设施。

协议比较表

协议 连接类型 代理要求 推荐的代理类型
HTTP/1.1 请求-响应, 保持活动 最低要求,任何 HTTP 代理 数据中心代理
WebSocket 持久双向通道 支持长连接,无超时 住宅代理
HTTP/2 多路复用的二进制流 透明的 TLS 隧道化,ALPN 住宅或数据中心代理
gRPC HTTP/2 + Protobuf, 通常 mTLS 低延迟,稳定性,HTTP/2 CONNECT 移动代理 用于绕过严格的过滤

如何为自己的技术栈选择协议和代理

协议的选择很少是自由的决定——它由目标 API 决定。但为该协议选择代理类型则是您的责任,这里应根据具体场景进行考虑:

通过 WebSocket 解析数据(例如,从市场网站流式传输报价或实时数据)需要稳定的长连接,避免超时中断。在这里,住宅代理效果更好——它们更少受到目标服务的反欺诈算法影响,并且能够保持连接的时间比典型的数据中心池更长。

通过外部代理与 gRPC 微服务集成(例如,从不同地理位置测试 API)需要低延迟,并在整个路径上支持 HTTP/2。适合使用具有良好路由的住宅 IP,而对于时间敏感的任务——如果目标服务积极过滤数据中心范围,则使用移动代理。

对 REST/GraphQL API 的大规模 HTTP/2 请求,使用多路复用——这是最常见的场景,如果目标服务默认不阻止数据中心 IP 范围,快速且便宜的数据中心代理就足够了。

一般规则是:协议对连接稳定性的“挑剔”程度越高(WebSocket、gRPC),使用住宅或移动 IP 的意义越大。请求越简单和短小(普通 HTTP/1.1 或没有长会话的 HTTP/2),使用数据中心代理会更舒适——它们更快并提供更高的带宽。

常见错误及其解决方案

错误 1: WebSocket 每 30-60 秒断开一次。 原因是代理或中间负载均衡器关闭了“非活动”连接。解决方案:在应用层启用 ping/pong 帧,间隔小于代理的超时(通常 20-25 秒就足够)。

错误 2: gRPC 通过代理时出现 UNAVAILABLE 错误,而直接连接正常。 代理不支持 HTTP/2 CONNECT 隧道化。解决方案:检查提供商文档以确认是否支持 HTTP/2,或者使用 SOCKS5 代理,它在不解析协议的情况下隧道化 TCP。

错误 3: HTTP/2 不知不觉中降级到 HTTP/1.1。 这通常是由于代理缺少 ALPN 支持。请在代码中明确检查协议版本(如上面的 httpx 示例)——如果没有检查响应中的 http_version,不要依赖于“所有正常”。

错误 4: 通过代理多路复用 gRPC 时延迟高。 通常是由于代理提供商的中间节点过多。解决方案:提前通过简单的 RTT 请求测试延迟,并选择具有直接路由的提供商,特别是对于时间敏感的 gRPC 调用。

结论

WebSocket、HTTP/2 和 gRPC 需要不同的代理设置方法,正是因为它们在不同的传输模型上工作:从简单的“放开 TCP 连接”到严格支持 HTTP/2 CONNECT 和 ALPN 的 gRPC。请在代码中明确检查协议版本,提前测试长连接的稳定性,并在需要最大透明度的隧道化时选择 SOCKS5。

如果您的技术栈包括长时间存在的 WebSocket 连接或对延迟敏感的 gRPC 调用,建议尝试 住宅代理——它们提供比典型的数据中心池更稳定和可预测的连接。对于简单的高带宽 HTTP/2 请求,适合使用 数据中心代理,而对于目标服务有激进反欺诈过滤的任务——使用 移动代理。