API reference

HTTP server

16/16Coverage15/16Strict (zero caveats)

Two server APIs. http.createServer((req, res) => …) is the real Node.js shape — Node's own http server layers (_http_common/_http_incoming/_http_outgoing/_http_server) ported to TypeScript over net and a native HTTP/1 parser with llhttp's strict rules, so a program written against it runs unchanged under Node. Connections are ordinary callbacks on the single event loop; an async handler awaits without blocking other connections. The bespoke model where a handler returns a { status, body, headers } object is this project's own re-imagining, reachable only via the explicitly-non-Node klain:http specifier (`import http from 'klain:http'`) — it is not on the Node http namespace and keeps its fiber-per-connection engine. Everything below the two createServer rows is the klain:http model.

Exact Works, with caveats Not supported

http.createServer((req, res) => …)

Exact
http.createServer((req: IncomingMessage, res: ServerResponse) => void).listen(port, cb?)

The real Node.js server shape — a program written against it runs unchanged under Node. req is an IncomingMessage (a Readable: 'data'/'end', pipe, for await, headers/rawHeaders/trailers); res is a ServerResponse (an OutgoingMessage) that streams to the socket. Node's _http_server layering in TypeScript over net's Socket and a native HTTP/1 parser with llhttp's strict rules and HPE_* error codes.

Returns

Server — The server handle; .listen() starts it.

http.createServer((req: IncomingMessage, res: ServerResponse) => {
  res.setHeader("X-Powered-By", "KlainMainLang")
  res.writeHead(200, { "Content-Type": "text/plain" })
  res.end("Hello, world")
}).listen(8080)
See it in a full example →
⚠ Differences from standard
  • res surface: writeHead/setHeader/appendHeader/getHeader(s)/removeHeader/hasHeader/write/end/flushHeaders/writeContinue, statusCode/statusMessage (default reason phrase from http.STATUS_CODES). The body is chunked or Content-Length as Node decides; res.write returns false past highWaterMark and 'drain' follows, over HTTP and HTTPS alike.
  • Keep-alive and pipelining, the Date header, and requireHostHeader's 400 behave as in Node; an async handler is an ordinary callback — the server keeps serving while it awaits.
  • Several http and https servers run on different ports in one program, all on the same event loop.

const server = http.createServer(cb?)

Works, with caveats
server.listen(port?|{ port, host, backlog }?, cb?); server.close(cb?); server.address(); server.on('request'/'upgrade', cb)

The standard Node idiom: bind to a variable, .listen(0) for an ephemeral port, read .address().port, .close() from inside a callback. listen is net.Server.listen — port, host, backlog, an options object or a Unix-socket path; the default bind is dual-stack ::. The chained form const server = http.createServer(cb).listen(0, readyCb) also works.

const server = http.createServer((req, res) => {
  res.writeHead(200); res.end("hi")
})
server.listen(0, () => {
  const port: number = server.address().port
  setTimeout(() => server.close(), 100)
})
See it in a full example →
⚠ Differences from standard
  • listen returns at once; 'listening' (and the ready callback) and a bind 'error' (EADDRINUSE/EACCES) fire on the next tick, as in Node — with no 'error' listener the error is thrown from there.
  • Events: 'request', 'connection' (a net.Socket), 'upgrade' ((req, socket, head)), 'connect', 'checkContinue', 'checkExpectation', 'clientError', 'dropRequest', 'listening', 'error', 'close'. Without a 'clientError' listener the server answers Node's 400/408/431 and closes; a truncated request reports Node's HPE_INVALID_EOF_STATE, an oversized header block HPE_HEADER_OVERFLOW.
  • close(cb)/closeAllConnections()/closeIdleConnections()/setTimeout; options requestTimeout/headersTimeout (swept every connectionsCheckingInterval by an unref'd timer), keepAliveTimeout (+ keepAliveTimeoutBuffer), maxHeaderSize, maxRequestsPerSocket, joinDuplicateHeaders, requireHostHeader, noDelay, keepAlive, highWaterMark, rejectNonStandardBodyWrites.
  • The IncomingMessage/ServerResponse options (custom classes) are not accepted.
  • res.setHeaders takes a Map only — a Headers argument is not accepted.

klain:http — http.listen(port, handler)

Exact
import http from 'klain:http'; http.listen(port: number, handler: (req: HttpRequest) => Res): void

This project's own server model, under the explicitly-non-Node klain:http specifier — a handler returns a { status, body, headers } object rather than writing to a res. One fiber per accepted connection (ucontext.h), so a slow connection never blocks the others, running on the shared select() event loop alongside timers, fetch, and the other socket APIs. Not on the Node http namespace: `import http from 'klain:http'`.

Parameters
portnumber
Port to bind.
handler(req: HttpRequest) => Res
Returns an object with status / body / optional headers.
Returns

void — Blocks until http.close().

import http from 'klain:http'
http.listen(8080, (req: HttpRequest): Res => {
  const name = req.query.has('name') ? req.query.get('name') : 'stranger'
  return { status: 200, body: 'hello, ' + name }
})
See it in a full example →

http.listen(port, handler, { workers: N })

Exact
http.listen(port, handler, { workers: N }): void

Multi-process clustering: fork()s N-1 workers sharing the listening socket — parallelism across cores on top of each process's own fiber concurrency. cluster.isPrimary / cluster.worker (undefined in the original process, the Worker with ids 1..N-1 in each fork) branch on the current process. Works under both memory modes. os.cpus().length is the natural N. On Windows, which has no fork(), each worker is a re-spawned process that runs the program from the top (as a Node cluster worker does) and the primary deals connections out round-robin, itself included.

import cluster from 'cluster'
http.listen(8081, (req: HttpRequest): Res => {
  return { status: 200, body: 'served by worker ' + (cluster.worker?.id ?? 0).toString() }
}, { workers: os.cpus().length })
See it in a full example →

req.method / req.path

Exact
req.method: string; req.path: string

The request line, parsed. Any method token (GET/POST/PUT/DELETE/…), not GET-only. req.path excludes the query string — that's req.query.

Returns

string — The HTTP method / the path without query.

if (req.path === '/hello' && req.method === 'GET') { /* ... */ }
See it in a full example →

req.headers

Exact
req.headers: Map<string, string>

Request headers as a Map. Keys are lowercased for case-insensitive lookup, matching real HTTP semantics.

Returns

Map<string, string> — Lowercased-key header map.

const greeting = req.headers.has('x-greeting') ? req.headers.get('x-greeting') : 'hello'
See it in a full example →

req.query

Exact
req.query: Map<string, string>

The query string, split from req.path and parsed (a=b&c=d). Both keys and values are percent-decoded via the same decodeURIComponent primitive this compiler already had.

Returns

Map<string, string> — Parsed, percent-decoded query params.

const name = req.query.has('name') ? req.query.get('name') : 'stranger'
See it in a full example →

req.body

Exact
req.body: string

The request body as a string — Content-Length-aware, accumulated across reads, capped at 10MiB. Binary-safe: the buffer, the string value, and every string operation carry a length header, so an embedded null byte survives (an "ab\0cd" body reports length 5 and echoes 5 bytes).

Returns

string — The full request body.

req.bodyBytes()

Exact
req.bodyBytes(): ArrayBuffer

Wraps the same already-buffered, Content-Length-accurate bytes into an ArrayBuffer (no copy) — an explicit ArrayBuffer view for callers that want one. req.body is itself binary-safe now, so this is a convenience rather than the only NUL-safe path.

Returns

ArrayBuffer — The request body as bytes.

response headers field

Exact
return { status, body, headers?: Map<string, string> }

An optional headers field on the returned response object, fully user-controlled — including Content-Type if set. Omitting the field behaves byte-identically to before it existed.

Returns

Map<string, string> — Response headers to send.

const h = new Map<string, string>()
h.set('Content-Type', 'text/plain')
return { status: 200, body: 'hi', headers: h }
See it in a full example →

response bodyBytes field

Exact
return { status, bodyBytes?: ArrayBuffer }

An optional ArrayBuffer response body that wins outright over body's own content whenever present and non-null. The response writer writes it via an explicit-length write() (not a NUL-terminated sprintf), so a binary payload with embedded null bytes reaches the socket whole. The plain string body is itself binary-safe now, so bodyBytes is an explicit-ArrayBuffer convenience.

Returns

ArrayBuffer — Binary response body.

See it in a full example →

streaming response body

Exact
return { status, body: ReadableStream<string | Uint8Array> }

When the returned object's body field is statically a ReadableStream, the response goes out Transfer-Encoding: chunked, written chunk-at-a-time by a promise-reaction-driven writer — the handler returns immediately and a slow producer holds only its own connection open. The response is persistent: it advertises Connection: keep-alive and re-arms the connection for the next request at stream end (unless the client or handler asked to close). http.close() still waits for in-flight streams.

Returns

ReadableStream — The streamed response body.

See it in a full example →
⚠ Differences from standard
  • A stream error mid-response closes the connection without the chunked terminator — nothing else is signallable mid-stream.
  • HTTPS/1.1 streaming still sends Connection: close (the union-body string branch now re-arms keep-alive like the buffered and chunked paths).

req.stream()

Exact
req.stream(): ReadableStream<Uint8Array>

Streams the request body as Uint8Array chunks read off the socket. The server `req` is itself a Node Readable over this body: `for await (const chunk of req)`, `req.on('data'|'end')`, and `req.pipe(dest)` all consume the same underlying stream (cached on first use). Using it switches the program to headers-complete dispatch: the handler runs once headers are parsed and the body flows as chunks (fiber-yield on EAGAIN); the 10MiB cap then applies to the header block only. Piped into a streaming response body, it echoes without ever buffering the payload.

Returns

ReadableStream<Uint8Array> — The request body stream.

See it in a full example →
⚠ Differences from standard
  • req.body/req.bodyBytes() after consuming req.stream() throw a catchable TypeError (WHATWG "body already disturbed") — though before consuming the stream they still drain the remaining body into the buffer in place and stay correct.
  • A handler may mix response body shapes across branches by declaring a union body: string | ReadableStream<Uint8Array> — the writer branches on the runtime tag.

process.on('SIGINT'/'SIGTERM', handler)

Exact
process.on('SIGINT' | 'SIGTERM', handler: () => void)

Graceful shutdown. The handler runs from ordinary control flow at the top of a loop iteration (never real signal context) and may clean up and process.exit().

Returns

void — Registers the shutdown handler.

http.close()

Exact
http.close(): void

Formal, graceful listener teardown — a bare global function reachable only from inside a running request handler, a setTimeout/setInterval callback, or a process.on handler. Stops accepting immediately and lets already-open connections finish before http.listen()'s call returns control to the rest of the program (matching Node's default server.close()). In a { workers: N } cluster, one worker's .close() drains and exits every worker via a shared mmap'd flag, with no external signal.

Returns

void — Begins graceful shutdown.

if (req.path === '/shutdown') {
  http.close()
  return { status: 200, body: 'shutting down' }
}
See it in a full example →

http.closeAllConnections()

Exact
http.closeAllConnections(): void

The forceful counterpart to http.close() (Node's server.closeAllConnections()): shutdown(2)s every in-flight connection so each fiber unwinds through its own EOF path. It shuts down the calling handler's own connection too (Node parity), so send the response before calling it if the response must be delivered. In a cluster, one worker's call force-closes every worker's connections via the same shared mmap flag.

Returns

void — Force-closes in-flight connections.

See it in a full example →