# _static

URL: /pounce/api/_static/
Section: api
Description: Static file serving with modern optimizations.

Designed for Bengal SSG output and Chirp static assets. Supports:
- Protocol-owned zero-copy sendfile for supported HTTP/1 connections
- ETag generation from mtime + size
- 304 Not Modified responses
- Range requests (Accept-Ranges, Content-Range, 206)
- Precompressed file serving (.gz, .zst variants)
- MIME type detection
- Security: path traversal prevention, hidden file blocking

Example:
    config = ServerConfig(static_files={"/static": "./public"})
    # Requests to /static/* will be served from ./public/

---

> For a complete page index, fetch /pounce/llms.txt.

Open LLM text
(/pounce/api/_static/index.txt)

Share with AI

Ask Claude
(https://claude.ai/new?q=Please%20help%20me%20understand%20this%20documentation%3A%20%2Fpounce%2Fapi%2F_static%2Findex.txt)

Ask ChatGPT
(https://chatgpt.com/?q=Please%20help%20me%20understand%20this%20documentation%3A%20%2Fpounce%2Fapi%2F_static%2Findex.txt)

Ask Gemini
(https://gemini.google.com/app?q=Please%20help%20me%20understand%20this%20documentation%3A%20%2Fpounce%2Fapi%2F_static%2Findex.txt)

Ask Copilot
(https://copilot.microsoft.com/?q=Please%20help%20me%20understand%20this%20documentation%3A%20%2Fpounce%2Fapi%2F_static%2Findex.txt)

Module

#
`_static`

Static file serving with modern optimizations.

Designed for Bengal SSG output and Chirp static assets. Supports:

- Protocol-owned zero-copy sendfile for supported HTTP/1 connections

- ETag generation from mtime + size

- 304 Not Modified responses

- Range requests (Accept-Ranges, Content-Range, 206)

- Precompressed file serving (.gz, .zst variants)

- MIME type detection

- Security: path traversal prevention, hidden file blocking

Example:

```
config = ServerConfig(static_files={"/static": "./public"})
# Requests to /static/* will be served from ./public/
```

4Classes3Functions

## Classes

`_RangeNotSatisfiable`

0

▼

Sentinel for a valid but unsatisfiable Range request (-> 416).

Bases:

`Enum`

Sentinel for a valid but unsatisfiable Range request (-> 416).

`StaticMount`

9

▼

Configuration for a static file mount point.

Configuration for a static file mount point.

#### Attributes

Name
Type
Description

`url_path`

`str`

—

`directory`

`Path`

—

`cache_control`

`str`

—

`precompressed`

`bool`

—

`follow_symlinks`

`bool`

—

`index_file`

`str | None`

—

`extra_mime_types`

`dict[str, str]`

—

`stat_cache`

`bool`

—

`stat_cache_size`

`int`

—

`StaticFile`

8

▼

Resolved static file with metadata.

Resolved static file with metadata.

#### Attributes

Name
Type
Description

`path`

`Path`

—

`size`

`int`

—

`mtime`

`float`

—

`mime_type`

`str`

—

`etag`

`str`

—

`encoding`

`str | None`

—

`cache_control`

`str`

—

`vary`

`bool`

—

`StaticFiles`

24

▼

ASGI-compatible static file handler.

Can be used as middleware or integrated into Worker.

Example…

ASGI-compatible static file handler.

Can be used as middleware or integrated into Worker.

Example as middleware:

```
from pounce import StaticFiles

app = StaticFiles(
    app,
    mounts=[
        StaticMount("/static", Path("./public")),
        StaticMount("/assets", Path("./dist")),
    ]
)
```

Example in Worker (built-in):
config = ServerConfig(static_files={"/static": "./public"})

#### Methods

Internal Methods
24

▼

`__init__`

2

▼

Initialize static file handler.

`def __init__(self, app: ASGIApp | None = None, *, mounts: list[StaticMount]) -> None`

##### Parameters

Name
Type
Description

`app`
`—`

Optional ASGI app to call if path doesn't match (middleware mode)

Default:`None`

`mounts`
`—`

List of static mount configurations

`_prepare_mounts`

1

`list[StaticMount (/pounce/api/_static/#StaticMount)]`

▼

Normalize and validate mount configurations.

`def _prepare_mounts(self, mounts: list[StaticMount]) -> list[StaticMount]`

##### Parameters

Name
Type
Description

`mounts`
`—`

##### Returns

`list[StaticMount (/pounce/api/_static/#StaticMount)]`

Sorted list of mounts (longest url_path first for correct matching)

`__call__`

3

▼

Handle ASGI request.

If path matches a static mount, serve the file. Otherwise…

async

`async def __call__(self, scope: dict[str, Any], receive: Receive, send: Send) -> None`

Handle ASGI request.

If path matches a static mount, serve the file. Otherwise, call the app.

##### Parameters

Name
Type
Description

`scope`
`—`

`receive`
`—`

`send`
`—`

`_resolve_file`

2

`StaticFile (/pounce/api/_static/#StaticFile) | None`

▼

Resolve URL path to static file, using the opt-in stat cache.

When the cache i…

`def _resolve_file(self, url_path: str, accept_encoding: bytes | None) -> StaticFile | None`

Resolve URL path to static file, using the opt-in stat cache.

When the cache is enabled (at least one mount sets `stat_cache` (/pounce/api/_static/#StaticMount)) a
repeat request revalidates the cached entry with a single`stat()`
on the already-resolved path; if mtime and size are unchanged it is
returned directly, skipping`resolve()`, `lstat()`, and the
precompressed probes. A changed mtime/size (e.g. a Bengal rebuild) or
a now-missing file invalidates the entry and falls through to a full,
security-checked re-resolution.

##### Parameters

Name
Type
Description

`url_path`
`—`

`accept_encoding`
`—`

##### Returns

`StaticFile (/pounce/api/_static/#StaticFile) | None`

StaticFile if found and valid, None otherwise

`_mount_caches`

1

`bool`

▼

Whether the mount serving ``url_path`` opts into the stat cache.

`def _mount_caches(self, url_path: str) -> bool`

##### Parameters

Name
Type
Description

`url_path`
`—`

##### Returns

`bool`

`_resolve_file_uncached`

2

`StaticFile (/pounce/api/_static/#StaticFile) | None`

▼

Resolve URL path to static file (full security-checked path).

`def _resolve_file_uncached(self, url_path: str, accept_encoding: bytes | None) -> StaticFile | None`

##### Parameters

Name
Type
Description

`url_path`
`—`

`accept_encoding`
`—`

##### Returns

`StaticFile (/pounce/api/_static/#StaticFile) | None`

StaticFile if found and valid, None otherwise

`_find_precompressed`

4

`tuple[Path, str | None]`

▼

Find precompressed variant if available and client supports.

Priority: zstd > …

`def _find_precompressed(self, path: Path, mount: StaticMount, accept_encoding: bytes | None, original_stat: os.stat_result) -> tuple[Path, str | None]`

Find precompressed variant if available and client supports.

Priority: zstd > gzip > identity

##### Parameters

Name
Type
Description

`path`
`—`

`mount`
`—`

`accept_encoding`
`—`

`original_stat`
`—`

##### Returns

`tuple[Path, str | None]`

(file_path, encoding) where encoding is "gzip", "zstd", or None

`_validate_precompressed`

2

`bool`

▼

Validate a precompressed variant against the same security checks as the origin…

`def _validate_precompressed(self, path: Path, mount: StaticMount) -> bool`

Validate a precompressed variant against the same security checks as the original.

Checks path traversal, hidden files, and symlinks.

##### Parameters

Name
Type
Description

`path`
`—`

`mount`
`—`

##### Returns

`bool`

True if the precompressed path is safe to serve

`_get_mime_type`

2

`str`

▼

Get MIME type for file.

**Lookup order:**
1. Mount-specific extra_mime_types (…

`def _get_mime_type(self, path: Path, mount: StaticMount | None = None) -> str`

Get MIME type for file.

Lookup order:

- Mount-specific extra_mime_types (user overrides)

- stdlib mimetypes.guess_type()

- _MODERN_MIME_TYPES fallback for modern web extensions

- application/octet-stream

##### Parameters

Name
Type
Description

`path`
`—`

`mount`
`—`

Default:`None`

##### Returns

`str`

MIME type string

`_generate_etag`

3

`str`

▼

Generate ETag from mtime, size, and encoding.

Uses weak ETag (W/) because we u…

`def _generate_etag(self, mtime: float, size: int, encoding: str | None = None) -> str`

Generate ETag from mtime, size, and encoding.

Uses weak ETag (W/) because we use mtime, not content hash.
Encoding is included so that compressed and uncompressed variants
of the same file produce distinct ETags (RFC 7232).

##### Parameters

Name
Type
Description

`mtime`
`—`

`size`
`—`

`encoding`
`—`

Default:`None`

##### Returns

`str`

ETag header value (e.g., W/"5f3c-1a2b" or W/"5f3c-1a2b-gzip")

`_check_not_modified`

2

`bool`

▼

Check if client has cached version (If-None-Match).

`def _check_not_modified(self, headers: list[tuple[bytes, bytes]], file: StaticFile) -> bool`

##### Parameters

Name
Type
Description

`headers`
`—`

`file`
`—`

##### Returns

`bool`

True if client cache is valid (send 304), False otherwise

`_modified_since`

2

`bool`

▼

Whether the file was modified after the supplied instant.

Compares whole secon…

staticmethod

`def _modified_since(file: StaticFile, since: datetime) -> bool`

Whether the file was modified after the supplied instant.

Compares whole seconds: HTTP-dates have one-second resolution, while a
filesystem mtime may carry sub-second precision. Truncating both to
seconds avoids spurious "modified" results from microsecond drift.

##### Parameters

Name
Type
Description

`file`
`—`

`since`
`—`

##### Returns

`bool`

`_if_range_matches`

2

`bool`

▼

Whether an If-Range value matches the current representation.

A range request …

`def _if_range_matches(self, if_range: bytes, file: StaticFile) -> bool`

Whether an If-Range value matches the current representation.

A range request guarded by If-Range serves 206 only on a match; on a
mismatch the full 200 entity is returned (RFC 9110 §13.1.5).

Pounce emits only weak ETags, which MUST NOT be used in an If-Range
comparison. So an ETag-valued If-Range (anything quoted or weak-prefixed)
can never match -> full 200. A date-valued If-Range matches only when
the file has not been modified since that date.

##### Parameters

Name
Type
Description

`if_range`
`—`

`file`
`—`

##### Returns

`bool`

`_parse_range_header`

2

`list[tuple[int, int]] | …`

▼

Parse Range header and return list of (start, end) byte ranges.

Format: "bytes…

`def _parse_range_header(self, range_header: str, file_size: int) -> list[tuple[int, int]] | None | _RangeNotSatisfiable`

Parse Range header and return list of (start, end) byte ranges.

Format: "bytes=0-499" or "bytes=500-999" or "bytes=-500"

Distinguishes three outcomes (RFC 7233):

- Malformed Range (bad syntax,`start > end`) -> `None`; the
caller ignores the header and serves a full 200 response.

- Valid but unsatisfiable (an explicit`start >= file_size`) ->
`_RANGE_NOT_SATISFIABLE` (/pounce/api/_static/#_RANGE_NOT_SATISFIABLE); the caller sends 416.

- Satisfiable -> a coalesced list of (start, end) tuples. An explicit
`end` (/pounce/api/_otel/#_NoOpSpan) past EOF is clamped to `file_size - 1`rather than
rejected. Requests with more than`_MAX_RANGES`parts are treated
as abusive and ignored (`None`-> full 200).

##### Parameters

Name
Type
Description

`range_header`
`—`

`file_size`
`—`

##### Returns

`list[tuple[int, int]] | None | _RangeNotSatisfiable (/pounce/api/_static/#_RangeNotSatisfiable)`

List of (start, end) tuples (inclusive), ``None`` if the header
should be ignored, or the unsatisfiable sentinel.

`_coalesce_ranges`

1

`list[tuple[int, int]]`

▼

Merge overlapping or adjacent byte ranges.

Sorting then merging bounds the wor…

staticmethod

`def _coalesce_ranges(ranges: list[tuple[int, int]]) -> list[tuple[int, int]]`

Merge overlapping or adjacent byte ranges.

Sorting then merging bounds the work (and response size) of a
multi-range request to the size of the file itself.

##### Parameters

Name
Type
Description

`ranges`
`—`

##### Returns

`list[tuple[int, int]]`

Coalesced ranges sorted by start offset.

`_get_header`

2

`bytes | None`

▼

Get header value by name (case-insensitive).

`def _get_header(self, headers: list[tuple[bytes, bytes]], name: bytes) -> bytes | None`

##### Parameters

Name
Type
Description

`headers`
`—`

`name`
`—`

##### Returns

`bytes | None`

Header value as bytes, or None if not found

`_send_304`

2

▼

Send 304 Not Modified response.

async

`async def _send_304(self, file: StaticFile, send: Send) -> None`

##### Parameters

Name
Type
Description

`file`
`—`

`send`
`—`

`_send_416`

2

▼

Send 416 Range Not Satisfiable (RFC 7233 §4.4).

Includes ``Content-Range: byte…

async

`async def _send_416(self, file: StaticFile, send: Send) -> None`

Send 416 Range Not Satisfiable (RFC 7233 §4.4).

Includes`Content-Range: bytes */<size>`so the client learns the
current representation length, plus ETag and Accept-Ranges.

##### Parameters

Name
Type
Description

`file`
`—`

`send`
`—`

`_send_206`

4

▼

Send 206 Partial Content response (RFC 7233).

Single range: Content-Range head…

async

`async def _send_206(self, file: StaticFile, ranges: list[tuple[int, int]], send: Send, *, sendfile_enabled: bool = False) -> None`

Send 206 Partial Content response (RFC 7233).

Single range: Content-Range header with the range body.
Multiple ranges: multipart/byteranges body with MIME boundary.

##### Parameters

Name
Type
Description

`file`
`—`

`ranges`
`—`

`send`
`—`

`sendfile_enabled`
`—`

Default:`False`

`_send_206_single`

4

▼

Send a single-range 206 response.

async

`async def _send_206_single(self, file: StaticFile, range_pair: tuple[int, int], send: Send, *, sendfile_enabled: bool = False) -> None`

##### Parameters

Name
Type
Description

`file`
`—`

`range_pair`
`—`

`send`
`—`

`sendfile_enabled`
`—`

Default:`False`

`_send_206_multipart`

3

▼

Send a multipart/byteranges 206 response (RFC 7233 §4.1).

Each part has its ow…

async

`async def _send_206_multipart(self, file: StaticFile, ranges: list[tuple[int, int]], send: Send) -> None`

Send a multipart/byteranges 206 response (RFC 7233 §4.1).

Each part has its own Content-Type and Content-Range headers,
separated by a MIME boundary. Sendfile is not used here because
the part headers must be interleaved with file data.

##### Parameters

Name
Type
Description

`file`
`—`

`ranges`
`—`

`send`
`—`

`_send_file`

4

▼

Send full file response (200 OK).

async

`async def _send_file(self, file: StaticFile, method: str, send: Send, *, sendfile_enabled: bool = False) -> None`

##### Parameters

Name
Type
Description

`file`
`—`

`method`
`—`

`send`
`—`

`sendfile_enabled`
`—`

Default:`False`

`_send_file_body`

5

▼

Send file body, using protocol-owned zero-copy sendfile when available.

When t…

async

`async def _send_file_body(self, path: Path, offset: int, count: int, send: Send, *, sendfile_enabled: bool = False) -> None`

Send file body, using protocol-owned zero-copy sendfile when available.

When the ASGI scope advertises`pounce.sendfile`, emit a Pounce
extension message describing the file range. The bridge and active
protocol own framing, byte accounting, and socket writes.

Falls back to chunked reads through ASGI send otherwise.

Tiny bodies (`count < _SENDFILE_MIN_SIZE`) always take the
read()+write() fallback even when sendfile is advertised: the
per-response transport detach/re-attach overhead outweighs the
zero-copy benefit below that measured threshold (issue #127).

##### Parameters

Name
Type
Description

`path`
`—`

`offset`
`—`

`count`
`—`

`send`
`—`

`sendfile_enabled`
`—`

Default:`False`

`_send_file_range`

5

▼

Send file range (for 206 responses).

async

`async def _send_file_range(self, path: Path, start: int, count: int, send: Send, *, sendfile_enabled: bool = False) -> None`

##### Parameters

Name
Type
Description

`path`
`—`

`start`
`—`

`count`
`—`

`send`
`—`

`sendfile_enabled`
`—`

Default:`False`

## Functions

`_http_date`

1

`str`

▼

Format an mtime (epoch seconds) as an RFC 9110 IMF-fixdate (GMT).

`def _http_date(mtime: float) -> str`

##### Parameters

Name
Type
Description

`mtime`
`float`

##### Returns

`str`

`_parse_http_date`

1

`datetime | None`

▼

Parse an HTTP-date header value to a timezone-aware UTC datetime.

Returns ``No…

`def _parse_http_date(value: bytes) -> datetime | None`

Parse an HTTP-date header value to a timezone-aware UTC datetime.

Returns`None`for unparseable input so callers can ignore a bad
conditional header and serve the full response (RFC 9110 §13.1.3).

##### Parameters

Name
Type
Description

`value`
`bytes`

##### Returns

`datetime | None`

`create_static_handler`

5

`StaticFiles (/pounce/api/_static/#StaticFiles)`

▼

Create StaticFiles handler from simple dict config.

`def create_static_handler(mounts: dict[str, str], cache_control: str = 'public, max-age=3600', precompressed: bool = True, follow_symlinks: bool = False, index_file: str | None = 'index.html') -> StaticFiles`

##### Parameters

Name
Type
Description

`mounts`
`dict[str, str]`

Dict of {url_path: directory} mappings

`cache_control`
`str`

Cache-Control header value

Default:`'public, max-age=3600'`

`precompressed`
`bool`

Serve .gz/.zst if available

Default:`True`

`follow_symlinks`
`bool`

Allow serving symlinked files

Default:`False`

`index_file`
`str | None`

Filename to serve for directories

Default:`'index.html'`

##### Returns

`StaticFiles (/pounce/api/_static/#StaticFiles)`
