diff --git a/benchmark/baremetal.sh b/benchmark/baremetal.sh index 504920e..deb8c37 100755 --- a/benchmark/baremetal.sh +++ b/benchmark/baremetal.sh @@ -179,13 +179,13 @@ main() { NAMES=("static-web (preload+gc400)" "Bun") # ====================================================================== - # Test 1: static-web --preload --gc-percent 400 (production mode) + # Test 1: static-web --preload --gc-percent 400 --no-etag (production mode) # ====================================================================== echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}" - echo -e "${BOLD} [ static-web — production: --preload --gc-percent 400 ]${RESET}" + echo -e "${BOLD} [ static-web — production: --preload --gc-percent 400 --no-etag ]${RESET}" echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}" - "$BIN" --quiet --no-compress --preload --gc-percent 400 --port "$PORT" "$abs_root" & + "$BIN" --quiet --no-compress --no-etag --preload --gc-percent 400 --port "$PORT" "$abs_root" & SERVER_PID=$! sleep "$SETTLE_SECONDS" wait_for_port "$PORT" diff --git a/cmd/static-web/main.go b/cmd/static-web/main.go index 946d31d..692df00 100644 --- a/cmd/static-web/main.go +++ b/cmd/static-web/main.go @@ -101,6 +101,9 @@ func runServe(args []string) { // Compression. noCompress := fs.Bool("no-compress", false, "disable response compression") + // Headers. + noEtag := fs.Bool("no-etag", false, "disable ETag generation and If-None-Match validation") + // Security. cors := fs.String("cors", "", "allowed CORS origins, comma-separated or * for all") dirListing := fs.Bool("dir-listing", false, "enable directory listing") @@ -150,6 +153,7 @@ func runServe(args []string) { preload: *preload, gcPercent: *gcPercent, noCompress: *noCompress, + noEtag: *noEtag, cors: *cors, dirListing: *dirListing, noDotfileBlock: *noDotfileBlock, @@ -249,6 +253,7 @@ type flagOverrides struct { preload bool gcPercent int noCompress bool + noEtag bool cors string dirListing bool noDotfileBlock bool @@ -319,6 +324,11 @@ func applyFlagOverrides(cfg *config.Config, f flagOverrides) error { cfg.Compression.Enabled = false } + // Headers. + if f.noEtag { + cfg.Headers.EnableETags = false + } + // Security. if f.cors != "" { parts := strings.Split(f.cors, ",") diff --git a/docs/fonts.css b/docs/fonts.css new file mode 100644 index 0000000..4a1a758 --- /dev/null +++ b/docs/fonts.css @@ -0,0 +1,61 @@ +/* =========================== + Local Fonts - Self-hosted + =========================== */ + +/* Outfit - Sans-serif for all text */ +@font-face { + font-family: 'Outfit'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url('fonts/outfit-400.woff2') format('woff2'); +} + +@font-face { + font-family: 'Outfit'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url('fonts/outfit-500.woff2') format('woff2'); +} + +@font-face { + font-family: 'Outfit'; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url('fonts/outfit-600.woff2') format('woff2'); +} + +@font-face { + font-family: 'Outfit'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url('fonts/outfit-700.woff2') format('woff2'); +} + +@font-face { + font-family: 'Outfit'; + font-style: normal; + font-weight: 800; + font-display: swap; + src: url('fonts/outfit-800.woff2') format('woff2'); +} + +/* Fira Code - Monospace for source code and configs */ +@font-face { + font-family: 'Fira Code'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url('fonts/firacode-400.woff2') format('woff2'); +} + +@font-face { + font-family: 'Fira Code'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url('fonts/firacode-500.woff2') format('woff2'); +} diff --git a/docs/fonts/firacode-400.woff2 b/docs/fonts/firacode-400.woff2 new file mode 100644 index 0000000..ed70084 Binary files /dev/null and b/docs/fonts/firacode-400.woff2 differ diff --git a/docs/fonts/firacode-500.woff2 b/docs/fonts/firacode-500.woff2 new file mode 100644 index 0000000..ed70084 Binary files /dev/null and b/docs/fonts/firacode-500.woff2 differ diff --git a/docs/fonts/outfit-400.woff2 b/docs/fonts/outfit-400.woff2 new file mode 100644 index 0000000..85e3332 Binary files /dev/null and b/docs/fonts/outfit-400.woff2 differ diff --git a/docs/fonts/outfit-500.woff2 b/docs/fonts/outfit-500.woff2 new file mode 100644 index 0000000..85e3332 Binary files /dev/null and b/docs/fonts/outfit-500.woff2 differ diff --git a/docs/fonts/outfit-600.woff2 b/docs/fonts/outfit-600.woff2 new file mode 100644 index 0000000..85e3332 Binary files /dev/null and b/docs/fonts/outfit-600.woff2 differ diff --git a/docs/fonts/outfit-700.woff2 b/docs/fonts/outfit-700.woff2 new file mode 100644 index 0000000..85e3332 Binary files /dev/null and b/docs/fonts/outfit-700.woff2 differ diff --git a/docs/fonts/outfit-800.woff2 b/docs/fonts/outfit-800.woff2 new file mode 100644 index 0000000..85e3332 Binary files /dev/null and b/docs/fonts/outfit-800.woff2 differ diff --git a/docs/index.html b/docs/index.html index 82addb0..d0ddce8 100644 --- a/docs/index.html +++ b/docs/index.html @@ -6,7 +6,7 @@ static-web — High-Performance Go Static File Server @@ -37,7 +37,7 @@ @@ -45,12 +45,7 @@ rel="icon" href="data:image/svg+xml," /> - - - + @@ -80,7 +75,7 @@ "programmingLanguage": "Go", "license": "https://github.com/BackendStack21/static-web/blob/main/LICENSE", "codeRepository": "https://github.com/BackendStack21/static-web", - "description": "A production-grade, blazing-fast static web file server written in Go. ~141k req/sec with fasthttp — 55% faster than Bun. Features in-memory LRU cache, TTL-aware cache expiry, HTTP/2, TLS 1.2+, gzip and brotli compression, and comprehensive security headers.", + "description": "A production-grade, blazing-fast static web file server written in Go. ~148k req/sec with fasthttp — 59% faster than Bun. Features in-memory LRU cache, TTL-aware cache expiry, HTTP/2, TLS 1.2+, gzip and brotli compression, and comprehensive security headers.", "author": { "@type": "Person", "name": "Rolando Santamaria Maso", @@ -92,7 +87,7 @@ "priceCurrency": "USD" }, "featureList": [ - "~141k req/sec — 55% faster than Bun's native static server", + "~148k req/sec — 59% faster than Bun's native static server", "In-memory LRU cache with ~28 ns/op lookup", "Startup preloading with path-safety cache pre-warming", "TTL-aware cache expiry with optional automatic stale-entry eviction", @@ -229,7 +224,7 @@

static-web

- 141k + 148k req/sec (fasthttp)
@@ -302,7 +297,7 @@

Everything You Need

Near-Zero Alloc Hot Path

-

~141k req/sec — 55% faster than Bun. Built on fasthttp with direct ctx.SetBody() and pre-formatted headers — no formatting allocations on cache hits.

+

~148k req/sec — 59% faster than Bun. Built on fasthttp with direct ctx.SetBody() and pre-formatted headers — no formatting allocations on cache hits.

@@ -331,13 +326,13 @@

Security Hardened

Smart Caching

-

Byte-accurate LRU cache with startup preloading, configurable max size, per-file size cap, optional TTL expiry, ETag, and live flush via SIGHUP without downtime.

+

Byte-accurate LRU cache with startup preloading, configurable max size, per-file size cap, optional TTL expiry, optional ETag (disable via --no-etag), and live flush via SIGHUP without downtime.

HTTP/2 & Range Requests

- Full HTTP/2 support, byte-range serving for video / large files, conditional requests (ETag, + Full HTTP/2 support, byte-range serving for video / large files, conditional requests (optional ETag via --no-etag to disable, If-Modified-Since, 304).

@@ -412,8 +407,26 @@

Getting Started

# Serve current directory on :8080 static-web . -# Serve a build output folder on port 3000 -static-web --port 3000 ./dist +# Production: preload + GC tuning + no-etag for max throughput +static-web --preload --gc-percent 400 --no-etag ./dist + +# Serve on port 3000 with custom index +static-web --port 3000 --index default.html ./dist + +# Disable caching entirely +static-web --no-cache . + +# Disable compression (useful for already-compressed assets) +static-web --no-compress ./dist + +# Enable directory listing +static-web --dir-listing ./public + +# Allow dotfiles +static-web --no-dotfile-block ./files + +# CORS + CSP +static-web --cors "https://app.example.com" --csp "default-src 'self'" ./dist # Scaffold a config file static-web init @@ -454,17 +467,27 @@

Getting Started

max_bytes = 268435456 # 256 MiB max_file_size = 10485760 # 10 MiB gc_percent = 400 # tune GC for throughput +ttl = "5m" # optional TTL expiry [compression] enabled = true min_size = 1024 +level = 5 # 1=fastest, 9=best precompressed = true +[headers] +static_max_age = 3600 # 1 hour for static assets +html_max_age = 0 # no-cache for HTML +enable_etags = false # disable for max throughput +immutable_pattern = "*.js" # mark .js as immutable + [security] -block_dotfiles = true -directory_listing = false -cors_origins = ["https://app.example.com"] -csp = "default-src 'self'" +block_dotfiles = true +directory_listing = false +cors_origins = ["https://app.example.com"] +csp = "default-src 'self'" +referrer_policy = "strict-origin-when-cross-origin" +permissions_policy = "geolocation=(), microphone=(), camera=()"
@@ -494,6 +517,9 @@

Getting Started

ENV STATIC_SERVER_ADDR=":8080" ENV STATIC_FILES_ROOT="/public" ENV STATIC_CACHE_ENABLED="true" +ENV STATIC_CACHE_PRELOAD="true" +ENV STATIC_HEADERS_ENABLE_ETAGS="false" +ENV STATIC_COMPRESSION_ENABLED="true" EXPOSE 8080 CMD ["static-web", "/public"] @@ -606,15 +632,15 @@

Performance Benchmarks

static-web (fasthttp) - ~141,000 - 619 µs - 2.46 ms + ~148,000 + 599 µs + 2.42 ms Bun (native static) - ~90,000 - 1.05 ms - 2.33 ms + ~93,000 + 1.03 ms + 2.13 ms static-web (old net/http) @@ -637,9 +663,9 @@

fasthttp + Preload

-

55% Faster Than Bun

+

59% Faster Than Bun

- With fasthttp + preload, static-web reaches ~141k req/sec55% faster than Bun at ~90k req/sec, while offering full security headers, TLS, and compression out of the box. + With fasthttp + preload, static-web reaches ~148k req/sec59% faster than Bun at ~93k req/sec, while offering full security headers, TLS, and compression out of the box.

diff --git a/docs/styles.css b/docs/styles.css index 4dce2dd..9d00c91 100644 --- a/docs/styles.css +++ b/docs/styles.css @@ -19,8 +19,8 @@ --gradient-accent: linear-gradient(135deg, #22d3ee 0%, #34d399 100%); --gradient-glow: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0.05) 100%); --shadow-glow: 0 0 60px rgba(255,255,255,0.05); - --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; - --font-mono: 'JetBrains Mono', 'Fira Code', monospace; + --font-sans: 'Outfit', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + --font-mono: 'Fira Code', 'JetBrains Mono', monospace; --radius-sm: 6px; --radius-md: 12px; --radius-lg: 20px;