simple-proxy:一个轻量代理转发工具
{Back to Index}
Table of Contents
1. 简介
simple-proxy 是一个基于 Python 的小型 TCP/应用代理工具,底层网络 I/O 由 py-netty 驱动。
最基本的工作方式是把客户端连接和远端服务连接接起来:
Figure 1: simple-proxy 的整体定位
它和完整的反向代理或 API 网关不是同一类软件。simple-proxy 更像一个可以随时启动的网络工具箱:不需要编写服务端代码,就可以在本机端口前后插入一层转发、观测、协议适配或故障注入逻辑。
simple-proxy 的价值不在于取代成熟的 Nginx、HAProxy 或专业 SOCKS 网关,而在于提供一个随手可用的网络实验台:
- 用最少的配置把两个 TCP 端点接起来。
- 根据需要增加 TLS、SNI、流量记录、监控和延迟。
- 在 HTTP、SOCKS5、Stub、文件服务和 Echo 模式之间快速切换。
- 通过 py-netty 的 EventLoop 和非阻塞 Channel,以较小的代码量处理大量 I/O 事件。
2. simple-proxy 的运行结构
simple-proxy 启动时会根据参数选择一个 Handler:
- 默认使用 =ProxyChannelHandler=,建立一条客户端连接和一条远端连接,双向转发字节。
--http-proxy使用 =HttpProxyChannelHandler=,先解析 HTTP 请求行或 =CONNECT=。--socks5-proxy使用 =Socks5ProxyChannelHandler=,按握手、认证、请求三个状态处理协议。--http-stub=、–echo-proxy=、=–shell-proxy= 使用对应的内置服务 Handler。--file-server走标准库 HTTP 服务实现,不经过 TCP Proxy 的普通转发路径。
普通 TCP Proxy 的一次连接大致如下:
Figure 2: TCP Proxy 的连接和双向转发时序
workers 控制接收连接的 Worker EventLoop 数量,=proxy-workers= 控制代理主动建立的客户端连接使用的 EventLoop 数量。默认值都是 1;I/O 较多时可以调整,但线程数量不是越多越好,仍需要结合 CPU、连接数和数据量测试。
3. py-netty 的底层原理
3.1. EventLoop:一个线程处理许多连接
py-netty 的核心是 selectors.DefaultSelector=。每个 =EventLoop 持有一个 selector、一个 Channel 表和一个任务队列,并在自己的线程中循环:
- 调用 selector 等待可读、可写或异常事件。
- 对监听 Socket 执行 accept,把新连接交给 Handler。
- 对普通 Socket 执行非阻塞 recv,把字节交给 =channel_read=。
- 对可写 Socket 刷新待发送队列。
- 处理其他线程提交的任务和连接超时。
底层 selector 会根据操作系统选择 epoll、kqueue 或 select。这样既避免了每个连接创建一个阻塞线程,也让上层 Handler 只关心连接事件和字节数据。
3.2. ServerBootstrap 与 Bootstrap
ServerBootstrap 有两组 EventLoopGroup:
- parent group,也可以叫 Boss,负责监听端口和 accept。
- child group,也可以叫 Worker,负责处理每一个已建立的客户端 Socket。
接收连接后,py-netty 创建 NioSocketChannel=,把它注册到 Worker 的 EventLoop,并初始化对应 Handler。simple-proxy 的 Handler 再通过 =Bootstrap.connect 建立到远端的另一个 =NioSocketChannel=。因此一次转发至少涉及两个 Channel:客户端到代理、代理到远端。
Figure 3: py-netty 的核心对象关系
3.3. 跨线程操作与 eventfd
Channel 的 Socket 操作应当由所属 EventLoop 执行。如果其他线程调用 =write=、修改监听事件或注册 Channel,py-netty 会把任务放进 EventLoop 的任务队列,再通过 eventfd 唤醒 selector。EventLoop 被唤醒后取出任务执行,从而避免多个线程直接同时操作同一个 Socket。
这也是 EventLoop 模型的重要边界:应用可以从其他线程提交任务,但真正的 recv、send、selector 注册和状态修改仍集中到 I/O 线程中。
3.4. 读写与背压
Socket 读写都是非阻塞的:
- 读事件到来后,=recvall= 会连续读取若干次,并根据最近一次读取量自适应调整缓冲区大小。
- 写入能够立即完成时直接调用 send;如果只写出一部分,剩余数据会放入待发送队列,等待下一次可写事件。
- 待发送字节达到高水位线时,Channel 变为不可写;下降到低水位线后恢复可写。默认高水位线约为 64 KiB,低水位线约为 32 KiB。
- simple-proxy 会监听上下游 Channel 的可写状态,并通过
set_auto_read暂停另一端继续读取,避免慢接收方导致内存中的待发送数据无限增长。
这套机制并没有改变 TCP 的拥塞控制,但把应用层的读写速度和内核 Socket 缓冲区连接起来,是代理工具能够稳定转发大数据的重要原因。
3.5. TLS 握手
客户端连接远端 TLS 服务时,py-netty 先把普通 Socket 包装成非阻塞 =SSLSocket=。TLS 握手可能需要读取数据,也可能需要写出数据,因此:
SSLWantReadError会让 selector 等待可读事件。SSLWantWriteError会让 selector 等待可写事件。- 握手完成后才触发
channel_handshake_complete和 =channel_active=。
这使 TLS 握手不会阻塞整个 EventLoop。simple-proxy 可以在监听端和远端分别配置 TLS,两个方向的 TLS 会话相互独立。
3.6. Handler 与 Future
py-netty 当前是轻量的单 Handler 回调模型。Handler 主要接收 channel_active=、=channel_read=、=channel_inactive=、=channel_writability_changed=、=channel_handshake_complete 和 exception_caught 等事件;=ChannelHandlerContext= 提供 write 与 close 操作。
它借鉴了 Netty 的命名和事件驱动思想,但不是完整 Java Netty Pipeline:当前版本没有多 Handler Pipeline,也没有内置完整的 HTTP 编解码器、ByteBuf 对象池或引用计数管理。应用通常直接处理 Python =bytes=,这正适合 simple-proxy 的“收到什么就转发什么”场景。
连接建立、写入和关闭都通过 ChannelFuture 表示异步结果。=sync()= 可以等待结果,监听器则可以在结果完成后异步执行逻辑。simple-proxy 使用这些 Future 等待远端连接建立,并在任一端关闭时关闭另一端。
4. 功能总览
| 功能 | 说明 | 典型用途 |
|---|---|---|
| TCP Proxy | 转发任意 TCP 字节流 | 调试数据库、缓存、内部服务或自定义协议 |
| TLS | 远端 TLS、监听端 TLS、SNI、ALPN | 测试 HTTPS/TLS 服务和证书配置 |
| HTTP Proxy | 支持普通 HTTP 请求与 CONNECT 隧道 | 配置浏览器、curl 或程序的 HTTP 代理 |
| SOCKS5 Proxy | 支持 IPv4、域名、IPv6 和可选认证 | 为不支持 HTTP Proxy 的程序提供代理 |
| HTTP Stub | 接收 HTTP/1.x 请求并返回固定成功响应 | 模拟依赖服务、测试客户端行为 |
| File Server | 浏览目录和通过网页上传文件 | 临时共享文件、测试上传逻辑 |
| Echo / Shell | 回显 TCP 数据,或提供 Shell 交互 | 连通性测试、受控的远程调试 |
| 流量观测 | 控制台打印、保存 TCP 流、连接状态监控 | 查看数据方向、速率、总量和持续时间 |
| 故障注入 | 读写方向分别增加毫秒级延迟 | 模拟慢网络和验证超时处理 |
| TLS disguise | 对特定连接转发到伪装 HTTPS 服务或目标 | 实验性流量分流与探测响应 |
其中 TCP Proxy 是默认模式,其他模式由命令行选项选择。HTTP Proxy 和 SOCKS5 Proxy 会先解析各自的协议握手,再为每个客户端建立到目标的连接;普通 TCP Proxy 则直接转发收到的字节。
5. 基础用法
5.1. TCP 转发
假设本机有一个 TCP 服务监听 127.0.0.1:9000=,让 simple-proxy 在 =127.0.0.1:8080 接收连接:
simple-proxy -l 127.0.0.1 -p 8080 -r 127.0.0.1 -rp 9000
-l 和 -p 指定监听地址和端口,=-r= 和 -rp 指定远端地址和端口。监听地址默认是 =localhost=,监听端口默认是 =8080=;如果使用 =-g=,则监听所有网卡:
simple-proxy -g -p 8080 -r 10.0.0.12 -rp 9000
5.2. 远端 TLS 与本地 TLS
--tls 表示 simple-proxy 连接远端时使用 TLS。例如,把本地的明文 TCP 连接转发到 HTTPS 服务:
simple-proxy --tls -r example.com -rp 443 -p 8080
如果远端使用 IP 地址但证书依赖域名,可以显式指定 SNI:
simple-proxy --tls -r 203.0.113.10 -rp 443 \
--server-name-indication example.com -p 8080
-ss 则表示监听端使用 TLS。证书和私钥需要同时指定:
simple-proxy -ss -kf server.key -cf server.crt \
-r 127.0.0.1 -rp 9000 -p 8443
因此,=-ss= 和 --tls 分别对应连接的两端:前者保护客户端到代理的连接,后者保护代理到远端的连接。=-alpn= 可用于启用 h2 与 http/1.1 的 ALPN 配置。
5.3. 流量输出与连接监控
使用 -c 在控制台输出 TCP 流量详情,使用 -f 将流量保存到 tcpflow 目录:
simple-proxy --tls -r example.com -rp 443 -p 8443 \
-ss -s -c -f
使用 -m 打开连接状态监控,=-mi= 调整刷新间隔,单位是秒:
simple-proxy -m -mi 3 -p 8080 -r 127.0.0.1 -rp 9000
监控信息包括连接数、收发方向、速率、累计字节数和连接持续时间。流量保存适合定位协议和时序问题,但不应在未经脱敏的情况下用于包含真实凭据的生产流量。
6. 应用层代理与内置服务
6.1. HTTP Proxy
启动 HTTP 代理:
simple-proxy --http-proxy -p 8080
然后让 curl 使用它:
http_proxy=http://127.0.0.1:8080 \ https_proxy=http://127.0.0.1:8080 \ curl -I https://example.com/
HTTP Proxy 同时处理普通 HTTP 请求和 HTTPS 的 CONNECT 请求。可以设置基本认证:
simple-proxy --http-proxy -p 8080 \ --proxy-username demo --proxy-password 'change-me'
还可以使用 --proxy-transform 把客户端请求的目标地址映射到另一个地址,适合测试域名、端口或服务替身之间的切换:
simple-proxy --http-proxy -p 8080 \
--proxy-transform example.com 443 127.0.0.1 8443
如果需要让 HTTP Proxy 通过一个上游 SOCKS5 连接,可配置 --internal-socks5-host 和 =–internal-socks5-port=。
6.2. SOCKS5 Proxy
启动 SOCKS5 代理:
simple-proxy --socks5-proxy -p 1080
SOCKS5 处理流程包括方法协商、可选用户名密码认证和 CONNECT 请求。目标地址可以是 IPv4、域名或 IPv6,也支持和 HTTP Proxy 类似的目标转换:
simple-proxy --socks5-proxy -p 1080 \ --proxy-username demo --proxy-password 'change-me'
6.3. HTTP Stub
HTTP Stub 接收 HTTP/1.0 或 HTTP/1.1 请求并返回固定的 =200 OK=,同时记录请求摘要:
simple-proxy --http-stub -p 8080 curl -v http://127.0.0.1:8080/test
加上 -c 后,可以输出请求头和最多约 1 KiB 的请求体预览。它适合测试“依赖服务已经成功响应”这一分支,不适合模拟完整的业务 API。
6.4. 文件服务器
使用 --file-server 启动文件服务,=-d= 指定根目录:
simple-proxy --file-server -d ./public -p 8000
目录请求会展示文件列表,文件可以直接下载;访问 /upload 可以通过浏览器上传文件。也可以用 curl 测试上传:
curl -F 'file=@README.md' http://127.0.0.1:8000/upload
加上 -ss 可以使用 HTTPS。没有提供证书和私钥时,文件服务器会生成临时自签名证书,因此测试访问通常需要 =curl -k=。
6.5. Echo 与 Shell Proxy
Echo 模式把收到的数据原样写回,适合验证 TCP 链路:
simple-proxy --echo-proxy -p 7000
Shell Proxy 可以把 TCP 连接接入命令行 Shell:
simple-proxy --shell-proxy -p 9000
该模式具有直接的命令执行能力,只能在本机或完全可信的隔离网络中临时使用;启动后应尽快停止服务,不应绑定 -g 暴露到局域网或公网。
7. TLS disguise 与白名单
simple-proxy 还提供实验性的 TLS disguise 选项。它可以启动一个伪装的 HTTPS 服务,也可以把某些连接转发到 --disguise-tls-ip 与 --disguise-tls-port 指定的目标:
simple-proxy --run-disguise-tls-server \
-wl 127.0.0.1,10.0.0.0/8
在普通 TCP Proxy 中,程序可以根据远端客户端地址执行白名单判断。未通过白名单的连接可以关闭,或者在配置 disguise 目标后转发到另一个服务。实现会在收到首个数据包后识别部分 TLS 探测流量,因此这不是通用的 TLS 安全策略,也不能替代防火墙、认证和访问控制。
8. 为什么推荐这个小工具
8.1. 1. 启动成本很低
一个 pip 包和一条命令就能得到 TCP 转发、HTTP Proxy、SOCKS5、文件服务器或 HTTP Stub。很多临时网络问题不值得专门写一个服务端,simple-proxy 正好填补了这段空白。
8.2. 2. 功能集中在网络调试最常用的动作
它把转发、TLS、SNI、流量记录、延迟注入和连接监控放在同一个 CLI 中。排查“请求究竟有没有到达”“哪一端关闭了连接”“慢网络下客户端是否超时”等问题时,可以逐步打开这些能力,而不必更换工具链。
8.3. 3. 同一套 I/O 模型覆盖多个模式
HTTP Proxy、SOCKS5 和普通 TCP Proxy 都建立在 py-netty 的事件驱动 Channel 之上。协议解析部分不同,但连接生命周期、双向转发、异常处理、背压和关闭传播可以复用同一套模型,代码结构清楚,也方便阅读和修改。
8.4. 4. 适合学习 Python 网络编程
py-netty 的代码量小,能够看到 selector、EventLoop、非阻塞 connect、部分写入、Future 和 TLS 握手如何组合起来。对于熟悉 Java Netty、但想理解 Python Socket 事件循环的人,它是一个容易跟踪的对照实现。
9. 小结
如果你的问题是“我需要一个临时代理来观察、转发或模拟网络连接”,它值得优先尝试;如果问题是“我需要一个面对不可信用户的长期生产网关”,则应该把它当作调试工具,而不是最终基础设施。