static-web

module
v1.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 8, 2026 License: MIT

README

static-web

A production-grade, high-performance static web file server written in Go. Built on fasthttp for maximum throughput — ~141k req/sec, 55% faster than Bun's native static server.

Table of Contents


Quick Start

# Go install (requires Go 1.26+)
go install github.com/BackendStack21/static-web/cmd/static-web@latest

# Serve the current directory
static-web .

# Serve a build output directory on port 3000
static-web --port 3000 ./dist

# Scaffold a config file
static-web init

CLI

For the full flag reference, subcommand documentation, and installation options, see CLI.md.

static-web --help

Features

Feature Detail
In-memory LRU cache Size-bounded, byte-accurate; ~28 ns/op lookup with 0 allocations. Optional startup preload for instant cache hits.
gzip compression On-the-fly via pooled gzip.Writer; pre-compressed .gz/.br sidecar support
HTTP/2 Automatic ALPN negotiation when TLS is configured
Conditional requests ETag, 304 Not Modified, If-Modified-Since, If-None-Match
Range requests Byte ranges via custom parseRange/serveRange implementation for video and large files
TLS 1.2 / 1.3 Modern cipher suites; configurable cert/key paths
Security headers X-Content-Type-Options, X-Frame-Options, Content-Security-Policy, Referrer-Policy, Permissions-Policy
HSTS Strict-Transport-Security on all HTTPS responses; configurable max-age
HTTP→HTTPS redirect Automatic 301 redirect on the HTTP port when TLS is active
Method whitelist Only GET, HEAD, OPTIONS are accepted (TRACE/PUT/POST blocked)
Dotfile protection Blocks .env, .git/, etc. by default
Directory listing Optional HTML directory index with breadcrumb navigation, sorted entries, human-readable sizes, and dotfile filtering
Symlink escape prevention EvalSymlinks re-verified against root; symlinks pointing outside root are blocked
CORS Configurable per-origin or wildcard (* emits literal *, never reflected)
Graceful shutdown SIGTERM/SIGINT drains in-flight requests with configurable timeout
Live cache flush SIGHUP flushes both the in-memory file cache and the path-safety cache without downtime

Architecture

HTTP request
     │
     ▼
┌─────────────────┐
│ recoveryMiddleware │  ← panic → 500, log stack
└────────┬────────┘
         │
┌────────▼────────┐
│ loggingMiddleware │  ← logs method/path/status/duration
└────────┬────────┘
         │
┌────────▼────────────────────────────────────────┐
│ security.Middleware                              │
│  • Method whitelist (GET/HEAD/OPTIONS only)      │
│  • Security headers (set BEFORE path check)      │
│  • PathSafe: null bytes, path.Clean, EvalSymlinks│
│  • Path-safety cache (sync.Map, pre-warmed)      │
│  • Dotfile blocking                              │
│  • CORS (preflight + per-origin or wildcard *)   │
│  • Injects validated path into ctx.SetUserValue  │
└────────┬────────────────────────────────────────┘
         │
┌────────▼────────────────────────────────────────┐
│ handler.FileHandler                              │
│  • Cache hit → direct ctx.SetBody() fast path    │
│  • Range/conditional → custom serveRange()       │
│  • Cache miss → os.Stat → disk read → cache put  │
│  • Large files (> max_file_size) bypass cache    │
│  • Encoding negotiation: brotli > gzip > plain   │
│  • Preloaded files served instantly on startup   │
│  • Custom 404 page (path-validated)              │
└─────────────────────────────────────────────────┘
         │
┌────────▼────────────────────────────────────────┐
│ compress.Middleware (post-processing)             │
│  • Compresses response body after handler runs   │
│  • Skips 1xx/204/304, non-compressible types     │
│  • Respects q=0 explicit denial                  │
└─────────────────────────────────────────────────┘
Request path through the cache
GET /app.js
  │
  ├─ cache.Get("/app.js") hit?
  │     YES → serveFromCache (direct ctx.SetBody, no syscall) → done
  │
  └─ NO → resolveIndexPath → cache.Get(canonicalURL) hit?
              YES → serveFromCache → done
              NO  → os.Stat → os.ReadFile → cache.Put → serveFromCache

When preload = true, every eligible file is loaded into cache at startup. The path-safety cache (sync.Map) is also pre-warmed, so the very first request for any preloaded file skips both filesystem I/O and EvalSymlinks.


Performance

End-to-end HTTP benchmarks

Measured on Apple M-series, localhost (no Docker), serving 3 small static files via bombardier -c 100 -n 100000:

Server Avg Req/sec p50 Latency p99 Latency Throughput
static-web (fasthttp + preload) ~141,000 619 µs 2.46 ms 469 MB/s
Bun (native static serve) ~90,000 1.05 ms 2.33 ms 306 MB/s
static-web (old net/http) ~76,000 1.25 ms 3.15 ms

With preload = true and the fasthttp engine, static-web delivers ~141k req/sec55% faster than Bun's native static serving, while offering full security headers, TLS, and compression out of the box.

Micro-benchmarks

Measured on Apple M2 Pro (go test -bench=. -benchtime=5s):

Benchmark ops/s ns/op allocs/op
BenchmarkCacheGet 35–42 M 28–29 0
BenchmarkCacheGetParallel 6–8 M 139–148 0
Key design decisions
  • fasthttp engine: Built on fasthttp — pre-allocated per-connection buffers with near-zero allocation hot path. Cache hits bypass all string formatting; headers are pre-computed at cache-population time.
  • tcp4 listener: IPv4-only listener eliminates dual-stack overhead on macOS/Linux — a 2× throughput difference vs "tcp".
  • Preload at startup: preload = true reads all eligible files into RAM before the first request — eliminating cold-miss latency.
  • Direct ctx.SetBody() fast path: cache hits bypass range/conditional logic entirely; pre-formatted Content-Type and Content-Length headers are assigned directly.
  • Custom Range implementation: parseRange()/serveRange() handle byte-range requests without http.ServeContent.
  • Post-processing compression: compress middleware runs after the handler, compressing the response body in a single pass.
  • Path-safety cache: sync.Map-based cache eliminates per-request filepath.EvalSymlinks syscalls. Pre-warmed from preload.
  • GC tuning: gc_percent = 400 reduces garbage collection frequency — the hot path avoids all formatting allocations, with only minimal byte-to-string conversions from fasthttp's []byte API.
  • Cache-before-stat: os.Stat is never called on a cache hit — the hot path is pure memory.
  • Zero-alloc AcceptsEncoding: walks the Accept-Encoding header byte-by-byte without strings.Split.
  • Pre-computed ETagFull: the W/"..." string is built when the file is cached.

Security Model

Path Safety (internal/security)

Every request URL is validated through PathSafe before any filesystem access:

  1. Null byte rejection — prevents C-level path truncation.
  2. path.Clean normalisation — collapses /../, //, etc.
  3. Prefix check — ensures the resolved path starts with the absolute root (separator-aware to prevent /rootsuffix collisions).
  4. EvalSymlinks re-verification — resolves the canonical real path and re-checks the prefix. Symlinks pointing outside root return ErrPathTraversal. Non-existent paths (ENOENT) fall back to the already-checked candidate.
  5. Dotfile blocking — each path segment is checked for a leading ..
HTTP Security Headers

Set on every response including 4xx/5xx errors:

Header Default Value
X-Content-Type-Options nosniff
X-Frame-Options SAMEORIGIN
Content-Security-Policy default-src 'self'
Referrer-Policy strict-origin-when-cross-origin
Permissions-Policy geolocation=(), microphone=(), camera=()
Strict-Transport-Security max-age=31536000 (HTTPS only)
Method Whitelist

Only GET, HEAD, and OPTIONS are accepted. All other methods (including TRACE, PUT, POST, DELETE, PATCH) receive 405 Method Not Allowed. This means TRACE-based XST attacks are impossible by design.

CORS
  • Wildcard (["*"]): emits the literal string *. The request Origin is never reflected. Vary: Origin is not added (correct per RFC 6454).
  • Specific origins: each allowed origin is compared exactly. Matching origins receive Access-Control-Allow-Origin: <origin> and Vary: Origin.
  • Preflight (OPTIONS): returns 204 with Access-Control-Allow-Methods, Access-Control-Allow-Headers, and Access-Control-Max-Age: 86400.
DoS Mitigations
Mitigation Value
ReadTimeout 10 s (covers full read phase including headers — Slowloris protection)
WriteTimeout 10 s
IdleTimeout 75 s (keep-alive)
MaxRequestBodySize 0 (no body accepted — static server)

Configuration Reference

Copy config.toml.example to config.toml and edit as needed. The server starts without a config file using sensible defaults.

[server]
Key Type Default Description
addr string :8080 HTTP listen address
tls_addr string :8443 HTTPS listen address
redirect_host string Canonical host used for HTTP→HTTPS redirects
tls_cert string Path to TLS certificate (PEM)
tls_key string Path to TLS private key (PEM)
read_timeout duration 10s Full request read deadline (covers headers; Slowloris protection)
write_timeout duration 10s Response write deadline
idle_timeout duration 75s Keep-alive idle timeout
shutdown_timeout duration 15s Graceful drain window
[files]
Key Type Default Description
root string ./public Directory to serve
index string index.html Index file for directory requests
not_found string Custom 404 page (relative to root)
[cache]
Key Type Default Description
enabled bool true Toggle in-memory LRU cache
preload bool false Load all eligible files into cache at startup
max_bytes int 268435456 Cache size cap (bytes)
max_file_size int 10485760 Max file size to cache (bytes)
ttl duration 0 Entry TTL (0 = no expiry; flush with SIGHUP)
gc_percent int 0 Go GC target percentage (0 = use Go default of 100)
[compression]
Key Type Default Description
enabled bool true Enable compression
min_size int 1024 Minimum bytes to compress
level int 5 gzip level (1–9)
precompressed bool true Serve .gz/.br sidecar files
[headers]
Key Type Default Description
immutable_pattern string Glob for immutable assets
static_max_age int 3600 Cache-Control max-age for non-HTML (seconds)
html_max_age int 0 Cache-Control max-age for HTML (seconds)
[security]
Key Type Default Description
block_dotfiles bool true Block .-prefixed path components
directory_listing bool false Enable directory index listing
cors_origins []string [] Allowed CORS origins (["*"] for wildcard)
csp string default-src 'self' Content-Security-Policy value
referrer_policy string strict-origin-when-cross-origin Referrer-Policy value
permissions_policy string geolocation=(), microphone=(), camera=() Permissions-Policy value
hsts_max_age int 31536000 HSTS max-age in seconds (HTTPS only; 0 disables)
hsts_include_subdomains bool false Add includeSubDomains to HSTS header

Environment Variables

All environment variables override the corresponding TOML setting. Useful for containers.

Variable Config Field
STATIC_SERVER_ADDR server.addr
STATIC_SERVER_TLS_ADDR server.tls_addr
STATIC_SERVER_REDIRECT_HOST server.redirect_host
STATIC_SERVER_TLS_CERT server.tls_cert
STATIC_SERVER_TLS_KEY server.tls_key
STATIC_SERVER_READ_TIMEOUT server.read_timeout
STATIC_SERVER_WRITE_TIMEOUT server.write_timeout
STATIC_SERVER_IDLE_TIMEOUT server.idle_timeout
STATIC_SERVER_SHUTDOWN_TIMEOUT server.shutdown_timeout
STATIC_FILES_ROOT files.root
STATIC_FILES_INDEX files.index
STATIC_FILES_NOT_FOUND files.not_found
STATIC_CACHE_ENABLED cache.enabled
STATIC_CACHE_PRELOAD cache.preload
STATIC_CACHE_MAX_BYTES cache.max_bytes
STATIC_CACHE_MAX_FILE_SIZE cache.max_file_size
STATIC_CACHE_TTL cache.ttl
STATIC_CACHE_GC_PERCENT cache.gc_percent
STATIC_COMPRESSION_ENABLED compression.enabled
STATIC_COMPRESSION_MIN_SIZE compression.min_size
STATIC_COMPRESSION_LEVEL compression.level
STATIC_SECURITY_BLOCK_DOTFILES security.block_dotfiles
STATIC_SECURITY_CSP security.csp
STATIC_SECURITY_CORS_ORIGINS security.cors_origins (comma-separated)

TLS / HTTPS

Set tls_cert and tls_key to enable HTTPS:

[server]
addr     = ":80"
tls_addr = ":443"
redirect_host = "static.example.com"
tls_cert = "/etc/ssl/certs/server.pem"
tls_key  = "/etc/ssl/private/server.key"

When TLS is configured:

  • HTTP requests on addr are automatically redirected to HTTPS. Set redirect_host when tls_addr listens on all interfaces (for example :443) so redirects use a canonical host instead of the incoming Host header.
  • HTTP/2 is enabled automatically via ALPN negotiation.
  • HSTS (Strict-Transport-Security) is added to all HTTPS responses (configurable max-age).
  • Minimum TLS version is 1.2; preferred cipher suites are ECDHE+AES-256-GCM and ChaCha20-Poly1305.

Pre-compressed Files

Place .gz and .br sidecar files alongside originals. The server serves them automatically when the client signals support:

public/
  app.js
  app.js.gz    ← served for Accept-Encoding: gzip
  app.js.br    ← served for Accept-Encoding: br (preferred over gzip)
  style.css
  style.css.gz

Generate sidecars from the Makefile:

make precompress   # runs gzip and brotli on all .js/.css/.html/.json/.svg

Note: On-the-fly brotli encoding is not implemented. Only .br sidecar files are served with brotli encoding.


HTTP Signals

Signal Action
SIGTERM Graceful shutdown (drains in-flight requests up to shutdown_timeout)
SIGINT Graceful shutdown
SIGHUP Flush in-memory file cache and path-safety cache; re-reads config pointer in main

Note: SIGHUP reloads the config pointer in main but the live middleware chain holds references to the old config. A full restart is required for config changes to take effect. SIGHUP is useful for flushing both the file cache and the path-safety cache without downtime.


Building & Development

Prerequisites
  • Go 1.26+
  • GNU Make
Commands
make build        # compile → bin/static-web
make release      # compile stripped binary → bin/static-web
make install      # install to $(GOPATH)/bin
make run          # build + run with ./config.toml
make test         # go test -race ./...
make bench        # go test -bench=. -benchtime=5s ./...
make lint         # go vet ./...
make precompress  # generate .gz/.br sidecars for public/
make clean        # remove bin/
Running Tests
go test -race ./...                       # full suite with race detector
go test -run TestPathSafe ./internal/security/...  # specific test
go test -bench=BenchmarkCacheGet -benchtime=10s ./internal/cache/
Code Quality Gates

All PRs must pass:

go build ./...    # clean compile
go vet ./...      # static analysis
go test -race ./... # all tests, race-free

Known Limitations

Limitation Detail
Brotli on-the-fly Not implemented. Only pre-compressed .br sidecar files are served.
SIGHUP config reload Reloads the config struct pointer in main only. Live middleware chains hold old references — full restart required for config changes to propagate.

Directories

Path Synopsis
benchmark
fasthttp-hello command
Bare-minimum fasthttp server: pre-allocated response, no middleware, no allocs.
Bare-minimum fasthttp server: pre-allocated response, no middleware, no allocs.
raw-hello command
Bare-minimum Go HTTP server: pre-allocated response, no middleware, no allocs.
Bare-minimum Go HTTP server: pre-allocated response, no middleware, no allocs.
cmd
static-web command
Command static-web is the entry point for the static web file server.
Command static-web is the entry point for the static web file server.
internal
cache
Package cache provides a size-bounded in-memory LRU cache for static files.
Package cache provides a size-bounded in-memory LRU cache for static files.
compress
Package compress provides HTTP response compression middleware and utilities.
Package compress provides HTTP response compression middleware and utilities.
config
Package config provides configuration loading for the static web server.
Package config provides configuration loading for the static web server.
defaults
Package defaults embeds the built-in static assets (index.html, 404.html, style.css) that ship with the binary.
Package defaults embeds the built-in static assets (index.html, 404.html, style.css) that ship with the binary.
handler
Package handler provides the core HTTP file-serving handler and middleware composition for the static web server.
Package handler provides the core HTTP file-serving handler and middleware composition for the static web server.
headers
Package headers provides HTTP caching utilities and response header helpers.
Package headers provides HTTP caching utilities and response header helpers.
security
Package security provides path safety checks and HTTP security middleware for the static web server.
Package security provides path safety checks and HTTP security middleware for the static web server.
server
Package server provides an HTTP/HTTPS server with graceful shutdown support, built on top of fasthttp.
Package server provides an HTTP/HTTPS server with graceful shutdown support, built on top of fasthttp.
version
Package version holds build-time version information injected via -ldflags.
Package version holds build-time version information injected via -ldflags.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL