Files
cursor-byok/cursor-backend/server.go
2026-08-13 22:01:18 +08:00

145 lines
4.5 KiB
Go

// server.go 负责固定上游服务、流量捕获和调试界面的生命周期。
package main
import (
"errors"
"fmt"
"io"
"log"
"net"
"net/http"
"net/http/httputil"
"net/url"
"sync"
"sync/atomic"
"time"
)
// Server 运行 Cursor API 转发服务及其本机调试界面。
type Server struct {
config Config
upstream *url.URL
store *exchangeStore
counter atomic.Uint64
serviceServer *http.Server
serviceLn net.Listener
runMu sync.Mutex
captureMu sync.RWMutex
}
// New 创建固定转发到 Cursor API 的协议调试服务。
func New(config Config) (*Server, error) {
config = config.normalized()
if err := validateLoopbackAddress(config.ServiceAddr); err != nil {
return nil, fmt.Errorf("服务监听地址无效:%w", err)
}
upstream, err := url.Parse(defaultUpstreamURL)
if err != nil {
return nil, fmt.Errorf("解析固定上游地址:%w", err)
}
store, err := newPersistentExchangeStore(config.DatabasePath, config.MaxExchanges)
if err != nil {
return nil, err
}
server := &Server{
config: config,
upstream: upstream,
store: store,
}
server.counter.Store(store.maxNumericID())
server.serviceServer = &http.Server{
Handler: server.newServiceHandler(),
ErrorLog: log.New(io.Discard, "", 0),
}
return server, nil
}
// Start 启动同时承载 API 转发和调试界面的单端口服务。
func (server *Server) Start() error {
server.runMu.Lock()
defer server.runMu.Unlock()
if server.serviceLn != nil {
return errors.New("Cursor API 调试服务已经启动")
}
serviceListener, err := net.Listen("tcp", server.config.ServiceAddr)
if err != nil {
return fmt.Errorf("启动 API 服务监听失败:%w", err)
}
server.serviceLn = serviceListener
go func() { _ = server.serviceServer.Serve(serviceListener) }()
return nil
}
// Close 立即关闭监听器、活跃连接并释放捕获存储。
func (server *Server) Close() error {
server.runMu.Lock()
serviceServer := server.serviceServer
server.serviceLn = nil
server.runMu.Unlock()
var errorsList []error
if serviceServer != nil {
if err := serviceServer.Close(); err != nil && !errors.Is(err, http.ErrServerClosed) {
errorsList = append(errorsList, err)
}
}
if server.store != nil {
if err := server.store.close(); err != nil {
errorsList = append(errorsList, err)
}
}
return errors.Join(errorsList...)
}
// ServiceAddr 返回 Cursor API 服务监听地址。
func (server *Server) ServiceAddr() string { return server.config.ServiceAddr }
// UIURL 返回可在浏览器中打开的调试界面地址。
func (server *Server) UIURL() string {
return "http://" + browserAddress(server.config.ServiceAddr) + debugBasePath + "/"
}
// DatabasePath 返回捕获数据库路径。
func (server *Server) DatabasePath() string {
return server.config.DatabasePath
}
// newServiceHandler 创建单端口调试路由和固定上游流式转发。
func (server *Server) newServiceHandler() http.Handler {
reverseProxy := httputil.NewSingleHostReverseProxy(server.upstream)
reverseProxy.FlushInterval = -1
reverseProxy.ErrorLog = log.New(io.Discard, "", 0)
originalDirector := reverseProxy.Director
reverseProxy.Director = func(request *http.Request) {
originalDirector(request)
request.Host = server.upstream.Host
request.Header["X-Forwarded-For"] = nil
}
reverseProxy.Transport = &http.Transport{
Proxy: nil,
DialContext: (&net.Dialer{Timeout: 10 * time.Second, KeepAlive: 30 * time.Second}).DialContext,
ForceAttemptHTTP2: true,
DisableCompression: true,
MaxIdleConns: 200,
MaxIdleConnsPerHost: 32,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
reverseProxy.ModifyResponse = server.captureResponse
reverseProxy.ErrorHandler = func(writer http.ResponseWriter, request *http.Request, upstreamErr error) {
server.failExchange(request, upstreamErr)
http.Error(writer, "Cursor API upstream unavailable", http.StatusBadGateway)
}
forwardHandler := http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
reverseProxy.ServeHTTP(writer, server.captureRequest(request))
})
debugHandler := http.StripPrefix(debugBasePath, server.newUIHandler())
mux := http.NewServeMux()
mux.Handle(debugBasePath+"/", debugHandler)
mux.HandleFunc(debugBasePath, func(writer http.ResponseWriter, request *http.Request) {
http.Redirect(writer, request, debugBasePath+"/", http.StatusTemporaryRedirect)
})
mux.Handle("/", forwardHandler)
return mux
}