跳转到内容

本地代理模式与 API 网关路由

本地代理模式与 API 网关路由

学习目标

  • 理解 CC Switch 本地代理的启动流程和接管机制
  • 掌握按端点路径自动路由到 Claude/Codex/Gemini 三种协议的设计
  • 了解热切换如何实现”不重启 Claude Code 即可切换供应商”
  • 识别代理接管 Live 配置时的安全风险与防护策略

前置知识

下文假设你已理解上述概念,直接聚焦 CC Switch 的具体实现。


项目实践

代理启动流程

CC Switch 的代理服务启动分为四个阶段:

关键文件:

  • src-tauri/src/services/proxy.rs — 代理服务业务逻辑(启动/停止/接管/恢复)
  • src-tauri/src/proxy/server.rs — axum HTTP 服务实现
  • src-tauri/src/proxy/handlers.rs — 请求分发与响应处理

按端点路径自动路由

代理服务器不需要用户指定”这个请求属于哪个应用”——它根据请求的 URL 路径自动判断:

/v1/messages → Claude 协议
/v1/chat/completions → OpenAI/Codex 协议
/v1/responses → OpenAI Responses API
/v1beta/* → Gemini 协议

实现逻辑在 src-tauri/src/proxy/handlers.rs 的入口处理函数中:

fn detect_app_from_path(uri_path) {
if uri_path.starts_with("/v1/messages") → Claude
if uri_path.starts_with("/v1/chat/completions") → Codex
if uri_path.starts_with("/v1/responses") → Codex
if uri_path.starts_with("/v1beta/") → Gemini
}

这样 Claude Code、Codex、Gemini CLI 可以共享同一个代理地址(如 http://127.0.0.1:15721),无需各自配置不同的端口。

热切换实现

CC Switch 的核心优势之一是:代理运行中切换供应商,Claude Code 无需重启。

hot_switch_provider(app_type, new_provider_id) {
1. 获取 per-app 切换锁 (SwitchLockManager)
2. 更新数据库: current_provider = new_provider_id
3. 更新 Live 备份 (从新供应商配置生成)
4. 同步 Live 配置 (新供应商的 base_url + 占位符)
5. 通知代理服务器: server.set_active_target(app_type, id, name)
6. 代理服务器内部切换活跃路由目标
}

为什么 Claude Code 不需要重启

  1. Live 配置中的 ANTHROPIC_BASE_URL 始终指向本地代理(http://127.0.0.1:15721
  2. 切换供应商时,代理服务器内部更新活跃目标(set_active_target
  3. 下次 Claude Code 发起请求时,代理自动路由到新供应商
  4. Token 由代理层从数据库读取并注入,Claude Code 无需感知

Codex 的限制:Codex 在代理运行时也可以热切换,但 Codex 的 config.toml 中的 base_url 需要在切换后更新(CC Switch 自动完成),Codex CLI 本身是否支持热加载取决于其实现。

接管配置的写入策略

代理接管时,写入 Live 文件的内容经过精心设计:

Claude 接管 (apply_claude_takeover_fields):

  • 写入 ANTHROPIC_BASE_URL = "http://127.0.0.1:<port>"
  • 写入 ANTHROPIC_AUTH_TOKEN = "PROXY_MANAGED"(占位符)
  • 移除所有模型覆盖字段(ANTHROPIC_MODEL 等),由代理统一写入稳定的角色别名
  • 模型别名:claude-haiku-4-5claude-sonnet-4-6claude-opus-4-7,代理内部映射到上游真实模型名

Codex 接管 (apply_codex_proxy_toml_config):

  • 修改 config.tomlbase_url 为代理地址
  • 强制设置 wire_api = "responses"(代理统一使用 Responses API 与客户端通信)
  • 修改 auth.jsonOPENAI_API_KEY = "PROXY_MANAGED"

Gemini 接管

  • 写入 GOOGLE_GEMINI_BASE_URL = "http://127.0.0.1:<port>"
  • 写入 GEMINI_API_KEY = "PROXY_MANAGED"

退出清理

应用正常退出时(cleanup_before_exit):

1. 检查是否存在 Live 备份或占位符残留
2. 如果有 → 从备份恢复 Live 配置
3. 删除所有 Live 备份
4. 清除 live_takeover_active 标志
5. 清除所有 Provider 健康状态

关键设计:清理使用 stop_with_restore_keep_state,保留 proxy_config.enabled 状态,以便下次启动时自动恢复代理。


问题与规避

陷阱 1:0.0.0.0 监听地址导致客户端无法连接

问题:代理默认监听 0.0.0.0(所有网卡),但 CLI 工具无法用 0.0.0.0 作为目标地址连接。

CC Switch 的解决方案:写入 Live 配置时,将 0.0.0.0 转换为 127.0.0.1,将 :: 转换为 ::1。IPv6 地址需要加方括号:[::1]

陷阱 2:官方供应商通过代理可能导致封号

问题:如果用户切换到官方供应商(如 Anthropic 官方 API),但代理仍在运行并将请求通过代理转发,可能触发官方的反代理检测。

CC Switch 的解决方案:在 set_takeover_for_app 中检测当前供应商是否为 category: "official",如果是,向前端发射 proxy-official-warning 事件提示用户。

陷阱 3:接管写入过程中进程崩溃

问题:如果备份完成但接管写入中途崩溃,Live 文件可能处于部分修改状态。

CC Switch 的解决方案

  • 备份在接管之前完成,即使接管失败,下次启动时可通过备份恢复
  • 启动时检测 live_takeover_active 标志 + Live 占位符残留,自动触发恢复
  • 所有 Live 写入走原子写入路径(write_json_file 使用临时文件 + rename)

设计取舍

为什么选择本地代理而非直接修改配置?

方案优势劣势
每次切换写 Live 文件简单直接需要重启 CLI 工具
本地代理热切换、自动故障转移、协议转换增加一层网络跳转(~1ms 延迟)
环境变量覆盖无需改文件每个终端会话需要单独设置

本地代理的核心价值在于热切换 + 故障转移——用户可以在代理运行中随时切换供应商,且故障自动切换对用户透明。~1ms 的额外延迟在 AI 请求的秒级延迟中可以忽略。

单代理 vs 多代理端口

CC Switch 选择所有应用共享一个代理端口,而非为每个应用启动独立代理。

维度单代理多代理
资源消耗一个 axum 服务多个服务实例
配置复杂度一个地址每个应用一个地址
路由逻辑按路径自动区分天然区分
故障隔离一个故障影响全部独立故障

对于本地桌面应用,单代理的资源消耗几乎为零,且按路径路由的实现成熟可靠,因此单代理是合理选择。


参考来源