From 74a215b89469370143775d008226326361ea68fa Mon Sep 17 00:00:00 2001 From: yusing Date: Sun, 21 Sep 2025 11:52:42 +0800 Subject: [PATCH] feat(agentproxy): simplify configuration handling and related header management --- agent/pkg/agentproxy/config.go | 76 +++++++++++++++++++++++++++++ agent/pkg/agentproxy/headers.go | 23 ++------- agent/pkg/handler/proxy_http.go | 33 ++++--------- internal/route/reverse_proxy.go | 19 +++++--- internal/route/types/http_config.go | 4 +- 5 files changed, 104 insertions(+), 51 deletions(-) create mode 100644 agent/pkg/agentproxy/config.go diff --git a/agent/pkg/agentproxy/config.go b/agent/pkg/agentproxy/config.go new file mode 100644 index 00000000..8a61f53f --- /dev/null +++ b/agent/pkg/agentproxy/config.go @@ -0,0 +1,76 @@ +package agentproxy + +import ( + "encoding/base64" + "encoding/json" + "net/http" + "strconv" + "time" + + route "github.com/yusing/go-proxy/internal/route/types" +) + +type Config struct { + Scheme string `json:"scheme,omitempty"` + Host string `json:"host,omitempty"` // host or host:port + + route.HTTPConfig +} + +func ConfigFromHeaders(h http.Header) (Config, error) { + cfg, err := proxyConfigFromHeaders(h) + if err != nil { + return cfg, err + } + if cfg.Host == "" { + cfg = proxyConfigFromHeadersLegacy(h) + } + return cfg, nil +} + +func proxyConfigFromHeadersLegacy(h http.Header) (cfg Config) { + cfg.Host = h.Get(HeaderXProxyHost) + isHTTPS, _ := strconv.ParseBool(h.Get(HeaderXProxyHTTPS)) + cfg.NoTLSVerify, _ = strconv.ParseBool(h.Get(HeaderXProxySkipTLSVerify)) + responseHeaderTimeout, err := strconv.Atoi(h.Get(HeaderXProxyResponseHeaderTimeout)) + if err != nil { + responseHeaderTimeout = 0 + } + cfg.ResponseHeaderTimeout = time.Duration(responseHeaderTimeout) * time.Second + + cfg.Scheme = "http" + if isHTTPS { + cfg.Scheme = "https" + } + + return +} + +func proxyConfigFromHeaders(h http.Header) (cfg Config, err error) { + cfg.Scheme = h.Get(HeaderXProxyScheme) + cfg.Host = h.Get(HeaderXProxyHost) + + cfgBase64 := h.Get(HeaderXProxyConfig) + cfgJSON, err := base64.StdEncoding.DecodeString(cfgBase64) + if err != nil { + return cfg, err + } + + err = json.Unmarshal(cfgJSON, &cfg) + return cfg, err +} + +func (cfg *Config) SetAgentProxyConfigHeadersLegacy(h http.Header) { + h.Set(HeaderXProxyHost, cfg.Host) + h.Set(HeaderXProxyHTTPS, strconv.FormatBool(cfg.Scheme == "https")) + h.Set(HeaderXProxySkipTLSVerify, strconv.FormatBool(cfg.NoTLSVerify)) + h.Set(HeaderXProxyResponseHeaderTimeout, strconv.Itoa(int(cfg.ResponseHeaderTimeout.Round(time.Second).Seconds()))) +} + +func (cfg *Config) SetAgentProxyConfigHeaders(h http.Header) { + h.Set(HeaderXProxyHost, cfg.Host) + h.Set(HeaderXProxyScheme, string(cfg.Scheme)) + cfgJSON, _ := json.Marshal(cfg.HTTPConfig) + cfgBase64 := base64.StdEncoding.EncodeToString(cfgJSON) + h.Set(HeaderXProxyConfig, cfgBase64) +} diff --git a/agent/pkg/agentproxy/headers.go b/agent/pkg/agentproxy/headers.go index 098b5bc6..30e2735c 100644 --- a/agent/pkg/agentproxy/headers.go +++ b/agent/pkg/agentproxy/headers.go @@ -1,27 +1,14 @@ package agentproxy -import ( - "net/http" - "strconv" +const ( + HeaderXProxyScheme = "X-Proxy-Scheme" + HeaderXProxyHost = "X-Proxy-Host" + HeaderXProxyConfig = "X-Proxy-Config" ) +// deprecated const ( - HeaderXProxyHost = "X-Proxy-Host" HeaderXProxyHTTPS = "X-Proxy-Https" HeaderXProxySkipTLSVerify = "X-Proxy-Skip-Tls-Verify" HeaderXProxyResponseHeaderTimeout = "X-Proxy-Response-Header-Timeout" ) - -type AgentProxyHeaders struct { - Host string - IsHTTPS bool - SkipTLSVerify bool - ResponseHeaderTimeout int -} - -func SetAgentProxyHeaders(r *http.Request, headers *AgentProxyHeaders) { - r.Header.Set(HeaderXProxyHost, headers.Host) - r.Header.Set(HeaderXProxyHTTPS, strconv.FormatBool(headers.IsHTTPS)) - r.Header.Set(HeaderXProxySkipTLSVerify, strconv.FormatBool(headers.SkipTLSVerify)) - r.Header.Set(HeaderXProxyResponseHeaderTimeout, strconv.Itoa(headers.ResponseHeaderTimeout)) -} diff --git a/agent/pkg/handler/proxy_http.go b/agent/pkg/handler/proxy_http.go index bb475103..8919060d 100644 --- a/agent/pkg/handler/proxy_http.go +++ b/agent/pkg/handler/proxy_http.go @@ -1,10 +1,9 @@ package handler import ( - "crypto/tls" + "fmt" "net/http" "net/http/httputil" - "strconv" "time" "github.com/yusing/go-proxy/agent/pkg/agent" @@ -24,31 +23,17 @@ func NewTransport() *http.Transport { } func ProxyHTTP(w http.ResponseWriter, r *http.Request) { - host := r.Header.Get(agentproxy.HeaderXProxyHost) - isHTTPS, _ := strconv.ParseBool(r.Header.Get(agentproxy.HeaderXProxyHTTPS)) - skipTLSVerify, _ := strconv.ParseBool(r.Header.Get(agentproxy.HeaderXProxySkipTLSVerify)) - responseHeaderTimeout, err := strconv.Atoi(r.Header.Get(agentproxy.HeaderXProxyResponseHeaderTimeout)) + cfg, err := agentproxy.ConfigFromHeaders(r.Header) if err != nil { - responseHeaderTimeout = 0 - } - - if host == "" { - http.Error(w, "missing required headers", http.StatusBadRequest) + http.Error(w, fmt.Sprintf("failed to parse agent proxy config: %s", err.Error()), http.StatusBadRequest) return } - scheme := "http" - if isHTTPS { - scheme = "https" - } - transport := NewTransport() - if skipTLSVerify { - transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} - } - - if responseHeaderTimeout > 0 { - transport.ResponseHeaderTimeout = time.Duration(responseHeaderTimeout) * time.Second + transport.TLSClientConfig, err = cfg.BuildTLSConfig(r.URL) + if err != nil { + http.Error(w, fmt.Sprintf("failed to build TLS client config: %s", err.Error()), http.StatusInternalServerError) + return } r.URL.Scheme = "" @@ -58,8 +43,8 @@ func ProxyHTTP(w http.ResponseWriter, r *http.Request) { rp := &httputil.ReverseProxy{ Director: func(r *http.Request) { - r.URL.Scheme = scheme - r.URL.Host = host + r.URL.Scheme = cfg.Scheme + r.URL.Host = cfg.Host }, Transport: transport, } diff --git a/internal/route/reverse_proxy.go b/internal/route/reverse_proxy.go index 67156a48..c1ec4dbb 100755 --- a/internal/route/reverse_proxy.go +++ b/internal/route/reverse_proxy.go @@ -19,6 +19,7 @@ import ( "github.com/yusing/go-proxy/internal/task" "github.com/yusing/go-proxy/internal/types" "github.com/yusing/go-proxy/internal/watcher/health/monitor" + "github.com/yusing/go-proxy/pkg" ) type ReveseProxyRoute struct { @@ -43,7 +44,7 @@ func NewReverseProxyRoute(base *Route) (*ReveseProxyRoute, gperr.Error) { trans = a.Transport() proxyURL = nettypes.NewURL(agent.HTTPProxyURL) } else { - tlsConfig, err := httpConfig.BuildTLSConfig(base.ProxyURL) + tlsConfig, err := httpConfig.BuildTLSConfig(&base.ProxyURL.URL) if err != nil { return nil, err } @@ -68,15 +69,19 @@ func NewReverseProxyRoute(base *Route) (*ReveseProxyRoute, gperr.Error) { } if a != nil { - headers := &agentproxy.AgentProxyHeaders{ - Host: base.ProxyURL.Host, - IsHTTPS: base.ProxyURL.Scheme == "https", - SkipTLSVerify: httpConfig.NoTLSVerify, - ResponseHeaderTimeout: int(httpConfig.ResponseHeaderTimeout.Seconds()), + cfg := agentproxy.Config{ + Scheme: base.ProxyURL.Scheme, + Host: base.ProxyURL.Host, + HTTPConfig: httpConfig, } + setHeaderFunc := cfg.SetAgentProxyConfigHeaders + if !a.Version.IsOlderThan(pkg.Ver(0, 18, 6)) { + setHeaderFunc = cfg.SetAgentProxyConfigHeadersLegacy + } + ori := rp.HandlerFunc rp.HandlerFunc = func(w http.ResponseWriter, r *http.Request) { - agentproxy.SetAgentProxyHeaders(r, headers) + setHeaderFunc(r.Header) ori(w, r) } } diff --git a/internal/route/types/http_config.go b/internal/route/types/http_config.go index 1bf866dc..95bc336a 100644 --- a/internal/route/types/http_config.go +++ b/internal/route/types/http_config.go @@ -3,12 +3,12 @@ package route import ( "crypto/tls" "crypto/x509" + "net/url" "os" "strings" "time" "github.com/yusing/go-proxy/internal/gperr" - nettypes "github.com/yusing/go-proxy/internal/net/types" ) type HTTPConfig struct { @@ -25,7 +25,7 @@ type HTTPConfig struct { } // BuildTLSConfig creates a TLS configuration based on the HTTP config options. -func (cfg *HTTPConfig) BuildTLSConfig(targetURL *nettypes.URL) (*tls.Config, gperr.Error) { +func (cfg *HTTPConfig) BuildTLSConfig(targetURL *url.URL) (*tls.Config, gperr.Error) { tlsConfig := &tls.Config{} // Handle InsecureSkipVerify (legacy NoTLSVerify option)