http4s – Library for Functional HTTP API

turned on laptop computer
Reading Time: 3 minutes

http4s is Scala’s answer to Java’s servlets. Apart from being a minimal, idiomatic Scala interface for HTTP services, it has a range of impressive features as well:

  • Typeful : Everything in http4s is a type. Standard headers are semantic types, codecs are typeclass, and even requests and responses are immutable models
  • Functional : Cats is one of the supporting pillar to http4s, and cats-effect manages I/O in this library making the library functional in nature
  • Streaming : FS2, a streaming library, is another pillar of http4s. It provides request and response body in a streaming way, thus processing large payloads in constant space.
  • Cross-Platform : http4s cross-builds for Scala.js and Scala Native. We can deploy the code to browsers and the JVM.

HttpRoutes[F] – Heart of http4s

The http4s library is based on the concepts of Request and Response. A request is mapped to a response through a set of function of type Request => Response. These functions are routes, and a server is set of such routes. Without getting into a a lot of details, consider the following points:

  1. Producing a response from a request requires some side-effects, like interaction with database, with external services, etc. This transforms our original route function to Request => F[Reponse]
  2. Not every request has a response, hence the type of the function can be tranlsated to Request => F[Option[Response] => OptionT[F, Response] (monad transformer)
  3. Cats Kleisli monad transformer Kleisli[F[_], A, B] is a wrapper around function A => F[B], thus transforming our route function to Kleisli[OptionT[F, *], Request, Response]

Finally, the http4s library defines a type alias for the Kleisli monad transformer that is easier to understand for human beings: HttpRoutes[F]

Creating endpoint

We can create a simple route type, its endpoint and behaviour using http4s as follows:

val route: HttpRoutes[IO] = HttpRoutes.of[IO] {

  case GET -> Root / "hello" / name => Ok(s"""Hello, ${name}!""")

}

This can be represented diagrammatically as:

Testing the endpoint

One beautiful thing about the HttpRoutes[F] model is that we don’t need a server to test our route. We can construct our own request and experiment directly in the REPL. For the above endpoint, we need to create a Request and a Response wrapped in IO for testing.

//Create Request

val helloRequest: Request[IO] = Request[IO](Method.GET, uri"/hello/John")

// Output

helloRequest: Request[IO] = Request(method=GET, uri=/hello/John,

 httpVersion=HTTP/1.1, headers=Headers(), entity=Entity.Empty)

//Create Response

val response: IO[Response[IO]] = route.orNotFound.run(helloRequest)

// Output

response: IO[Response[[+A]cats.effect.IO[A]]] = IO(...)

// To run the effect

response.unsafeRunSync()

//Output

Response(

     status=200,

     httpVersion=HTTP/1.1,

     headers=Headers(Content-Type: text/plain; charset=UTF-8,

     Content-Length: 12), entity=Entity.Strict(12 bytes total)

    )

// To read the reponse

response.flatMap(_.as[String]).unsafeRunSync()

//Output

res0: String = Hello, John!

Creating Server and Client using http4s

http4s supports multiple server backends that we can use in production. We will be using the library native backend support for Ember to create server as well as client.

Ember Server

We can use the following code to create an ember server using configurable host-port values and route(s) creating the HttpApp:

EmberServerBuilder

      .default[IO]

      .withHost(Host.fromString(host).get)

      .withPort(Port.fromInt(port).get)

      .withHttpApp(route.orNotFound)

      .use(_ => IO.never)

      .as(ExitCode.Success)

The above piece of code can be written inside the cats-effect provided IOApp trait that has an abstract run method returning IO[ExitCode]. An IOApp runs the process and adds a JVM shutdown hook to interrupt the infinite process and gracefully shut down your server when a SIGTERM is received. The orNotFound API takes care of handling any route that is not defined in the server.

Ember Client

Similarly, Blaze can be used to create Http client as well using the following code inside IOApp:

EmberClientBuilder.default[IO].build

    .use { client =>

          val response = client.expect[String]("http://localhost/hello/John")

          println(s"Response: ${response.unsafeRunSync()") //For testing

    }

//Output

Response: Hello, John

Conclusion

The http4s ecosystem helps us in creating server API over HTTP. The library fully embraces the functional programming paradigm, using Cats and Cats Effect as building blocks. It is easy to define routes and behaviours, tests these routes without a server, create a server and a client. In future blogs, we will look into how we can use the same library to extract query parameters from the endpoints, create middlewares, and provide authentication and streaming behaviour.

References:

Written by 

Software Consultant with 2+ years of experience, with a strong inclination towards Big Data Analytics and Data Science.