Serving Static Files
Every web application eventually needs to deliver files — stylesheets,
images, scripts, HTML pages. You could open files manually and stream
bytes into the response, but that means handling content types, caching
headers, conditional requests, range requests, directory traversal
attacks, and a dozen other details that are easy to get wrong. The
serve_static middleware handles all of this in a single line.
Code snippets assume using namespace boost::http; is in effect.
|
Quick Start
Point serve_static at a directory on disk and register it as
middleware:
#include <boost/http/server/serve_static.hpp>
router r;
r.use( serve_static("/var/www/public") );
That’s it. Requests are now mapped to files under /var/www/public.
A request for /css/style.css serves the file
/var/www/public/css/style.css. The middleware automatically:
-
Detects
Content-Typefrom the file extension -
Generates
ETagandLast-Modifiedheaders -
Responds to conditional requests with
304 Not Modified -
Handles
Rangerequests for partial content -
Redirects directory URLs that lack a trailing slash
-
Serves
index.htmlwhen a directory is requested
If a file is not found, the request passes through to the next handler in the chain — exactly as you would expect from middleware.
How Requests Map to Files
The mapping is straightforward. The request path is appended to the document root:
| Document Root | Request Path | File Served |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
redirect to |
Only GET and HEAD methods are served. Other methods pass through
to the next handler (or return 405 Method Not Allowed when
fallthrough is disabled).
Configuration
Pass a serve_static_options struct to customize behavior:
serve_static_options opts;
opts.max_age = 86400; // cache for one day
opts.immutable = true; // assets never change at the same URL
opts.dotfiles = dotfiles_policy::deny;
router r;
r.use( serve_static("/var/www/public", opts) );
Every option has a sensible default. Override only what you need.
Options Reference
| Option | Default | Description |
|---|---|---|
|
|
How to handle dotfiles ( |
|
|
Seconds for the |
|
|
Advertise support for range requests with |
|
|
Generate an |
|
|
When a file is not found, pass the request to the next handler instead of returning 404. |
|
|
Set the |
|
|
Redirect directory requests that are missing a trailing slash
(e.g. |
|
|
Append |
|
|
Serve |
Caching
Browsers and proxies rely on HTTP caching headers to avoid
re-downloading files that haven’t changed. serve_static supports
three caching mechanisms that work together.
ETags and Conditional Requests
When etag is enabled, every response includes an ETag header
derived from the file’s size and modification time. On subsequent
requests the browser sends If-None-Match with the stored ETag.
If the file hasn’t changed, the server responds with
304 Not Modified — no body, no wasted bandwidth.
The same principle applies to Last-Modified and If-Modified-Since.
Both mechanisms are enabled by default.
Cache-Control
Set max_age to tell browsers how long a response is fresh:
opts.max_age = 3600; // one hour
For assets with content-hashed filenames (like app.a1b2c3.js),
the URL changes whenever the content changes. These files can be
cached aggressively:
opts.max_age = 31536000; // one year
opts.immutable = true; // never revalidate
The immutable directive tells modern browsers they don’t need to
send conditional requests at all — the file at this URL will never
change.
Dotfile Handling
Files and directories whose names begin with a dot are often
sensitive: .env, .git, .htaccess. The dotfiles option
controls how serve_static treats them.
| Policy | Behavior |
|---|---|
|
Pretend the file doesn’t exist. If |
|
Return |
|
Serve dotfiles like any other file. |
The default is ignore, which is the safest choice. Use deny when
you want to actively reject requests for dotfiles. Use allow only
when you have a specific reason to expose them.
opts.dotfiles = dotfiles_policy::deny;
Range Requests
Large files benefit from range requests. A video player can seek to
any position without downloading the entire file. A download manager
can resume an interrupted transfer. When accept_ranges is enabled,
serve_static advertises Accept-Ranges: bytes and honors Range
headers by responding with 206 Partial Content and the requested
byte range.
This is enabled by default. No configuration is needed.
Fallthrough
The fallthrough option determines what happens when serve_static
cannot serve a request — either because the file doesn’t exist or the
HTTP method isn’t GET/HEAD.
When fallthrough is true (the default), unmatched requests pass
to the next handler. This is how middleware is supposed to work: each
handler tries to do its job, and if it can’t, it steps aside.
router r;
r.use( serve_static("/var/www/public") ); // try files first
r.use( serve_index("/var/www/public") ); // then directory listings
r.add( method::get, "/api/status", // then API routes
[](route_params& rp) -> route_task {
co_await rp.send("OK");
co_return route_done;
});
When fallthrough is false, the middleware returns an error response
directly (404 for missing files, 405 for wrong methods) instead of
passing through.
Combining with serve_index
A common pattern pairs serve_static with serve_index so that
directories without an index.html get a browsable file listing:
router r;
r.use( serve_static(root) );
r.use( serve_index(root) );
When a request arrives for a directory:
-
serve_staticlooks forindex.htmlin that directory. -
If found, it serves the index page.
-
If not found and
fallthroughis true, the request reachesserve_index, which generates a directory listing.
Mounting on a Subpath
Register serve_static under a specific route prefix to serve files
from a namespace:
router r;
r.use( "/assets", serve_static("/var/www/assets") );
Now /assets/logo.png maps to /var/www/assets/logo.png, while
requests outside /assets/ are unaffected.
Example: Production Configuration
A production-ready setup might look like this:
serve_static_options opts;
opts.max_age = 86400;
opts.etag = true;
opts.last_modified = true;
opts.dotfiles = dotfiles_policy::deny;
opts.redirect = true;
router r;
r.use( serve_static("/var/www/public", opts) );
r.use( serve_index("/var/www/public") );
This configuration:
-
Caches files for 24 hours
-
Uses ETags and Last-Modified for revalidation
-
Blocks access to dotfiles
-
Redirects
/docsto/docs/ -
Falls back to directory listings when no index file exists
See Also
-
Route Patterns — how request paths are matched to handlers