proxy.py 实战:构建可定制的采集转发网关

安装 proxy.py,编写按目标主机添加诊断头的插件,用回显请求检查转发行为,并区分 HTTP 修改与 HTTPS 隧道的能力。

亿牛云技术团队2026年10月28日5 分钟阅读

多个采集程序都需要添加某个诊断头时,可以修改每个客户端,也可以把这条规则放在统一转发网关。proxy.py 提供 Python 插件接口,适合把明确的请求处理放在应用外。

本文在本机启动网关,只对指定测试主机添加任务标识,用回显服务检查目标收到的请求。这里修改的是普通 HTTP 请求,不启用 TLS 解密,也不把它写成能改所有 HTTPS 内容的通用方案。

安装与启动一个基本代理

使用 Python 3.12,终端命令采用 Bash:

python3.12 -m venv .venv
source .venv/bin/activate
python -m pip install proxy.py
python -m pip show proxy.py
proxy --hostname 127.0.0.1 --port 8899

记录实际安装版本。插件接口可能变化,升级时要重新核对使用的基类方法,而不只是确认包能导入。安装与插件示例见 proxy.py 官方仓库。

保持代理运行,在另一个终端请求测试站:

curl --max-time 20 --noproxy ''   --proxy http://localhost:8899 http://httpbin.org/headers

确认返回的是请求头 JSON。若所在网络不允许普通 HTTP,可以改用你自己控制的 HTTP 回显端点;不要悄悄把地址改成 HTTPS 后继续期待下文的请求头修改。

结束基本代理后,再加载插件,避免同一端口被两个进程占用。

编写有明确范围的请求规则

在工作目录创建 gateway.py:

import logging
from typing import Optional
from proxy.http.parser import HttpParser
from proxy.http.proxy import HttpProxyBasePlugin
 
logger = logging.getLogger(__name__)
 
class JobHeaderPlugin(HttpProxyBasePlugin):
    def before_upstream_connection(self, request: HttpParser) -> Optional[HttpParser]:
        return request
 
    def handle_client_request(self, request: HttpParser) -> Optional[HttpParser]:
        if request.host and request.host.lower() == b"httpbin.org":
            request.add_header(b"X-Collection-Job", b"docs-demo")
            logger.info("applied job header to host=%r", request.host)
        return request

启动时让 Python 能导入当前目录中的模块:

PYTHONPATH="$PWD" proxy --hostname 127.0.0.1 --port 8899   --plugins gateway.JobHeaderPlugin

--plugins 后面是“模块名.类名”,不是文件路径。基类允许返回请求继续转发;这里没有添加拒绝或重写目标的逻辑。添加请求头的方法接收 bytes,不能随意把 Python 字符串与字节混用。

规则只命中 httpbin.org。把诊断头附加给所有网站,会把内部标识发送到不需要它的来源;真实任务应配置允许主机与任务头,不用示例中的固定值承担全站业务。

检查目标真正收到了什么

再次运行前面的 curl。在返回的 headers 中寻找 X-Collection-Job,期望值为 docs-demo。大小写显示可能由回显服务规范化,不要用大小写差异判断头是否丢失。

这个检查同时覆盖客户端代理配置、网关插件加载、请求修改与目标回显。仅看到网关日志打印“applied”,还不能证明目标实际收到修改后的头。

如果改用自己的回显主机,插件里的主机判断也要一起改。否则网关仍能正常转发,但规则不命中,结果不会包含任务头。

为什么 HTTPS 请求不一定出现这个头

普通 HTTP 代理可以读取并修改明文请求。HTTPS 常通过 CONNECT 建立隧道,目标请求头在 TLS 内部。没有解密的网关看到的是建立隧道的请求,不是加密后的业务请求。

因此把测试 URL 改成 https://httpbin.org/headers 后,不能期待这段插件给目标 HTTPS 请求加头。若只需要添加业务请求头,优先在浏览器或 HTTP 客户端配置。TLS interception 涉及证书生成、客户端信任与独立运维要求,不属于本例。

这也解释了为什么“代理能访问 HTTPS”与“代理能修改 HTTPS 正文”是两项不同能力。

接入多个采集程序

对普通 HTTP 客户端,设置代理地址为 http://localhost:8899;对支持环境变量的工具,可按其约定配置 HTTP_PROXY。不要默认每个库都会读取这些变量,应通过回显或网关连接日志确认请求确实经过代理。

跨容器使用时,客户端的 localhost 不指向网关容器。需要明确服务地址与监听范围。本例仅监听本机,不提供跨主机访问;扩展时先确定哪些调用方被允许连接,而不是直接开放监听。

插件日志只记录命中的主机,没有请求路径、认证头和正文。诊断数据越详细,越应考虑其中是否包含账号或查询条件,不能为了排错把所有请求内容输出到共享日志。

常见问题与处理

提示找不到 gateway。 确认当前目录有 gateway.py,启动命令中的 PYTHONPATH 指向它的目录,并且使用安装了 proxy.py 的同一环境。

插件导入成功但头没增加。 检查主机判断是否匹配、请求是否真走 HTTP、客户端是否绕过代理。先用正文中的 curl 建立对照,再检查业务程序的配置。

连接被拒绝。 检查网关是否仍运行,端口是否冲突,网络命名空间是否一致。这一步还没进入字段修改逻辑。

响应 200,却是其他页面。 检查目标返回的 Content-Type 与主体,确认没有被网络中间页替代。状态码不能代替回显字段检查。

新增插件参数不生效。 先用 --help 检查当前版本和插件参数加载方式,避免把旧接口的参数注册写法混进新版本。当前示例不引入自定义参数,便于先建立最小主线。

程序停止后代理还占端口。 在实际启动网关的终端结束服务,或让进程管理器停止该实例。不要让每个采集任务都随意关闭共享网关。

只有当规则确实需要统一管理时,才值得增加转发层。规则范围、TLS 边界和目标回显三件事讲清楚,网关才能带来可解释的行为。

需要企业代理方案?

我们可根据目标站点、并发规模与稳定性目标提供定制方案。