HTTP
The HTTP surface is the one you already know how to hold: Request, Response, Routes,
Handler, Middleware, Server, Client. Bodies on the wire are streams.
Body.asString / asBytes are for in-memory bodies; use collect otherwise. maxBodyBytes is a
cap, not a buffer.
If you are building a hub, start at The hub and come back here for the knobs.
Routes and the path DSL
Method.GET / "users" / int("id") is a PathCodec. int, long, string, uuid, and
trailing are the captures. Unmatched paths are 404; wrong method is 405.
{
val routes = Routes(
Method.GET / "users" / int("id") -> { (id: Int) =>
ZIO.succeed(Response.text(id.toString))
}
)
for
ok <- routes(Request.get("/users/3"))
miss <- routes(Request.get("/users/ada"))
yield (ok.body.asString, miss.status)
}(3,Status(404,Not Found)) "shows" / int("id") /shows/1 match id = 1
handler runs
Middleware
@@ wraps this Routes value. ++ is try left, then right on 404/405.
Gzip, decompress, CORS, auth, and Middleware.timeout attach to whoever you wrap.
Global gzip is all @@ Middleware.compress(), which is still wrapping routes, not a
server flag. Partial gzip is (api ++ files) @@ Middleware.compress() ++ sse.
Why that split exists is on What stays open.
{
val routes =
Routes(Method.GET / "x" -> Handler.text("ok")) @@ Middleware.requestId()
routes(Request.get("/x")).map { res =>
res.header("X-Request-Id").exists(_.nonEmpty)
}
}trueroutes @@ (Middleware.requestId() ++ Middleware.cors()) request → cors → requestId → handler cors is outer. requestId still stamps X-Request-Id.
Server
Server.install / Server.serve are ordinary ZIO fibers on every platform. JVM accept
defaults to Loom (JvmScheduler.Loom); JvmScheduler.Default still binds. JS is Node
net / tls. Native is POSIX sockets plus OpenSSL. TLS is a compose-time layer, not a
protocol flag: Server.serve(app).provide(Server.Config.defaults, Tls.pem(certPem, keyPem)).
HTTP/2 (ALPN h2) is the JVM bind. JS and Native serve HTTP/1.1 on that same Routes
value. Idle, header, and connection caps live on Server.Config. See
What stays open.
{
val routes = Routes(Method.GET / "health" -> Handler.text("ok"))
ZIO.scoped {
Server
.install(routes, Server.Config.default.copy(host = "127.0.0.1", port = 0))
.flatMap { server =>
server.port.flatMap { port =>
Client.get(s"http://127.0.0.1:$port/health").map { res =>
(res.status, res.body.asString)
}
}
}
}
}(Status(200,OK),ok)Client, files, compression
Client.get / Client.batched are the HTTP/1.1 client. JVM pools sockets. JS uses
fetch. Native opens a POSIX (or TLS) socket per call. One-shot helpers take
Client.Config (default named values, including connect and read timeouts).
A caller-given file is Files.fromPath (jailed under SafePath for directory roots).
Compression is @@ Middleware.compress (gzip in core). Add heddle-brotli and pass
Brotli.compressor when you want br. Incoming Content-Encoding is
@@ Middleware.decompress(maxBytes = ...).
Brotli.encode(zio.Chunk.fromArray("hi".getBytes("UTF-8"))).nonEmptytrue