zena:http
import {…} from 'zena:http';
HTTP server and middleware library for Zena.
Provides an Express-like, Zipadee-inspired HTTP framework targeting WebAssembly GC
components implementing wasi:http.
Classes
App
class App
An HTTP application managing a middleware pipeline.
Examples ​
import { App } from 'zena:http';
export async function main(): Future<void> {
let app = new App();
app.use(async (req, res, next) => {
res.body = 'Hello, world!';
});
let server = await app.listen(8080);
console.log(`Listening on http://127.0.0.1:${server.port}`);
await server.done;
}
new(options: AppOptions | null = null)
use(middleware: Middleware): void
Adds a middleware to the application pipeline.
async handleRequest(req: Own<Request>, res: Response): Future<void>
Directly dispatches a Request and Response through the application pipeline.
Catches thrown HttpError instances and translates them to HTTP status codes
and responses. Any uncaught general Error results in a 500 Internal Server Error.
handle(wireRequest: Own<WireRequest>): Future<Result<Own<WireResponse>, ErrorCode>>
Dispatches a WASI wasi:http/types request through the application
pipeline and returns the wire response.
async listen(port: u16 = as u16): Future<Own<Server>>
Binds an HTTP server on 127.0.0.1:{port} (or an ephemeral port when
port is 0) and starts the accept loop in the background.
Returns a future of the server's owner, which resolves once the
listener is bound, or rejects if binding fails (such as when port
is already in use). The owner closes the server when it leaves
scope: hold it for as long as the server should run (await server.done), or call keepOpen() to run it until close().
Cancelling the enclosing CancelScope closes the server and releases
its socket.
AppOptions
class AppOptions
Configuration options for the HTTP App.
var dev: boolean
If true, include error details in responses when errors are thrown.
WireRequest
resource class Request extends Resource
WIT resource request.
new(handle: i32)
static new_(headers: Own<Fields>, contents: Option<Stream<u8>>, trailers: Future<Result<Option<Own<Fields>>, ErrorCode>>, options: Option<Own<RequestOptions>>): (Own<Request>, Future<Result<void, ErrorCode>>)
getMethod(): Method
setMethod(method: Method): boolean
getPathWithQuery(): Option<String>
setPathWithQuery(pathWithQuery: Option<String>): boolean
getScheme(): Option<Scheme>
setScheme(scheme: Option<Scheme>): boolean
getAuthority(): Option<String>
setAuthority(authority: Option<String>): boolean
getOptions(): Option<Own<RequestOptions>>
getHeaders(): Own<Fields>
static consumeBody(this_: Own<Request>, res: Future<Result<void, ErrorCode>>): (Stream<u8>, Future<Result<Option<Own<Fields>>, ErrorCode>>)
WireResponse
resource class Response extends Resource
WIT resource response.
new(handle: i32)
static new_(headers: Own<Fields>, contents: Option<Stream<u8>>, trailers: Future<Result<Option<Own<Fields>>, ErrorCode>>): (Own<Response>, Future<Result<void, ErrorCode>>)
getStatusCode(): u16
setStatusCode(statusCode: u16): boolean
getHeaders(): Own<Fields>
static consumeBody(this_: Own<Response>, res: Future<Result<void, ErrorCode>>): (Stream<u8>, Future<Result<Option<Own<Fields>>, ErrorCode>>)
ErrorCode
sealed class ErrorCode
WIT variant error-code.
case ErrorCodeDNSTimeout
case ErrorCodeDNSError(value: DNSErrorPayload)
case ErrorCodeDestinationNotFound
case ErrorCodeDestinationIPProhibited
case ErrorCodeDestinationIPUnroutable
case ErrorCodeConnectionRefused
case ErrorCodeConnectionTerminated
case ErrorCodeConnectionTimeout
case ErrorCodeConnectionReadTimeout
case ErrorCodeConnectionWriteTimeout
case ErrorCodeConnectionLimitReached
case ErrorCodeTLSProtocolError
case ErrorCodeTLSCertificateError
case ErrorCodeTLSAlertReceived(value: TLSAlertReceivedPayload)
case ErrorCodeHTTPRequestDenied
case ErrorCodeHTTPRequestLengthRequired
case ErrorCodeHTTPRequestBodySize(value: Option<u64>)
case ErrorCodeHTTPRequestMethodInvalid
case ErrorCodeHTTPRequestURIInvalid
case ErrorCodeHTTPRequestURITooLong
case ErrorCodeHTTPRequestHeaderSectionSize(value: Option<u32>)
case ErrorCodeHTTPRequestHeaderSize(value: Option<FieldSizePayload>)
case ErrorCodeHTTPRequestTrailerSectionSize(value: Option<u32>)
case ErrorCodeHTTPRequestTrailerSize(value: FieldSizePayload)
case ErrorCodeHTTPResponseIncomplete
case ErrorCodeHTTPResponseHeaderSectionSize(value: Option<u32>)
case ErrorCodeHTTPResponseHeaderSize(value: FieldSizePayload)
case ErrorCodeHTTPResponseBodySize(value: Option<u64>)
case ErrorCodeHTTPResponseTrailerSectionSize(value: Option<u32>)
case ErrorCodeHTTPResponseTrailerSize(value: FieldSizePayload)
case ErrorCodeHTTPResponseTransferCoding(value: Option<String>)
case ErrorCodeHTTPResponseContentCoding(value: Option<String>)
case ErrorCodeHTTPResponseTimeout
case ErrorCodeHTTPUpgradeFailed
case ErrorCodeHTTPProtocolError
case ErrorCodeLoopDetected
case ErrorCodeConfigurationError
case ErrorCodeInternalError(value: Option<String>)
Fields
resource class Fields extends Resource
WIT resource fields.
new(handle: i32)
static create(): Own<Fields>
static fromList(entries: Array<(String, FixedArray<u8>)>): inline Result<Own<Fields>, HeaderError>
get(name: String): Array<FixedArray<u8>>
has(name: String): boolean
set(name: String, value: Array<FixedArray<u8>>): inline Result<void, HeaderError>
delete(name: String): inline Result<void, HeaderError>
getAndDelete(name: String): inline Result<Array<FixedArray<u8>>, HeaderError>
append(name: String, value: FixedArray<u8>): inline Result<void, HeaderError>
copyAll(): Array<(String, FixedArray<u8>)>
clone(): Own<Fields>
Request
resource class Request extends Resource
Represents an incoming HTTP request.
Provides typed access to the method, URL, headers, and body stream,
as well as a mutable path property used by routers and prefix mounting.
A resource class, held as Own<Request>: a handler owns the request
it is given and hands it on with next(req), so whoever holds it last
reads or releases its body. Reading the body consumes the request
(text, bytes, body); read anything else you need from it first.
A request dropped with its body unread releases the wire request,
and with it the body the host holds.
new(url: URL, method: String = 'GET', headers: HashMap<String, GrowableArray<String>> = new HashMap<String, GrowableArray<String>>(), bodyStream: Stream<u8> | null = null)
Constructs a Request with explicit parameters.
new fromUrl(urlString: String, method: String = 'GET', headers: Map<String, String> | null = null, bodyStream: Stream<u8> | null = null)
Constructs a Request from a string URL and optional headers.
new fromWire(wire: Own<WireRequest>)
Constructs a Request over an incoming wasi:http request, which it
reads from as its members are asked for.
var bodyLimit: i64
The most bytes text() and bytes() read of the body, or -1 for no
limit. A body over it is refused with an HttpError of 413, which
App answers: one whose Content-Length is over it before anything
is read, and one that does not say once it passes it. A server sets
it before its handlers read, and a handler that takes more raises it.
method: String { get; }
The HTTP method in uppercase ('GET', 'POST', 'PUT', etc.).
url: URL { get; }
The request URL.
path: String { get; set; }
The request path, defaults to url.pathname or '/'.
Middleware like mount() or routers can rewrite path to represent
the current subpath within a mounted handler hierarchy.
origin: String { get; }
The origin of the request URL (e.g. "https://example.com:8080").
protocol: String { get; }
The protocol of the request URL without the trailing colon (e.g. "http", "https").
host: String { get; }
The Host of the request.
query: String { get; }
The query string including the leading '?', or empty if none.
getHeader(name: String): String | null
Returns the first value of the header with the given name, or null if absent.
getHeaders(name: String): Array<String>
Returns all values of the header with the given name.
cookie(name: String): String | null
The value of the cookie name, or null when the request has none.
Reads every Cookie header, since HTTP/2 lets a browser send one per
cookie (parseCookies).
hasHeader(name: String): boolean
Returns true if the header with the given name is present.
headerNames(): Array<String>
Returns all header names present in the request.
body(): Stream<u8>
The body stream. Consumes the request.
text(): Future<String>
Reads and decodes the full request body as text, within
bodyLimit. Consumes the request.
bytes(): Future<FixedArray<u8>>
Reads the full request body as bytes, within bodyLimit. Consumes
the request.
Response
class Response
Represents an HTTP response being built by server middleware.
Defaults to status code 404 until a body or status code is set.
Setting body automatically updates statusCode to 200 if it was not
explicitly set beforehand.
hasCustomHeaders: boolean { get; }
True if custom headers (other than the fast-path content-type) have been set.
contentTypeHeader: String | null { get; }
Fast-path content-type header value if set without custom multi-value headers.
statusCode: i32 { get; set; }
The HTTP status code.
statusCodeSet: boolean { get; }
Whether the status code was explicitly set.
body: ResponseBody { get; set; }
The response body.
type: String | null { get; set; }
The Content-Type header.
length: Option<i32> { get; set; }
The Content-Length header as an integer, or none if absent.
setHeader(name: String, value: String): void
Sets a response header, replacing any previous values for this header.
appendHeader(name: String, value: String): void
Appends a value to a response header without removing previous values.
setCookie(name: String, value: String, options: CookieOptions = {}): void
Sets the cookie name to value, with the attributes options gives,
in a Set-Cookie header of its own (serializeCookie).
clearCookie(name: String, path: String = '/', domain: String | null = null): void
Tells the browser to drop the cookie name: an empty value that
expires at once. path and domain must be the ones it was set with,
or the browser keeps it.
getHeader(name: String): String | null
Returns the first value of a response header, or null if absent.
getHeaders(name: String): Array<String>
Returns all values of a response header.
hasHeader(name: String): boolean
Returns true if the response header is present.
removeHeader(name: String): void
Removes a response header.
headerNames(): Array<String>
Returns all header names currently set on this response.
forEachHeader(fn: (name: String, value: String) => void): void
Calls fn(name, value) for each custom header entry on this response.
setLength(value: i32): void
Sets Content-Length header.
redirect(url: String, alt: String = '/'): void
Performs an HTTP redirect.
Sets the Location header to url. If the current status code is not
a 3xx redirect status (300-399), it defaults to 302.
HttpError
class HttpError extends Error
An Error class for HTTP errors that includes a status code and an optional private message for server-side logging.
new(message: String | null = null)
Router
class Router
A router that matches requests by HTTP method and URL path pattern.
Supports named parameters (:id) and wildcards (*).
Examples ​
let router = new Router();
router.get('/users', async (req, res, next, params) => {
res.body = 'Users list';
});
router.get('/users/:id', async (req, res, next, params) => {
let id = params['id'] ?? 'unknown';
res.body = 'User ' + id;
});
app.use(router.routes());
all(pattern: String, handler: RouteHandler): void
Registers a route for any HTTP method.
get(pattern: String, handler: RouteHandler): void
Registers a GET route.
post(pattern: String, handler: RouteHandler): void
Registers a POST route.
put(pattern: String, handler: RouteHandler): void
Registers a PUT route.
delete(pattern: String, handler: RouteHandler): void
Registers a DELETE route.
patch(pattern: String, handler: RouteHandler): void
Registers a PATCH route.
routes(): Middleware
Returns a Middleware function that dispatches matching routes.
Server
resource class Server extends Resource
A running HTTP server.
A resource class, held as Own<Server>: its owner closes it when it
leaves scope, so the server runs for as long as something holds it.
await server.done holds it until it closes. keepOpen() trades the
owner for a handle that keeps it running until close(), which an
ordinary object can store.
new(app: App, port: u16)
Binds the listener; start runs the accept loop.
port: u16 { get; }
The port this server is listening on.
isOpen: boolean { get; }
True if the server is still open.
done: Future<void> { get; }
Future that completes when the server is closed or stops running.
isDone: boolean { get; }
True if the server has finished running and is closed.
close(): void
Closes the server and releases its socket.
keepOpen(): Unmanaged<Server>
Keeps the server running after its owner's scope ends: the handle
this returns never closes it implicitly, and an ordinary class or
record can store it. The server then runs until close(), or until
the scope it was started in is cancelled.
start(): void
Runs the accept loop in the background, once.
HTMLPartial
final class HTMLPartial implements AsyncIterable<String>
Represents a partial HTML template result that can be rendered with interpolated values. It supports indentation, nested templates, iterables of values, futures, and HTML escaping of values.
The toString() method returns the rendered HTML as a string, propagating
the indentation level. If the template contains a Future, toString()
throws AsyncInSyncIteration; consume async templates with for await,
collectResult, pipeToStream, or htmlStream.
To support streaming HTTP and hybrid sync/async iteration, HTMLPartial is
an AsyncIterable<String> whose iterator yields Ready steps synchronously
for string chunks and Pending steps only when awaiting a Future.
HTMLIterator
final class HTMLIterator implements AsyncIterator<String>
Hybrid sync/async iterator over an HTMLPartial that yields AsyncStep<String>:
Ready (1, chunk, _) synchronously for strings, Pending (2, _, future)
when awaiting an interpolated Future<TemplateValue>, and Done (0, _, _)
when exhausted.
UnsafeHTML
final class UnsafeHTML
Represents a value that should be rendered as raw HTML without escaping.
Type aliases
ResponseBody
type ResponseBody = String | FixedArray<u8> | Stream<u8> | HTMLPartial | null
Valid body representations for a response.
Middleware
type Middleware = (req: Own<Request>, res: Response, next: NextFunction) => Future<void>
A middleware function handling an HTTP request and response pair.
The middleware owns the request: it hands it on with next(req), or
answers itself and drops it. After next(req) the request is the
downstream middleware's, so read what you need from it first; a
second next(req), or any use of req after it, is a compile error.
NextFunction
type NextFunction = (req: Own<Request>) => Future<void>
A callback function invoked by middleware to pass control, and the request, to the downstream middleware.
RouteHandler
type RouteHandler = (req: Own<Request>, res: Response, next: NextFunction, params: Map<String, String>) => Future<void>
Route handler callback receiving the request, response, downstream next function,
and extracted path parameters. The handler owns the request, as a
middleware does: next(req) hands it to the next matching route.
SendOptions
type SendOptions = {root?: String, index?: String | null, maxAge?: Duration, immutable?: boolean, hidden?: boolean, brotli?: boolean, gzip?: boolean, format?: boolean, extensions?: Array<String>, notFound?: String, setHeaders?: (res: Response, path: String, stat: FileStat) => void}
Options for send and serve. Every field is optional.
CookieOptions
type CookieOptions = {maxAge?: Duration, path?: String, domain?: String, secure?: boolean, httpOnly?: boolean, sameSite?: String}
A cookie's attributes, for serializeCookie and Response.setCookie.
CorsOptions
type CorsOptions = {origin?: Origin, allowMethods?: String | Iterable<String> | null, allowHeaders?: String | Iterable<String>, secureContext?: boolean, exposeHeaders?: String | Iterable<String>, maxAge?: Duration, credentials?: boolean, credentialsFor?: CredentialsFn}
Options for cors. Every field is optional.
Origin
type Origin = String | null | OriginFn
An origin specification: a static origin string, null to disable CORS,
or a per-request callback.
OriginFn
type OriginFn = (req: Borrow<Request>) => String | null | Future<String | null>
Callback returning the allowed origin for a request, or null to
disable CORS for that request. May return synchronously or as a
Future. It borrows the request, so an async callback reads it
before its first await.
CredentialsFn
type CredentialsFn = SyncCredentialsFn | AsyncCredentialsFn
Callback deciding whether credentials are allowed for a request.
SyncCredentialsFn
type SyncCredentialsFn = (req: Borrow<Request>) => boolean
Synchronous callback deciding whether credentials are allowed for a request.
AsyncCredentialsFn
type AsyncCredentialsFn = (req: Borrow<Request>) => Future<boolean>
Asynchronous callback deciding whether credentials are allowed for a request.
TemplateValue
type TemplateValue = String | HTMLPartial | UnsafeHTML | null | Iterable<TemplateValue> | Future<TemplateValue>
Values that may be interpolated into an html template.
PipeOptions
type PipeOptions = {flushSize?: i32}
Options for pipeToStream.
Functions
fromWireRequest
function fromWireRequest(wire: Own<WireRequest>): Own<Request>
Creates a Request from a low-level WASI HTTP Request resource.
toWireResponse
function toWireResponse(res: Response): Own<WireResponse>
Converts a Response into a low-level WASI WireResponse resource.
handleWireRequest
async function handleWireRequest(app: App, wire: Own<WireRequest>): Future<Result<Own<WireResponse>, ErrorCode>>
Dispatches an incoming WASI wire request through an App's middleware pipeline.
textResponse
function textResponse(body: String, status: i32 = 200, contentType: String = 'text/plain; charset=utf-8'): Own<WireResponse>
Builds a low-level wire response carrying body as text.
requestText
async function requestText(request: Own<WireRequest>): Future<String>
Reads a wire request's body to the end and decodes it as text.
bytesStream
function bytesStream(bytes: FixedArray<u8>): Stream<u8>
A byte stream carrying bytes, then end-of-stream: a response or
request body, or a file's new contents, built from bytes in hand.
compose
function compose(middleware: Array<Middleware>): Middleware
Composes an array of middleware functions into a single middleware.
Middleware executes in the order given, unwinding in reverse order as downstream futures resolve (the onion model).
mount
function mount(prefix: String, middleware: Middleware): Middleware
Mounts middleware at a URL path prefix.
If req.path starts with prefix, req.path is rewritten to the matched subpath
before middleware runs, and restored when it passes the request on with next(req).
If req.path does not start with prefix, middleware is bypassed and next(req) is called.
matchPath
function matchPath(prefix: String, path: String): String | null
Checks if path starts with prefix and returns the subpath.
Returns null if path does not start with prefix.
Examples ​
matchPath('/prefix', '/prefix') // => '/'
matchPath('/prefix/', '/prefix') // => null
matchPath('/prefix', '/prefix/users') // => '/users'
matchPath('/prefix/', '/prefix/users')// => '/users'
matchPath('/prefix', '/other') // => null
statusText
function statusText(status: i32): String
Standard HTTP status code text lookup.
send
function send(req: Borrow<Request>, res: Response, path: String, options: SendOptions = {}): Future<String | null>
The directory files are served from. The working directory if
absent. serve takes the root as its own argument and ignores
this one.
The file to serve for a path naming a directory. None if absent
for send; index.html if absent for serve. Null turns it off.
Cache-Control max-age. Zero if absent.
Add immutable to Cache-Control: the file at this URL never
changes. Off if absent.
Serve paths with a segment that starts with a dot. Off if absent: such paths are passed over, as if missing.
Serve <file>.br with Content-Encoding: br when it exists and
the client accepts brotli. On if absent.
Serve <file>.gz likewise, for gzip. On if absent.
Serve the index file for a directory named without a trailing slash, as well as with one. On if absent.
Extensions to try, in order, for a path whose last segment has no
extension: ['html'] serves /about from about.html. None if
absent.
The file under root to serve with status 404 when serve finds
no matching file and next leaves the response unhandled. Ignored
by send.
Called with the response, the file's path and its metadata before the default headers are set, so headers it sets win.
Sends the file at path, relative to options.root, as the
response: sets the status, Content-Type, Content-Length,
Last-Modified, ETag, Cache-Control and
X-Content-Type-Options, and the file as the body. Answers a
matching If-None-Match with 304 and a HEAD request without a body.
Returns the path served, relative to the preopen it is under, or
null when nothing was sent: a hidden path, or a directory with no
index to serve. A missing file throws HttpError(404), and a path
that climbs out of the root throws HttpError(403).
Examples ​
app.use(async (req, res, next) => {
if (req.path == '/hello') {
await send(req, res, 'hello.html', {root: 'pages'});
} else {
await next(req);
}
});
serve
function serve(root: String, options: SendOptions = {}): Middleware
Middleware that serves the files under root for GET and HEAD
requests, at req.path (so under mount, at the path below the
mount point). A request it does not answer — another method, a
missing or hidden file — goes on to next. When options.notFound
is set and next leaves a GET or HEAD request unhandled, serves that
file under root with status 404.
Examples ​
let app = new App();
app.use(serve('public', {notFound: '404.html'}));
app.use(mount('/assets', serve('dist', {maxAge: milliseconds(86400000)})));
httpDate
function httpDate(epochSeconds: i64): String
An HTTP date (RFC 9110 IMF-fixdate) for seconds since the Unix
epoch: Tue, 13 Oct 2026 15:16:23 GMT. Times before the epoch are
given as the epoch.
mimeType
function mimeType(path: String): String | null
The Content-Type for a file, from its extension (case-insensitive),
or null for an extension not listed. Text types carry
charset=utf-8.
Examples ​
mimeType('index.html') // 'text/html; charset=utf-8'
mimeType('logo.SVG') // 'image/svg+xml'
mimeType('archive.xyz') // null
parseCookies
function parseCookies(header: String | null): HashMap<String, String>
How long the browser keeps the cookie, in whole seconds (Max-Age).
Absent: until the browser's session ends.
The paths the browser sends it for (Path).
The host and subdomains it is sent to (Domain). Absent: the origin's host only.
Sent only over HTTPS (Secure).
Hidden from the page's script (HttpOnly).
Strict, Lax or None (SameSite).
The cookies in a Cookie header, by name. A value in double quotes
loses them; values are otherwise as sent, with no decoding. When a name
comes twice, the first wins: browsers send the cookie with the longest
path first. Pairs without = are skipped.
serializeCookie
function serializeCookie(name: String, value: String, options: CookieOptions = {}): String
A Set-Cookie header's value: name=value and the attributes options
gives. Throws when name is not a token, value has a byte a cookie
may not, or an attribute would break the header, so that nothing a
caller passes can add an attribute of its own.
cors
function cors(options: CorsOptions = {}): Middleware
Middleware that sets Cross-Origin Resource Sharing (CORS) headers on
responses and answers preflight OPTIONS requests.
Examples ​
let app = new App();
app.use(cors());
html
function html(strings: TemplateStringsArray, values: FixedArray<TemplateValue>): HTMLPartial
A template tag function that accepts an HTML string.
Interpolated values are escaped by default so that they are not interpreted
as HTML. To interpolate a value as raw HTML, use unsafeHTML.
unsafeHTML
function unsafeHTML(s: String): UnsafeHTML
Wraps a string in an UnsafeHTML object, which indicates that the
string should be rendered as raw HTML without escaping.
escape
function escape(str: String): String
Replaces characters which have special meaning in HTML (&<>"') with
escaped HTML entities (&, <, >, ", ').
pipeToStream
async function pipeToStream(partial: HTMLPartial, writer: StreamWriter<u8>, options: PipeOptions = {}): Future<void>
Writes an HTMLPartial to a StreamWriter<u8>.
If flushSize is positive, small string chunks are buffered until at least
flushSize bytes have accumulated, at which point they are written to the
stream. Buffered chunks are always flushed before awaiting a Future, so
the reader receives the HTML prefix immediately while async work is pending.
Does not call writer.close() when done, matching Zipadee's pipeToWritable.
htmlStream
function htmlStream(partial: HTMLPartial, flushSize: i32 = 1024): Stream<u8>
Creates a Stream<u8> that streams the rendered bytes of partial.
Uses a default flushSize of 1024 bytes (slightly below typical Ethernet
MTU) and always flushes before awaiting any embedded Future.
collectResult
async function collectResult(result: AsyncIterable<String>): Future<String>
Joins an AsyncIterable<String> (such as an HTMLPartial) into a single
String, awaiting any pending chunks.