@marcel91200 / caddy Mirror · read-only / commits / 63f4e387
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 11, 07:15 UTC

caddyhttp: allocate the request UUID lazily instead of on every request (#7936)

addHTTPVarsToReplacer ran SetVar(ctx, "uuid", new(requestID)) for every request during replacer setup, allocating a *requestID unconditionally even though it is only ever read by the {http.request.uuid} placeholder. The UUID value itself was already generated lazily (requestID.String caches on first call), but the container was not. Allocate the *requestID on first access instead: the uuid placeholder now creates and stores it in the request's vars table when absent, so repeated references within a request still share one instance (and thus one UUID), while requests that never reference the UUID pay nothing. Add a benchmark for the per-request replacer setup and a regression test for the uuid placeholder, which previously had no coverage. Benchmark (BenchmarkAddHTTPVarsToReplacer, linux/arm64, count=6), common path where the UUID is never referenced: before: ~700 ns/op 352 B/op 9 allocs/op after: ~690 ns/op 336 B/op 8 allocs/op One 16-byte allocation removed per request; a GC-pressure win rather than a latency one.

Julien Voisin committed Aug 11, 2026 at 13:21 UTC 63f4e387c33e42864ad80ed704595299c3f8128b
2 files changed +47 -3
modules/caddyhttp/replacer.go
+8 -3
@@ -57,7 +57,6 @@ func NewTestReplacer(req *http.Request) *caddy.Replacer {
57
58 func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.ResponseWriter) {
59 SetVar(req.Context(), "start_time", time.Now())
60 - SetVar(req.Context(), "uuid", new(requestID))
60
61 httpVars := func(key string) (any, bool) {
62 if req != nil {
@@ -213,8 +212,14 @@ func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.Respo
212 return time.Since(start).Seconds() * 1e3, true // multiply seconds to preserve decimal (see #4666)
213
214 case "http.request.uuid":
216 - // fetch the UUID for this request
217 - id := GetVar(req.Context(), "uuid").(*requestID)
215 + // fetch the UUID for this request, generating and caching it
216 + // on first access so requests that never reference the UUID
217 + // don't pay for the allocation
218 + id, ok := GetVar(req.Context(), "uuid").(*requestID)
219 + if !ok {
220 + id = new(requestID)
221 + SetVar(req.Context(), "uuid", id)
222 + }
223
224 // set it to this request's access log
225 extra := req.Context().Value(ExtraLogFieldsCtxKey).(*ExtraLogFields)
modules/caddyhttp/replacer_test.go
+39
@@ -296,3 +296,42 @@ func TestHTTPProtoNameNormalization(t *testing.T) {
296 }
297 }
298 }
299 +
300 +// BenchmarkAddHTTPVarsToReplacer measures the per-request replacer setup, which
301 +// is the common path where the request UUID is never referenced.
302 +func BenchmarkAddHTTPVarsToReplacer(b *testing.B) {
303 + req := httptest.NewRequest(http.MethodGet, "http://example.com/foo?a=b", nil)
304 + req.Header.Set("User-Agent", "test-agent")
305 + ctx := context.WithValue(req.Context(), VarsCtxKey, make(map[string]any))
306 + ctx = context.WithValue(ctx, ExtraLogFieldsCtxKey, new(ExtraLogFields))
307 + req = req.WithContext(ctx)
308 +
309 + b.ReportAllocs()
310 + for b.Loop() {
311 + repl := caddy.NewReplacer()
312 + addHTTPVarsToReplacer(repl, req, nil)
313 + }
314 +}
315 +
316 +// TestHTTPVarReplacementUUID verifies the lazily-allocated request UUID is
317 +// generated on first access and stays stable across references.
318 +func TestHTTPVarReplacementUUID(t *testing.T) {
319 + req := httptest.NewRequest(http.MethodGet, "http://example.com/", nil)
320 + repl := caddy.NewReplacer()
321 + ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
322 + ctx = context.WithValue(ctx, VarsCtxKey, make(map[string]any))
323 + ctx = context.WithValue(ctx, ExtraLogFieldsCtxKey, new(ExtraLogFields))
324 + req = req.WithContext(ctx)
325 + addHTTPVarsToReplacer(repl, req, nil)
326 +
327 + first, ok := repl.GetString("http.request.uuid")
328 + if !ok || first == "" {
329 + t.Fatalf("expected a non-empty uuid, got %q (ok=%t)", first, ok)
330 + }
331 +
332 + second, _ := repl.GetString("http.request.uuid")
333 + if first != second {
334 + t.Errorf("expected stable uuid across references: %q != %q", first, second)
335 + }
336 +}
337 +