1: Some Simple Examples

Making a Simple GET Request

The main HttpClient class provides the actions necessary for making HTTP calls; it’s immutable and therefore thread-safe. In the following example, an HttpClient instance is created, then used to fetch a URL.

import uk.co.bigbeeconsultants.http.HttpClient
import uk.co.bigbeeconsultants.http.response.Response
import java.net.URL

object Example1a {
  def main(args: Array[String]) {
    val httpClient = new HttpClient
    val response: Response = httpClient.get(new URL("http://www.google.com/"))
    println(response.status)
    println(response.body.asString)
  }
}

Under the hood, this simply uses java.net.URLConnection to make a blocking request to the specified server. The model here is deliberately simple: you ask for it, then you wait and, when it completes, you get it. It’s a blocking (a.k.a. synchronous) call. If you want asynchronous calls, just use a Future to do the same thing. The main benefit here is you decide the threading model; no unwanted threads are imposed on you by the HTTP client. For example, if you already have lots of concurrent requests going on, adding futures may actually worsen the overall performance, so what’s needed are simple blocking requests. Conversely, if you have few requests to make, and you know they might be slow, and you don’t need the results until some other stuff has been done, then futures may be ideal.

Response is a case class. It includes the original request, along with the status, headers and response body received from the webserver.

Making a GET Request With Modified Options

Config is a case class that sets the general options used by requests; it’s immutable and therefore thread-safe. In this example, we set a longer connect timeout, a shorter read timeout, and we disable the following of redirects.

import uk.co.bigbeeconsultants.http._
import uk.co.bigbeeconsultants.http.header.HeaderName._

object Example1b {
  // create a Config different from the default values
  val config = Config(connectTimeout = 10000,
    readTimeout = 1000,
    followRedirects = false)

  val httpClient = new HttpClient(config)

  def main(args: Array[String]) {
    val response = httpClient.get("http://www.x.org/")

    println(response.status.code) // prints "302"
    println(response.headers(LOCATION).value) // prints "/wiki/"
  }
}

Using an implicit conversion from String to URL, the parameter passed to get needs less ceremony. It means the same, though.

You can see the last two lines access the status code directly and then one of the response headers.

The two examples above are just an App and, in each case, the HttpClient instance was created on the fly before we used it. In a larger project, you may perhaps benefit from creating and configuring only one instance of HttpClient, using your favourite application framework (Spring etc) and dependency-injecting the HttpClient into the collaborators that use it. It’s designed to be used in this way when necessary.

Looking at The Response

As we’ve seen, Response includes the original request, the status, headers and response body received from the webserver. ResponseBody is a trait providing the type and length of the content, plus the actual data, which is an array of bytes that can be converted to a string for you when this makes sense. We did that in the first example, and again in this one:

import uk.co.bigbeeconsultants.http._

object Example1c {
  val httpClient = new HttpClient

  def main(args: Array[String]) {
    val response = httpClient.get("http://www.google.com/")

    println(response.body.contentLength)
    println(response.body.asBytes.length) // the same as above
    println(response.body.asString.length)
    println(response.body.isTextual) // true
    println(response.body.contentType.mediaType) // prints "text/html"
    println(response.body.contentType.charsetOrUTF8) // prints "ISO-8859-1" for example
  }
}

The content length is the number of bytes received, and is the same as the asBytes length, regardless of whether compression was used. The content-length header may often have a smaller value because of compression.

The asString length may be the same as the asBytes length, or different, depending on the character encoding. In Example1c, the character encoding is one byte per character so the two lengths are the same.

Use asBytes when receiving binary data and asString for textual data. The expression response.body.isTextual can help you decide which; if the data is binary, asString will throw an exception.