phasync
Request handlers / StaticFiles

Class

StaticFiles

Serves the files of a directory, and passes every other request on to the application's handler.

Introduction#

It is what --public=<dir> installs; wrap your own handler for the same behaviour.

A GET or HEAD request whose path names a file below the directory gets that file: streamed, with its Content-Type (by extension), Content-Length, Last-Modified and ETag; 304 Not Modified for If-None-Match or If-Modified-Since; a single byte range (Range: bytes=) as 206 Partial Content, or 416 when it lies past the end. A directory is served by its index.html, after a redirect to the path with a trailing slash.

Not served, but passed on to the application: other methods, a path that leaves the directory (.., a symlink pointing outside it), a name starting with a dot (.env, .git/; .well-known/ is served), a PHP file (.php, .phtml, .phar, .inc: a source is never sent), and a directory without index.html. Nothing is ever listed.

Class synopsis#

final class StaticFiles {
    /* Methods */
    public StaticFiles::__construct(string $directory)
    public StaticFiles::wrap(Closure $app): Closure
}

Methods#

MethodDescription
__constructServe the files below $directory.
wrapA handler that answers the requests naming a file from the directory, and calls $app for every other.

Examples#

Example 1Files, and the application for the rest
use Swerve\ClientRequest;
use Swerve\RequestHandler;
use Swerve\StaticFiles;

// Files below public/ are served; every other request reaches the application
$files = new StaticFiles(__DIR__ . '/public');
$app   = new RequestHandler($files->wrap(function (ClientRequest $request) {
    $request->sendResponseHeaders(404, ['content-type' => 'text/plain']);
    $request->write("No page at {$request->getTarget()}\n");
}));

if (debug_backtrace()) {
    return $app;   // swerve loads this file: it is the application's swerve.php
}

// Run directly: serve the application with swerve, and send it requests
require __DIR__ . '/../serve.php';
touch(__DIR__ . '/public/hello.txt', 1767225600);   // a fixed date, for the same output every run
serve(__FILE__, [
    "GET /hello.txt HTTP/1.1\r\nHost: example\r\n\r\n",
    "GET /missing.txt HTTP/1.1\r\nHost: example\r\n\r\n",
]);
Output
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Last-Modified: Thu, 01 Jan 2026 00:00:00 GMT
ETag: "6955b900-12"
Accept-Ranges: bytes
Server: Swerve
Content-Length: 18
Connection: close

Hello from a file

HTTP/1.1 404 Not Found
content-type: text/plain
Server: Swerve
Transfer-Encoding: chunked
Connection: close

18
No page at /missing.txt

0
Example 2
$files = new Swerve\StaticFiles(__DIR__ . '/public');
return new Swerve\RequestHandler($files->wrap(function (Swerve\ClientRequest $request) {
    // what is not a file
}));