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 interfaceGenerates a response on the serving worker, with bounded transport buffering.static interfacestatic final classOne part of amultipart/form-datarequest body: a form field, or an uploaded file when it has a filename.static 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()booleanisSecure()Whether this server speaks TLS, so a client of it must use https.static WebSocketrefuseUpgrade(int status, String body) An endpoint aHttpServer.WebSocketHandlerreturns to refuse the upgrade withstatusandbodyrather than the 404 a null answers -- a 403 for a client that is not allowed in, a 400 for a handshake missing what the endpoint needs.static WebSocketrefuseUpgrade(HttpServer.Response response) refuseUpgrade(int, String)with a whole response, so the refusal can carry its own headers and content type.static voidrequireVirtualThreadStack(int bytes) Makes every virtual thread this process creates from now on have at leastbytesof C stack.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
-
refuseUpgrade
An endpoint aHttpServer.WebSocketHandlerreturns to refuse the upgrade withstatusandbodyrather than the 404 a null answers -- a 403 for a client that is not allowed in, a 400 for a handshake missing what the endpoint needs. The status must be 400 or higher; anything else is answered as a 500, because a refusal that reads as a success would leave the client waiting for a session that never starts. The body is sent astext/plainand may be null. -
refuseUpgrade
refuseUpgrade(int, String)with a whole response, so the refusal can carry its own headers and content type. -
requireVirtualThreadStack
public static void requireVirtualThreadStack(int bytes) Makes every virtual thread this process creates from now on have at least
bytesof C stack. Call it beforestart; it only ever raises the size, so it composes withCN1_HTTP_VT_STACKand with other callers. The stack is mapped lazily, so a generous size costs address space, not memory.Parameters
bytes: the C stack a connection's handler needs
Throws
IllegalStateException: when a server has already started andbyteswould raise the size. Hosts and connections that exist keep the stack they were created with, so a late raise would protect only some of the handlers and overflow the others the same way. A call that asks for no more than the current size is a no-op at any time.
-
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
-
isSecure
public boolean isSecure()Whether this server speaks TLS, so a client of it must use https. -
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.
-