HTTP server
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.
http.createServer((req, res) => …)
ExactThe 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.
ReturnsServer — 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)ressurface:writeHead/setHeader/appendHeader/getHeader(s)/removeHeader/hasHeader/write/end/flushHeaders/writeContinue,statusCode/statusMessage(default reason phrase fromhttp.STATUS_CODES). The body is chunked orContent-Lengthas Node decides;res.writereturnsfalsepasthighWaterMarkand'drain'follows, over HTTP and HTTPS alike.- Keep-alive and pipelining, the
Dateheader, andrequireHostHeader's 400 behave as in Node; anasynchandler 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 caveatsThe 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)
})listenreturns 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'(anet.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'sHPE_INVALID_EOF_STATE, an oversized header blockHPE_HEADER_OVERFLOW. close(cb)/closeAllConnections()/closeIdleConnections()/setTimeout; optionsrequestTimeout/headersTimeout(swept everyconnectionsCheckingIntervalby an unref'd timer),keepAliveTimeout(+keepAliveTimeoutBuffer),maxHeaderSize,maxRequestsPerSocket,joinDuplicateHeaders,requireHostHeader,noDelay,keepAlive,highWaterMark,rejectNonStandardBodyWrites.- The
IncomingMessage/ServerResponseoptions (custom classes) are not accepted. res.setHeaderstakes aMaponly — aHeadersargument is not accepted.
klain:http — http.listen(port, handler)
ExactThis 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.
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 }
})http.listen(port, handler, { workers: N })
ExactMulti-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 })req.method / req.path
ExactThe request line, parsed. Any method token (GET/POST/PUT/DELETE/…), not GET-only. req.path excludes the query string — that's req.query.
Returnsstring — The HTTP method / the path without query.
if (req.path === '/hello' && req.method === 'GET') { /* ... */ }req.headers
ExactRequest headers as a Map. Keys are lowercased for case-insensitive lookup, matching real HTTP semantics.
ReturnsMap<string, string> — Lowercased-key header map.
const greeting = req.headers.has('x-greeting') ? req.headers.get('x-greeting') : 'hello'req.query
ExactThe 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.
ReturnsMap<string, string> — Parsed, percent-decoded query params.
const name = req.query.has('name') ? req.query.get('name') : 'stranger'req.body
ExactThe 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).
Returnsstring — The full request body.
req.bodyBytes()
ExactWraps 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.
ReturnsArrayBuffer — The request body as bytes.
response headers field
ExactAn 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.
ReturnsMap<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 }response bodyBytes field
ExactAn 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.
ReturnsArrayBuffer — Binary response body.
streaming response body
ExactWhen 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.
ReturnsReadableStream — The streamed response body.
- 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()
ExactStreams 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.
ReturnsReadableStream<Uint8Array> — The request body stream.
req.body/req.bodyBytes()after consumingreq.stream()throw a catchableTypeError(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
bodyshapes across branches by declaring a unionbody: string | ReadableStream<Uint8Array>— the writer branches on the runtime tag.
process.on('SIGINT'/'SIGTERM', handler)
ExactGraceful 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().
Returnsvoid — Registers the shutdown handler.
http.close()
ExactFormal, 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.
Returnsvoid — Begins graceful shutdown.
if (req.path === '/shutdown') {
http.close()
return { status: 200, body: 'shutting down' }
}http.closeAllConnections()
ExactThe 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.
Returnsvoid — Force-closes in-flight connections.