From 625bf4dfdc3d10642f251f5fb1507698d7e3fa31 Mon Sep 17 00:00:00 2001 From: yusing Date: Sat, 2 Nov 2024 07:14:03 +0800 Subject: [PATCH] improved HTTP performance, especially when match_domains are used; api json string fix --- internal/api/v1/utils/utils.go | 3 +- internal/net/http/common.go | 5 +- internal/net/http/reverse_proxy_mod.go | 2 +- internal/route/http.go | 72 +++++++++++++++----------- 4 files changed, 48 insertions(+), 34 deletions(-) diff --git a/internal/api/v1/utils/utils.go b/internal/api/v1/utils/utils.go index 01015a2..9cf97a8 100644 --- a/internal/api/v1/utils/utils.go +++ b/internal/api/v1/utils/utils.go @@ -2,6 +2,7 @@ package utils import ( "encoding/json" + "fmt" "net/http" "github.com/yusing/go-proxy/internal/logging" @@ -23,7 +24,7 @@ func RespondJSON(w http.ResponseWriter, r *http.Request, data any, code ...int) switch data := data.(type) { case string: - j = []byte(`"` + data + `"`) + j = []byte(fmt.Sprintf("%q", data)) case []byte: j = data default: diff --git a/internal/net/http/common.go b/internal/net/http/common.go index cfce0e5..935667f 100644 --- a/internal/net/http/common.go +++ b/internal/net/http/common.go @@ -16,13 +16,12 @@ var ( Proxy: http.ProxyFromEnvironment, DialContext: defaultDialer.DialContext, ForceAttemptHTTP2: true, - MaxIdleConns: 100, - MaxIdleConnsPerHost: 10, + MaxIdleConnsPerHost: 100, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, } DefaultTransportNoTLS = func() *http.Transport { - var clone = DefaultTransport.Clone() + clone := DefaultTransport.Clone() clone.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} return clone }() diff --git a/internal/net/http/reverse_proxy_mod.go b/internal/net/http/reverse_proxy_mod.go index b4d4448..f763be2 100644 --- a/internal/net/http/reverse_proxy_mod.go +++ b/internal/net/http/reverse_proxy_mod.go @@ -404,7 +404,7 @@ func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) { rw.WriteHeader(res.StatusCode) - err = U.Copy2(req.Context(), rw, res.Body) + _, err = io.Copy(rw, res.Body) if err != nil { if !errors.Is(err, context.Canceled) { p.errorHandler(rw, req, err, true) diff --git a/internal/route/http.go b/internal/route/http.go index 5230665..29db4a6 100755 --- a/internal/route/http.go +++ b/internal/route/http.go @@ -64,6 +64,11 @@ func SetFindMuxDomains(domains []string) { if len(domains) == 0 { findMuxFunc = findMuxAnyDomain } else { + for i, domain := range domains { + if !strings.HasPrefix(domain, ".") { + domains[i] = "." + domain + } + } findMuxFunc = findMuxByDomains(domains) } } @@ -210,40 +215,51 @@ func (r *HTTPRoute) addToLoadBalancer() { func ProxyHandler(w http.ResponseWriter, r *http.Request) { mux, err := findMuxFunc(r.Host) + if err == nil { + mux.ServeHTTP(w, r) + return + } // Why use StatusNotFound instead of StatusBadRequest or StatusBadGateway? // On nginx, when route for domain does not exist, it returns StatusBadGateway. // Then scraper / scanners will know the subdomain is invalid. // With StatusNotFound, they won't know whether it's the path, or the subdomain that is invalid. - if err != nil { - if !middleware.ServeStaticErrorPageFile(w, r) { - logger.Err(err).Str("method", r.Method).Str("url", r.URL.String()).Msg("request") - errorPage, ok := errorpage.GetErrorPageByStatus(http.StatusNotFound) - if ok { - w.WriteHeader(http.StatusNotFound) - w.Header().Set("Content-Type", "text/html; charset=utf-8") - if _, err := w.Write(errorPage); err != nil { - logger.Err(err).Msg("failed to write error page") - } - } else { - http.Error(w, err.Error(), http.StatusNotFound) + if !middleware.ServeStaticErrorPageFile(w, r) { + logger.Err(err).Str("method", r.Method).Str("url", r.URL.String()).Msg("request") + errorPage, ok := errorpage.GetErrorPageByStatus(http.StatusNotFound) + if ok { + w.WriteHeader(http.StatusNotFound) + w.Header().Set("Content-Type", "text/html; charset=utf-8") + if _, err := w.Write(errorPage); err != nil { + logger.Err(err).Msg("failed to write error page") } + } else { + http.Error(w, err.Error(), http.StatusNotFound) } - return } - mux.ServeHTTP(w, r) } func findMuxAnyDomain(host string) (http.Handler, error) { hostSplit := strings.Split(host, ".") n := len(hostSplit) - if n <= 2 { + switch { + case n == 3: + host = hostSplit[0] + case n > 3: + var builder strings.Builder + builder.Grow(2*n - 3) + builder.WriteString(hostSplit[0]) + for _, part := range hostSplit[:n-2] { + builder.WriteRune('.') + builder.WriteString(part) + } + host = builder.String() + default: return nil, errors.New("missing subdomain in url") } - sd := strings.Join(hostSplit[:n-2], ".") - if r, ok := httpRoutes.Load(sd); ok { + if r, ok := httpRoutes.Load(host); ok { return r.handler, nil } - return nil, fmt.Errorf("no such route: %s", sd) + return nil, fmt.Errorf("no such route: %s", host) } func findMuxByDomains(domains []string) func(host string) (http.Handler, error) { @@ -251,20 +267,18 @@ func findMuxByDomains(domains []string) func(host string) (http.Handler, error) var subdomain string for _, domain := range domains { - if !strings.HasPrefix(domain, ".") { - domain = "." + domain - } - subdomain = strings.TrimSuffix(host, domain) - if len(subdomain) < len(host) { + if strings.HasSuffix(host, domain) { + subdomain = strings.TrimSuffix(host, domain) break } } - if len(subdomain) == len(host) { // not matched - return nil, fmt.Errorf("%s does not match any base domain", host) + + if subdomain != "" { // matched + if r, ok := httpRoutes.Load(subdomain); ok { + return r.handler, nil + } + return nil, fmt.Errorf("no such route: %s", subdomain) } - if r, ok := httpRoutes.Load(subdomain); ok { - return r.handler, nil - } - return nil, fmt.Errorf("no such route: %s", subdomain) + return nil, fmt.Errorf("%s does not match any base domain", host) } }