Skip to content

Commit 32a3be3

Browse files
author
mirkobrombin
committed
feat: implement shared reverse proxy management and byte slice pooling to improve performance and reduce memory access at each request
1 parent 1321ec1 commit 32a3be3

File tree

1 file changed

+56
-5
lines changed

1 file changed

+56
-5
lines changed

internal/server/handler.go

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"net/http/httputil"
77
"net/url"
88
"strings"
9+
"sync"
910
"time"
1011

1112
"github.com/mirkobrombin/goup/internal/config"
@@ -20,15 +21,16 @@ func createHandler(conf config.SiteConfig, logger *log.Logger, identifier string
2021

2122
if conf.ProxyPass != "" {
2223
// Set up reverse proxy handler if ProxyPass is set.
23-
proxyURL, err := url.Parse(conf.ProxyPass)
24+
proxy, err := getSharedReverseProxy(conf.ProxyPass)
2425
if err != nil {
2526
return nil, fmt.Errorf("invalid proxy URL: %v", err)
2627
}
27-
proxy := httputil.NewSingleHostReverseProxy(proxyURL)
28+
2829
handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2930
addCustomHeaders(w, conf.CustomHeaders)
3031
proxy.ServeHTTP(w, r)
3132
})
33+
3234
} else {
3335
// Serve static files from the root directory.
3436
fs := http.FileServer(http.Dir(conf.RootDirectory))
@@ -52,7 +54,7 @@ func createHandler(conf config.SiteConfig, logger *log.Logger, identifier string
5254
if reqTimeout == 0 {
5355
reqTimeout = 60 // Default to 60 seconds
5456
}
55-
timeout := time.Duration(conf.RequestTimeout) * time.Second
57+
timeout := time.Duration(reqTimeout) * time.Second
5658
siteMwManager.Use(middleware.TimeoutMiddleware(timeout))
5759

5860
// Add logging middleware last to ensure it wraps the entire request
@@ -70,11 +72,60 @@ func addCustomHeaders(w http.ResponseWriter, headers map[string]string) {
7072
w.Header().Set(key, value)
7173
}
7274

73-
// Expose custom headers to the client.
74-
exposeHeaders := []string{}
75+
exposeHeaders := make([]string, 0, len(headers))
7576
for key := range headers {
7677
exposeHeaders = append(exposeHeaders, key)
7778
}
7879

7980
w.Header().Set("Access-Control-Expose-Headers", strings.Join(exposeHeaders, ", "))
8081
}
82+
83+
var (
84+
sharedProxyMap = make(map[string]*httputil.ReverseProxy)
85+
sharedProxyMapMu sync.Mutex
86+
defaultTransport = &http.Transport{}
87+
88+
globalBytePool = &byteSlicePool{
89+
pool: sync.Pool{
90+
New: func() interface{} {
91+
return make([]byte, 32*1024)
92+
},
93+
},
94+
}
95+
)
96+
97+
type byteSlicePool struct {
98+
pool sync.Pool
99+
}
100+
101+
func (b *byteSlicePool) Get() []byte {
102+
return b.pool.Get().([]byte)
103+
}
104+
105+
func (b *byteSlicePool) Put(buf []byte) {
106+
if cap(buf) == 32*1024 {
107+
b.pool.Put(buf[:32*1024])
108+
}
109+
}
110+
111+
// getSharedReverseProxy returns a shared ReverseProxy for the given backend URL.
112+
func getSharedReverseProxy(rawURL string) (*httputil.ReverseProxy, error) {
113+
sharedProxyMapMu.Lock()
114+
defer sharedProxyMapMu.Unlock()
115+
116+
if rp, ok := sharedProxyMap[rawURL]; ok {
117+
return rp, nil
118+
}
119+
120+
parsedURL, err := url.Parse(rawURL)
121+
if err != nil {
122+
return nil, err
123+
}
124+
125+
rp := httputil.NewSingleHostReverseProxy(parsedURL)
126+
rp.Transport = defaultTransport
127+
rp.BufferPool = globalBytePool
128+
129+
sharedProxyMap[rawURL] = rp
130+
return rp, nil
131+
}

0 commit comments

Comments
 (0)