Class HttpServer
An HTTP/1.1 server built around a reactor and a bounded worker pool.
The split is the whole design. A parked ParparVM thread costs about 243KB on musl (see vm/benchmarks ThreadCost), so ten thousand connections cannot each have one. Here an IDLE connection is a descriptor the reactor watches, and only a connection with a request in flight occupies a worker. Workers are bounded, so overload sheds as queueing rather than as memory exhaustion.
Once a worker owns a connection its descriptor is switched to BLOCKING mode, so request parsing is a straight read loop rather than a resumable state machine. That spends a worker per in-flight request to avoid a large amount of complexity, and in-flight requests are the thing there are few of.
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic interfacestatic final classWhat a handler receives.static final classWhat a handler returns.static interfaceChooses the websocket endpoint for an upgrade request, or null when this router does not serve that path.static interfaceWhat aHttpServer.WebSocketRoutescallback puts its endpoints into.static interfaceWhere websocket routes come from. -
Method Summary
Modifier and TypeMethodDescriptionvoidBlocks until the server has fully stopped, draining included.intRequests being handled right now.A snapshot for a health or metrics endpoint.intConnections currently open.intgetPort()static HttpServerstart(String host, int port, int backlog, int workerCount, HttpServer.Handler handler) static HttpServerstart(String host, int port, int backlog, int workerCount, HttpServer.Handler handler, Tls tls) tls: terminate TLS here, or null to serve plaintext (correct behind a load balancer that already terminated it)
static HttpServerstart(String host, int port, int backlog, int workerCount, HttpServer.Handler handler, Tls tls, HttpServer.WebSocketRoutes webSockets) webSockets: asked for its routes before the listener accepts anything
voidstop()Stops with a default drain window.voidstop(int drainMillis) Stops accepting, lets in-flight requests finish, then closes what is left.
-
Method Details
-
start
public static HttpServer start(String host, int port, int backlog, int workerCount, HttpServer.Handler handler) throws IOException - Throws:
IOException
-
start
public static HttpServer start(String host, int port, int backlog, int workerCount, HttpServer.Handler handler, Tls tls) throws IOException tls: terminate TLS here, or null to serve plaintext (correct behind a load balancer that already terminated it)
- Throws:
IOException
-
start
public static HttpServer start(String host, int port, int backlog, int workerCount, HttpServer.Handler handler, Tls tls, HttpServer.WebSocketRoutes webSockets) throws IOException webSockets: asked for its routes before the listener accepts anything
The callback runs here rather than on the returned server because this method binds the listener and starts the poller threads before it returns: a client arriving in that window would find no websocket routes at all, and its upgrade would fall through to the ordinary handler and be answered as HTTP. There is no way to express that mistake through this signature.
- Throws:
IOException
-
getPort
public int getPort() -
getOpenConnections
public int getOpenConnections()Connections currently open. -
getActiveRequests
public int getActiveRequests()Requests being handled right now. This is what saturation looks like. -
getMetrics
A snapshot for a health or metrics endpoint. "draining" is what a load balancer needs to see to take this instance out of rotation before it stops answering. -
awaitTermination
public void awaitTermination()Blocks until the server has fully stopped, draining included.
A caller's main() must do this or something equivalent: the reactor and the workers run on threads ParparVM creates DETACHED, so when main returns the process exits and takes them with it -- silently, with status 0, which from outside looks exactly like a server that refuses connections.
Waiting on the reactor THREAD is not enough, and that was a real bug: stop() clears the running flag, the loop returns on its next timeout, main wakes up and the process ends while a worker is still writing a response. This waits on the drain finishing instead.
-
stop
public void stop(int drainMillis) Stops accepting, lets in-flight requests finish, then closes what is left.
The order matters. Closing the listener first means no new work arrives while the pool drains; draining before closing connections means a request already being served gets to produce its response instead of having the socket pulled out from under it, which is what a client sees as a truncated reply.
-
stop
public void stop()Stops with a default drain window.
-