Commit 31ecf37b0fff31d2230185f05764ba7049c2a8d5

Parents: a7cae071d1f5640e7871feecad938c9eec6ed95c

From: Moritz Poldrack <git@moritz.sh>
Date: Sat Nov 4 12:32:02 2023 +0700

server: replace net/http with fasthttp
Not for performance reasons, purely because I like the API more.

Stats

go.mod +5/-0
go.sum +10/-2
internal/conman/conman.go +5/-4
internal/handler/handler.go +0/-10
internal/handler/repo.go +21/-0
internal/handler/serve-http.go +26/-10
internal/handler/templates/index.go +6/-6
internal/handler/templates/overview.go +8/-5
internal/handler/wrap-repo.go +0/-30
main.go +7/-9

Changeset

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
diff --git a/go.mod b/go.mod
index 95b92e3ead80cd670dac096991ce17be7f03a531..9bd3d774a3e61e1e3ffe5e8603d6b9f1cb4082ac 100644
--- a/go.mod
+++ b/go.mod
@@ -6,6 +6,7 @@ require (
 	github.com/go-git/go-git/v5 v5.10.0
 	github.com/haya14busa/go-versionsort v0.0.0-20190326014014-5b3a40d6070a
 	github.com/rjeczalik/notify v0.9.3
+	github.com/valyala/fasthttp v1.50.0
 )
 
 require (
@@ -13,6 +14,7 @@ 	dario.cat/mergo v1.0.0 // indirect
 	github.com/Microsoft/go-winio v0.6.1 // indirect
 	github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect
 	github.com/acomagu/bufpipe v1.0.4 // indirect
+	github.com/andybalholm/brotli v1.0.6 // indirect
 	github.com/cloudflare/circl v1.3.3 // indirect
 	github.com/cyphar/filepath-securejoin v0.2.4 // indirect
 	github.com/emirpasic/gods v1.18.1 // indirect
@@ -21,9 +23,12 @@ 	github.com/go-git/go-billy/v5 v5.5.0 // indirect
 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
 	github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
 	github.com/kevinburke/ssh_config v1.2.0 // indirect
+	github.com/klauspost/compress v1.17.2 // indirect
 	github.com/pjbgf/sha1cd v0.3.0 // indirect
 	github.com/sergi/go-diff v1.1.0 // indirect
 	github.com/skeema/knownhosts v1.2.0 // indirect
+	github.com/stretchr/testify v1.8.4 // indirect
+	github.com/valyala/bytebufferpool v1.0.0 // indirect
 	github.com/xanzy/ssh-agent v0.3.3 // indirect
 	golang.org/x/crypto v0.14.0 // indirect
 	golang.org/x/mod v0.12.0 // indirect
diff --git a/go.sum b/go.sum
index 1eacf02abcfa8063a413bc8ab2a2d1d70cddd6f6..1b05a695ee664309508659b1896925652f55886e 100644
--- a/go.sum
+++ b/go.sum
@@ -7,6 +7,8 @@ github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg=
 github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
 github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ=
 github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
+github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
+github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
@@ -43,6 +45,8 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
 github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
 github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
+github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4=
+github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@@ -71,8 +75,13 @@ github.com/skeema/knownhosts v1.2.0 h1:h9r9cf0+u7wSE+M183ZtMGgOJKiL96brpaz5ekfJCpM=
 github.com/skeema/knownhosts v1.2.0/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
-github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
+github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
+github.com/valyala/fasthttp v1.50.0 h1:H7fweIlBm0rXLs2q0XbalvJ6r0CUPFWK3/bB4N13e9M=
+github.com/valyala/fasthttp v1.50.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
 github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
 github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
@@ -147,7 +156,6 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
 gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/internal/conman/conman.go b/internal/conman/conman.go
index ebf3e3e7d438bc3bd68a5afe1307c94d493f295c..28d35eab21e0833c4327f40fb698e970c2371d71 100644
--- a/internal/conman/conman.go
+++ b/internal/conman/conman.go
@@ -1,8 +1,9 @@
 package conman
 
 import (
-	"context"
 	"log/slog"
+
+	"github.com/valyala/fasthttp"
 )
 
 type contextKey uint8
@@ -11,12 +12,12 @@ // Logger is a context key for the logger.
 const Logger contextKey = iota + 1
 
 // StoreLogger stores a logger in the context.
-func StoreLogger(ctx context.Context, value *slog.Logger) context.Context {
-	return context.WithValue(ctx, Logger, value)
+func StoreLogger(ctx *fasthttp.RequestCtx, value *slog.Logger) {
+	ctx.SetUserValue(Logger, value)
 }
 
 // GetLogger retrieves a logger from the context.
-func GetLogger(ctx context.Context) *slog.Logger {
+func GetLogger(ctx *fasthttp.RequestCtx) *slog.Logger {
 	v := ctx.Value(Logger)
 	if v == nil {
 		return slog.Default().With("error", "context retrieval failed")
diff --git a/internal/handler/handler.go b/internal/handler/handler.go
index fdd6cf91d7965f9d3f5529b36657253d45a126d6..a726aca70e1b718e053fd4dbea4b23a67496e775 100644
--- a/internal/handler/handler.go
+++ b/internal/handler/handler.go
@@ -1,10 +1,8 @@
 package handler
 
 import (
-	"net/http"
 	"sync/atomic"
 
-	"git.sr.ht/~mpldr/gogit/internal/handler/templates"
 	"git.sr.ht/~mpldr/gogit/internal/types"
 )
 
@@ -13,8 +11,6 @@ 	settings *types.PageSettings
 	repolist []*types.Repo
 
 	requestID *atomic.Uint64
-
-	repos *http.ServeMux
 }
 
 func New() *Handler {
@@ -29,10 +25,4 @@ }
 
 func (h *Handler) UpdateRepos(repos []*types.Repo) {
 	h.repolist = repos
-	mux := getMux(repos)
-	mux.Handle("/", templates.Index(&types.OverviewData{
-		PageSettings: *h.settings,
-		Repos:        h.repolist,
-	}))
-	h.repos = mux
 }
diff --git a/internal/handler/repo.go b/internal/handler/repo.go
new file mode 100644
index 0000000000000000000000000000000000000000..c8a581a448d7c97e55ac4821fd2da2b8fb6ee672
--- /dev/null
+++ b/internal/handler/repo.go
@@ -0,0 +1,21 @@
+package handler
+
+import (
+	"strings"
+
+	"git.sr.ht/~mpldr/gogit/internal/conman"
+	"git.sr.ht/~mpldr/gogit/internal/handler/templates"
+	"git.sr.ht/~mpldr/gogit/internal/types"
+	"github.com/valyala/fasthttp"
+)
+
+func (h *Handler) repoHandler(ctx *fasthttp.RequestCtx, repo *types.Repo) {
+	log := conman.GetLogger(ctx)
+	log.Debug("handling repository request", "repo", repo.Name)
+	operation := strings.TrimPrefix(string(ctx.Path()), repo.Path)
+	operation = strings.SplitN(strings.TrimPrefix(operation, "/"), "/", 2)[0]
+	switch operation {
+	case "":
+		templates.Overview(repo)(ctx)
+	}
+}
diff --git a/internal/handler/serve-http.go b/internal/handler/serve-http.go
index 6915cdfe92567506a42c594dfa4ddf3efd6dbf60..f92c0ff37fbc203c1fbb0a1ba5df2e94744d2220 100644
--- a/internal/handler/serve-http.go
+++ b/internal/handler/serve-http.go
@@ -2,23 +2,39 @@ package handler
 
 import (
 	"log/slog"
-	"net/http"
+	"strings"
 
 	"git.sr.ht/~mpldr/gogit/internal/conman"
+	"git.sr.ht/~mpldr/gogit/internal/handler/templates"
+	"git.sr.ht/~mpldr/gogit/internal/types"
+	"github.com/valyala/fasthttp"
 )
 
-func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+func (h *Handler) Serve(ctx *fasthttp.RequestCtx) {
 	log := slog.With("requestID", h.requestID.Add(1))
-	log.Info("incoming request", "method", r.Method, "server", r.Host, "path", r.URL.String(), "remoteAddr", r.RemoteAddr)
-	r = r.WithContext(conman.StoreLogger(r.Context(), log))
+	log.Info("incoming request", "method", ctx.Method(), "server", ctx.Host(), "path", ctx.Path(), "remoteAddr", ctx.RemoteAddr())
+	conman.StoreLogger(ctx, log)
 
-	handl, pattern := h.repos.Handler(r)
-	slog.Debug("checking for repo handler", "pattern", pattern, "handler", handl)
-	if pattern != "" {
-		w.Header().Set("Content-Type", "text/html; charset=utf-8")
-		handl.ServeHTTP(w, r)
+	if string(ctx.Path()) == "/" {
+		templates.Index(&types.OverviewData{
+			PageSettings: *h.settings,
+			Repos:        h.repolist,
+		})(ctx)
 		return
 	}
 
-	handle404(w, r)
+	repo := h.lookupProject(string(ctx.Path()))
+	if repo == nil {
+		return
+	}
+	h.repoHandler(ctx, repo)
+}
+
+func (h *Handler) lookupProject(path string) *types.Repo {
+	for _, r := range h.repolist {
+		if strings.HasPrefix(path, r.Path) {
+			return r
+		}
+	}
+	return nil
 }
diff --git a/internal/handler/templates/index.go b/internal/handler/templates/index.go
index 19e5aad1507da62f0b4f74032f3da923f1ff8e80..b474f7c958765552df466c665488f1534aea55f9 100644
--- a/internal/handler/templates/index.go
+++ b/internal/handler/templates/index.go
@@ -3,19 +3,19 @@
 import (
 	"html/template"
 	"log/slog"
-	"net/http"
 
 	"git.sr.ht/~mpldr/gogit/internal/conman"
 	"git.sr.ht/~mpldr/gogit/internal/types"
+	"github.com/valyala/fasthttp"
 )
 
 var indexTmpl *template.Template
 
-func Index(data *types.OverviewData) http.HandlerFunc {
-	return func(w http.ResponseWriter, r *http.Request) {
-		conman.GetLogger(r.Context()).Debug("rendering index")
-		w.Header().Set("Content-Type", "text/html; charset=utf-8")
-		err := indexTmpl.Execute(w, data)
+func Index(data *types.OverviewData) fasthttp.RequestHandler {
+	return func(ctx *fasthttp.RequestCtx) {
+		conman.GetLogger(ctx).Debug("rendering index")
+		ctx.SetContentType("text/html; charset=utf-8")
+		err := indexTmpl.Execute(ctx, data)
 		if err != nil {
 			slog.Error("failed to execute template", "template", "index", "error", err)
 		}
diff --git a/internal/handler/templates/overview.go b/internal/handler/templates/overview.go
index 082182d68cfb58d2098ea0a5fcb1f344f38f328f..ca5f3a7a65b1bda30ea14928ac0c9d278bbe343e 100644
--- a/internal/handler/templates/overview.go
+++ b/internal/handler/templates/overview.go
@@ -3,17 +3,20 @@
 import (
 	"html/template"
 	"log/slog"
-	"net/http"
 
+	"git.sr.ht/~mpldr/gogit/internal/conman"
 	"git.sr.ht/~mpldr/gogit/internal/types"
+	"github.com/valyala/fasthttp"
 )
 
 var overviewTmpl *template.Template
 
-func Overview(repo *types.Repo) http.HandlerFunc {
-	return func(w http.ResponseWriter, r *http.Request) {
-		w.Header().Set("Content-Type", "text/html; charset=utf-8")
-		err := overviewTmpl.Execute(w, repo)
+func Overview(repo *types.Repo) fasthttp.RequestHandler {
+	return func(ctx *fasthttp.RequestCtx) {
+		conman.GetLogger(ctx).Debug("rendering overview", "repo", repo.Name)
+		ctx.SetContentType("text/html; charset=utf-8")
+
+		err := overviewTmpl.Execute(ctx, repo)
 		if err != nil {
 			slog.Error("failed to execute template", "template", "overview", "error", err)
 		}
diff --git a/internal/handler/wrap-repo.go b/internal/handler/wrap-repo.go
index a3a60247887f9ee935b0618ec18c830129f5775f..abeebd162ee44d9c076ced7c51310c2501f7b7af 100644
--- a/internal/handler/wrap-repo.go
+++ b/internal/handler/wrap-repo.go
@@ -1,31 +1 @@
 package handler
-
-import (
-	"net/http"
-	"strings"
-
-	"git.sr.ht/~mpldr/gogit/internal/conman"
-	"git.sr.ht/~mpldr/gogit/internal/handler/templates"
-	"git.sr.ht/~mpldr/gogit/internal/types"
-)
-
-func getMux(repos []*types.Repo) *http.ServeMux {
-	mux := http.NewServeMux()
-
-	for _, r := range repos {
-		wrapRepo(r, mux)
-	}
-
-	return mux
-}
-
-func wrapRepo(repo *types.Repo, mux *http.ServeMux) http.Handler {
-	mux.Handle(repo.Path, templates.Overview(repo)) // summary
-
-	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		conman.GetLogger(r.Context()).Debug("rendering repo", "path", repo.Path)
-
-		r.URL.Path = strings.TrimPrefix(r.URL.Path, repo.Path)
-		mux.ServeHTTP(w, r)
-	})
-}
diff --git a/main.go b/main.go
index 4bcbeea8a79608a775fce115f246ea54e6411755..defb472eb59d4cf20e0933aff6a4f9589c72bf46 100644
--- a/main.go
+++ b/main.go
@@ -1,7 +1,6 @@
 package main
 
 import (
-	"context"
 	"errors"
 	"log/slog"
 	"net/http"
@@ -13,6 +12,7 @@
 	"git.sr.ht/~mpldr/gogit/internal/handler"
 	"git.sr.ht/~mpldr/gogit/internal/handler/templates"
 	repomonitor "git.sr.ht/~mpldr/gogit/internal/repo-monitor"
+	"github.com/valyala/fasthttp"
 )
 
 var levels = map[string]slog.Level{
@@ -50,11 +50,9 @@ 	if p, ok := os.LookupEnv("PORT"); ok {
 		port = p
 	}
 
-	srv := &http.Server{
-		Addr:         port,
-		Handler:      h,
-		ReadTimeout:  5 * time.Second,
-		WriteTimeout: 5 * time.Second,
+	srv := &fasthttp.Server{
+		Handler: h.Serve,
+		Name:    "gogit",
 	}
 
 	slog.Debug("preparing templates")
@@ -66,7 +64,7 @@ 	}
 
 	slog.Debug("starting webserver")
 	go shutdownHandler(quitChan, srv)
-	err = srv.ListenAndServe()
+	err = srv.ListenAndServe(port)
 	switch {
 	case errors.Is(err, http.ErrServerClosed):
 		return
@@ -76,13 +74,13 @@ 		os.Exit(1)
 	}
 }
 
-func shutdownHandler(signal <-chan os.Signal, srv *http.Server) {
+func shutdownHandler(signal <-chan os.Signal, srv *fasthttp.Server) {
 	sig := <-signal
 	slog.Info("shutting down", "signal", sig)
 
 	shutdownComplete := make(chan struct{})
 	go func() {
-		err := srv.Shutdown(context.Background())
+		err := srv.Shutdown()
 		if err != nil {
 			slog.Error("failed to shutdown server", "error", err)
 		}