mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-08-17 19:47:10 +08:00
refactor: 0.1.0-beta
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user