дȫÏîĿÉó²éÎÊÌⲢ·¢²¼ v1.1.9
¸ÄÓñ¾»ú¶ÀÁ¢ CA£¬»ָ´ CLI TLS УÑ飬±£»¤ƾ¾ÝÓëÅäÖò¢·¢±£´档 д Cursor Êý¾ݡ¢»Ự·¾¶¡¢´úÀíÉúÃüÖÜÆڡ¢ģÐÍÇëÇóÓë Skills Îļþ²Ù×÷¡£ Ϊ¶ÀÁ¢ Tab ·þÎñÔö¼ӿͻ§¶˼øȨ£¬Éý¼¶ÒÀÀµÓ빤¾ßÁ´£¬ÍêÉƷ¢²¼ԪÐÅϢͬ²½¡£ Ñé֤£ºÁ½¸ö Go ģ¿éµIJâÊÔ/vet¡¢¹ؼüģ¿é race¡¢ǰ¶˲âÊÔ/Éú²ú¹¹½¨/Éó¼ơ¢ Go ¿ɴï©¶´ɨÃèÓë macOS/Linux ¿ÉÐÐģ¿齻²æ±àÒë¾ùͨ¹ý¡£ Windows ¸ôÀëÆô¶¯ͨ¹ý£»δÍê³ɵĵã»÷ðÑ̺ÍԭÉúƽ̨Ñé֤¼ûÉó²鱨¸档
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user