Handler Reference
Handler type hierarchy for Elide server routes.
Every Route has exactly one Handler, and each ServerBlock has a
default handler for unmatched requests. Handlers are open classes that
form a discriminated union via typealias Handler. Pick the concrete
subclass that matches the behavior you need:
StaticFiles — serve files from a local directory
ReverseProxy — forward requests to one or more upstream servers
Redirect — issue an HTTP redirect response
Cgi — execute a program per request via CGI (RFC 3875)
Respond — emit a fixed inline response (useful for health
checks, maintenance pages, and catch-all error routes)
Handlers can also be set directly on a ServerBlock.handler using a
bare status code integer (e.g., 404) as a shorthand for a Respond
with that status.
> This page is auto-generated from the PKL schema. See the guide for usage examples.
Types
HttpStatus
HTTP status code (100–599 inclusive).
typealias HttpStatus = UInt(isBetween(100, 599))CompressionAlgo
Compression algorithm for response bodies. Listed in typical priority order (best ratio first):
"zstd"— Zstandard; best ratio and speed for most workloads"br"— Brotli; excellent for text/HTML but slower to compress"gzip"— universal client support; lowest ratio of the three
typealias CompressionAlgo = "gzip" | "br" | "zstd"LoadBalancingPolicy
Load-balancing policy for distributing requests across a
ReverseProxy upstream pool.
"round_robin"— cycle through upstreams sequentially (respects
Upstream.weight for weighted distribution)
"least_connections"— send to the upstream with fewest active
"random"— uniform random selection"ip_hash"— hash the client IP for sticky sessions"first"— always send to the first healthy upstream"cookie"— sticky sessions via a server-set cookie"consistent_hash"— consistent hashing on request URI; minimizes
typealias LoadBalancingPolicy = "round_robin" | "least_connections" | "random" | "ip_hash" | "first" | "cookie" | "consistent_hash"RedirectStatus
Valid HTTP status codes for redirect responses.
301Moved Permanently — cacheable, method may change to GET302Found — not cached by default, method may change to GET307Temporary Redirect — preserves request method and body308Permanent Redirect — cacheable, preserves method and body
typealias RedirectStatus = UInt(this == 301 || this == 302 || this == 307 || this == 308)CacheControlPreset
Cache-Control header value for static asset responses. Common presets
are offered as literal options, but any valid Cache-Control string is
accepted:
"no-store"— never cache (sensitive data)"no-cache"— cache but always revalidate"private"— cache in the browser only, not CDN/proxy"public, max-age=3600"— cache 1 hour"public, max-age=86400"— cache 1 day"public, max-age=31536000, immutable"— cache 1 year, never
Custom example:
cacheControl = "public, max-age=600, stale-while-revalidate=60"typealias CacheControlPreset = "no-store" | "no-cache" | "private" | "public, max-age=3600" | "public, max-age=86400" | "public, max-age=31536000, immutable" | StringHandler
Union of all valid handler types. Use one of:
StaticFiles, ReverseProxy, Redirect, Cgi, or Respond.
typealias Handler = StaticFiles | ReverseProxy | Redirect | Cgi | Respond---
HeaderOps
Header mutation operations applied to a proxied request or response.
Mutations are applied in order: set, then add, then remove.
Used by ReverseProxy.requestHeaders and ReverseProxy.responseHeaders.
requestHeaders {
set { ["X-Forwarded-Proto"] = "https" }
remove { "Cookie" }
}| Field | Type | Default | Description |
|---|---|---|---|
set | Mapping | (empty) | Headers to set unconditionally. Overwrites any existing value with |
add | Mapping | (empty) | Headers to append. Adds a new header value without removing any |
remove | Listing | (empty) | Header names to remove entirely from the request or response. |
set
Headers to set unconditionally. Overwrites any existing value with the same name.
add
Headers to append. Adds a new header value without removing any existing values for the same name.
---
Upstream
A single upstream server in a ReverseProxy pool.
"backend:8080") can be used
instead of a full Upstream object. Use the object form when you need
to set weight, connection limits, or backup status.
| Field | Type | Default | Description |
|---|---|---|---|
address | String(!isEmpty) | (required) | Upstream address. Accepts host:port or a full URL: |
weight | UInt(isPositive) | 1 | Relative weight for weighted load balancing. Higher values receive |
maxConnections | UInt | 0 | Maximum simultaneous connections to this upstream. 0 (default) |
backup | Boolean | false | Mark this upstream as a backup. Backup upstreams only receive traffic |
address
Upstream address. Accepts host:port or a full URL:
`"backend-1:8080"`
`"http:<<>>
`" https://internal.example.com"`When a scheme is included, TLS is used for https:// targets.
weight
Relative weight for weighted load balancing. Higher values receive
proportionally more traffic. Only meaningful with policies that
support weights (e.g., "round_robin").
maxConnections
Maximum simultaneous connections to this upstream. 0 (default)
means unlimited. Set this to protect upstreams from overload.
backup
Mark this upstream as a backup. Backup upstreams only receive traffic when all primary (non-backup) upstreams are unhealthy.
---
HealthCheck
Active and passive health-check configuration for ReverseProxy
upstream pools.
Active checks send periodic probe requests to each upstream. Passive checks observe real traffic and mark an upstream unhealthy when proxied requests fail. Both can run simultaneously.
healthCheck { path = "/healthz"; interval = 5.s }| Field | Type | Default | Description |
|---|---|---|---|
path | String | "/health" | HTTP path probed by the active health checker. The probe issues a |
interval | Duration | 10.s | Interval between active health-check probes per upstream. |
timeout | Duration | 5.s | Maximum time to wait for a probe response before treating it as a |
unhealthyThreshold | UInt(isPositive) | 3 | Consecutive probe failures required to mark an upstream unhealthy. |
healthyThreshold | UInt(isPositive) | 2 | Consecutive probe successes required to restore an upstream to |
passive | Boolean | true | Enable passive health checks. When true, an upstream is marked |
circuitBreakerThreshold | UInt? | null | Number of passive failures within the observation window that trips |
path
HTTP path probed by the active health checker. The probe issues a
GET request to this path; a 2xx response is considered healthy.
timeout
Maximum time to wait for a probe response before treating it as a failure.
unhealthyThreshold
Consecutive probe failures required to mark an upstream unhealthy. A higher value makes the health checker more tolerant of transient errors but slower to detect real failures.
healthyThreshold
Consecutive probe successes required to restore an upstream to healthy status after it has been marked unhealthy.
passive
Enable passive health checks. When true, an upstream is marked
unhealthy after a proxied request fails without receiving any
response (connection refused, timeout, etc.). Passive checks react
faster than active probes but can be triggered by transient network
issues.
circuitBreakerThreshold
Number of passive failures within the observation window that trips
the circuit breaker, stopping all traffic to the upstream until the
next active probe succeeds. null (default) disables the circuit
breaker.
---
StaticFiles
Open class — can be extended.
Serves files from a local directory.
Static assets are served with ETag and Last-Modified headers for
conditional request support. When preCompress is enabled (the
default), Elide pre-compresses every file at startup and serves the
compressed variant directly, eliminating per-request compression
overhead.
handler = new StaticFiles { root = "./dist"; spaFallback = true }| Field | Type | Default | Description |
|---|---|---|---|
type | String | "static" (fixed) | (fixed) |
root | String(!isEmpty) | (required) | Root directory from which files are resolved. Paths are resolved |
index | String | "index.html" | Primary index file returned when a bare directory path is requested. |
spaFallback | Boolean | false | Serve index for any path that does not resolve to an existing file. |
preCompress | Boolean | true | Pre-compress all files at startup and cache the compressed variants |
compression | Listing | new { "zstd"; "br"; "gzip" } | Compression algorithms to offer. When the client Accept-Encoding |
etag | Boolean | true | Emit ETag headers for conditional request support (If-None-Match). |
rangeRequests | Boolean | true | Accept Range request headers for partial-content responses |
cacheControl | CacheControlPreset? | null | Cache-Control header value sent with every file response. null |
tryFiles | Listing | (empty) | Ordered list of file paths to try for each request (similar to nginx |
indexFiles | Listing | new { "index.html"; "index.htm" } | Index files to look for when a directory path is requested, tried in |
directoryListing | Boolean | false | Enable auto-generated HTML directory listings for paths that resolve |
directoryTemplate | String? | null | Custom HTML template for directory listing pages. null uses the |
autoindexSort | String | "name_asc" | Sort order for directory listings. Accepted values: |
root
Root directory from which files are resolved. Paths are resolved
relative to the working directory of the elide serve process.
root = "./public"
root = "/var/www/html"index
Primary index file returned when a bare directory path is requested.
This value is also used as the SPA fallback target when
spaFallback = true.
spaFallback
Serve index for any path that does not resolve to an existing file.
Required for single-page applications that handle client-side routing
(e.g., React Router, Vue Router). The response carries the original
request URL so the client-side router can match it.
preCompress
Pre-compress all files at startup and cache the compressed variants in memory. Highly recommended for production — eliminates per-request compression overhead at the cost of increased startup time and memory.
compression
Compression algorithms to offer. When the client Accept-Encoding
header supports multiple algorithms, the first match in this list
wins. Order from best ratio to widest support.
etag
Emit ETag headers for conditional request support (If-None-Match).
ETags are computed from file content hashes, enabling 304 Not
Modified responses that save bandwidth.
rangeRequests
Accept Range request headers for partial-content responses
(206 Partial Content). Required for resumable downloads and media
seeking.
cacheControl
Cache-Control header value sent with every file response. null
(default) omits the header, leaving caching behavior to the client
or downstream CDN. See CacheControlPreset for common values.
tryFiles
Ordered list of file paths to try for each request (similar to nginx
try_files). The first path that resolves to an existing file is
served. An empty list (default) disables this behavior.
indexFiles
Index files to look for when a directory path is requested, tried in listed order. The first file found is served.
directoryListing
Enable auto-generated HTML directory listings for paths that resolve
to a directory without a matching index file. When false (default),
such requests return 404.
directoryTemplate
Custom HTML template for directory listing pages. null uses the
built-in template. Ignored when directoryListing = false. The
template receives listing data as template variables.
autoindexSort
Sort order for directory listings. Accepted values:
"name_asc", "name_desc", "size_asc", "size_desc",
"mtime_asc", "mtime_desc".
---
ReverseProxy
Open class — can be extended.
Forwards requests to one or more upstream servers with load balancing, health checking, and optional response caching.
handler = new ReverseProxy {
upstreams { "backend-a:8080"; "backend-b:8080" }
loadBalancing = "least_connections"
healthCheck {}
}| Field | Type | Default | Description | |
|---|---|---|---|---|
type | String | "proxy" (fixed) | (fixed) | |
upstreams | Listing| Upstream>(!isEmpty) | (required) | Upstream server pool. At least one upstream is required. Accepts bare | |
stripPrefix | Boolean | false | Strip the matched route prefix before forwarding to the upstream. | |
loadBalancing | LoadBalancingPolicy | "round_robin" | Load-balancing policy for distributing requests across upstreams. | |
healthCheck | HealthCheck? | null | Active and passive health-check configuration. null (default) | |
timeout | Duration | 30.s | Maximum time to wait for an upstream to begin sending response | |
responseTimeout | Duration | 5.min | Maximum time for the upstream to send the complete response body | |
bufferResponse | Boolean | false | Buffer the full upstream response in memory before sending it to the | |
requestHeaders | HeaderOps | (empty) | Header mutations applied to the outgoing request sent to the | |
responseHeaders | HeaderOps | (empty) | Header mutations applied to the upstream's response before it is | |
websocket | Boolean | true | Enable WebSocket proxying via HTTP Upgrade handling. When true | |
preserveHost | Boolean | true | Preserve the original client Host header when forwarding to the | |
cache | ProxyCache? | null | Response cache for this reverse proxy. When set, cacheable upstream |
upstreams
Upstream server pool. At least one upstream is required. Accepts bare
strings ("host:port") or full Upstream objects for weight and
connection-limit control.
upstreams { "backend:8080" }
upstreams { new Upstream { address = "backend:8080"; weight = 2 } }stripPrefix
Strip the matched route prefix before forwarding to the upstream.
For example, a route matching "/api/**" with stripPrefix = true
forwards /api/users as /users.
loadBalancing
Load-balancing policy for distributing requests across upstreams.
See LoadBalancingPolicy for all options.
healthCheck
Active and passive health-check configuration. null (default)
disables health checks entirely — all upstreams are presumed healthy.
See HealthCheck for probe path, intervals, and circuit-breaker
options.
timeout
Maximum time to wait for an upstream to begin sending response
headers. If this timeout expires, the request fails with 504
Gateway Timeout.
responseTimeout
Maximum time for the upstream to send the complete response body after headers have been received. Protects against slow or stalled upstream responses.
bufferResponse
Buffer the full upstream response in memory before sending it to the client. Enables response body inspection and rewriting via middleware, but increases memory usage proportional to response size.
requestHeaders
Header mutations applied to the outgoing request sent to the
upstream. See HeaderOps for set/add/remove operations.
responseHeaders
Header mutations applied to the upstream's response before it is
forwarded to the client. See HeaderOps for set/add/remove
operations.
websocket
Enable WebSocket proxying via HTTP Upgrade handling. When true
(default), Upgrade: websocket requests are forwarded to the
upstream and the connection is held open bidirectionally.
preserveHost
Preserve the original client Host header when forwarding to the
upstream. When false, the Host header is rewritten to the
upstream's address. Most upstreams expect the original host; set to
false only when the upstream performs its own virtual-host routing.
cache
Response cache for this reverse proxy. When set, cacheable upstream
responses are stored and served from a two-tier (memory + disk)
cache. null (default) disables caching.
See ProxyCache for zone, size limit, and coalescing options.
cache = new ProxyCache {}
cache = new ProxyCache { lock = true }---
Redirect
Open class — can be extended.
Issues an HTTP redirect to a target URL.
handler = new Redirect {
target = "https://new.example.com{path}"
status = 301
}| Field | Type | Default | Description |
|---|---|---|---|
type | String | "redirect" (fixed) | (fixed) |
target | Uri | (required) | Destination URL for the redirect. Supports template variables that |
status | RedirectStatus | 308 | HTTP status code for the redirect response. The default (308) |
target
Destination URL for the redirect. Supports template variables that are expanded from the original request:
{path} — the matched request path (e.g., /old/page)
{query} — the raw query string (e.g., ?foo=bar)
Example: "https://new.example.com{path}{query}"
status
HTTP status code for the redirect response. The default (308)
issues a permanent redirect that preserves the request method.
See RedirectStatus for all options.
---
Cgi
Open class — can be extended.
Executes a program per request using the CGI (Common Gateway Interface) protocol (RFC 3875).
The program receives request metadata via environment variables
(REQUEST_METHOD, PATH_INFO, QUERY_STRING, etc.), reads the
request body from stdin, and writes response headers + body to stdout.
This handler can run any executable: shell scripts, Python, Ruby, PHP,
compiled binaries, or Elide JS/TS scripts (via the interpreter
field).
handler = new Cgi {
script = "./cgi-bin/report.py"
interpreter = "/usr/bin/python3"
timeout = 60.s
}| Field | Type | Default | Description |
|---|---|---|---|
type | String | "cgi" (fixed) | (fixed) |
script | String(!isEmpty) | (required) | Path to the CGI script or executable. Resolved relative to |
interpreter | String? | null | Interpreter used to execute the script (e.g., "/usr/bin/python3", |
env | Mapping | (empty) | Extra environment variables passed to the CGI process, in addition |
timeout | Duration | 30.s | Maximum execution time for the CGI process. If the process is still |
workingDir | String? | null | Working directory for the CGI process. When null (default), the |
script
Path to the CGI script or executable. Resolved relative to
workingDir (or the server's working directory if workingDir is
null).
interpreter
Interpreter used to execute the script (e.g., "/usr/bin/python3",
"/usr/bin/env ruby"). When null, the script is executed directly
and must be executable or have a shebang (#!/...) line.
env
Extra environment variables passed to the CGI process, in addition to the standard CGI variables. Useful for passing configuration or secrets without command-line arguments.
timeout
Maximum execution time for the CGI process. If the process is still
running after this duration, it is killed with SIGKILL and the
client receives a 504 Gateway Timeout response.
workingDir
Working directory for the CGI process. When null (default), the
server's own working directory is used.
---
Respond
Open class — can be extended.
Emits a fixed inline HTTP response without invoking any upstream or reading from disk. Useful for health-check endpoints, maintenance pages, stub APIs, and catch-all error routes.
handler = new Respond {
status = 503
contentType = "text/html; charset=utf-8"
body = "<h1>Down for maintenance</h1>"
headers { ["Retry-After"] = "3600" }
}| Field | Type | Default | Description |
|---|---|---|---|
type | String | "respond" (fixed) | (fixed) |
status | HttpStatus | 200 | HTTP status code for the response. |
body | String? | null | Response body text. null sends an empty body (content-length 0). |
contentType | String | "text/plain; charset=utf-8" | Value of the Content-Type response header. |
headers | Mapping | (empty) | Additional response headers. These are sent alongside the standard |
headers
Additional response headers. These are sent alongside the standard
headers (Content-Type, Content-Length).
---
ProxyCache
Open class — can be extended.
Response cache for a ReverseProxy handler.
Cacheable upstream responses are stored in a two-tier cache (memory hot tier + disk cold tier). Subsequent requests for the same resource are served from cache without contacting the upstream.
All fields have sensible defaults —cache = new ProxyCache {} enables
caching that works for most workloads. Cache keys are derived from the
request method, URI, and Vary-selected headers.
| Field | Type | Default | Description |
|---|---|---|---|
zone | String | "default" | Cache zone name. Used as the shard subdirectory on disk under path. |
path | String | "/var/cache/elide/proxy" | Base directory for on-disk cache files. Must be writable by the |
maxMemory | DataSize | 64.mb | Maximum size of the in-memory hot-tier cache. Frequently accessed |
maxDisk | DataSize | 512.mb | Maximum size of the on-disk cold-tier cache. Entries evicted from |
maxBodySize | DataSize | 10.mb | Maximum individual response body size to cache. Responses larger |
maxTtl | Duration | 0.s | Maximum TTL for cached entries, regardless of upstream |
forceCache | Boolean | false | Cache responses even when they lack explicit Cache-Control or |
lock | Boolean | false | Enable request coalescing (cache lock) to prevent thundering-herd |
lockTimeout | Duration | 5.s | Maximum time a coalesced request will wait for the first request to |
zone
Cache zone name. Used as the shard subdirectory on disk under path.
Use distinct zone names when multiple ReverseProxy handlers share
the same path directory.
path
Base directory for on-disk cache files. Must be writable by the server process.
maxMemory
Maximum size of the in-memory hot-tier cache. Frequently accessed entries are promoted here for sub-millisecond serving.
maxDisk
Maximum size of the on-disk cold-tier cache. Entries evicted from memory are written to disk. When this limit is reached, the least recently used entries are evicted.
maxBodySize
Maximum individual response body size to cache. Responses larger than this threshold are streamed directly to the client and bypass the cache entirely.
maxTtl
Maximum TTL for cached entries, regardless of upstream
Cache-Control directives. 0.s (default) means the cache honors
the upstream's TTL without imposing a cap.
forceCache
Cache responses even when they lack explicit Cache-Control or
Expires headers. Such responses are cached with a default TTL of
60 seconds. Useful for upstreams that omit cache headers but serve
cacheable content.
lock
Enable request coalescing (cache lock) to prevent thundering-herd stampedes. When enabled, concurrent requests for the same uncached resource are collapsed — only the first request is forwarded upstream and the remaining callers wait for its result.
lockTimeout
Maximum time a coalesced request will wait for the first request to
complete. If the lock is not released within this duration, the
waiting request is forwarded upstream independently. Only effective
when lock = true.
---