Files
Local-Cursor/internal/client/service.go
FuDie0915 188225fae6 Ð޸´ȫÏîĿÉó²éÎÊÌⲢ·¢²¼ v1.1.9
¸ÄÓñ¾»ú¶ÀÁ¢ CA£¬»ָ´ CLI TLS УÑ飬±£»¤ƾ¾ÝÓëÅäÖò¢·¢±£´档
Ð޸´ Cursor Êý¾ݡ¢»Ự·¾¶¡¢´úÀíÉúÃüÖÜÆڡ¢ģÐÍÇëÇóÓë Skills Îļþ²Ù×÷¡£
Ϊ¶ÀÁ¢ Tab ·þÎñÔö¼ӿͻ§¶˼øȨ£¬Éý¼¶ÒÀÀµÓ빤¾ßÁ´£¬ÍêÉƷ¢²¼ԪÐÅϢͬ²½¡£

Ñé֤£ºÁ½¸ö Go ģ¿éµIJâÊÔ/vet¡¢¹ؼüģ¿é race¡¢ǰ¶˲âÊÔ/Éú²ú¹¹½¨/Éó¼ơ¢
Go ¿ɴï©¶´ɨÃèÓë macOS/Linux ¿ÉÐÐģ¿齻²æ±àÒë¾ùͨ¹ý¡£
Windows ¸ôÀëÆô¶¯ͨ¹ý£»δÍê³ɵĵã»÷ðÑ̺ÍԭÉúƽ̨Ñé֤¼ûÉó²鱨¸档
2026-11-10 09:59:34 +08:00

200 lines
5.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package client
import (
"context"
"fmt"
"net/http"
"sync"
"time"
"cursor/internal/appdata"
backend "cursor/internal/backend"
serverconfig "cursor/internal/backend/server/config"
"cursor/internal/certs"
"cursor/internal/logger"
"cursor/internal/mitm"
"cursor/internal/netproxy"
)
const (
// publicAPITimeout 表示当前模块中的 publicAPITimeout 状态值。
publicAPITimeout = 15 * time.Second
// backendReadyTimeout 表示等待嵌入式 backend 就绪的最长时间。
backendReadyTimeout = 15 * time.Second
// backendHealthCheckInterval 表示轮询 backend 健康检查的间隔。
backendHealthCheckInterval = 1 * time.Second
// backendHealthCheckAttemptTimeout 限制单次健康检查耗时,避免一次阻塞吃掉全部启动预算。
backendHealthCheckAttemptTimeout = 1 * time.Second
)
// ProxyService 定义了当前模块中的 ProxyService 类型。
type ProxyService struct {
lifecycleMu sync.Mutex
// proxy 表示 IDE 版 MITM 代理。
proxy *mitm.ProxyServer
// certManager 用于在代理监听地址变化时重建 MITM 服务,同时为 CLI HTTPS 监听器签发证书。
certManager *certs.Manager
// backendHost 表示当前嵌入式 backend 服务。
backendHost *backend.Host
// mu 表示当前声明中的 mu。
mu sync.RWMutex
// lastError 表示当前声明中的 lastError。
lastError string
// cursorSettingsApplied 表示当前是否已完成宿主代理设置注入。
cursorSettingsApplied bool
// configMu 表示当前声明中的 configMu。
configMu sync.Mutex
// configPath 表示当前声明中的 configPath。
configPath string
// store 表示统一的配置存储。
store *serverconfig.Store
// caCertPEM 表示当前声明中的 caCertPEM。
caCertPEM []byte
// caFileMu 表示当前声明中的 caFileMu。
caFileMu sync.Mutex
// caFilePath 表示当前声明中的 caFilePath。
caFilePath string
// publicClient 表示当前声明中的 publicClient。
publicClient *http.Client
// logsRoot 表示当前声明中的 logsRoot。
logsRoot string
// modelTestMu 保护模型测速缓存。
modelTestMu sync.RWMutex
// modelTestResults 保存当前进程内的模型测速结果。
modelTestResults map[string]ModelAdapterTestResult
}
// NewProxyService 用于处理与 NewProxyService 相关的逻辑。
func NewProxyService(proxy *mitm.ProxyServer, certManager *certs.Manager, caCertPEM []byte) *ProxyService {
if err := appdata.EnsureAssistantHome(); err != nil {
logger.Errorf("ensure assistant home failed: %v", err)
}
copiedCert := make([]byte, len(caCertPEM))
copy(copiedCert, caCertPEM)
service := &ProxyService{
proxy: proxy,
certManager: certManager,
configPath: resolveUserConfigPath(),
logsRoot: resolveLogsRootPath(),
caCertPEM: copiedCert,
publicClient: netproxy.NewHTTPClient(publicAPITimeout),
modelTestResults: make(map[string]ModelAdapterTestResult),
}
service.store = serverconfig.NewStore(service.configPath, service.logsRoot)
host, err := backend.NewHost(service.store, service.certManager)
if err != nil {
logger.Errorf("init backend host failed: %v", err)
} else {
service.backendHost = host
}
return service
}
func (s *ProxyService) ensureBackendHost() error {
if s == nil {
return nil
}
s.configMu.Lock()
defer s.configMu.Unlock()
if s == nil {
return nil
}
if s.backend() != nil {
return nil
}
host, err := backend.NewHost(s.store, s.certManager)
if err != nil {
return err
}
s.setBackend(host)
return nil
}
func (s *ProxyService) ensureProxy(cfg serverconfig.Config) error {
if s == nil {
return nil
}
s.configMu.Lock()
defer s.configMu.Unlock()
if s == nil {
return nil
}
baseURL := ""
if s.backend() != nil {
baseURL = s.backend().BaseURL()
}
if baseURL == "" {
baseURL = "http://" + cfg.BackendListenAddr
}
listenAddr := cfg.ProxyListenAddr
if s.proxyServer() != nil {
snapshot := s.proxyServer().Snapshot()
if snapshot.ListenAddr == listenAddr {
return s.proxyServer().UpdateBaseURL(baseURL)
}
if snapshot.Running {
return fmt.Errorf("代理正在运行,不能从 %s 切换到 %s,请先停止服务", snapshot.ListenAddr, listenAddr)
}
}
proxyServer, err := mitm.NewProxyServer(listenAddr, baseURL, "", "", s.certManager)
if err != nil {
return err
}
s.setProxy(proxyServer)
return nil
}
func (s *ProxyService) waitForBackend(ctx context.Context) error {
if s == nil || s.backend() == nil {
return nil
}
ticker := time.NewTicker(backendHealthCheckInterval)
defer ticker.Stop()
var lastErr error
for {
healthCtx, healthCancel := context.WithTimeout(ctx, backendHealthCheckAttemptTimeout)
err := s.backend().HealthCheck(healthCtx)
healthCancel()
if err == nil {
return nil
}
lastErr = err
select {
case <-ctx.Done():
if lastErr != nil {
return fmt.Errorf("等待内置后端就绪失败: %w", lastErr)
}
return ctx.Err()
case <-ticker.C:
}
}
}
func (s *ProxyService) backend() *backend.Host {
s.mu.RLock()
defer s.mu.RUnlock()
return s.backendHost
}
func (s *ProxyService) proxyServer() *mitm.ProxyServer {
s.mu.RLock()
defer s.mu.RUnlock()
return s.proxy
}
func (s *ProxyService) setBackend(host *backend.Host) {
s.mu.Lock()
defer s.mu.Unlock()
s.backendHost = host
}
func (s *ProxyService) setProxy(proxy *mitm.ProxyServer) {
s.mu.Lock()
defer s.mu.Unlock()
s.proxy = proxy
}