Skip to Content
Static Serving

Static Serving

qaws serves files from one configured directory. It does not list directories, does not fall back to a single-page app shell, and does not proxy requests.

Methods

MethodBehavior
GETSends headers and body.
HEADSends the same headers as GET without the body.
Other methodsReturns 405 Method Not Allowed.

Request bodies are out of scope. qaws is a static file server, not an application server.

Directory indexes

A request for / resolves to index.html inside the serve directory:

GET / -> ./public/index.html

A request for a directory resolves to that directory’s index.html:

GET /docs/ -> ./public/docs/index.html

If trailing slash redirects are enabled, a directory request without the slash redirects with 308 Permanent Redirect:

GET /docs -> 308 Location: /docs/

Missing files

Missing files return 404 Not Found.

qaws does not provide SPA fallback behavior. If /app/settings is missing, it returns 404 even when /index.html exists.

Directory listing

Directory listing is disabled. If a directory does not have an index.html, qaws does not expose its contents.

Path normalization

qaws normalizes request paths before opening files. It rejects traversal and suspicious path forms instead of joining raw request text to the serve root.

Examples:

RequestResult
/Serves index.html.
/assets/app.cssServes the file if it exists.
/../build.zigRejected.
/assets/../../secretRejected.
paths containing backslashesRejected.

Dotfiles

Dotfile path segments are denied by default:

/.env -> 403 /assets/.secret -> 403

.well-known is allowed for common public metadata:

/.well-known/security.txt

MIME types

qaws sends common static MIME types and falls back to application/octet-stream.

Common examples:

ExtensionContent-Type
.htmltext/html; charset=utf-8
.csstext/css; charset=utf-8
.jsapplication/javascript; charset=utf-8
.jsonapplication/json; charset=utf-8
.pngimage/png
.jpg, .jpegimage/jpeg
.svgimage/svg+xml
.wasmapplication/wasm
unknownapplication/octet-stream

Conditional requests

Successful file responses include Last-Modified when http.last_modified is enabled and a weak representation ETag when http.etag is enabled. The ETag contains the selected file’s nanosecond mtime, size, and representation variant.

curl -i http://127.0.0.1:8080/ curl -i -H 'If-None-Match: W/"example"' http://127.0.0.1:8080/

If-None-Match, including *, takes precedence over If-Modified-Since. A matching validator returns 304 Not Modified without a body. ETags differ between identity, gzip, and Brotli representations of the same logical path.

Byte ranges

With http.range_requests enabled, qaws accepts one normal, open-ended, or suffix byte range:

curl -i -H 'Range: bytes=0-1023' http://127.0.0.1:8080/video.bin curl -i -H 'Range: bytes=4096-' http://127.0.0.1:8080/video.bin curl -i -H 'Range: bytes=-512' http://127.0.0.1:8080/video.bin

A satisfiable range returns 206 Partial Content with Content-Range. An unsatisfiable range returns 416 Range Not Satisfiable with Content-Range: bytes */<size>. A syntactically valid multipart range set is ignored and receives the normal 200 representation because multipart responses are not implemented.

Date-based If-Range can permit or suppress a range. qaws emits weak ETags, so an ETag does not strongly satisfy If-Range. Ranges address the bytes of the selected encoded representation, not the decoded identity file.

Precompressed sidecars

With http.precompressed enabled, a request for /assets/app.js may select /assets/app.js.br or /assets/app.js.gz according to Accept-Encoding tokens and q-values. Brotli wins over gzip, and gzip wins over identity when qualities are equal.

curl -I -H 'Accept-Encoding: br, gzip' http://127.0.0.1:8080/assets/app.js

The response keeps the original path’s MIME type and adds the selected Content-Encoding plus Vary: Accept-Encoding. Metadata, ETag, content length, and ranges describe the sidecar itself. If every available encoding, including identity, has quality zero, qaws returns 406 Not Acceptable.

Direct requests for .br or .gz files serve those files normally without recursive sidecar negotiation or an inferred Content-Encoding.

HEAD behavior

HEAD follows the same selection and conditional rules as GET, including ETags, sidecars, ranges, redirects, and errors, but never emits a response body. Content-Length continues to describe the body a corresponding GET would send.

Default headers

qaws includes:

Server: qaws/0.2.7 X-Content-Type-Options: nosniff ETag: W/"<mtime-ns>-<size>-identity" Accept-Ranges: bytes

nosniff is enabled by default to reduce browser MIME sniffing surprises. Encoded responses also include Content-Encoding and Vary: Accept-Encoding; partial responses include Content-Range.

qaws docs. Built for static hosting.