Portless 本地命名路由
阅读 Portless 路由源码:精确名称、访问主机与首个后缀匹配
依据固定源码复现路由优先级,区分 Tailscale 精确访问主机、忽略端口回退与可选通配匹配。
你将学会
- 先读优先级链,再画架构图
- 不要把首个匹配解释成最长后缀
- 保留源码身份,测试真实处理器
开始前需要
- 了解 HTTP 访问源、端口和命令行基础
- 能够区分回环、局域网与公网暴露
追踪命名请求,明确安装权限边界,并区分已观察行为和未测试集成。
先看结论
- 本地精确名称优先于 Tailscale 与通配层。
- 通配回退选择第一条匹配路由,不是最长后缀。
- 实验运行上游处理器,而不是教学重写版本。
先读优先级链,再画架构图
proxy.ts 中的 findRoute 会把请求访问主机转为小写,并移除明确的 :443。随后依次尝试本地主机名相等、Tailscale URL 的访问主机相等、忽略端口的 Tailscale 主机名相等,最后在关闭严格模式时尝试通配后缀。每一层都使用 Array.find,因此同层中的数组顺序有意义。
精确访问主机这一层,能在存在精确匹配时区分共用 tailnet 主机名、但端口不同的路由。实验中 :8443 到达后端 A,明确的 :443 到达后端 B。这些名称只是发往 127.0.0.1 的 Host 头测试输入,没有使用 Tailscale 客户端、账号、DNS 解析或隧道。
不要把首个匹配解释成最长后缀
若路由表先有 app.localhost,再有 nested.app.localhost,通配请求 child.nested.app.localhost 会到达第一个基础路由。实现不会按后缀具体程度排序。请求 nested.app.localhost 本身仍会到达嵌套路由,因为本地精确匹配先于通配层执行;严格模式则直接拒绝未登记的子域名。
只比较 Tailscale 主机名的回退同样受顺序影响:请求使用未匹配端口时,可能到达该主机名对应的第一条路由。这是可观察行为,不是建议依赖含糊名称。准备路由表时,不要假定端口不匹配必定拒绝,并应把期望的歧义处理方式写入测试。
保留源码身份,测试真实处理器
实验先验证六个源码模块的 Git blob 哈希,再不改变行为地转译 TypeScript,并在回环地址启动临时 HTTP 服务器。断言来自两个可辨识后端的真实响应,而不是单独重写一个匹配函数。24 项案例还覆盖动态路由、转发头、跳数保护和内部授权行为。
报告记录源码提交、运行时和编译器版本,明确将 TLS、HTTP/2、WebSocket 与操作系统变更标为未测或未执行。这些限制是结果的一部分,不能为了宣传而删去。未来版本可能改变规则,应重新审查新提交并重跑,而不能把当前快照写成永不变化的接口契约。
实施步骤
- 1
验证源码提交与模块哈希。
- 2
创建两个响应可区分的回环测试后端。
- 3
断言精确、严格、访问主机和歧义后缀案例。
- 4
保存运行环境及未测试的集成路径清单。
可复制示例
{
"测试路由顺序": ["app.localhost -> A", "nested.app.localhost -> B"],
"严格子域请求": {"host":"child.nested.app.localhost","status":404},
"通配子域请求": {"host":"child.nested.app.localhost","backend":"A"},
"精确嵌套请求": {"host":"nested.app.localhost","backend":"B"},
"已观察HTTP案例": 24
}常见问题
通配模式选择最具体的后缀吗?
检查的版本不是这样。前面精确匹配层失败后,它选择传入数组中的第一条匹配后缀路由。
实际启动了 Tailscale 吗?
没有。实验仅在回环连接中用测试 Host 头验证访问主机匹配,分享集成仍未验证。
资料来源
- packages/portless/src/proxy.ts来源核查 2026-09-08
- packages/portless/src/utils.ts来源核查 2026-09-08
- packages/portless/src/types.ts来源核查 2026-09-08