# Static File Serving

URL: /pounce/docs/features/static-files/
Section: features
Description: Chunked file serving with pre-compression and ETags

---

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

Pounce provides **high-performance static file serving** with pre-compressed file support, ETags, range requests, and automatic content negotiation.

## Quick Start

```python
from pounce import ServerConfig

config = ServerConfig(
    static_files={
        "/static": "./public",      # Serve ./public at /static
        "/assets": "./dist/assets", # Multiple mappings supported
    },
    static_precompressed=True,      # Serve .gz/.zst if available
    static_cache_control="public, max-age=3600",
)
```

Access files at:
- `http://localhost:8000/static/style.css` → `./public/style.css`
- `http://localhost:8000/assets/app.js` → `./dist/assets/app.js`

## Features

### Pre-Compressed Files
Automatically serves `.zst` or `.gz` files when available and the client accepts them:
- `style.css.zst` served for zstd-supporting clients
- `app.js.gz` served for gzip-only clients
- Falls back to uncompressed file

### ETag Support
Generates ETags based on file mtime and size for efficient caching:
- Client sends `If-None-Match` header
- Server responds with `304 Not Modified` if ETag matches
- Saves bandwidth and reduces server load

### Range Requests
Full support for byte-range requests (RFC 7233):
- Video streaming
- Resume downloads
- Seeking in audio files

## Configuration

### static_files
Map URL paths to filesystem directories:

```python
config = ServerConfig(
    static_files={
        "/": "./public",              # Root serving
        "/static": "./assets",        # Nested path
        "/downloads": "/var/files",   # Absolute paths OK
    }
)
```

### static_precompressed
Enable pre-compressed file serving:

```python
config = ServerConfig(
    static_precompressed=True,  # Default: True
)
```

Pounce checks for these variants in order:
1. `.zst` (zstd) — fast decompression, good compression
2. `.gz` (gzip) — universal compatibility
3. Original file

### static_cache_control
Set Cache-Control header for static files:

```python
config = ServerConfig(
    static_cache_control="public, max-age=86400",  # 24 hours
)
```

Recommendations:
- **Immutable assets**: `public, max-age=31536000, immutable`
- **Frequently updated**: `public, max-age=3600` (1 hour)
- **Private content**: `private, max-age=0, must-revalidate`

### static_index_file
Serve index files for directory requests:

```python
config = ServerConfig(
    static_index_file="index.html",  # Default
)
```

Makes `/` serve `./public/index.html` automatically.

### static_follow_symlinks
Control symlink following:

```python
config = ServerConfig(
    static_follow_symlinks=False,  # Default: False (security)
)
```

**Security:** Keep disabled in production to prevent directory traversal attacks.

## Pre-Compression Build Step

Pre-compress files to avoid runtime CPU overhead:

```bash
find ./public -type f \( -name "*.html" -o -name "*.css" -o -name "*.js" \) -exec zstd -k {} \;
find ./public -type f \( -name "*.html" -o -name "*.css" -o -name "*.js" \) -exec gzip -k {} \;
```

## Security

### Path Traversal Protection
Pounce validates all paths to prevent `../` attacks:
- Requests like `/static/../../../etc/passwd` are rejected
- Normalized paths checked against allowed directories

### Symlink Security
Symlinks disabled by default (`static_follow_symlinks=False`):
- Prevents escaping static directory via symlinks
- Enable only if you control all symlink targets

### MIME Type Safety
Correct Content-Type headers prevent XSS:
- `.html` → `text/html`
- `.js` → `application/javascript`
- `.css` → `text/css`
- Unknown → `application/octet-stream`

## See Also

- [Compression](/docs/deployment/compression/) — Content encoding
- [Production Deployment](/docs/deployment/production/) — CDN integration
- [Server Configuration](/docs/configuration/server-config/) — All config options
