improved HTTP performance, especially when match_domains are used; api json string fix

This commit is contained in:
yusing 2024-11-02 07:14:03 +08:00
parent 46b4090629
commit 625bf4dfdc
4 changed files with 48 additions and 34 deletions

View file

@ -2,6 +2,7 @@ package utils
import ( import (
"encoding/json" "encoding/json"
"fmt"
"net/http" "net/http"
"github.com/yusing/go-proxy/internal/logging" "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) { switch data := data.(type) {
case string: case string:
j = []byte(`"` + data + `"`) j = []byte(fmt.Sprintf("%q", data))
case []byte: case []byte:
j = data j = data
default: default:

View file

@ -16,13 +16,12 @@ var (
Proxy: http.ProxyFromEnvironment, Proxy: http.ProxyFromEnvironment,
DialContext: defaultDialer.DialContext, DialContext: defaultDialer.DialContext,
ForceAttemptHTTP2: true, ForceAttemptHTTP2: true,
MaxIdleConns: 100, MaxIdleConnsPerHost: 100,
MaxIdleConnsPerHost: 10,
TLSHandshakeTimeout: 10 * time.Second, TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second, ExpectContinueTimeout: 1 * time.Second,
} }
DefaultTransportNoTLS = func() *http.Transport { DefaultTransportNoTLS = func() *http.Transport {
var clone = DefaultTransport.Clone() clone := DefaultTransport.Clone()
clone.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} clone.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
return clone return clone
}() }()

View file

@ -404,7 +404,7 @@ func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(res.StatusCode) rw.WriteHeader(res.StatusCode)
err = U.Copy2(req.Context(), rw, res.Body) _, err = io.Copy(rw, res.Body)
if err != nil { if err != nil {
if !errors.Is(err, context.Canceled) { if !errors.Is(err, context.Canceled) {
p.errorHandler(rw, req, err, true) p.errorHandler(rw, req, err, true)

View file

@ -64,6 +64,11 @@ func SetFindMuxDomains(domains []string) {
if len(domains) == 0 { if len(domains) == 0 {
findMuxFunc = findMuxAnyDomain findMuxFunc = findMuxAnyDomain
} else { } else {
for i, domain := range domains {
if !strings.HasPrefix(domain, ".") {
domains[i] = "." + domain
}
}
findMuxFunc = findMuxByDomains(domains) findMuxFunc = findMuxByDomains(domains)
} }
} }
@ -210,11 +215,14 @@ func (r *HTTPRoute) addToLoadBalancer() {
func ProxyHandler(w http.ResponseWriter, r *http.Request) { func ProxyHandler(w http.ResponseWriter, r *http.Request) {
mux, err := findMuxFunc(r.Host) mux, err := findMuxFunc(r.Host)
if err == nil {
mux.ServeHTTP(w, r)
return
}
// Why use StatusNotFound instead of StatusBadRequest or StatusBadGateway? // Why use StatusNotFound instead of StatusBadRequest or StatusBadGateway?
// On nginx, when route for domain does not exist, it returns StatusBadGateway. // On nginx, when route for domain does not exist, it returns StatusBadGateway.
// Then scraper / scanners will know the subdomain is invalid. // 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. // 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) { if !middleware.ServeStaticErrorPageFile(w, r) {
logger.Err(err).Str("method", r.Method).Str("url", r.URL.String()).Msg("request") logger.Err(err).Str("method", r.Method).Str("url", r.URL.String()).Msg("request")
errorPage, ok := errorpage.GetErrorPageByStatus(http.StatusNotFound) errorPage, ok := errorpage.GetErrorPageByStatus(http.StatusNotFound)
@ -228,22 +236,30 @@ func ProxyHandler(w http.ResponseWriter, r *http.Request) {
http.Error(w, err.Error(), http.StatusNotFound) http.Error(w, err.Error(), http.StatusNotFound)
} }
} }
return
}
mux.ServeHTTP(w, r)
} }
func findMuxAnyDomain(host string) (http.Handler, error) { func findMuxAnyDomain(host string) (http.Handler, error) {
hostSplit := strings.Split(host, ".") hostSplit := strings.Split(host, ".")
n := len(hostSplit) 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") return nil, errors.New("missing subdomain in url")
} }
sd := strings.Join(hostSplit[:n-2], ".") if r, ok := httpRoutes.Load(host); ok {
if r, ok := httpRoutes.Load(sd); ok {
return r.handler, nil 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) { 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 var subdomain string
for _, domain := range domains { for _, domain := range domains {
if !strings.HasPrefix(domain, ".") { if strings.HasSuffix(host, domain) {
domain = "." + domain
}
subdomain = strings.TrimSuffix(host, domain) subdomain = strings.TrimSuffix(host, domain)
if len(subdomain) < len(host) {
break 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 { if r, ok := httpRoutes.Load(subdomain); ok {
return r.handler, nil return r.handler, nil
} }
return nil, fmt.Errorf("no such route: %s", subdomain) return nil, fmt.Errorf("no such route: %s", subdomain)
} }
return nil, fmt.Errorf("%s does not match any base domain", host)
}
} }