HiHuo
首页
博客
手册
工具
关于
首页
博客
手册
工具
关于
  • 代理技术全栈手册

    • 代理技术全栈手册 - HiHuo
    • 原理篇

      • 第01章 代理是什么:正向 / 反向 / 透明 / 隧道的统一模型
      • 第02章 代理与网络层级:L3 / L4 / L5 / L7 在哪里截断流量
      • 第03章 一个请求穿过代理的一生:连接生命周期全景
    • 协议篇

      • 第04章 HTTP 代理协议:绝对 URI、CONNECT 隧道、转发头与连接池
      • 第05章 HTTPS 与 TLS 代理:终止 / 透传 / MITM / SNI / mTLS
      • 第06章 SOCKS 协议:SOCKS4/4a/5 与 UDP ASSOCIATE 报文级解析
      • 第07章 HTTP/2、gRPC 与 HTTP/3(QUIC) 代理的挑战
      • 第08章 代理自动配置:PAC / WPAD / 系统代理 / NO_PROXY
    • 层级与转发篇

      • 第09章 L4 代理:TCP/UDP 转发与连接级负载均衡
      • 第10章 L7 代理:协议感知与基于内容的路由
      • 第11章 透明代理:iptables REDIRECT/DNAT、TPROXY 与 eBPF 劫持
      • 第12章 数据搬运的艺术:splice / sendfile / 零拷贝 / io_uring
    • 组件横评篇

      • 第13章 Nginx / OpenResty:反向代理、upstream 与 Lua 可编程
      • 第14章 HAProxy:L4/L7、ACL、健康检查与 stick table
      • 第15章 Envoy:xDS 动态配置与 filter chain,为何是云原生数据面
      • 第16章 Traefik / Caddy:自动服务发现与自动 HTTPS
      • 第17章 Squid 与正向/缓存代理:企业出网、缓存与审计
      • 第18章 mitmproxy:抓包、改包、脚本化调试
      • 第19章 内网穿透与隧道:frp / gost / SSH 隧道 / ngrok
      • 第20章 科学上网生态的技术原理(技术中立)
    • 多语言手写篇

      • 第21章 Go:100 行手写 HTTP/CONNECT + SOCKS5 代理
      • 第22章 Rust:基于 tokio 的高性能 TCP 代理
      • 第23章 Python:asyncio 实现,适合调试与脚本
      • 第24章 C:epoll 裸写与零拷贝,及语言选型对比
    • 容器与K8s篇

      • 第25章 Docker 里的代理:HTTP_PROXY、build/pull 与 daemon 配置
      • 第26章 Sidecar 与流量劫持:Istio init-container 的 iptables 原理
      • 第27章 Ingress 与南北流量:Ingress-nginx 与 Gateway API
      • 第28章 Egress 与出网治理:出口网关、registry mirror、审计
      • 第29章 Service Mesh 数据面:Envoy Sidecar 全链路
    • 进阶篇

      • 第30章 可编程代理:Lua / Wasm / eBPF / xDS,代理的"软件定义"
      • 第31章 性能调优:并发模型、连接池、超时与重试、压测
      • 第32章 排错决策树:502 / 504 / 握手失败 / 环路 / 泄漏
      • 第33章 代理安全:开放代理、SSRF、凭证泄漏与攻击面
    • 底层机制篇

      • 第34章 代理的背压与流控:一个代理最难的部分
      • 第35章 socket 与 TCP 状态机:半关闭、超时、连接生命周期
      • 第36章 HTTP/2 帧、流控与 HPACK:h2 代理的内部机制
      • 第37章 负载均衡算法推导与韧性状态机
      • 第38章 Capstone:把玩具代理改造成生产级骨架
    • 综合实战篇

      • 第39章 企业多跳转发链:拓扑、协议矩阵与贯穿性难题
      • 第40章 端到端实战:把 6 类流量全代理通
      • 第41章 更刁钻的流量:gRPC、长轮询、WebRTC、大文件、双向流
      • 第42章 可落地完整参考实现:一套能跑的多协议转发栈
    • 附录

      • 附录 A:代理协议报文速查(HTTP / SOCKS / PAC / PROXY protocol)
      • 附录 B:组件选型决策树
      • 附录 C:抓包与命令速查

第23章 Python:asyncio 实现,适合调试与脚本

学习目标

  • 用 asyncio 写出 HTTP/CONNECT 代理,体会 Python 写代理的"最易读"
  • 理解 asyncio 事件循环、StreamReader/StreamWriter,及 GIL 对代理的影响
  • 明白 Python 代理的定位:调试、原型、脚本、控制面,而非高吞吐数据面
  • 知道何时该用 Python(mitmproxy/proxy.py),何时该换 Go/Rust

前置知识

  • 第21章 Go、第22章 Rust(对照)、第04章 HTTP 代理协议
  • 一点 Python async/await

原理

Python 写代理的定位:可读性与开发速度优先

Python 写代理不是为了快,而是为了快速写出来、改起来爽。它的杀手锏是可读性和生态:

  • 几十行就能写出可用代理,逻辑一目了然
  • 改包/脚本化极方便——mitmproxy(第18章)本身就是 Python 写的,proxy.py 也是
  • 适合:调试工具、协议原型、Mock、控制面、中低流量场景

asyncio:Python 的异步 IO

asyncio 是 Python 内置的事件循环框架,用 async/await 写非阻塞 IO:

  • asyncio.start_server(handler, host, port):起一个异步 TCP 服务
  • StreamReader/StreamWriter:高层流式读写(await reader.read() / writer.write() + await writer.drain())
  • asyncio.gather(...):并发跑多个协程(用来做双向转发)

GIL 的影响:代理恰好不太受伤

Python 有 GIL(全局解释器锁),同一时刻只有一个线程执行字节码,CPU 密集任务无法多核并行。但好消息是:

代理是 IO 密集型,大部分时间在等网络。asyncio 单线程事件循环在等待 IO 时切换协程,CPU 几乎不闲——所以 GIL 对纯转发型代理影响有限。

真正的瓶颈是:①解析/加解密等 CPU 工作(如 TLS、改包)会被 GIL 限制;②要吃满多核得上多进程(SO_REUSEPORT 多 worker,类似 Nginx)。所以 Python 适合中低流量;高吞吐数据面仍属 Go/Rust/C。


代码:HTTP/CONNECT 代理(asyncio)

一个支持明文 HTTP 转发 + CONNECT 隧道的正向代理,对照 第21章 的 Go 版:

#!/usr/bin/env python3
# http_proxy.py —— python3 http_proxy.py,监听 :8080
import asyncio
from urllib.parse import urlsplit

async def pipe(reader, writer):
    """单向转发:把 reader 的数据搬到 writer,直到 EOF"""
    try:
        while data := await reader.read(65536):
            writer.write(data)
            await writer.drain()
    except Exception:
        pass
    finally:
        writer.close()

async def handle(creader, cwriter):
    # 读请求行:METHOD target VERSION
    line = await creader.readline()
    parts = line.decode("latin1").split()
    if len(parts) < 3:
        cwriter.close(); return
    method, target = parts[0], parts[1]

    # 读完剩余请求头
    headers = b""
    while True:
        h = await creader.readline()
        headers += h
        if h in (b"\r\n", b"\n", b""):
            break

    if method == "CONNECT":
        # 隧道:target 是 host:port(第04章 authority-form)
        host, _, port = target.partition(":")
        try:
            sreader, swriter = await asyncio.open_connection(host, int(port or 443))
        except Exception:
            cwriter.write(b"HTTP/1.1 502 Bad Gateway\r\n\r\n")
            await cwriter.drain(); cwriter.close(); return
        cwriter.write(b"HTTP/1.1 200 Connection Established\r\n\r\n")
        await cwriter.drain()
        # 双向转发(对应 Go 的两个 io.Copy / Rust 的 copy_bidirectional)
        await asyncio.gather(pipe(creader, swriter), pipe(sreader, cwriter))
    else:
        # 明文 HTTP:target 是 absolute-form(第04章),转成 origin-form 发后端
        u = urlsplit(target)
        host, port = u.hostname, u.port or 80
        try:
            sreader, swriter = await asyncio.open_connection(host, port)
        except Exception:
            cwriter.write(b"HTTP/1.1 502 Bad Gateway\r\n\r\n")
            await cwriter.drain(); cwriter.close(); return
        path = u.path or "/"
        if u.query:
            path += "?" + u.query
        # 绝对 URI → 相对路径(第04章的代理转换)
        swriter.write(f"{method} {path} HTTP/1.1\r\n".encode() + headers)
        await swriter.drain()
        await asyncio.gather(pipe(creader, swriter), pipe(sreader, cwriter))

async def main():
    server = await asyncio.start_server(handle, "0.0.0.0", 8080)
    print("HTTP 代理监听 127.0.0.1:8080")
    async with server:
        await server.serve_forever()

asyncio.run(main())
python3 http_proxy.py &
curl -x http://127.0.0.1:8080 http://httpbin.org/ip          # 明文 HTTP → handle 的 else 分支
curl -x http://127.0.0.1:8080 https://example.com/ -s -o /dev/null -w "%{http_code}\n"  # CONNECT → 200

对照三种语言:逻辑骨架完全一样(读请求→判 CONNECT→拨号→双向转发),Python 的 asyncio.gather(pipe, pipe) = Rust 的 copy_bidirectional = Go 的两个 go io.Copy。差别只在表达密度与运行时性能。

脚本化改包:Python 的主场

Python 的真正优势是"顺手改包"。给上面的 pipe 加几行就能篡改流量——这正是 mitmproxy 的能力来源(第18章):

async def pipe(reader, writer, transform=None):
    while data := await reader.read(65536):
        if transform:
            data = transform(data)        # 想改什么改什么,一行的事
        writer.write(data)
        await writer.drain()
    writer.close()

灵活性与性能

维度Python + asyncio
开发速度最快,代码最短最易读
改包/脚本最方便(mitmproxy/proxy.py 即证)
吞吐/延迟三种语言里最低(解释执行 + GIL)
多核需多进程(SO_REUSEPORT)
适合调试工具、原型、Mock、控制面、中低流量
不适合高吞吐核心数据面

排错

现象根因解决
协程不跑忘 await / 没进事件循环用 asyncio.run 驱动、该 await 的 await
整个服务卡死在协程里调了阻塞函数(如同步 requests)用异步库,或 run_in_executor
writer.write 不生效没 await writer.drain()写后 drain,背压才正确
高负载下 CPU 单核打满GIL,单进程单核多进程 + SO_REUSEPORT
大量 ResourceWarningwriter 没 close转发结束成对 close

本章小结

  • Python + asyncio 写代理:最易读、最适合调试/原型/脚本,mitmproxy 即用它写就。
  • 三语言骨架一致:asyncio.gather(pipe,pipe) = Rust copy_bidirectional = Go 双 io.Copy。
  • GIL 对纯转发型代理影响有限(IO 密集),但 CPU 工作受限、吃满多核要多进程。
  • 定位:调试/控制面/中低流量用 Python,高吞吐数据面交给 Go/Rust/C。

下一章 第24章 C + epoll,下探到性能极限:用 C 裸写 epoll 事件循环 + splice 零拷贝,并对全篇四种语言做选型总结。

Prev
第22章 Rust:基于 tokio 的高性能 TCP 代理
Next
第24章 C:epoll 裸写与零拷贝,及语言选型对比