Skip to content

ServeFiles

[Source]

Serve files from a directory on disk.

Small files (below the chunk threshold) are served as a single response with Content-Length. Large files are streamed using chunked transfer encoding. When the client does not support chunked encoding (HTTP/1.0), small files are still served normally, but large files are rejected with 505 HTTP Version Not Supported to prevent memory exhaustion.

HEAD requests are optimized: the handler responds with Content-Type and Content-Length headers without reading the file, regardless of file size.

Responses include caching headers:

  • ETag: Weak ETag computed from file metadata (W/"<inode>-<size>-<mtime>"). On Windows, FileInfo.inode is always 0, reducing collision resistance to size+mtime only.
  • Last-Modified: RFC 7231 IMF-fixdate from the file's modification time.
  • Cache-Control: Configurable via the cache_control constructor parameter. Defaults to "public, max-age=3600". Pass None to omit.

Conditional requests are supported per RFC 7232:

  • If-None-Match is checked first (ETag comparison using weak matching).
  • If-Modified-Since is checked only when If-None-Match is absent.
  • When either matches, the handler responds with 304 Not Modified (cache headers included, no body).

Custom content types can be added via the content_types parameter:

let types = hobby.ContentTypes
  .add("webp", "image/webp")
  .add("avif", "image/avif")
hobby.ServeFiles(root where content_types = types)

Routes must use *filepath as the wildcard parameter name:

use "files"
use hobby = "hobby"
use stallion = "stallion"
use lori = "lori"

actor Main
  new create(env: Env) =>
    let auth = lori.TCPListenAuth(env.root)
    let root = FilePath(FileAuth(env.root), "./public")
    hobby.Application
      .>get("/static/*filepath", hobby.ServeFiles(root))
      .serve(auth, stallion.ServerConfig("0.0.0.0", "8080"), env.out)

Path traversal is prevented by Pony's FilePath.from(), which rejects any resolved path that is not a child of the base directory.

When a request resolves to a directory, ServeFiles looks for an index.html file inside it. If found, the index file is served with the correct text/html content type and caching headers. If no index.html exists, the directory request returns 404.

class val ServeFiles is
  Handler val

Implements


Constructors

create

[Source]

Create a handler that serves files under root.

root must have FileLookup, FileStat, and FileRead capabilities. chunk_threshold is the file size in kilobytes at or above which chunked streaming is used instead of a single response. Default: 1024 (1 MB).

cache_control sets the Cache-Control header value. Defaults to "public, max-age=3600" (1 hour). Pass None to omit the header.

content_types controls the file extension to MIME type mapping. Defaults to a ContentTypes with 17 common extensions. Chain .add() calls to add custom mappings.

If the route uses a wildcard name other than *filepath, param lookup will fail and the handler will return 500. Always use *filepath.

new val create(
  root: FilePath val,
  chunk_threshold: USize val = 1024,
  cache_control: (String val | None val) = "public, max-age=3600",
  content_types: ContentTypes val = reference)
: ServeFiles val^

Parameters

Returns


Public Functions

apply

[Source]

fun box apply(
  ctx: Context ref)
: None val ?

Parameters

Returns