roadrunner_static (roadrunner v0.7.0)
View SourceBuilt-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 ...
Precompressed-sibling serving
When a request's Accept-Encoding opts into a compressed encoding
and the requested file has a matching precompressed sibling on disk,
the sibling is served verbatim with the corresponding
Content-Encoding plus Vary: Accept-Encoding. This matches nginx's
gzip_static on behaviour and lets operators pre-compress build
assets once instead of paying the compression cost per request.
Two encodings are negotiated, brotli first:
Accept-Encoding: brwith a<file>.brsibling on disk →Content-Encoding: brAccept-Encoding: gzipwith a<file>.gzsibling on disk →Content-Encoding: gzip
Brotli wins when the client accepts both and the .br sibling
exists; gzip is the fallback. With neither sibling on disk (or
neither encoding accepted) the raw file is served. Roadrunner never
compresses on the fly — it only sendfiles a sibling an operator
placed on disk, so there is no brotli or gzip codec dependency.
Accept-Encoding is matched via plain substring (br, gzip)
rather than full RFC 9110 §12.5.3 qvalue ranking. Browsers and
clients that reach the static path send these tokens plainly.
The original file's ETag is reused for every variant, so a follow-up
If-None-Match returns 304 regardless of which variant was first
served. A Range request disables the precompressed path on that
request — byte offsets over a compressed representation have subtle
semantics and the simple "Range wins" rule matches what nginx does.
The Content-Type always reflects the original file's extension,
not the sibling's.
Symlink policy
#{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 insidedirare 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 (nginxdisable_symlinks offequivalent). 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 a node-global ETS table 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
Types
Precompressed siblings found on disk for a regular file, keyed by
encoding. A key is present only when that sibling is a regular file;
the value is its size in bytes. #{} means neither sibling exists.
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.
Types
-type siblings() :: #{br => non_neg_integer(), gz => non_neg_integer()}.
Precompressed siblings found on disk for a regular file, keyed by
encoding. A key is present only when that sibling is a regular file;
the value is its size in bytes. #{} means neither sibling exists.
Functions
-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.
-spec handle(roadrunner_req:request()) -> roadrunner_handler:result().