refactor(misc): enhance performance on bytes pool, entrypoint, access log and route context handling

- Introduced benchmark tests for Entrypoint and ReverseProxy to evaluate performance.
- Updated Entrypoint's ServeHTTP method to improve route context management.
- Added new test file for entrypoint benchmarks and refined existing tests for route handling.
This commit is contained in:
yusing
2025-09-14 00:03:27 +08:00
parent e0d25e475c
commit f31b1b5ed3
11 changed files with 623 additions and 33 deletions

View File

@@ -13,7 +13,6 @@ import (
"github.com/yusing/go-proxy/internal/route/routes"
"github.com/yusing/go-proxy/internal/task"
"github.com/yusing/go-proxy/internal/types"
"github.com/yusing/go-proxy/internal/utils/strutils"
)
type Entrypoint struct {
@@ -73,12 +72,13 @@ func (ep *Entrypoint) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w = accesslog.NewResponseRecorder(w)
defer ep.accessLogger.Log(r, w.(*accesslog.ResponseRecorder).Response())
}
mux, err := ep.findRouteFunc(r.Host)
route, err := ep.findRouteFunc(r.Host)
if err == nil {
r = routes.WithRouteContext(r, route)
if ep.middleware != nil {
ep.middleware.ServeHTTP(mux.ServeHTTP, w, routes.WithRouteContext(r, mux))
ep.middleware.ServeHTTP(route.ServeHTTP, w, r)
} else {
mux.ServeHTTP(w, r)
route.ServeHTTP(w, r)
}
return
}
@@ -106,20 +106,23 @@ func (ep *Entrypoint) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
func findRouteAnyDomain(host string) (types.HTTPRoute, error) {
hostSplit := strutils.SplitRune(host, '.')
target := hostSplit[0]
if r, ok := routes.GetHTTPRouteOrExact(target, host); ok {
idx := strings.IndexByte(host, '.')
if idx != -1 {
target := host[:idx]
if r, ok := routes.HTTP.Get(target); ok {
return r, nil
}
}
if r, ok := routes.HTTP.Get(host); ok {
return r, nil
}
return nil, fmt.Errorf("%w: %s", ErrNoSuchRoute, target)
return nil, fmt.Errorf("%w: %s", ErrNoSuchRoute, host)
}
func findRouteByDomains(domains []string) func(host string) (types.HTTPRoute, error) {
return func(host string) (types.HTTPRoute, error) {
for _, domain := range domains {
if strings.HasSuffix(host, domain) {
target := strings.TrimSuffix(host, domain)
if target, ok := strings.CutSuffix(host, domain); ok {
if r, ok := routes.HTTP.Get(target); ok {
return r, nil
}

View File

@@ -0,0 +1,154 @@
package entrypoint
import (
"io"
"net"
"net/http"
"net/http/httptest"
"net/url"
"strconv"
"strings"
"testing"
"github.com/yusing/go-proxy/internal/route"
"github.com/yusing/go-proxy/internal/route/routes"
"github.com/yusing/go-proxy/internal/task"
"github.com/yusing/go-proxy/internal/types"
)
type noopResponseWriter struct {
statusCode int
written []byte
}
func (w *noopResponseWriter) Header() http.Header {
return http.Header{}
}
func (w *noopResponseWriter) Write(b []byte) (int, error) {
w.written = b
return len(b), nil
}
func (w *noopResponseWriter) WriteHeader(statusCode int) {
w.statusCode = statusCode
}
type noopTransport struct{}
func (t noopTransport) RoundTrip(req *http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(strings.NewReader("1")),
Request: req,
Header: http.Header{},
}, nil
}
func BenchmarkEntrypointReal(b *testing.B) {
var ep Entrypoint
var req = http.Request{
Method: "GET",
URL: &url.URL{Path: "/", RawPath: "/"},
Host: "test.domain.tld",
}
ep.SetFindRouteDomains([]string{})
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Length", "1")
w.Write([]byte("1"))
}))
defer srv.Close()
url, err := url.Parse(srv.URL)
if err != nil {
b.Fatal(err)
}
host, port, err := net.SplitHostPort(url.Host)
if err != nil {
b.Fatal(err)
}
portInt, err := strconv.Atoi(port)
if err != nil {
b.Fatal(err)
}
r := &route.Route{
Alias: "test",
Scheme: "http",
Host: host,
Port: route.Port{Proxy: portInt},
HealthCheck: &types.HealthCheckConfig{Disable: true},
}
err = r.Validate()
if err != nil {
b.Fatal(err)
}
err = r.Start(task.RootTask("test", false))
if err != nil {
b.Fatal(err)
}
var w noopResponseWriter
b.ResetTimer()
for b.Loop() {
ep.ServeHTTP(&w, &req)
// if w.statusCode != http.StatusOK {
// b.Fatalf("status code is not 200: %d", w.statusCode)
// }
// if string(w.written) != "1" {
// b.Fatalf("written is not 1: %s", string(w.written))
// }
}
}
func BenchmarkEntrypoint(b *testing.B) {
var ep Entrypoint
var req = http.Request{
Method: "GET",
URL: &url.URL{Path: "/", RawPath: "/"},
Host: "test.domain.tld",
}
ep.SetFindRouteDomains([]string{})
r := &route.Route{
Alias: "test",
Scheme: "http",
Host: "localhost",
Port: route.Port{
Proxy: 8080,
},
HealthCheck: &types.HealthCheckConfig{
Disable: true,
},
}
err := r.Validate()
if err != nil {
b.Fatal(err)
}
err = r.Start(task.RootTask("test", false))
if err != nil {
b.Fatal(err)
}
rev, ok := routes.HTTP.Get("test")
if !ok {
b.Fatal("route not found")
}
rev.(types.ReverseProxyRoute).ReverseProxy().Transport = noopTransport{}
var w noopResponseWriter
b.ResetTimer()
for b.Loop() {
ep.ServeHTTP(&w, &req)
if w.statusCode != http.StatusOK {
b.Fatalf("status code is not 200: %d", w.statusCode)
}
}
}

View File

@@ -15,6 +15,9 @@ func addRoute(alias string) {
routes.HTTP.Add(&route.ReveseProxyRoute{
Route: &route.Route{
Alias: alias,
Port: route.Port{
Proxy: 80,
},
},
})
}