roadrunner_static (roadrunner v0.2.3)

View Source

Built-in static file handler.

Configure via a 3-tuple route with #{dir => Path} opts and a *path wildcard segment carrying the relative file path:

{~"/static/*path", roadrunner_static, #{dir => ~"/var/www"}}

Reads the file from disk, sets Content-Type from the extension, returns 404 on a missing file or any path that contains ...

Gzip-sibling serving

When a request carries Accept-Encoding: gzip and the requested file has a <file>.gz sibling on disk, the sibling is served verbatim with Content-Encoding: gzip plus Vary: Accept-Encoding. This matches nginx's gzip_static on behaviour and lets operators pre-compress build assets once instead of paying the deflate cost per request.

Accept-Encoding is matched via plain substring (gzip) rather than full RFC 9110 §12.5.3 qvalue ranking. The static path is typically hit by browsers and benchmark clients that always include gzip plainly. Brotli (.br) siblings are not served — gzip is the universally supported encoding.

The original file's ETag is reused for the gzip variant, so a follow-up If-None-Match returns 304 regardless of which variant was first served. A Range request disables the gzip path on that request — byte offsets over a compressed representation have subtle semantics and the simple "Range wins" rule matches what nginx does.

#{symlink_policy => Policy} (default refuse_escapes) controls how symlinks inside the docroot are handled. The policy applies to the leaf of the requested path — symlinks in intermediate directories are still followed by the kernel.

  • refuse_escapes (default) — symlinks whose target resolves inside dir are followed; symlinks pointing outside (e.g. an absolute target like /etc/passwd, or a relative target with .. segments) return 404. Stricter than nginx/Apache defaults but matches what an operator typically wants for a public docroot.
  • follow — every symlink is followed regardless of where it points (nginx disable_symlinks off equivalent). Use only when the docroot's filesystem permissions prevent untrusted writes.
  • refuse — every symlink returns 404, even safe in-docroot ones.

Metadata cache

#{cache_ttl_ms => N} caches the stat result (size, mtime) for each regular file in persistent_term for N milliseconds. Hot paths skip the per-request read_link_info syscall after the first hit. Symlinks always bypass the cache because the policy gate needs the un-followed lookup.

The default is 0 (disabled). Enabling the cache assumes files are immutable during the TTL window: the cached size feeds the Content-Length header while sendfile reads the file fresh from disk, so a file replaced or resized mid-window produces a length / body mismatch (truncated or short response). Safe for deploy-then-restart workflows with versioned-by-hash assets; unsafe for mutable files (user uploads, dev hot-reload).

Set to a positive integer to opt in (e.g., cache_ttl_ms => 1000), or infinity for "cache for the lifetime of the listener; only a listener restart re-stats". nginx's open_file_cache makes the same trade-off and is also off by default.

Call roadrunner_static:cache_clear/0 to flush every cached entry without a listener restart — useful after a deploy that swaps files under an infinity (or long) TTL.

Summary

Functions

Drop every cached static-file metadata entry. Pair with cache_ttl_ms => infinity (or any TTL longer than your deploy cycle) to flush stale metadata after replacing files in the docroot, without restarting the listener.

Functions

cache_clear()

-spec cache_clear() -> ok.

Drop every cached static-file metadata entry. Pair with cache_ttl_ms => infinity (or any TTL longer than your deploy cycle) to flush stale metadata after replacing files in the docroot, without restarting the listener.

Walks persistent_term:get/0 once to find the static-meta keys — cheap on small caches, O(N) over all persistent_term entries on a busy node. Intended for occasional, deploy-time use, not a per-request hot path.

handle(Req)