Documentation

You are viewing the documentation for the 2.2.3 release in the 2.2.x series of releases. The latest stable release series is 3.0.x.

§The Play WS API

Sometimes we would like to call other HTTP services from within a Play application. Play supports this via its WS library, which provides a way to make asynchronous HTTP calls.

There are two important parts to using the WS API: making a request, and processing the response. We’ll discuss how to make both GET and POST HTTP requests first, and then show how to process the response from WS. Finally, we’ll discuss some common use cases.

§Making a Request

To use WS, first import the following:

import play.api.libs.ws._
import scala.concurrent.Future

To build an HTTP request, you start with WS.url() to specify the URL.

val holder : WSRequestHolder = WS.url(url)

This returns a WSRequestHolder that you can use to specify various HTTP options, such as setting headers. You can chain calls together to construct complex requests.

val complexHolder : WSRequestHolder = holder.withHeaders(...)
                                            .withTimeout(...)
                                            .withQueryString(...)

You end by calling a method corresponding to the HTTP method you want to use. This ends the chain, and uses all the options defined on the built request in the WSRequestHolder.

val futureResponse : Future[Response] = complexHolder.get()

This returns a Future[Response] where the Response contains the data returned from the server.

§Request with authentication

If you need to use HTTP authentication, you can specify it in the builder, using a username, password, and an AuthScheme. Options for the AuthScheme are BASIC, DIGEST, KERBEROS, NONE, NTLM, and SPNEGO.

import com.ning.http.client.Realm.AuthScheme

WS.url(url).withAuth(user, password, AuthScheme.BASIC).get()

§Request with follow redirects

If an HTTP call results in a 302 or a 301 redirect, you can automatically follow the redirect without having to make another call.

WS.url(url).withFollowRedirects(true).get()

§Request with query parameters

Parameters can be specified as a series of key/value tuples.

WS.url(url).withQueryString("paramKey" -> "paramValue").get()

§Request with additional headers

Headers can be specified as a series of key/value tuples.

WS.url(url).withHeaders("headerKey" -> "headerValue").get()

If you are sending plain text in a particular format, you may want to define the content type explicitly.

WS.url(url).withHeaders("Content-Type" -> "text-xml").post(xmlString)

§Request with virtual host

A virtual host can be specified as a string.

WS.url(url).withVirtualHost("192.168.1.1").get()

§Request with time out

If you need to give a server more time to process, you can use withTimeout to set a value in milliseconds. You may want to use this for extremely large files.

WS.url(url).withTimeout(1000).get()

§Submitting form data

To post url-form-encoded data a Map[String, Seq[String]] needs to be passed into post.

WS.url(url).post(Map("key" -> Seq("value")))

§Submitting JSON data

The easiest way to post JSON data is to use the JSON library.

import play.api.libs.json._
val data = Json.obj(
  "key1" -> "value1",
  "key2" -> "value2"
)
val futureResponse: Future[Response] = WS.url(url).post(data)

§Submitting XML data

The easiest way to post XML data is to use XML literals. XML literals are convenient, but not very fast. For efficiency, consider using an XML view template, or a JAXB library.

val data = <person>
  <name>Steve</name>
  <age>23</age>
</person>
val futureResponse: Future[Response] = WS.url(url).post(data)

§Processing the Response

Working with the Response is easily done by mapping inside the Future.

The examples given below have some common dependencies that will be shown once here for brevity.

An execution context, required for Future.map:

implicit val context = scala.concurrent.ExecutionContext.Implicits.global

and a case class that will be used for serialization / deserialization:

case class Person(name: String, age: Int)

§Processing a response as JSON

You can process the response as a JSON object by calling response.json.

val futureResult: Future[String] = WS.url(url).get().map {
  response =>
    (response.json \ "person" \ "name").as[String]
}

The JSON library has a useful feature that will map an implicit Reads[T] directly to a class:

import play.api.libs.json._
import play.api.libs.functional.syntax._

implicit val personReads: Reads[Person] = (
  (__ \ "name").read[String]
    and (__ \ "age").read[Int]
  )(Person)

val futureResult: Future[JsResult[Person]] = WS.url(url).get().map {
  response => (response.json \ "person").validate[Person]
}

§Processing a response as XML

You can process the response as an XML literal by calling response.xml.

val futureResult: Future[scala.xml.NodeSeq] = WS.url(url).get().map {
  response =>
    response.xml \ "message"
}

§Processing large responses

Calling get() or post() will cause the body of the request to be loaded into memory before the response is made available. When you are downloading with large, multi-gigabyte files, this may result in unwelcome garbage collection or even out of memory errors.

WS lets you use the response incrementally by using an iteratee.

import play.api.libs.iteratee._

def fromStream(stream: OutputStream): Iteratee[Array[Byte], Unit] = Cont {
  case [email protected] =>
    stream.close()
    Done((), e)
  case Input.El(data) =>
    stream.write(data)
    fromStream(stream)
  case Input.Empty =>
    fromStream(stream)
}

val outputStream: OutputStream = new BufferedOutputStream(new FileOutputStream(file))
val futureResponse: Future[Unit] = WS.url(url).withTimeout(3000).get {
  headers =>
    fromStream(outputStream)
}.flatMap(_.run)

This is an iteratee that will receive a portion of the file as an array of bytes, write those bytes to an OutputStream, and close the stream when it receives the EOF signal. Until it receives an EOF signal, the iteratee will keep running.

WS doesn’t send EOF to the iteratee when it’s finished – instead, it redeems the returned future.
In fact, WS has no right to feed EOF, since it doesn’t control the input. You may want to feed the result of multiple WS calls into that iteratee (maybe you’re building a tar file on the fly), and if WS feeds EOF, the stream will close unexpectedly. Sending EOF to the stream is the caller’s responsibility.

We do this by calling Iteratee.run which will push an EOF into the iteratee when the future is redeemed.

POST and PUT calls use a slightly different API than GET calls: instead of post(), you call postAndRetrieveStream(body) which has the same effect.

WS.url(url).postAndRetrieveStream(body) { headers =>
  Iteratee.foreach { bytes => logger.info("Received bytes: " + bytes.length) }
}

§Common Patterns and Use Cases

§Chaining WS calls

Using for comprehensions is a good way to chain WS calls in a trusted environment. You should use for comprehensions together with Future.recover to handle possible failure.

val futureResponse: Future[Response] = for {
  responseOne <- WS.url(urlOne).get()
  responseTwo <- WS.url(responseOne.body).get()
  responseThree <- WS.url(responseTwo.body).get()
} yield responseThree

futureResponse.recover {
  case e: Exception =>
    val exceptionData = Map("error" -> Seq(e.getMessage))
    WS.url(exceptionUrl).post(exceptionData)
}

§Using in a controller

You can compose several promises and end with a Future[Result] that can be handled directly by the Play server, using the Action.async builder defined in Handling Asynchronous Results.

def feedTitle(feedUrl: String) = Action.async {
  WS.url(feedUrl).get().map { response =>
    Ok("Feed title: " + (response.json \ "title").as[String])
  }
}

§Advanced Usage

You can also get access to the underlying async client.

import com.ning.http.client.AsyncHttpClient

val client:AsyncHttpClient = WS.client

This is important in a couple of cases. WS has a couple of limitations that require access to the client:

§Configuring WS

Use the following properties to configure the WS client

§Timeouts

There are 3 different timeouts in WS. Reaching a timeout causes the WS request to interrupt.

You can define each timeout in application.conf with respectively: ws.timeout.connection, ws.timeout.idle, ws.timeout.request.

Alternatively, ws.timeout can be defined to target both Connection Timeout and Connection Idle Timeout.

The request timeout can be specified for a given connection with withRequestTimeout.

Example:

WS.url("http://playframework.org/").withRequestTimeout(10000 /* in milliseconds */)

Next: OpenID Support in Play