A minimal PSR-15 middleware that serves static files from a public directory.
- Serves only regular, readable files inside the public directory; path traversal and symlinks pointing outside are rejected.
- Handles
GETandHEADrequests, any other method is passed to the next handler. - Sends
Content-Type(based on the file extension,application/octet-streamas fallback),Content-Length,ETagandX-Content-Type-Options: nosniff. - Responds with
304 Not Modifiedif theIf-None-Matchheader matches the file'sETag. - Passes the request to the next handler if no matching file exists.
- php: ^8.3
- psr/http-factory: ^1.1
- psr/http-message: ^1.1|^2.0
- psr/http-server-handler: ^1.0.2
- psr/http-server-middleware: ^1.0.2
Through Composer as chubbyphp/chubbyphp-static-file.
composer require chubbyphp/chubbyphp-static-file "^1.4"Register the middleware before the routing of your PSR-15 based framework, so that static files are served without hitting a route.
<?php
declare(strict_types=1);
namespace App;
use Chubbyphp\StaticFile\StaticFileMiddleware;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;
/** @var ResponseFactoryInterface $responseFactory */
$responseFactory = ...;
/** @var StreamFactoryInterface $streamFactory */
$streamFactory = ...;
$app = ...;
$app->add(new StaticFileMiddleware(
$responseFactory,
$streamFactory,
__DIR__ . '/public'
));The constructor accepts two optional arguments:
$hashAlgorithm(default:md5): the algorithm used to calculate theETag, must be supported by hash_algos().$mimetypes(default: bundled list based on the Apache mime.types, regenerate withphp generate-mimetypes.php): a map of file extension to mime type.
$app->add(new StaticFileMiddleware(
$responseFactory,
$streamFactory,
__DIR__ . '/public',
'sha256',
['css' => 'text/css', 'js' => 'text/javascript', 'png' => 'image/png']
));2026 Dominik Zogg