use web/types.{Request, Response, HttpError, text, param}
use web/router.{route_get}
use web/middleware.{with_logging}
use web_jvm/server.{serve_app}
fn hello(req: Request) -> Result[Response, HttpError] !io = {
let name = param(req, "name")?
Ok(text(200, "hello, ${name}\n"))
}
pub fn main() -> Unit !io =
serve_app(8001, [route_get("/hello/{name}", hello)], [with_logging])
[deps]
web = "../packages/web"
web_jvm = "../packages/web-jvm"
What is in web_jvm/server#
start(cfg, routes, middleware) | binds and answers a ServerHandle; join waits on it, stop ends it, handle_port reads the bound port (for port: 0) |
serve_app(port, routes, middleware), serve_app_with(cfg, routes, middleware) | start and join in one call |
ServerConfig | host, port, max_body, and the ErrorFormat for the errors the server words itself |
input_source(stream) | a web/types.BodySource over a Java InputStream, for streaming(...) |
start refuses a route table it cannot dispatch and a max_body that is not positive, before it binds. A handler that panics, or fails with a JVM Error, gets a 500 if nothing was sent yet, and the exchange is always closed. Everything else (requests, responses, routing, guards, body ceilings, CORS, error wording) is documented in packages/web.
This package starts at 1.0.0 with the server API web 6 had in web/server; see packages/web/CHANGELOG.md, 7.0.0, for the migration.
1.1.0#
A request body the JDK's stream cannot read (a chunk size that is not hex, a chunk cut short, a body that ends before its Content-Length) is answered 400 with {"error":"malformed request body"}. It was a 500: the exception went up as a panic. A temp file for a spilled body is still removed. The same failures get the same answer from web/exchange (the single-exchange server of packages/web), and scripts/web-exchange-contract holds the two servers to the same bytes. The server now calls the shared decisions that web 7.1 made public (serve_through, body_ceiling, framing, pump_with and the rest) instead of keeping its own copies. Needs web 7.1.
API#
web_jvm/server
ServerConfig
type ServerConfig = { host: String, port: Int, max_body: Int, errors: ErrorFormat }
Where the server listens, the in-memory body ceiling its requests get (a
positive byte count; start panics on anything else, since there is no
"unbounded" — routes that need more opt out with the "raw-body" or
"stream-body" tag), and the wording of the errors it renders itself. port 0
asks the OS for a free port; handle_port reports the one actually bound. default_errors() is
the neutral wording — an application whose clients expect another error
shape states it here (web/types.ErrorFormat).
ServerHandle
type ServerHandle
A running server, as start returns it: join blocks on it, stop ends
it, and handle_port reads the port it actually bound (the point of port 0).
server.input_source
fn input_source(s: InputStream) -> BodySource
A BodySource over a Java InputStream: what a JVM program passes to
streaming when its upstream is a stream (an HttpClient response body, a
file). A read(n) returns what the stream has ready, not n bytes: it
waits for one byte, then takes whatever else is already buffered up to n,
which is read(byte[])'s behaviour. readNBytes(n) alone would wait to
fill the whole chunk, and a download proxied from a slow upstream would
stall until 8 KiB had arrived instead of passing each piece on, which is the
first-byte-out-immediately property the streaming body exists for.
server.handle_port
fn handle_port(h: ServerHandle) -> Int
The port the server actually bound — with port 0 in the config, the one the OS picked.
server.start
fn start(cfg: ServerConfig, routes: List[Route], middleware: List[fn(fn(Request) -> Result[Response, HttpError] !io) -> fn(Request) -> Result[Response, HttpError] !io]) -> ServerHandle !io
Bind cfg.host:cfg.port, serve requests on virtual threads, and return at
once with the handle — the caller decides what the program does next
(join to block, stop from wherever shutdown is decided). A broken route
table panics here, before the socket is bound.
server.join
fn join(h: ServerHandle) -> Unit !io
Block until stop() releases this handle. A server's main() usually ends here — returning would let the JVM exit out from under the request threads.
server.stop
fn stop(h: ServerHandle, grace_secs: Int) -> Unit !io
Stop the server: the listening socket closes at once, in-flight exchanges get up to grace_secs to finish (the JDK's stop blocks for that), the request executor is shut down, and then every join() blocked on this handle returns. Every one of those happens even if the JDK's own stop throws.
server.serve_app
fn serve_app(port: Int, routes: List[Route], middleware: List[fn(fn(Request) -> Result[Response, HttpError] !io) -> fn(Request) -> Result[Response, HttpError] !io]) -> Unit !io
The old shape, kept: start + join on 127.0.0.1, default body ceiling, default error wording. Most programs want exactly this.
server.serve_app_with
fn serve_app_with(cfg: ServerConfig, routes: List[Route], middleware: List[fn(fn(Request) -> Result[Response, HttpError] !io) -> fn(Request) -> Result[Response, HttpError] !io]) -> Unit !io
start + announce + join under a config the caller owns — the entry point for a program that configures anything (host, error wording) and still wants main() to end by blocking.