Book Image

Play Framework Essentials

By : Julien Richard-Foy
Book Image

Play Framework Essentials

By: Julien Richard-Foy

Overview of this book

Table of Contents (14 chapters)
Play Framework Essentials
Credits
About the Author
About the Reviewers
www.PacktPub.com
Preface
Index

Building HTTP responses


Actions process HTTP requests and return HTTP responses. So far, you have seen how HTTP requests were routed by the framework to call your application's code. Now, let's see how you can reply with HTTP responses.

An HTTP response has a status code and some optional headers and can be followed by a body. So, to build a response, you have to at least supply a status code.

For instance, your current action definitions return HTTP responses with status code 501 (Not Implemented). Try changing it to Ok (or ok() in Java) and reload the page in your browser (or perform a request with cURL), you should get a response with status code 200 (OK). Play provides helpers to build responses with common status codes. Examples of other predefined statuses are NotFound (notFound() in Java), BadRequest (badRequest() in Java) or InternalServerError (internalServerError() in Java).

More generally, you can build an HTTP response with any status code by using the Status function (or status in Java). For instance, Status(200) (or status(200) in Java) builds an empty response with status 200 and the Content-Length header set to 0.

In addition to a status code, HTTP responses can contain data in a body. For instance, the Application.index action that was provided with the application skeleton returns an HTML document. Alternatively, we can make it return a text document:

val index = Action {
  Ok("Just Play Scala")
}

The equivalent Java code is as follows:

public static Result index() {
  return ok("Just Play Java");
}

In Scala, you can supply a body to your response by calling the apply method of a status; Ok("Just Play Scala") builds an HTTP response with status code 200 and a body that contains "Just Play Scala". Similarly, in Java, you can just pass the response body as a parameter to the status function.

Clients consuming a web service might want (or need) to know the content type of response body data. HTTP responses have a header for this, which is Content-Type. As the value of this header is tied to the type of values you send, Play automatically infers the former from the latter, freeing you from writing redundant code.

In practice, in Scala, writing Ok("foo") builds an HTTP response with a Content-Type header set to text/plain because "foo" has type String. Play infers the right content type by using the type of the value you pass as response body. The type signature of the apply method of the Result type is the following:

def apply[A](a: A)(implicit w: play.api.mvc.Writeable[A]): Result

This means that you can supply a response of type A only if you provide an implicit value of type play.api.mvc.Writeable[A] . The Writeable typeclass actually tells Play which content type to use (and how to serialize it to the HTTP response body). For convenience, Play provides implicit Writeable values for common content types such as JSON, HTML, XML, and plain text. However, if you want to send data of a type that is not currently supported by Play, you have to define the corresponding Writeable instance.

Note

Typeclasses are a feature of the Haskell programming language to achieve ad hoc polymorphism (http://www.haskell.org/tutorial/classes.html). It can be encoded in Scala using parameterized types and implicit parameters.

In Java, the result method helpers, such as ok() and notFound(), are overloaded to support common data types (for example, String, byte[]); ok("foo") builds an HTTP response with a Content-Type header set to text/plain. Data types that are not directly supported must implement the play.mvc.Content interface to be correctly handled by Play. This interface specifies which content type to use and how to serialize the data to the HTTP response body.

Serializing application data in JSON

Now that you know how to build an HTTP response containing a body, your last step to bootstrap your web service consists of returning your business data as a JSON document in the body of your HTTP responses.

Play comes with a rich library for JSON manipulation. Let's start by returning a JSON value in the details action:

import play.api.libs.json.Json

def details(id: Long) = Action {
  shop.get(id) match {
    case Some(item) =>
      Ok(Json.obj(
        "id" -> item.id,
        "name" -> item.name,
        "price" -> item.price
      ))
    case None => NotFound
  }
}

This code tries to retrieve the item in the shop and if found, returns it as a JSON object. The Json.obj function builds a JSON object from a list of name-value pairs. If there is no item with the ID passed as a parameter, the action returns a NotFound response. JSON objects have type JsValue, and Play has a built-in Writeable[JsValue] instance that sets the content type of a JSON response body to application/json.

The equivalent Java code is as follows:

import play.libs.json.Json;

public static Result details(Long id) {
  Item item = shop.get(id);
  if (item != null) {
    return ok(Json.toJson(item));
  } else {
    return notFound();
  }
}

In Java, Play uses the Jackson library (http://jackson.codehaus.org/) to automatically serialize the Item value so that you don't need to explicitly tell how to transform an Item into a JSON object. The Jackson object mapper that performs this task can handle some simple data structures like the Item class, but for more complex data structures (for example, involving cycles or bidirectional associations), you might have to supply your own serialization process by annotating your types with Jackson annotations.

The Scala API does not follow this approach because the Scala language gives convenient mechanisms that allow you to tell how to serialize data without relying on reflection and with minimal boilerplate.

If you call this action from your HTTP client, you will get a response like the following (assuming your shop has an item with the ID 42):

$ curl http://localhost:9000/items/42
{"id":42,"price":4.2,"name":"Play Framework Essentials"}

Similar to the implementation of the details action, here is how you can implement the list action and return the list of the items in the shop as a JSON array. The Java version of the code is as follows:

public static Result list() {
  return ok(Json.toJson(shop.list()));
}

Again, the Json.toJson call delegates the JSON serialization of the list of items to Jackson.

The Scala version of the code is as follows:

val list = Action {
  Ok(Json.arr(shop.list.map(item => Json.obj(
    "id" -> item.id,
    "name" -> item.name,
    "price" -> item.price
  )): _*))
}

We use the Json.arr method to create a JSON array and pass it a collection of JSON objects as a parameter.

You might have noticed that the code defining these JSON objects from the items duplicates the code already written in the details action. Instead, you should isolate the logic corresponding to the serialization of an item into a JSON object as a function and reuse it. Actually, you can do even better; the Play JSON library defines a play.api.libs.json.Writes[A] typeclass that captures the serialization logic for the type A, so you can just write an implicit value of type Writes[Item] and Play will use it when needed. This typeclass has just one method, which is writes(item: Item): JsValue that defines how to transform an Item into a JSON value. The JsValue type is an algebraic data type representing JSON values. For now, you have seen how to define JSON objects (represented by the JsObject type in Play) and arrays (JsArray), using Json.obj and Json.arr, respectively, but there are other types of JsValue such as numbers (JsNumber), strings (JsString), booleans (JsBoolean), and null (JsNull).

Your first reusable JSON serializer for items can be defined and used as follows:

import play.api.libs.json.Writes

implicit val writesItem = Writes[Item] {
  case Item(id, name, price) =>
    Json.obj(
      "id" -> id,
      "name" -> name,
      "price" -> price
    )
}

val list = Action {
  Ok(Json.toJson(shop.list))
}

def details(id: Long) = Action {
  shop.get(id) match {
    case Some(item) => Ok(Json.toJson(item))
    case None => NotFound
  }
}

The implicit value, writesItem, defines the serialization logic for Items. Then, in the list and details actions, we use Json.toJson to transform our items into JSON objects. This toJson method has the following signature:

def toJson[A](a: A)(implicit Writes[A]): JsValue

This means that it can serialize any value of type A if there is an implicit value of type Writes[A] in the implicit scope. Fortunately, Play defines such JSON serializers for common types and can combine them by chaining implicits; this is why Play is able to serialize an Item as well as a List[Item].

Though the code of your writesItem is quite concise, it follows a repetitive pattern. Each field of the Item class is serialized to a JSON field of the same name. Hopefully, Play provides a macro that generates JSON serializers following this pattern, so the previous serializer can be synthesized by just writing the following:

implicit val writesItem = Json.writes[Item]

You might be wondering why automatic generation of JSON serializers is not the default behavior. There are two reasons for this. First, the automatic generation mechanism cannot handle all data types (for example, cyclic data types). Secondly, sometimes you don't want to use the same names for your JSON object fields and your Scala object fields.