如何在Python中实现基于异步IO的轻量级动态代理?

如何在Python中实现基于异步IO的轻量级动态代理?

Python

asyncio + aiohttp 不能直接做动态代理,因其是应用层HTTP客户端,无法透传TLS握手、丢弃原始请求头、不支持CONNECT方法,必须用asyncio.open_connection实现TCP层字节级双向桥接。

为什么 asyncio + aiohttp 不能直接做动态代理

直接用 aiohttp.ClientSession 转发 HTTP 请求看似可行,但会丢掉原始请求头(比如 HostConnectionUpgrade)、无法透传 TLS 握手、不支持 CONNECT 方法——这意味着 HTTPS 流量根本转不过去,浏览器会报 ERR_TUNNEL_CONNECTION_FAILED。动态代理不是“发个 GET 就完事”,它得在 TCP 层维持双向流,而 aiohttp 是应用层 HTTP 客户端,天生不处理底层 socket 透传。

必须用 asyncio.open_connection + socket-level relay

核心是绕过 HTTP 解析,把 client socket 和 upstream socket 做字节级双向桥接。关键点有三个:

  • 收到客户端 CONNECT 请求后,先解析目标 host:port(如 example.com:443),再用 asyncio.open_connection(host, port) 建立上游连接
  • 建立成功后返回 HTTP/1.1 200 Connection established,然后立即启动两个并发 task:asyncio.create_task(pipe(client_reader, upstream_writer))asyncio.create_task(pipe(upstream_reader, client_writer))
  • pipe() 函数必须用 while True 循环 + await reader.read(8192) 拉取数据,不能用 readuntil()readline(),否则二进制流量(如 TLS record)会被截断

示例片段(简化版 pipe):

async def pipe(reader, writer):
    try:
        while True:
            data = await reader.read(8192)
            if not data:
                break
            writer.write(data)
            await writer.drain()
    except (ConnectionResetError, OSError):
        pass

如何支持 HTTP/HTTPS + 自动协议识别

浏览器发起代理请求时,HTTP 明文走普通请求,HTTPS 先发 CONNECT。服务端不能假设所有连接都是 HTTPS,得根据首行判断:

Python 3.14.3

微软官方的 Python 扩展,是 VS Code 安装量最高的扩展(209M+)。集成 IntelliSense(通过 Pylance)、调试(通过 Python Debugger)、代码检查、格式化、重构和单元测试等功能。支持 Jupyter Notebook、虚拟环境管理和多 Python 版本切换。

  • 若首行匹配 ^CONNECT [^ ]+:[0-9]+ HTTP/ → 进入 CONNECT 处理流程
  • 若首行匹配 ^(GET|POST|HEAD|PUT|DELETE) .+ HTTP/ → 当作 HTTP 代理,需重写 Host 头并转发完整请求(含 body)
  • 其他情况(如 HTTP/2 preface、TLS ClientHello 前几个字节)→ 直接按 raw TCP 转发,不解析

注意:不要尝试读完整 HTTP header 再判断,因为 TLS ClientHello 可能紧贴在 CONNECT 后发送(尤其 Chrome),必须用 reader.read(1)reader.peek(1) 配合超时做轻量试探。

实际部署时最常被忽略的坑

本地测试跑通不等于线上可用。真实环境里这几个点几乎必踩:

  • Linux 默认每个进程最多 1024 个文件描述符,代理每条连接占 2 个 socket,100 并发就爆了 —— 必须调 ulimit -n 65536 并在代码里设 asyncio.get_event_loop().set_default_executor(ThreadPoolExecutor(max_workers=32))
  • 客户端空闲连接不主动关闭,上游服务器可能发 FIN,但你的 pipe() 任务没监听 reader.at_eof() 就会卡住 —— 每次 read() 后都要检查 if reader.at_eof(): break
  • 没有设置 socket timeout,某个坏连接 hang 死整个 task —— asyncio.wait_for(reader.read(n), timeout=30) 是底线
  • Windows 上 ProactorEventLoopsocket.dup() 支持差,建议显式用 SelectorEventLoop

动态代理真正的复杂度不在“怎么转”,而在“怎么不崩”——连接生命周期管理、错误传播方向、buffer 大小与吞吐的平衡,这些细节没压测过就上线,扛不住真实浏览器行为。

本文地址:https://www.sztg.com.cn/article/690497.html

如非特殊说明,本站内容均来自于网友自主分享,如有任何问题均请联系我们进行处理!

猜您喜欢