mitmproxy.net.server_spec 模块深度解析:ServerSpec 类型与 parse() 解析规则
mitmproxy.net.server_spec 是 mitmproxy 中用于描述“上游代理或目标服务器”的基础模块。它定义了 ServerSpec 类型别名和唯一的对外函数 parse(),负责把形如 https://example.com:443、example.org、[::1]:8080 这样的服务器地址字符串解析为结构化的 (scheme, (host, port)) 元组。读完本文,你将掌握 ServerSpec 的完整取值范围、parse() 的正则解析规则与默认端口逻辑,以及它在 upstream、reverse 等代理模式中的实际调用链。
1. 模块定位:一个模块,一个类型,一个函数
该模块的文档页位于 API 文档入口,对应源码为 mitmproxy/net/server_spec.py。模块 docstring 开篇即点明其职责:
Server specs are used to describe an upstream proxy or server.
模块只导出两样东西,结构极其精简:
ServerSpec:服务器地址的类型别名;parse():把字符串解析为ServerSpec的函数(带@cache装饰器)。
理解这两个入口,就理解了整个模块。
2. ServerSpec 类型:scheme 与 (host, port) 的元组组合
源码中的类型定义如下(见 mitmproxy/net/server_spec.py#L11-L14):
ServerSpec = tuple[
Literal["http", "https", "http3", "tls", "dtls", "tcp", "udp", "dns", "quic"],
tuple[str, int],
]
也就是说,一个合法的 ServerSpec 是二元组:
| 位置 | 内容 | 约束 |
|---|---|---|
| 第一项 | 协议 scheme | 必须是 http / https / http3 / tls / dtls / tcp / udp / dns / quic 九种之一 |
| 第二项 | (host, port) 元组 |
host 为字符串(支持 DNS 名、IPv4、IPv6),port 为 0–65535 的整数 |
例如 parse("http://example.com", "https") 返回 ("http", ("example.com", 80))。
这个类型在代码库中有多处直接消费,说明它是 mitmproxy 内部“服务器地址”的统一表示:
- mitmproxy/connection.py#L293:
Server连接对象的via字段类型为server_spec.ServerSpec | None,用于记录“经由哪个上游代理”; - mitmproxy/proxy/layers/http/init.py#L110:HTTP 代理层的
via字段同样使用该类型; - 示例脚本 examples/contrib/change_upstream_proxy.py 直接
from mitmproxy.net.server_spec import ServerSpec,并在request钩子里改写flow.server_conn.via = ServerSpec(("http", address))来动态切换上游代理。
3. parse() 函数:签名、参数与异常
parse() 的签名与官方示例(摘自 mitmproxy/net/server_spec.py#L29-L40 的 docstring):
@cache
def parse(server_spec: str, default_scheme: str) -> ServerSpec:
"""
Parses a server mode specification, e.g.:
- http://example.com/
- example.org
- example.com:443
*Raises:*
- ValueError, if the server specification is invalid.
"""
要点说明:
- 参数
server_spec:待解析的地址字符串。scheme 可省略,省略时使用default_scheme;port 也可省略,省略时使用该 scheme 的默认端口。 - 参数
default_scheme:字符串,当输入未带scheme://前缀时的兜底协议。调用方根据业务语义传入不同默认值(后文第 5 节会看到upstream模式传"http"、reverse模式传"https")。 - 返回值:
ServerSpec,即(scheme, (host, port))。 - 异常:任何非法输入都抛出
ValueError,且错误信息能精确定位到具体出错阶段(见第 6 节测试验证)。 @cache装饰器:相同输入直接返回缓存结果,parse()在代理模式解析等路径上可被反复调用而无需重复正则匹配。
4. 正则与校验:解析规则逐层拆解
4.1 匹配正则
解析的核心是一条 VERBOSE 正则(mitmproxy/net/server_spec.py#L16-L26):
server_spec_re = re.compile(
r"""
^
(?:(?P<scheme>\w+)://)? # scheme is optional
(?P<host>[^:/]+|\[.+\]) # hostname can be DNS name, IPv4, or IPv6 address.
(?::(?P<port>\d+))? # port is optional
/? # we allow a trailing backslash, but no path
$
""",
re.VERBOSE,
)
逐段解读:
| 片段 | 含义 |
|---|---|
(?:(?P<scheme>\w+)://)? |
scheme 可选,形如 http://、tls://;整个前缀可有可无 |
| `(?P[^:/]+ | [.+])` |
(?::(?P<port>\d+))? |
port 可选,以 :数字 形式出现 |
/? |
允许一个结尾斜杠(如 http://example.com/),但不允许任何路径——带 /path 的输入会匹配失败 |
4.2 三层校验
正则命中之后,parse() 还做三层显式校验,每层抛出信息不同的 ValueError:
- scheme 白名单(mitmproxy/net/server_spec.py#L45-L60):显式给出的 scheme 或
default_scheme都必须属于九种合法值,否则抛出Invalid server scheme: {scheme}。例如ftp://example.com会被拒绝。 - host 校验(mitmproxy/net/server_spec.py#L62-L67):若 host 以
[开头并以]结尾,先剥掉括号(即[::1]解析为::1);随后调用 mitmproxy/net/check.py 中的check.is_valid_host(host)。该函数接受合法的 DNS 标签(字母、数字、-、_,单标签不超过 63 字节)、完整主机名(总长不超过 255 字节,符合 RFC 1035)、以及可通过ipaddress解析的 IPv4/IPv6 地址,非法输入抛出Invalid hostname: {host}。 - port 校验(mitmproxy/net/server_spec.py#L69-L83):显式端口直接取整;未给端口时按下表查默认值,查不到(即
tls/dtls/tcp/udp/quic……不,是其中五个无默认端口的 scheme)就抛出Port specification missing;最后用check.is_valid_port确认0 <= port <= 65535,越界抛出Invalid port: {port}。
4.3 scheme 默认端口表
源码中的默认端口映射(mitmproxy/net/server_spec.py#L73-L79):
| scheme | 默认端口 |
|---|---|
http |
80 |
https |
443 |
quic |
443 |
http3 |
443 |
dns |
53 |
tls / dtls / tcp / udp |
无默认端口,必须显式指定 port |
这是一个容易踩坑的细节:tcp、udp、tls、dtls 这类裸传输层协议没有公认默认端口,所以 parse("example.com", "tcp") 会直接抛出 Port specification missing,而 parse("smtp.example.com:25", "tcp") 才能成功得到 ("tcp", ("smtp.example.com", 25))。
5. 调用链:parse() 在代理模式解析中的位置
parse() 最主要的生产调用方是代理模式解析模块 mitmproxy/proxy/mode_specs.py。该模块负责解析 --mode 参数,其通用语法为:
mode [: mode_configuration] [@ [listen_addr:]listen_port]
例如 reverse:https://example.com@127.0.0.1:443 表示在 localhost 的 443 端口上启动一个反向代理。模式名之后的 mode_configuration 部分正是 server_spec.parse() 的输入。两个关键子类:
UpstreamMode(mitmproxy/proxy/mode_specs.py#L207-L220):对应--mode upstream:http://proxy:8080。它调用server_spec.parse(self.data, default_scheme="http"),随后强制要求 scheme 只能是http或https,否则抛出invalid upstream proxy scheme——也就是说,虽然parse()本身认识九种 scheme,但 upstream 模式只接受其中两种。ReverseMode(mitmproxy/proxy/mode_specs.py#L223-L246):对应--mode reverse:https://target。它调用server_spec.parse(self.data, default_scheme="https"),接受全部九种 scheme,并根据解析结果决定监听协议:http3/dtls/udp/quic走 UDP,dns/https同时监听 TCP 与 UDP,其余走 TCP;dns的默认监听端口还会被覆写为 53。
从源码结构看,parse() 的 default_scheme 参数设计正是为了这种“同一段字符串在不同模式下语义不同”的场景:example.com 作为 upstream 数据解释为 http://example.com:80,作为 reverse 数据解释为 https://example.com:443。
另一个实际使用场景是 examples/contrib/upstream_pac.py 脚本,它在运行时把 PAC 脚本选出的代理 URL 送入 server_spec.parse(proxy_url, "http"),动态决定每条流量走哪个上游代理;ctx.options.direct_upstream_proxy 也经由同一函数解析。
6. 测试用例验证:合法输入与四类错误
单元测试 test/mitmproxy/net/test_server_spec.py 用参数化方式覆盖了合法路径与全部错误分支,是最好的行为规格说明。
6.1 合法输入行为
| 输入 spec | default_scheme | 解析结果 |
|---|---|---|
example.com |
https |
("https", ("example.com", 443)) |
http://example.com |
https |
("http", ("example.com", 80)) |
smtp.example.com:25 |
tcp |
("tcp", ("smtp.example.com", 25)) |
http://127.0.0.1 |
https |
("http", ("127.0.0.1", 80)) |
http://[::1] |
https |
("http", ("::1", 80)) |
http://[::1]/ |
https |
("http", ("::1", 80))(结尾斜杠被允许) |
https://[::1]/ |
https |
("https", ("::1", 443)) |
http://[::1]:8080 |
https |
("http", ("::1", 8080)) |
可以看到:IPv6 字面量必须用方括号包裹(否则冒号会与端口语法冲突),解析结果中括号被剥离。
6.2 错误分支(均抛 ValueError)
| 输入 | 异常信息 | 触发阶段 |
|---|---|---|
: |
Invalid server specification |
正则整体不匹配 |
ftp://example.com |
Invalid server scheme |
scheme 白名单校验 |
$$$ |
Invalid hostname |
check.is_valid_host 校验 |
example.com:999999 |
Invalid port |
端口范围校验 |
example.com(default_scheme=tcp) |
Port specification missing |
该 scheme 无默认端口 |
7. 小结与使用建议
mitmproxy.net.server_spec 虽然只有 85 行左右源码,却是 mitmproxy 所有“指向某台服务器”配置的公共入口:--mode upstream:、--mode reverse: 的目标地址、connection.via 上游记录、PAC 动态代理选择最终都收敛到同一个 parse() 函数。使用或扩展该模块时,记住三条规则即可:
- 输入形如
[scheme://]host[:port][/],路径部分不被接受; - scheme 缺省时由调用方决定默认协议(upstream 为
http,reverse 为https); tls/dtls/tcp/udp没有默认端口,必须显式写 port,否则解析失败。
如需进一步深入,可继续阅读 mitmproxy/proxy/mode_specs.py 中各 ProxyMode 子类的校验逻辑,以及 test/mitmproxy/proxy/test_mode_specs.py 中对整体模式语法的测试。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00