TrueAsync\StaticHandler
(PHP 8.6+, true_async_server 0.6+)
Built-in static file handler (issue #13). One instance = one prefix mount. Attached to the server via HttpServer::addStaticHandler().
Entirely in C: requests do not spawn coroutines and do not enter the PHP VM — files are served through libuv async fs ops straight into the response stream.
namespace TrueAsync;
final class StaticHandler
{
public function __construct(string $urlPrefix, string $rootDirectory);
// index / fallthrough
public function setIndexFiles(string ...$files): static;
public function disableIndex(): static;
public function setOnMissing(StaticOnMissing $mode): static;
// precompressed sidecars
public function enablePrecompressed(string ...$encodings): static;
public function disablePrecompressed(): static;
// security
public function setDotfilePolicy(StaticDotfiles $policy): static;
public function setSymlinkPolicy(StaticSymlinks $policy): static;
public function hide(string ...$globs): static;
// cache / headers
public function setEtagEnabled(bool $enabled): static;
public function setCacheControl(string $value): static;
public function setOpenFileCache(int $maxEntries, int $ttlSeconds = 60): static;
public function disableOpenFileCache(): static;
public function setHeader(string $name, string $value): static;
// directory listing
public function setBrowseEnabled(bool $enabled): static;
// MIME
public function setMimeType(string $extension, string $contentType): static;
// introspection
public function getUrlPrefix(): string;
public function getRootDirectory(): string;
public function isLocked(): bool;
}Constructor
__construct
public StaticHandler::__construct(string $urlPrefix, string $rootDirectory)| Parameter | Requirements |
|---|---|
$urlPrefix | URL prefix. Must start and end with /. Example: "/static/". |
$rootDirectory | Absolute path to a directory on disk; canonicalised at attach time. |
Index / fallthrough
setIndexFiles
public StaticHandler::setIndexFiles(string ...$files): staticFile names served when a directory URL is requested. Default ["index.html"]. An empty list disables index lookup.
disableIndex
public StaticHandler::disableIndex(): staticEquivalent to setIndexFiles() with no arguments.
setOnMissing
public StaticHandler::setOnMissing(StaticOnMissing $mode): staticWhat to do when the requested path does not resolve to a regular file inside the root:
| Value | Behaviour |
|---|---|
StaticOnMissing::NOT_FOUND (default) | 404 in C, the request never reaches the PHP VM |
StaticOnMissing::NEXT | Control is handed back to the dispatcher and a normal handler coroutine is spawned — the request goes to addHttpHandler() |
Precompressed sidecars
enablePrecompressed
public StaticHandler::enablePrecompressed(string ...$encodings): staticEnables serving precompressed sidecars (main.css.br, main.css.gz, main.css.zst) when the client allows them through Accept-Encoding. Arguments are content-coding names: "br", "gzip", "zstd". Unknown names — InvalidArgumentException from the setter.
disablePrecompressed
public StaticHandler::disablePrecompressed(): staticSecurity
setDotfilePolicy
public StaticHandler::setDotfilePolicy(StaticDotfiles $policy): staticA "dotfile" is any path segment that starts with ., including .. (which is always rejected by the traversal guard regardless of policy).
| Behaviour | |
|---|---|
StaticDotfiles::DENY (default) | 404 on any path containing a dotfile component |
StaticDotfiles::ALLOW | dotfiles are served as regular files |
StaticDotfiles::IGNORE | as if the file did not exist (passthrough governed by StaticOnMissing) |
setSymlinkPolicy
public StaticHandler::setSymlinkPolicy(StaticSymlinks $policy): static| Behaviour | |
|---|---|
StaticSymlinks::REJECT (default) | 404 on any symlink in the path. O_NOFOLLOW + per-segment lstat — a symlink is never traversed |
StaticSymlinks::FOLLOW | symlinks are followed; the post-realpath() target must stay inside the root |
StaticSymlinks::OWNER_MATCH | follow only when the symlink and its target share the same uid |
hide
public StaticHandler::hide(string ...$globs): staticGlob patterns: matching paths return 404 regardless of whether they exist. Comparison is relative to the root and uses / as the separator.
Cache / headers
setEtagEnabled
public StaticHandler::setEtagEnabled(bool $enabled): staticToggle weak ETag (default true). When enabled, every 200 carries an ETag: W/"…" derived from (mtime_ns, size, ino); If-None-Match / If-Modified-Since produce 304.
setCacheControl
public StaticHandler::setCacheControl(string $value): staticLiteral Cache-Control. An empty string suppresses emission.
setOpenFileCache
public StaticHandler::setOpenFileCache(int $maxEntries, int $ttlSeconds = 60): staticnginx-style open-file cache: caches resolved path, fstat metadata, MIME, ETag, and Last-Modified for the last N requests. Within ttlSeconds, repeat requests hit the cache and skip realpath/stat/MIME walks.
Disabled by default. It pays off on cold dentry caches / large docroots / network filesystems. On a warm-dentry local disk, syscalls already cost sub-microseconds — the HashTable-lookup overhead eats the win.
$maxEntries == 0 — disable.
disableOpenFileCache
public StaticHandler::disableOpenFileCache(): staticSugar for setOpenFileCache(0).
setHeader
public StaticHandler::setHeader(string $name, string $value): staticFixed header, evaluated once at attach time. Emitted on every 200 and 304 (except Content-* headers per RFC 9110 §15.4.5).
Directory listing
setBrowseEnabled
public StaticHandler::setBrowseEnabled(bool $enabled): staticToggle HTML listing when a directory is requested without an index. Default false.
Reserved for PR #6 — currently a no-op; accepted by the setter without effect.
MIME
setMimeType
public StaticHandler::setMimeType(string $extension, string $contentType): staticOverride Content-Type for files with the given extension. Extension — lowercased, no leading dot.
Introspection
getUrlPrefix / getRootDirectory
public StaticHandler::getUrlPrefix(): string
public StaticHandler::getRootDirectory(): stringisLocked
public StaticHandler::isLocked(): booltrue after the handler is attached to the server via addStaticHandler(). A locked handler rejects every setter with a runtime exception.
Enums
See the individual pages:
(All three are enum: int under the TrueAsync namespace.)
Example
use TrueAsync\StaticHandler;
use TrueAsync\StaticOnMissing;
use TrueAsync\StaticDotfiles;
$static = (new StaticHandler('/static/', '/var/www/public'))
->setIndexFiles('index.html', 'index.htm')
->enablePrecompressed('br', 'gzip')
->setOnMissing(StaticOnMissing::NEXT)
->setDotfilePolicy(StaticDotfiles::DENY)
->setCacheControl('public, max-age=31536000, immutable')
->setEtagEnabled(true)
->setOpenFileCache(maxEntries: 1024, ttlSeconds: 60)
->setHeader('Strict-Transport-Security', 'max-age=63072000')
->hide('*.bak', '*.tmp', 'private/**');
$server->addStaticHandler($static);