Getting Started
ZIO HTTP is a powerful library that is used to build highly performant HTTP-based services and clients using functional scala and ZIO and uses Netty as its core. ZIO HTTP has powerful functional domains which help in creating, modifying, composing apps easily. Let's start with the HTTP domain. The first step when using ZIO HTTP is creating an HTTP app.
Http
Http
is a domain that models HTTP apps using ZIO and works over any request and response types. Http
Domain provides different constructors to create HTTP apps, Http.text
, Http.html
, Http.fromFile
, Http.fromData
, Http.fromStream
, Http.fromEffect
.
Creating a "Hello World" app
Creating an HTTP app using ZIO Http is as simple as given below, this app will always respond with "Hello World!"
import zio.http._
val app = Http.text("Hello World!")
An app can be made using any of the available constructors on zio.Http
.
Routing
For handling routes, Http Domain has a collect
method that, accepts different requests and produces responses. Pattern matching on the route is supported by the framework.
The example below shows how to create routes:
import zio.http._
val app = Http.collect[Request] {
case Method.GET -> !! / "fruits" / "a" => Response.text("Apple")
case Method.GET -> !! / "fruits" / "b" => Response.text("Banana")
}
You can create typed routes as well. The below example shows how to accept count as Int
only.
import zio.http._
val app = Http.collect[Request] {
case Method.GET -> !! / "Apple" / int(count) => Response.text(s"Apple: $count")
}
Composition
Apps can be composed using operators in Http
:
- Using the
++
operator. The way it works is, if none of the routes match ina
, then the control is passed on to theb
app.
import zio.http._
val a = Http.collect[Request] { case Method.GET -> !! / "a" => Response.ok }
val b = Http.collect[Request] { case Method.GET -> !! / "b" => Response.ok }
val app = a ++ b
- Using the
<>
operator. The way it works is, ifa
fails, then the control is passed on to theb
app.
import zio.http._
val a = Http.fail(new Error("SERVER_ERROR"))
val b = Http.text("OK")
val app = a <> b
ZIO Integration
For creating effectful apps, you can use collectZIO
and wrap Response
with UIO
to produce ZIO effect value.
val app = Http.collectZIO[Request] {
case Method.GET -> !! / "hello" => UIO(Response.text("Hello World"))
}
Accessing the Request
To access the request use @
as it binds a matched pattern to a variable and can be used while creating a response.
import zio.http._
val app = Http.collectZIO[Request] {
case req @ Method.GET -> !! / "fruits" / "a" =>
UIO(Response.text("URL:" + req.url.path.asString + " Headers: " + req.getHeaders))
case req @ Method.POST -> !! / "fruits" / "a" =>
req.bodyAsString.map(Response.text(_))
}
Testing
Since Http
is a function of the form A => ZIO[R, Option[E], B]
to test it you can simply call an Http
like a function.
import zio.test._
import zio.http._
object Spec extends DefaultRunnableSpec {
def spec = suite("http")(
test("should be ok") {
val app = Http.ok
val req = Request()
assertZIO(app(req))(equalTo(Response.ok))
}
)
}
When we call the app
with the request
it calls the apply method of Http
via zio.test
package
Socket
Socket
is functional domain in ZIO HTTP. It provides constructors to create socket apps.
A socket app is an app that handles WebSocket connections.
Creating a socket app
Socket app can be created by using Socket
constructors. To create a socket app, you need to create a socket that accepts WebSocketFrame
and produces ZStream
of WebSocketFrame
.
Finally, we need to convert socketApp to Response
using toResponse
, so that we can run it like any other HTTP app.
The below example shows a simple socket app, we are using collect
which returns a stream with WebsSocketTextFrame "BAR" on receiving WebsSocketTextFrame "FOO".
import zio.socket._
private val socket = Socket.collect[WebSocketFrame] { case WebSocketFrame.Text("FOO") =>
ZStream.succeed(WebSocketFrame.text("BAR"))
}
private val app = Http.collectZIO[Request] {
case Method.GET -> !! / "greet" / name => UIO(Response.text(s"Greetings {$name}!"))
case Method.GET -> !! / "ws" => socket.toResponse
}
Server
As we have seen how to create HTTP apps, the only thing left is to run an HTTP server and serve requests. ZIO HTTP provides a way to set configurations for your server. The server can be configured according to the leak detection level, request size, address etc.
Starting an HTTP App
To launch our app, we need to start the server on a port. The below example shows a simple HTTP app that responds with empty content and a 200
status code, deployed on port 8090
using Server.start
.
import zio.http._
import zio.http.Server
import zio._
object HelloWorld extends App {
val app = Http.ok
override def run(args: List[String]): URIO[zio.ZEnv, ExitCode] =
Server.start(8090, app).exitCode
}