diff --git a/modules/caddyhttp/fileserver/calculate_etag_bench_test.go b/modules/caddyhttp/fileserver/calculate_etag_bench_test.go new file mode 100644 index 00000000000..ab6cde79114 --- /dev/null +++ b/modules/caddyhttp/fileserver/calculate_etag_bench_test.go @@ -0,0 +1,27 @@ +package fileserver + +import ( + "io/fs" + "testing" + "time" +) + +type benchFileInfo struct { + size int64 + mtime time.Time +} + +func (fi benchFileInfo) Name() string { return "file.txt" } +func (fi benchFileInfo) Size() int64 { return fi.size } +func (fi benchFileInfo) Mode() fs.FileMode { return 0o644 } +func (fi benchFileInfo) ModTime() time.Time { return fi.mtime } +func (fi benchFileInfo) IsDir() bool { return false } +func (fi benchFileInfo) Sys() any { return nil } + +func BenchmarkCalculateEtag(b *testing.B) { + fi := benchFileInfo{size: 1234567, mtime: time.Unix(1700000000, 123456789)} + b.ReportAllocs() + for b.Loop() { + _ = calculateEtag(fi) + } +} diff --git a/modules/caddyhttp/fileserver/staticfiles.go b/modules/caddyhttp/fileserver/staticfiles.go index 3ca7452e112..7dd87503d9d 100644 --- a/modules/caddyhttp/fileserver/staticfiles.go +++ b/modules/caddyhttp/fileserver/staticfiles.go @@ -765,12 +765,14 @@ func calculateEtag(d os.FileInfo) string { if !usefulModTime(mtime) { return "" } - var sb strings.Builder - sb.WriteRune('"') - sb.WriteString(strconv.FormatInt(mtime.UnixNano(), 36)) - sb.WriteString(strconv.FormatInt(d.Size(), 36)) - sb.WriteRune('"') - return sb.String() + // A quoted etag of two base-36 int64 values is 30 bytes, thus fitting + // comfortably in 32 bytes, on the stack to prevent allocations + var buf [32]byte + b := append(buf[:0], '"') + b = strconv.AppendInt(b, mtime.UnixNano(), 36) + b = strconv.AppendInt(b, d.Size(), 36) + b = append(b, '"') + return string(b) } // Finds the first corresponding etag file for a given file in the file system and return its content