02 · HTTP Server
A minimal JSON server built with Node’s http module. Listening on a port
needs a network grant.
Source: examples/02-http-server
Run
cd examples/02-http-server
3va run server.js --allow-net=localhostServer running at http://localhost:3000curl localhost:3000/ # {"message":"Hello from 3va HTTP server!"}
curl localhost:3000/time # {"time":"2026-09-24T23:17:59.074Z"}
curl -i localhost:3000/nope # HTTP/1.1 404 Not Found … {"error":"Not found"}| Path | Response |
|---|---|
/ | JSON greeting |
/time | Current ISO timestamp |
| anything else | 404 |
How it works
http.createServer() and server.listen() are 3va’s Node-compatible
implementations. Before binding the socket, 3va checks the network
permission. listen() without a host binds to 127.0.0.1 unless
--allow-net names 0.0.0.0, :: or *.
HTTPS and WebSocket upgrade
https.createServer({ key, cert }) is a real TLS listener — the key/cert
must be PEM and a server created without them fails on listen() rather
than serving plaintext. And http servers emit 'upgrade' for
Connection: upgrade requests, so ws works end to end:
const https = require("https");
const server = https
.createServer({ key, cert }, (req, res) => res.end("ok"))
.listen(8443, "127.0.0.1");
server.on("upgrade", (req, socket, head) => {
// ws performs the 101 handshake and drives the frames itself
});socket is the raw duplex socket; head holds the bytes read behind the
handshake, delivered once. The socket is bridged by a driver task that reads
at most 1 MiB ahead of the app and drops a peer that stops reading while
more than 32 MiB is queued for it.
Things to keep in mind
-
Without
--allow-netthe server never starts, and the process still exits with code 0. The permission failure goes to the server’serrorevent, which this example doesn’t listen for. 3va reports it on exit:[!] 1 permission was denied during this run: bind a local server on 0.0.0.0 needs any --allow-net grant (e.g. --allow-net=127.0.0.1)The exit code is still 0, so a supervisor or CI job won’t notice. Add a handler so the process fails:
server.on("error", (err) => { console.error(err.code, err.message); process.exit(1); }); // → EACCES Network access denied. Run with --allow-net=0.0.0.0 -
For
listen(), the host in--allow-netlimits what binds. Without a host,listen(3000)binds127.0.0.1. Binding every interface needs a grant naming them:--allow-net=0.0.0.0,::or*(this is thecheck_bindrule in the permissions crate). Outbound calls (fetch,http.request, TCP connect) are still matched strictly against the granted host. -
--allow-net=localhostkeeps the server on loopback. Becauselisten(3000)binds127.0.0.1without a wildcard grant, the server accepts connections only on the loopback interface. To serve every interface, grant a wildcard or bind explicitly:server.listen(3000, "0.0.0.0"). -
The port isn’t limited either.
--allow-net=localhost:9999still lets the server listen on 3000. -
Port
3000is hard-coded in this example. Reading it fromPORTwould also need--allow-env=PORT(see 09).