mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-08-17 03:27:02 +08:00
111 lines
3.5 KiB
Go
111 lines
3.5 KiB
Go
package proxydebugger
|
|
|
|
import (
|
|
"embed"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/fs"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
//go:embed web/*
|
|
var webAssets embed.FS
|
|
|
|
func (server *Server) newUIHandler() http.Handler {
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("GET /api/status", server.handleStatus)
|
|
mux.HandleFunc("GET /api/exchanges", server.handleExchangeList)
|
|
mux.HandleFunc("GET /api/exchanges/{id}", server.handleExchangeDetail)
|
|
mux.HandleFunc("DELETE /api/exchanges", server.handleClearExchanges)
|
|
mux.HandleFunc("GET /api/events", server.handleEvents)
|
|
mux.HandleFunc("GET /api/ca.crt", server.handleCACertificate)
|
|
assets, _ := fs.Sub(webAssets, "web")
|
|
fileServer := http.FileServer(http.FS(assets))
|
|
mux.Handle("/", fileServer)
|
|
return securityHeaders(mux)
|
|
}
|
|
|
|
func (server *Server) handleStatus(writer http.ResponseWriter, _ *http.Request) {
|
|
writeJSON(writer, http.StatusOK, map[string]any{
|
|
"proxyAddr": server.config.ProxyAddr,
|
|
"uiAddr": server.config.UIAddr,
|
|
"targetHost": server.config.TargetHost,
|
|
"running": true,
|
|
})
|
|
}
|
|
|
|
func (server *Server) handleExchangeList(writer http.ResponseWriter, _ *http.Request) {
|
|
writeJSON(writer, http.StatusOK, server.store.summaries())
|
|
}
|
|
|
|
func (server *Server) handleExchangeDetail(writer http.ResponseWriter, request *http.Request) {
|
|
id := strings.TrimSpace(request.PathValue("id"))
|
|
exchange, ok := server.store.get(id)
|
|
if !ok {
|
|
writeJSON(writer, http.StatusNotFound, map[string]string{"error": "请求记录不存在"})
|
|
return
|
|
}
|
|
writeJSON(writer, http.StatusOK, exchange)
|
|
}
|
|
|
|
func (server *Server) handleClearExchanges(writer http.ResponseWriter, _ *http.Request) {
|
|
server.store.clear()
|
|
writer.WriteHeader(http.StatusNoContent)
|
|
}
|
|
|
|
func (server *Server) handleEvents(writer http.ResponseWriter, request *http.Request) {
|
|
flusher, ok := writer.(http.Flusher)
|
|
if !ok {
|
|
http.Error(writer, "当前响应不支持流式刷新", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
writer.Header().Set("Content-Type", "text/event-stream")
|
|
writer.Header().Set("Cache-Control", "no-cache")
|
|
writer.Header().Set("Connection", "keep-alive")
|
|
updates, unsubscribe := server.store.subscribe()
|
|
defer unsubscribe()
|
|
fmt.Fprint(writer, "event: ready\ndata: {}\n\n")
|
|
flusher.Flush()
|
|
heartbeat := time.NewTicker(15 * time.Second)
|
|
defer heartbeat.Stop()
|
|
for {
|
|
select {
|
|
case <-request.Context().Done():
|
|
return
|
|
case event, open := <-updates:
|
|
if !open {
|
|
return
|
|
}
|
|
payload, _ := json.Marshal(event)
|
|
fmt.Fprintf(writer, "event: update\ndata: %s\n\n", payload)
|
|
flusher.Flush()
|
|
case <-heartbeat.C:
|
|
fmt.Fprint(writer, ": heartbeat\n\n")
|
|
flusher.Flush()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (server *Server) handleCACertificate(writer http.ResponseWriter, _ *http.Request) {
|
|
writer.Header().Set("Content-Type", "application/x-x509-ca-cert")
|
|
writer.Header().Set("Content-Disposition", `attachment; filename="cursor-local-proxy-ca.crt"`)
|
|
_, _ = writer.Write(server.caCertPEM)
|
|
}
|
|
|
|
func writeJSON(writer http.ResponseWriter, status int, payload any) {
|
|
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
writer.WriteHeader(status)
|
|
_ = json.NewEncoder(writer).Encode(payload)
|
|
}
|
|
|
|
func securityHeaders(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
|
writer.Header().Set("X-Content-Type-Options", "nosniff")
|
|
writer.Header().Set("Referrer-Policy", "no-referrer")
|
|
writer.Header().Set("Content-Security-Policy", "default-src 'self'; script-src 'self'; style-src 'self'; connect-src 'self'")
|
|
next.ServeHTTP(writer, request)
|
|
})
|
|
}
|