mitmproxy 自定义 Options 开发指南:类型化配置、configure 事件与 YAML 持久化全解析
【免费下载链接】mitmproxyAn interactive TLS-capable intercepting HTTP proxy for penetration testers and software developers.项目地址: https://gitcode.com/GitHub_Trending/mi/mitmproxy
在 mitmproxy 中,驱动代理运行时行为的一切设置都集中存放在一个全局options 存储(global options store)中。它不仅是内置功能(端口、模式、上游代理等)的配置中枢,也是第三方 addon 与主程序交互最标准、最强大的接口:addon 只需声明一个带类型注解的选项,就能自动获得命令行参数、交互式编辑器、YAML 配置文件、类型校验与回滚等一系列完整支持。本文围绕 docs/src/content/addons/options.md 展开,结合源码(OptManager、Loader、ConfigureHook等)深入讲解如何为 addon 定义选项、响应配置变更,并解释这套类型化配置系统在 optmanager.py 中是如何实现的。读完本文,你将能写出"声明即配置"的生产级 addon,并理解配置在命令行、交互界面与~/.mitmproxy/config.yaml之间的流转机制。
Options 机制概览:一个贯穿全工具链的配置中枢
在 mitmproxy 的设计里,options 是"会决定 mitmproxy 及其 addon 行为"的设置项。它们有三种修改途径,且互相等价:
- 配置文件:从 YAML 配置文件中读取;
- 命令行:通过
--set等标志在启动时覆盖; - 交互式修改:用户可在 mitmproxy / mitmweb 的界面里即时改动。
所有选项都必须带类型注解。mitmproxy 对支持的类型有统一的序列化 / 反序列化能力,并在交互式程序中提供标准化的类型编辑方式。一旦尝试赋入错误类型的值,系统会报错。这意味着addon 选项只要声明了类型,就自动获得整个 mitmproxy 工具链的完整支持——这是该设计最核心的收益。
这套机制还与更上层的配置概念打通:根据 docs/src/content/concepts/options.md 的说明,所有 mitmproxy 工具共享位于~/.mitmproxy/config.yaml的 YAML 配置文件,绝大多数命令行标志本质上是底层 option 的别名,交互式工具中的改动也只是在修改运行时 options 存储中的值。因此 addon 自定义的选项也会出现在集中配置文件与选项编辑器中,与内置选项地位完全对等。
第一个示例:用load事件声明一个布尔选项
声明自定义选项的入口是load事件。它收到一个 addonmanager.py 中的Loader实例,addon 在此完成选项与命令的注册。最直接的例子是仓库自带的 examples/addons/options-simple.py:
""" Add a new mitmproxy option. Usage: mitmproxy -s options-simple.py --set addheader=true """ from mitmproxy import ctx class AddHeader: def __init__(self): self.num = 0 def load(self, loader): loader.add_option( name="addheader", typespec=bool, default=False, help="Add a count header to responses", ) def response(self, flow): if ctx.options.addheader: self.num = self.num + 1 flow.response.headers["count"] = str(self.num) addons = [AddHeader()]这里的关键点:
loader.add_option(name, typespec, default, help)注册了一个名为addheader、类型为bool、默认值为False的选项;- 在
response事件中,通过 ctx.options.addheader 读取当前值——ctx是 mitmproxy 提供给 addon 的全局上下文单例,其中的options对象即全局 options 存储; - 因为访问的是"当前值",用户任何时候改动选项都会立即影响后续请求的处理。
运行与验证
用 console 模式加载脚本并启动代理:
> mitmproxy -s ./examples/addons/options-simple.py此时通过代理发起一个请求(-I只取响应头):
> env http_proxy=http://localhost:8080 curl -I http://google.com由于选项默认值为false,响应头里不会出现count。现在在 mitmproxy 界面中按下O进入选项编辑器,找到addheader——你会发现 mitmproxy 知道它是布尔类型,并允许你在true/false之间切换。将它设为true后再发一次请求:
> env http_proxy=http://localhost:8080 curl -I http://google.com HTTP/1.1 301 Moved Permanently Location: http://www.google.com/ Content-Length: 219 count: 1count: 1表明选项已被启用,并成功为第一个响应注入了计数响应头。
命令行覆盖:--set标志
同一个选项也可以在启动命令行中直接覆盖,适用于所有工具:
mitmproxy -s ./examples/addons/options-simple.py --set addheader=true注意--set的取值在命令行里是字符串("true"),需要被正确转换回bool。这正是类型系统发挥作用的地方:options 存储会按选项声明的类型把字符串解析为对应的 Python 值(解析规则见后文"底层实现"一节的_parse_setval)。
响应配置变更:configure事件与校验回滚
有些场景下,仅在某次事件里读一下选项值是不够的——我们希望在用户改动选项的瞬间就采取行动,例如校验值的合法性并及时反馈。这就是configure事件的作用。
根据 hooks.py 中的ConfigureHook的定义,当配置发生变化时,configure会被调用,其updated参数是包含所有被改动选项名的集合(set-like 对象)。addon 可以判断某个选项是否在集合中,再从ctx.options读取新值。同时要注意:configure 会在启动阶段被调用一次,updated 集合包含全部选项——因此第一个被触发的configure会用默认值(或配置文件中已设置的值)初始化 addon 状态。
configure最常见的用途之一是校验选项:如果在校验过程中抛出 exceptions.OptionsError,则本次更新的所有改动都会被自动回滚,并向用户显示错误。仓库示例 examples/addons/options-configure.py 完整演示了这一模式:
"""React to configuration changes.""" from typing import Optional from mitmproxy import ctx from mitmproxy import exceptions class AddHeader: def load(self, loader): loader.add_option( name="addheader", typespec=Optional[int], default=None, help="Add a header to responses", ) def configure(self, updates): if "addheader" in updates: if ctx.options.addheader is not None and ctx.options.addheader > 100: raise exceptions.OptionsError("addheader must be <= 100") def response(self, flow): if ctx.options.addheader is not None: flow.response.headers["addheader"] = str(ctx.options.addheader) addons = [AddHeader()]这个例子与上一个有两处显著差异:
- 选项类型是
typing.Optional[int],向 mitmproxy 表明None是该选项的合法取值——即"未设置"。相应地,response里要判空后再使用。 configure会被调用两次:先用默认值None,之后一旦用户修改,就用新值再次调用。本例规定取值不得超过 100,否则抛出OptionsError。
触发错误回滚的验证
用错误值加载脚本,会看到如下错误输出:
> mitmdump -s ./examples/addons/options-configure.py --set addheader=1000 Loading script: ./examples/addons/options-configure.py /Users/cortesi/mitmproxy/mitmproxy/venv/bin/mitmdump: addheader must be <= 100OptionsError抛出后,本次设置(addheader=1000)被整体回滚,选项保持之前的合法值,用户得到明确的错误提示。
支持的选项类型
根据文档,选项支持以下类型,并可通过相应注解扩展:
| 类型 | 注解写法 | 说明 |
|---|---|---|
| 原始类型 | str、int、float、bool | 最基础的标量选项 |
| 可选值 | typing.Optional[...](如Optional[int]) | 允许取None,即"未设置" |
| 值序列 | collections.abc.Sequence(如Sequence[str]) | 允许一次配置多个值 |
这套类型不仅是声明,还贯穿校验与 CLI 生成。从源码 typecheck.py 的check_option_type可以看出运行时校验对 Union / Optional、tuple、Sequence、IO、Any都有专门分支:例如 Sequence 的元素会逐个递归校验,float选项接受整数输入。而 typecheck.py 的typespec_to_str则负责把注解渲染成人类可读的描述文本(如optional str、sequence of str),用于帮助文本与配置转储。
值得补充的是Loader.add_option的真实签名还接受一个可选参数choices(一个字符串序列),用于限定字符串选项的候选值。addon 注册选项时,若同名选项已存在且签名(name / typespec / default / help / choices)完全一致则静默复用;签名不同则打印 "Over-riding existing option" 警告并覆盖。
命令行里三种类型的实际表现
- 布尔值解析非常宽容(见 optmanager.py 的
_parse_setval):true/false显式赋值,省略值时视为true(--set addheader即开启),特殊的toggle关键字会翻转当前值。 - 整型/字符串选项按对应类型解析,字符串不合法时抛出 "Failed to parse option ... " 的
OptionsError;普通str/int不允许缺省值,Optional类型则可显式置空。 Sequence[str]选项可以通过重复传参累积多个值。官方文档给出的例子是:
mitmweb --set ignore_hosts=example.com --set ignore_hosts=example.org从源码看,make_parser(见下节)为Sequence[str]选项生成的是action="append"的命令行参数,天然支持多次指定。
底层实现:Options 如何完成类型校验、回滚与配置加载
要真正用好这套机制,理解 optmanager.py 中的OptManager与_Option至关重要。
_Option:每个选项的最小单元
每个注册的选项都被包装成一个 optmanager.py 中的_Option实例,携带name、typespec、default、help、choices,以及当前值value。它有如下设计细节:
- 构造与赋值时都会调用
typecheck.check_option_type校验,把非法值挡在门外; - 当前值未显式设置时以
unset哨兵标记,读取时返回默认值; - 读取永远返回深拷贝(
current()与default属性),保证外部对返回值的修改不会意外污染存储中的选项状态; reset()恢复默认值,has_changed()判断是否偏离默认值。
注册、更新与回滚
- OptManager.add_option 把
_Option放入内部字典后,立即通过changed信号广播updated={name}——这正是configure事件触发的源头(AddonManager 将options.changed信号接到自己的_configure_all,见 addonmanager.py)。 - 更新的主干是
update_known(optmanager.py):批量赋新值后用with self.rollback(...)包裹并发起changed广播。 - rollback 上下文管理器 是整个机制的关键:更新前深拷贝全部选项,若广播期间任何 handler 抛出
OptionsError,则整体回滚到旧状态、触发errored信号并再次广播changed(以便界面刷新为已回滚的值)。这正是上一节"校验失败即回滚"的底层来源——configure中的异常经由changed信号传播,被rollback捕获处理。 subscribe(func, opts)提供了更精细的订阅:只对指定选项列表的变化回调,可用于对特定选项做轻量级监听。
从命令行字符串到类型化值
命令行标志与--set最终都会进入 OptManager.set 与_parse_setval:前者把name=value规格按选项分组,后者按typespec把字符串转换为 Python 值(bool 的toggle、int 的严格解析、Sequence 的取多值等规则即来自此函数)。未知选项在defer=False时直接抛出OptionsError;开启 defer 时会被暂存,待选项注册后再通过process_deferred()应用——这让配置文件中声明的选项可以出现在脚本选项之前。
自动生成命令行参数
OptManager.make_parser 把"声明即得 CLI"落实到了 argparse 层面:遍历 options 自动为每个选项创建参数。细节包括:
- 布尔选项生成一对互斥参数
--flag/--no-flag(store_true / store_false),短选项自动挂在"非默认值"一侧; - 字符串选项自动附带
choices校验; Sequence[str]选项使用action="append",帮助文本标注 "May be passed multiple times."。
内置的绝大多数命令行开关都因此是自动派生的,这也是 concepts 文档声称"几乎所有命令行标志都是底层 option 别名"的直接原因。
YAML 配置的加载与持久化
配置存储层的加载与保存全部位于 optmanager.py:
parse(text)用 ruamel.yaml 安全加载 YAML,并针对语法错误给出带行号定位的可读报错;load(opts, text)应用解析结果;特殊处理了scripts键——把相对路径重写为相对于配置文件所在目录,因此配置里写- scripts/addon.py不必依赖启动目录;load_paths(*paths)依次加载多个配置文件,后者覆盖前者,不存在的路径直接忽略;serialize/save做往返式(round-trip)持久化:默认只写出相对默认值有变化的选项,保留原文件中已有的注释与默认项,并自动清理配置中已不存在的未知选项。- 各工具提供
--options标志:dump_defaults(optmanager.py)会向标准输出转储带注释的 YAML——每个选项含默认值、帮助文本以及由typespec_to_str生成的类型说明,是核对当前环境全部可用选项与默认值的权威参考。
实战建议与设计要点
基于以上机制,编写带选项的 addon 时可遵循以下建议:
- 尽早声明、类型明确:在
load事件里用loader.add_option注册所有选项,typespec 严格写清bool、int、Optional[str]或Sequence[str]。类型会一路"传染"出 CLI 参数、编辑器和 YAML 校验。 - 把校验放在
configure而非事件内:只有configure里抛出的OptionsError才能触发自动回滚和用户提示;在事件里判错只能中断单个请求,无法阻止状态进入非法值。 - 记住 configure 的启动调用:
configure启动时会用全部选项调用一次,可直接在此初始化派生状态;若依赖多个选项的组合,可在configure中判断多个 key 是否同时在updates里,或在所有需要后手动兜底。 - 善用 choices 与 Sequence:有限候选用
choices约束;需要白名单、忽略列表等多值场景用Sequence[str],命令行重复传参即可累积。 - 文档与发现:面向使用者的选项说明写在
help参数中即可——它会出现在选项编辑器、--help与--options转储中,无需另写文档。
延伸阅读
- 配置文件的完整语义、内置选项总表与各工具编辑器的用法,参见 docs/src/content/concepts/options.md;
- 选项存储与加载/序列化的实现细节:mitmproxy/optmanager.py;
load事件的 Loader 与configure的触发链:mitmproxy/addonmanager.py、mitmproxy/hooks.py;- 类型校验与类型到文本的转换:mitmproxy/utils/typecheck.py;
- 两个可直接运行参考的完整 addon:examples/addons/options-simple.py、examples/addons/options-configure.py。
【免费下载链接】mitmproxyAn interactive TLS-capable intercepting HTTP proxy for penetration testers and software developers.项目地址: https://gitcode.com/GitHub_Trending/mi/mitmproxy
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考