fix(proxy): resolve actual port for ephemeral (port 0) listen config

When listen_port is 0 the OS assigns the port at bind time, so the
configured value can no longer be trusted for building takeover URLs.

- server: read listener.local_addr() after bind and propagate the
  actual port to the global proxy port, status, and ProxyServerInfo
- services: start the proxy before takeover when port is 0 so live
  configs get the real port instead of :0, and persist the resolved
  port back to the DB for DB-only URL paths; stop the pre-started
  server on any takeover failure
- claude_desktop: reject an unresolved :0 port instead of emitting a
  broken gateway URL
- build_proxy_urls: prefer the running server's port and error out if
  the port is still 0

Add tests for takeover with an ephemeral port and the claude_desktop
:0 rejection; switch existing codex takeover tests to an ephemeral
port for isolation.
This commit is contained in:
Jason
2026-06-06 18:02:41 +08:00
parent aa09c9cb62
commit 2985ad2c14
3 changed files with 196 additions and 8 deletions
+8 -4
View File
@@ -112,11 +112,15 @@ impl ProxyServer {
let listener = tokio::net::TcpListener::bind(&addr)
.await
.map_err(|e| ProxyError::BindFailed(e.to_string()))?;
let local_addr = listener
.local_addr()
.map_err(|e| ProxyError::BindFailed(e.to_string()))?;
let actual_port = local_addr.port();
log::info!("[{}] 代理服务器启动于 {addr}", log_srv::STARTED);
log::info!("[{}] 代理服务器启动于 {local_addr}", log_srv::STARTED);
// 更新全局代理端口,用于系统代理检测
crate::proxy::http_client::set_proxy_port(self.config.listen_port);
crate::proxy::http_client::set_proxy_port(actual_port);
// 保存关闭句柄
*self.shutdown_tx.write().await = Some(shutdown_tx);
@@ -125,7 +129,7 @@ impl ProxyServer {
let mut status = self.state.status.write().await;
status.running = true;
status.address = self.config.listen_address.clone();
status.port = self.config.listen_port;
status.port = actual_port;
drop(status);
// 记录启动时间
@@ -213,7 +217,7 @@ impl ProxyServer {
Ok(ProxyServerInfo {
address: self.config.listen_address.clone(),
port: self.config.listen_port,
port: actual_port,
started_at: chrono::Utc::now().to_rfc3339(),
})
}