Ð޸´ȫÏîĿÉó²éÎÊÌⲢ·¢²¼ v1.1.9

¸ÄÓñ¾»ú¶ÀÁ¢ CA£¬»ָ´ CLI TLS УÑ飬±£»¤ƾ¾ÝÓëÅäÖò¢·¢±£´档
Ð޸´ Cursor Êý¾ݡ¢»Ự·¾¶¡¢´úÀíÉúÃüÖÜÆڡ¢ģÐÍÇëÇóÓë Skills Îļþ²Ù×÷¡£
Ϊ¶ÀÁ¢ Tab ·þÎñÔö¼ӿͻ§¶˼øȨ£¬Éý¼¶ÒÀÀµÓ빤¾ßÁ´£¬ÍêÉƷ¢²¼ԪÐÅϢͬ²½¡£

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

View File

@@ -2,7 +2,9 @@ package main
import (
"bytes"
"context"
"crypto/sha256"
"crypto/subtle"
"encoding/base64"
"errors"
"fmt"
@@ -58,7 +60,8 @@ var defaultUpstreamTargets = map[string]string{
}
type appConfig struct {
Token string
Token string `yaml:"token"`
ClientToken string `yaml:"clientToken"`
}
type serverApp struct {
@@ -97,9 +100,65 @@ func newServerApp(cfg appConfig, client *http.Client, upstreamTargets map[string
return app
}
const maxRequestBytes int64 = 64 << 20
type trackingWriter struct {
http.ResponseWriter
started bool
}
func (w *trackingWriter) WriteHeader(code int) {
if !w.started {
w.started = true
w.ResponseWriter.WriteHeader(code)
}
}
func (w *trackingWriter) Write(p []byte) (int, error) {
if !w.started {
w.WriteHeader(http.StatusOK)
}
return w.ResponseWriter.Write(p)
}
func (w *trackingWriter) Flush() {
if !w.started {
w.WriteHeader(http.StatusOK)
}
if f, ok := w.ResponseWriter.(http.Flusher); ok {
f.Flush()
}
}
func (app *serverApp) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
if err := app.handleProxy(writer, request); err != nil {
http.Error(writer, err.Error(), http.StatusBadGateway)
w := &trackingWriter{ResponseWriter: writer}
expected := formatBearerAuthorization(app.config.ClientToken)
if app.config.ClientToken == "" || subtle.ConstantTimeCompare([]byte(request.Header.Get("Authorization")), []byte(expected)) != 1 {
w.Header().Set("WWW-Authenticate", "Bearer")
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
if request.Method != http.MethodPost {
w.Header().Set("Allow", "POST")
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
if request.ContentLength > maxRequestBytes {
http.Error(w, "request too large", http.StatusRequestEntityTooLarge)
return
}
request.Body = http.MaxBytesReader(w, request.Body, maxRequestBytes)
if err := app.handleProxy(w, request); err != nil {
if !w.started {
status := http.StatusBadGateway
var limit *http.MaxBytesError
if errors.As(err, &limit) {
status = http.StatusRequestEntityTooLarge
}
if errors.Is(err, context.Canceled) {
status = 499
}
http.Error(w, http.StatusText(status), status)
} else {
log.Printf("流式转发中断 path=%s", request.URL.Path)
}
}
}
@@ -160,11 +219,19 @@ func loadConfig(path string) (appConfig, error) {
if err != nil {
return appConfig{}, err
}
token, err := parseTokenYAML(contents)
if err != nil {
var cfg appConfig
if err = yaml.Unmarshal(contents, &cfg); err != nil {
return appConfig{}, err
}
return appConfig{Token: token}, nil
cfg.Token = strings.TrimSpace(cfg.Token)
cfg.ClientToken = strings.TrimSpace(cfg.ClientToken)
if cfg.Token == "" || cfg.ClientToken == "" {
return appConfig{}, fmt.Errorf("token 和独立 clientToken 均不能为空")
}
if cfg.Token == cfg.ClientToken {
return appConfig{}, fmt.Errorf("clientToken 必须与上游 token 不同")
}
return cfg, nil
}
func parseTokenYAML(contents []byte) (string, error) {
@@ -277,7 +344,9 @@ func buildCursorChecksum(authorization string) string {
}
func newHTTPClient() *http.Client {
return &http.Client{}
transport := http.DefaultTransport.(*http.Transport).Clone()
transport.ResponseHeaderTimeout = 30 * time.Second
return &http.Client{Transport: transport, CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }}
}
func cloneUpstreamTargets(input map[string]string) map[string]string {