本地代理模式与 API 网关路由
本地代理模式与 API 网关路由
学习目标
- 理解 CC Switch 本地代理的启动流程和接管机制
- 掌握按端点路径自动路由到 Claude/Codex/Gemini 三种协议的设计
- 了解热切换如何实现”不重启 Claude Code 即可切换供应商”
- 识别代理接管 Live 配置时的安全风险与防护策略
前置知识
- 请求整流与多协议格式转换:三大 AI API 协议的差异与转换策略
- SSOT 双写架构与崩溃恢复:SQLite 作为唯一数据源,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 不需要重启:
- Live 配置中的
ANTHROPIC_BASE_URL始终指向本地代理(http://127.0.0.1:15721) - 切换供应商时,代理服务器内部更新活跃目标(
set_active_target) - 下次 Claude Code 发起请求时,代理自动路由到新供应商
- 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-5、claude-sonnet-4-6、claude-opus-4-7,代理内部映射到上游真实模型名
Codex 接管 (apply_codex_proxy_toml_config):
- 修改
config.toml的base_url为代理地址 - 强制设置
wire_api = "responses"(代理统一使用 Responses API 与客户端通信) - 修改
auth.json的OPENAI_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 服务 | 多个服务实例 |
| 配置复杂度 | 一个地址 | 每个应用一个地址 |
| 路由逻辑 | 按路径自动区分 | 天然区分 |
| 故障隔离 | 一个故障影响全部 | 独立故障 |
对于本地桌面应用,单代理的资源消耗几乎为零,且按路径路由的实现成熟可靠,因此单代理是合理选择。
参考来源
- Tauri 2 文档. https://tauri.app/
- axum 0.7 框架文档. https://docs.rs/axum/0.7/
- Anthropic Messages API. https://docs.anthropic.com/en/api/messages