Skip to content

Commit 1321ec1

Browse files
author
mirkobrombin
committed
feat: migrate to fasthttp for better performances
before: ⏲️ Benchmark: GET / completed in 22.146ms after: ⏲️ Benchmark: GET / completed in 15.611ms Tested using the `-b` flag.
1 parent df8385c commit 1321ec1

File tree

4 files changed

+157
-32
lines changed

4 files changed

+157
-32
lines changed

go.mod

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,25 @@ require (
88
github.com/rivo/tview v0.0.0-20241103174730-c76f7879f592
99
github.com/sirupsen/logrus v1.9.3
1010
github.com/spf13/cobra v1.8.1
11+
github.com/valyala/fasthttp v1.58.0
1112
github.com/yookoala/gofast v0.8.0
1213
)
1314

1415
require (
16+
github.com/andybalholm/brotli v1.1.1 // indirect
1517
github.com/gdamore/encoding v1.0.0 // indirect
1618
github.com/gdamore/tcell/v2 v2.7.1 // indirect
1719
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
1820
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
1921
github.com/inconshreveable/mousetrap v1.1.0 // indirect
22+
github.com/klauspost/compress v1.17.11 // indirect
2023
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
2124
github.com/mattn/go-runewidth v0.0.15 // indirect
2225
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
2326
github.com/quic-go/qpack v0.5.1 // indirect
2427
github.com/rivo/uniseg v0.4.7 // indirect
2528
github.com/spf13/pflag v1.0.5 // indirect
29+
github.com/valyala/bytebufferpool v1.0.0 // indirect
2630
go.uber.org/mock v0.4.0 // indirect
2731
golang.org/x/crypto v0.31.0 // indirect
2832
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect

go.sum

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
2+
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
13
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
24
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
35
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
@@ -28,6 +30,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:
2830
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
2931
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
3032
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
33+
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
34+
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
3135
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
3236
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
3337
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
@@ -63,6 +67,12 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
6367
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
6468
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
6569
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
70+
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
71+
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
72+
github.com/valyala/fasthttp v1.58.0 h1:GGB2dWxSbEprU9j0iMJHgdKYJVDyjrOwF9RE59PbRuE=
73+
github.com/valyala/fasthttp v1.58.0/go.mod h1:SYXvHHaFp7QZHGKSHmoMipInhrI5StHrhDTYVEjK/Kw=
74+
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
75+
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
6676
github.com/yookoala/gofast v0.8.0 h1:UmGTeBj2EF5gvS58ByE9HFdQ9MeYSUIwf7JN9aFno3Y=
6777
github.com/yookoala/gofast v0.8.0/go.mod h1:OJU201Q6HCaE1cASckaTbMm3KB6e0cZxK0mgqfwOKvQ=
6878
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=

internal/server/server.go

Lines changed: 131 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,12 @@ import (
1212
"github.com/mirkobrombin/goup/internal/logger"
1313
"github.com/mirkobrombin/goup/internal/plugin"
1414
"github.com/mirkobrombin/goup/internal/server/middleware"
15+
"github.com/mirkobrombin/goup/internal/tools"
1516
"github.com/mirkobrombin/goup/internal/tui"
1617
log "github.com/sirupsen/logrus"
18+
19+
"github.com/valyala/fasthttp"
20+
"github.com/valyala/fasthttp/fasthttpadaptor"
1721
)
1822

1923
var (
@@ -105,6 +109,15 @@ func StartServers(configs []config.SiteConfig, enableTUI bool, enableBench bool)
105109
}
106110
}
107111

112+
func anyHasSSL(confs []config.SiteConfig) bool {
113+
for _, c := range confs {
114+
if c.SSL.Enabled {
115+
return true
116+
}
117+
}
118+
return false
119+
}
120+
108121
// startSingleServer starts a server for a single site configuration.
109122
func startSingleServer(conf config.SiteConfig, mwManager *middleware.MiddlewareManager, pm *plugin.PluginManager) {
110123
identifier := conf.Domain
@@ -139,57 +152,143 @@ func startSingleServer(conf config.SiteConfig, mwManager *middleware.MiddlewareM
139152
return
140153
}
141154

142-
server := createHTTPServer(conf, handler)
143-
startServerInstance(server, conf, logger)
155+
// If SSL is enabled, keep the original net/http + quic-go approach, since
156+
// fasthttp does not support QUIC (yet?).
157+
if conf.SSL.Enabled {
158+
server := createHTTPServer(conf, handler)
159+
startServerInstance(server, conf, logger)
160+
} else {
161+
// fasthttp for all other cases.
162+
startFasthttpServer(conf, handler, logger)
163+
}
144164
}
145165

146166
// startVirtualHostServer starts a server that handles multiple domains on the same port.
147167
func startVirtualHostServer(port int, configs []config.SiteConfig, mwManager *middleware.MiddlewareManager, pm *plugin.PluginManager) {
148168
identifier := fmt.Sprintf("port_%d", port)
149169
logger := loggers[identifier]
150170

151-
radixTree := radix.New()
171+
// If any of the sites has SSL enabled, we need to use the net/http server.
172+
if anyHasSSL(configs) {
173+
radixTree := radix.New()
152174

153-
for _, conf := range configs {
154-
if conf.ProxyPass == "" {
155-
if _, err := os.Stat(conf.RootDirectory); os.IsNotExist(err) {
156-
logger.Errorf("Root directory does not exist for %s: %v", conf.Domain, err)
175+
for _, conf := range configs {
176+
if conf.ProxyPass == "" {
177+
if _, err := os.Stat(conf.RootDirectory); os.IsNotExist(err) {
178+
logger.Errorf("Root directory does not exist for %s: %v", conf.Domain, err)
179+
}
180+
}
181+
182+
if err := pm.InitPluginsForSite(conf, logger); err != nil {
183+
logger.Errorf("Error initializing plugins for site %s: %v", conf.Domain, err)
184+
continue
185+
}
186+
187+
mwManagerCopy := mwManager.Copy()
188+
mwManagerCopy.Use(plugin.PluginMiddleware(pm))
189+
190+
handler, err := createHandler(conf, logger, identifier, mwManagerCopy)
191+
if err != nil {
192+
logger.Errorf("Error creating handler for %s: %v", conf.Domain, err)
193+
continue
157194
}
195+
196+
radixTree.Insert(conf.Domain, handler)
158197
}
159198

160-
if err := pm.InitPluginsForSite(conf, logger); err != nil {
161-
logger.Errorf("Error initializing plugins for site %s: %v", conf.Domain, err)
162-
continue
199+
serverConf := config.SiteConfig{
200+
Port: port,
163201
}
164202

165-
mwManagerCopy := mwManager.Copy()
166-
mwManagerCopy.Use(plugin.PluginMiddleware(pm))
203+
mainHandler := func(w_ http.ResponseWriter, r_ *http.Request) {
204+
host := r_.Host
205+
if colonIndex := strings.Index(host, ":"); colonIndex != -1 {
206+
host = host[:colonIndex]
207+
}
167208

168-
handler, err := createHandler(conf, logger, identifier, mwManagerCopy)
169-
if err != nil {
170-
logger.Errorf("Error creating handler for %s: %v", conf.Domain, err)
171-
continue
209+
if handler, found := radixTree.Get(host); found {
210+
handler.(http.Handler).ServeHTTP(w_, r_)
211+
} else {
212+
http.NotFound(w_, r_)
213+
}
172214
}
173215

174-
radixTree.Insert(conf.Domain, handler)
175-
}
216+
server := createHTTPServer(serverConf, http.HandlerFunc(mainHandler))
217+
startServerInstance(server, serverConf, logger)
176218

177-
serverConf := config.SiteConfig{
178-
Port: port,
179-
}
180-
mainHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
181-
host := r.Host
182-
if colonIndex := strings.Index(host, ":"); colonIndex != -1 {
183-
host = host[:colonIndex]
219+
} else {
220+
// fasthttp for all other cases.
221+
radixTree := radix.New()
222+
223+
for _, conf := range configs {
224+
if conf.ProxyPass == "" {
225+
if _, err := os.Stat(conf.RootDirectory); os.IsNotExist(err) {
226+
logger.Errorf("Root directory does not exist for %s: %v", conf.Domain, err)
227+
}
228+
}
229+
230+
if err := pm.InitPluginsForSite(conf, logger); err != nil {
231+
logger.Errorf("Error initializing plugins for site %s: %v", conf.Domain, err)
232+
continue
233+
}
234+
235+
mwManagerCopy := mwManager.Copy()
236+
mwManagerCopy.Use(plugin.PluginMiddleware(pm))
237+
238+
nethttpHandler, err := createHandler(conf, logger, identifier, mwManagerCopy)
239+
if err != nil {
240+
logger.Errorf("Error creating handler for %s: %v", conf.Domain, err)
241+
continue
242+
}
243+
244+
fasthttpHandler := fasthttpadaptor.NewFastHTTPHandler(nethttpHandler)
245+
radixTree.Insert(conf.Domain, fasthttpHandler)
184246
}
185247

186-
if handler, found := radixTree.Get(host); found {
187-
handler.(http.Handler).ServeHTTP(w, r)
188-
} else {
189-
http.NotFound(w, r)
248+
fasthttpMainHandler := func(ctx *fasthttp.RequestCtx) {
249+
host := string(ctx.Host())
250+
if colonIndex := strings.Index(host, ":"); colonIndex != -1 {
251+
host = host[:colonIndex]
252+
}
253+
254+
if handler, found := radixTree.Get(host); found {
255+
handler.(fasthttp.RequestHandler)(ctx)
256+
} else {
257+
ctx.SetStatusCode(fasthttp.StatusNotFound)
258+
}
259+
}
260+
261+
serverConf := config.SiteConfig{
262+
Port: port,
263+
}
264+
265+
server := &fasthttp.Server{
266+
Handler: fasthttpMainHandler,
267+
ReadTimeout: tools.TimeDurationOrDefault(serverConf.RequestTimeout),
268+
WriteTimeout: tools.TimeDurationOrDefault(serverConf.RequestTimeout),
190269
}
191-
})
192270

193-
server := createHTTPServer(serverConf, mainHandler)
194-
startServerInstance(server, serverConf, logger)
271+
logger.Infof("Serving on HTTP port %d with fasthttp", port)
272+
err := server.ListenAndServe(fmt.Sprintf(":%d", port))
273+
if err != nil {
274+
logger.Errorf("Fasthttp server error on port %d: %v", port, err)
275+
}
276+
}
277+
}
278+
279+
// startFasthttpServer starts a fasthttp server for the given site configuration.
280+
func startFasthttpServer(conf config.SiteConfig, nethttpHandler http.Handler, logger *log.Logger) {
281+
fasthttpHandler := fasthttpadaptor.NewFastHTTPHandler(nethttpHandler)
282+
283+
server := &fasthttp.Server{
284+
Handler: fasthttpHandler,
285+
ReadTimeout: tools.TimeDurationOrDefault(conf.RequestTimeout),
286+
WriteTimeout: tools.TimeDurationOrDefault(conf.RequestTimeout),
287+
}
288+
289+
logger.Infof("Serving on HTTP port %d with fasthttp", conf.Port)
290+
err := server.ListenAndServe(fmt.Sprintf(":%d", conf.Port))
291+
if err != nil {
292+
logger.Errorf("Fasthttp server error on port %d: %v", conf.Port, err)
293+
}
195294
}

internal/tools/time.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package tools
2+
3+
import "time"
4+
5+
// timeDurationOrDefault returns a time.Duration from the given seconds, or
6+
// a default value if seconds is less than or equal to 0.
7+
func TimeDurationOrDefault(seconds int) (dTimeout time.Duration) {
8+
if seconds <= 0 {
9+
seconds = 60
10+
}
11+
return time.Duration(seconds) * 1e9
12+
}

0 commit comments

Comments
 (0)