Just Handlers

Just Handlers

If you issue a request and receive a response, you need to create a handler. dispatch provides some handy handlers to convert the response body to a string, XML or using whatever function you have that converts a request to an output value.

Some of the as.* that you have out of the box include:

  • as.Response: Returns the raw response object.

  • as.String.utf8: Returns utf8 string.

  • as.Bytes: Returns bytes.

  • as.File(yourfile): Redirects the body to a file.

  • as.XML: Creates XML a scala scala.xml.Elem.

You can also pass in your own function that handles a full async-http-client Response and returns some value derived from it. For example, the response body is available as Reseponse.getResponseBody so you could do:

@ Http(host("localhost:9000") > { _.getResponseBody} ) onComplete println 
Success(<h1>you have reached the example server</h1>)

which does the same thing as as.String but illustrates the syntax somewhat. You could also define your function elsewhere and use it instead of {_.getResponseBody}. You should notice that this function only runs after the entire response has been received so you lose as-you-get-it processing capabilities using this approach. You can also provide your own AsyncHandler directly and use >.

Generally, you will wan to use OK and your handler, a function Response => T so that your function only runs if the call was successful:

@ Http(host("localhost:9000") OK { _.getResponseBody} ) onComplete println 
Success(<h1>you have reached the example server</h1>)
@ Http(host("localhost:9000") OK { _.getStatusCode} ) onComplete println 
Success(200)

The > function and AsyncHandler

The > function to create a handler is overloaded. You can provide a function, like we did in the last section, or an AsyncHandler.

There is also a handler at at.stream.Lines that takes a String => T function parameter that processes each line of response using the function parameter. If we alter our example-server program to return a well known set of lines:

} ~ path("lines") { 
        get { 
          complete("1\n2\n3\n")
        }
      }

then we can use it like:

@ Http(host("localhost:9000") / "lines"  > as.stream.Lines(println)) onComplete println 
1
2
3
Success(())

Using the handler this way is clearly being used for its side-effects. We also used the > method which directly takes an async-http-client AsyncHandler directly. When you want explicit control over response processing you would write your own AsyncHandler. You would use this, for example, to interface to a processing library like monix.

json response handling

XML is a bit deprecated today and the new hotness, for the past decade actually, has really been json.

dispatch has json support for a few libarries but does not restrict you to only using them. Everyone uses a different json library it seems.

First, we need to alter our example server to return some json, the route will add is:

~ path("json") { 
        get { 
          complete("""{ "result" : "hello world"}""")
        }
      }

There are a few add-ons for json support provided by dispatch. We will show the one based on lift json, which has not been updated in a long time, but still works of course:

load.ivy("net.databinder.dispatch" %% "dispatch-core" % "0.11.3")
load.ivy("net.databinder.dispatch" %% "dispatch-lift-json" % "0.11.3")
load.ivy("net.liftweb" %% "lift-json" % "latest.release")

import scala.async.Async._
import dispatch._, Defaults._
import scala.concurrent.Await
import scala.concurrent.duration._
import net.liftweb.json._
import JsonDSL._

def callit() = Http(url(s"http://localhost:9000/json") OK as.lift.Json)

callit() onComplete { r => println(s"Result $r")}

You call the handle just like the other handlers, as.lift.Json and get back a json object:

@ callit() onComplete { r => println(s"Result $r")} 

Result Success(JObject(List(JField(result,JString(hello world)))))

The code for the handler suggests how simple it is to create your own json handler:

object Json extends (Response => JValue) {
  def apply(r: Response) =
    (dispatch.as.String andThen JsonParser.parse)(r)
}

You can see that using a combinator of andThen you can piece together the handlers and build on previous handlers. First as.String reads the response body and converts it to a String then JsonParser.parse parses that result. You can do this because a handler is defined as a function and you can compose functions.

The circe library is the new hotness for json parsing, based on argonaut but faster (here on github). We just need a "handler" that takes a response to a circe json object, which is based on a cats library object since circe uses cats underneath:

load.ivy("net.databinder.dispatch" %% "dispatch-core" % "0.11.3")
Seq("io.circe" %% "circe-core",
  "io.circe" %% "circe-generic",
  "io.circe" %% "circe-parser") map (_ % "latest.release") foreach(load.ivy(_))

import dispatch._, Defaults._
import scala.concurrent.Await
import scala.concurrent.duration._

import com.ning.http.client.Response
import io.circe._, io.circe.generic.auto._, io.circe.parser._, io.circe.syntax._
import io.circe.{ Decoder, Encoder, Json }
import cats.data._

object CirceJson extends (Response => cats.data.Xor[ParsingFailure, Json]) {
  def apply(r: Response) =
    (dispatch.as.String andThen parse)(r)
}

def callit() = Http(url(s"http://localhost:9000/json") OK CirceJson)

callit() onComplete { r => println(s"Result $r")}

Which prints out:

Result Success(Right({
  "result" : "hello world"
}))

the result we expect.

Last updated