HTTP Client

How do we know the server is running? Let’s create a client with http4s to try our service.

A recap of the dependencies for this example, in case you skipped the service example. Ensure you have the following dependencies in your build.sbt:

scalaVersion := "2.11.8" // Also supports 2.10.x and 2.12.x

val http4sVersion = "0.19.0-SNAPSHOT"

// Only necessary for SNAPSHOT releases
resolvers += Resolver.sonatypeRepo("snapshots")

libraryDependencies ++= Seq(
  "org.http4s" %% "http4s-dsl" % http4sVersion,
  "org.http4s" %% "http4s-blaze-server" % http4sVersion,
  "org.http4s" %% "http4s-blaze-client" % http4sVersion

Then we create the service again so tut picks it up:

import cats.effect._
// import cats.effect._

import org.http4s._
// import org.http4s._

// import

import org.http4s.server.blaze._
// import org.http4s.server.blaze._

val service = HttpService[IO] {
  case GET -> Root / "hello" / name =>
    Ok(s"Hello, $name.")
// service: org.http4s.HttpService[cats.effect.IO] = Kleisli(org.http4s.HttpService$$$Lambda$36894/1064177704@68461ea5)

val builder = BlazeBuilder[IO].bindHttp(8080, "localhost").mountService(service, "/").start
// builder: cats.effect.IO[org.http4s.server.Server[cats.effect.IO]] = IO$1289214292

val server = builder.unsafeRunSync
// server: org.http4s.server.Server[cats.effect.IO] = BlazeServer(/

Creating the client

A good default choice is the Http1Client. The Http1Client maintains a connection pool and speaks HTTP 1.x.

Note: In production code you would want to use[F[_]: Effect]: Stream[F, Http1Client] to safely acquire and release resources. In the documentation we are forced to use .unsafeRunSync to create the client.

import org.http4s.client.blaze._
// import org.http4s.client.blaze._

val httpClient = Http1Client[IO]().unsafeRunSync
// httpClient: org.http4s.client.Client[cats.effect.IO] = Client(Kleisli(org.http4s.client.blaze.BlazeClient$$$Lambda$36920/615151802@5466ad05),IO$1149118016)

Describing a call

To execute a GET request, we can call expect with the type we expect and the URI we want:

val helloJames = httpClient.expect[String]("http://localhost:8080/hello/James")
// helloJames: cats.effect.IO[String] = IO$1647278114

Note that we don’t have any output yet. We have a IO[String], to represent the asynchronous nature of a client request.

Furthermore, we haven’t even executed the request yet. A significant difference between a IO and a scala.concurrent.Future is that a Future starts running immediately on its implicit execution context, whereas a IO runs when it’s told. Executing a request is an example of a side effect. In functional programming, we prefer to build a description of the program we’re going to run, and defer its side effects to the end.

Let’s describe how we’re going to greet a collection of people in parallel:

import cats._, cats.effect._, cats.implicits._
// import cats._
// import cats.effect._
// import cats.implicits._

import org.http4s.Uri
// import org.http4s.Uri

// import

def hello(name: String): IO[String] = {
  val target = Uri.uri("http://localhost:8080/hello/") / name
// hello: (name: String)cats.effect.IO[String]

val people = Vector("Michael", "Jessica", "Ashley", "Christopher")
// people: scala.collection.immutable.Vector[String] = Vector(Michael, Jessica, Ashley, Christopher)

val greetingList = fs2.async.parallelTraverse(people)(hello)
// greetingList: cats.effect.IO[scala.collection.immutable.Vector[String]] = IO$510613498

Observe how simply we could combine a single F[String] returned by hello into a scatter-gather to return a F[List[String]].

Making the call

It is best to run your F “at the end of the world.” The “end of the world” varies by context:

  • In a command line app, it’s your main method.
  • In an HttpService[F], an F[Response[F]] is returned to be run by the server.
  • Here in the REPL, the last line is the end of the world. Here we go:
val greetingsStringEffect ="\n"))
// greetingsStringEffect: cats.effect.IO[String] = <function1>

// res0: String =
// Hello, Michael.
// Hello, Jessica.
// Hello, Ashley.
// Hello, Christopher.

Cleaning up

Our client consumes system resources. Let’s clean up after ourselves by shutting it down:


If the client is created using[F](), it will be shut down when the resulting stream finishes.

Calls to a JSON API

Take a look at json.

Body decoding / encoding

The reusable way to decode/encode a request is to write a custom EntityDecoder and EntityEncoder. For that topic, take a look at entity.

If you prefer a more fine-grained approach, some of the methods take a Response[F] => F[A] argument, such as fetch or get, which lets you add a function which includes the decoding functionality, but ignores the media type.

client.fetch(req) {
  case Status.Successful(r) => r.attemptAs[A].leftMap(_.message).value
  case r =>[String]
    .map(b => Left(s"Request $req failed with status ${r.status.code} and body $b"))

However, your function has to consume the body before the returned F exits. Don’t do this:

// will come back to haunt you
client.get[EntityBody]("some-url")(response => response.body)

Passing it to a EntityDecoder is safe.

client.get[T]("some-url")(response => jsonOf(response.body))