Essential Play

Dave Gurnell and Noel Welsh

Version 1.0, April 2015

Introduction

Essential Play is aimed at beginner-to-intermediate Scala developers who want to get started using the Play 2 web framework. The material presented focuses on Play version 2.3, although the approaches introduced are generally applicable to Play 2.2+.

By the end of the course we will have a solid foundation in each of the main libraries Play provides for building sites and services:

Many thanks to Richard Dallaway, Jonathan Ferguson, and the team at Underscore for their invaluable contributions and extensive proof reading.

Conventions Used in This Book

This book contains a lot of technical information and program code. We use the following typographical conventions to reduce ambiguity and highlight important concepts:

Typographical Conventions

New terms and phrases are introduced in italics. After their initial introduction they are written in normal roman font.

Terms from program code, filenames, and file contents, are written in monospace font. Note that we do not distinguish between singular and plural forms. For example, might write String or Strings to refer to the java.util.String class or objects of that type.

References to external resources are written as hyperlinks. References to API documentation are written using a combination of hyperlinks and monospace font, for example: scala.Option.

Source Code

Source code blocks are written as follows. Syntax is highlighted appropriately where applicable:

object MyApp extends App {
  println("Hello world!") // Print a fine message to the user!
}

Some lines of program code are too wide to fit on the page. In these cases we use a continuation character (curly arrow) to indicate that longer code should all be written on one line. For example, the following code:

println("This code should all be written ↩
  on one line.")

should actually be written as follows:

println("This code should all be written on one line.")

Callout Boxes

We use three types of callout box to highlight particular content:

Tip callouts indicate handy summaries, recipes, or best practices.

Advanced callouts provide additional information on corner cases or underlying mechanisms. Feel free to skip these on your first read-through—come back to them later for extra information.

Warning callouts indicate common pitfalls and gotchas. Make sure you read these to avoid problems, and come back to them if you’re having trouble getting your code to run.

1 Getting Started

In this chapter we will discuss how to get started with Play. Our main focus will be on building and running the exercises in this book, but we will also discuss installing and using SBT, the Scala Build System, to compile, test, run, and deploy Play projects.

1.1 Installing the Exercises

The exercises and sample code in this book are all packaged with a copy of SBT. All you need to get started are Git, a Java runtime, and an Internet connection to download other dependencies.

Start by cloning the Github repository for the exercises:

bash$ git clone https://github.com/underscoreio/essential-play-code.git

bash$ cd essential-play-code

dave@Jade ~/d/p/essential-play-code> git status
# On branch exercises...

bash$ ls -1
chapter1-hello
chapter2-calc
chapter2-chat
# And so on...

The repository has two branches, exercises and solutions, each containing a set of self-contained Play projects in separate directories. We have included one exercise to serve as an introduction to SBT. Change to the chapter1-hello directory and start SBT using the shell script provided:

bash$ cd chapter1-hello

bash$ ./sbt.sh
# Lots of output here...
# The first run will take a while...

[app] $

“Downloading the Internet”

The first commands you run in SBT will cause it to download various dependencies, including libraries for Play, the Scala runtime, and even the Scala compiler. This process can take a while and is affectionately known to Scala developers as “downloading the Internet”.

These files are only downloaded once, after which SBT caches them on your system. Be prepared for delays of up to a few minutes:

  • the first time you start SBT;
  • the first time you compile your code;
  • the first time you compile your unit tests.

Things will speed up considerably once these files are cached.

Once SBT is initialised, your prompt should change to [app] $, which is the name of the Play project we’ve set up for you. You are now interacting with SBT. Compile the project using the compile command to check everything is working:

[app] $ compile
# Lots of output here...
# The first run will take a while...
[info] Updating {file:/Users/dave/dev/projects/essential-play-code/}app...
[info] Resolving jline#jline;2.12 ...
[info] Done updating.
[info] Compiling 3 Scala sources and 1 Java source to ↩
       /Users/dave/dev/projects/essential-play-code/target/scala-2.11/classes...
[success] Total time: 7 s, completed 13-Jan-2015 11:15:39

[app] $

If the project compiles successfully, try running it. Enter run to start a development web server and access it at http://localhost:9000 to test out the app:

[app] $ run

--- (Running the application from SBT, auto-reloading is enabled) ---

[info] play - Listening for HTTP on /0:0:0:0:0:0:0:0:9000

(Server started, use Ctrl+D to stop and go back to the console...)

# Play waits until we open a web browser...
[info] play - Application started (Dev)

If everything worked correctly you should see the message "Hello world!" in your browser. Congratulations—you have run your first Play web application!

1.1.1 Other Exercises in this Book

The process you have used here is the same for each exercise in this book:

  1. change to the relevant exercise directory;
  2. start SBT;
  3. issue the relevant SBT commands to compile and run your code.

You will find instructions for each exercise in the text of the book. Also look out for comments like the following in the exercise source code:

// TODO: Complete this bit!

These tell you where you need to modify the code to complete the exercises. There are complete solutions to each exercise in the solutions branch of the repository.

Getting Help

Resist the temptation to look at the solutions if you get stuck! You will make mistakes when you first start programming Play applications, but mistakes are the best way to teach yourself.

If you do get stuck, join our Gitter chat room to get help from the authors and other students.

Try to get the information you need to solve the immediate problem without gaining complete access to the solution code. You’ll proceed slower this way but you’ll learn a lot faster and the knowledge will stick with you longer.

1.2 Installing SBT

As we discussed in the previous section, each exercise and solution is bundled with its own scripts and binaries for SBT. This is a great setup for this book, but after you’ve finished the exercises you will want to install SBT properly so you can work on your own applications. In this section we will discuss the options available to you to do this.

1.2.1 How Does SBT Work?

SBT relies heavily on account-wide caches to store project dependencies. By default these caches are located in two folders:

SBT downloads dependencies on demand and caches them for future use in ~/.ivy2. In fact, the JAR we run to boot SBT is actually a launcher (typically named sbt-launch.jar) that downloads and caches the correct versions of SBT and Scala needed for our project.

This means we can use a single launcher to compile and run projects with different version requirements for libraries, SBT, and Scala. We are can use separate launchers for each project, or a single launcher that covers all projects on our development machine. The shared caches allow multiple SBT launchers to work indepdently without conflict.

Despite the convenience of these account-wide caches, they have two important drawbacks to be aware of:

  1. the first time we build a project we must be connected to the Internet for SBT to download the required dependencies; and

  2. as we saw in the previous section, the first build of a project may take a long time.

1.2.2 Flavours of SBT

SBT is available from a number of sources under a variety of different names. Here are the main options available, any of which is a suitable starting point for your own applications:

Legacy Play Distributions

Older downloads from http://playframework.com shipped with a built-in play command that was also an alias for SBT. However, the old Play distributions configured SBT with non-standard cache directories that meant it did not play nicely with other installs.

We recommend replacing any copies of the legacy play command with one of the other options described above. Newer versions of Play are shipped with Activator, which interoperates well with other locally installed copies of SBT.

1.3 Using SBT

At the beginning of this chapter we cloned the Git repository of the exercises for this book and ran our first SBT commands on the chapter1-hello sample project. Let’s revisit this project to investigate the standard SBT commands for compiling, running, and deploying Play applications.

Change to the chapter1-hello directory if you are not already there and start SBT using the shell script provided:

bash$ cd essential-play-code/chapter1-hello

bash$ ./sbt.sh

[app] $

SBT with and without Play

Play is distributed in two components:

  • a set of libraries used by our web applications at runtime;

  • an SBT plugin that customises the default behaviour of SBT, adding and altering commands to help us build applications for the web.

This section covers the behaviour of SBT with the Play plugin activated. We have included callout boxes like this one to highlight the differences from vanilla SBT.

1.3.1 Interative and Batch Modes

We can start SBT in two modes: interactive mode and batch mode. Batch mode is useful for continuous integration and delivery, while interactive mode is faster and more convenient for use in development. Most of our time in this book will be spent in interactive mode.

We start interactive mode be running SBT with no command line arguments. SBT displays a command prompt where we can enter commands such as compile, run, and clean to build our code. Pressing Ctrl+D quits SBT when we’re done:

bash$ ./sbt.sh

[app] $ compile
# SBT compiles our code and we end up back in SBT...

[app] $ ^D
# Ctrl+D quits back to the OS command prompt

bash$

We start SBT in batch mode by issuing commands as arguments on the OS command line. SBT executes the commands immediately and then exits back to the OS. The commands—compile, run, clean and so on—are the same in both modes:

bash$ ./sbt.sh compile
# SBT compiles our code and we end up back on the OS command prompt...

bash$

The SBT command prompt

The default SBT command prompt is a single echelon:

>

Play changes this to the name of the project surrounded by square brackets:

[app] $

You will find the prompt changing as you switch back and forth between Play projects and vanilla Scala projects.

Directory structure of non-Play projects

By default SBT uses two directories to store application and test code:

  • src/main/scala—Scala application code;
  • src/test/scala—Scala unit tests.

Play replaces these with the app, app/assets, views, public, conf, and test directories, providing locations for the extra files required to build a web application.

1.3.2 Common SBT Commands

The following table contains a summary of the most useful SBT commands for working with Play. Each command is covered in more detail below.

Many commands have dependencies listed in the right-hand column. For example, compile depends on update, run depends on compile, and so on. When we run a command SBT automatically runs its dependencies as well. For example, whwnever we run the compile command, SBT will run update for us automatically.

SBT Command Purpose Notes and Dependencies
update Resolves and caches library dependencies No dependencies
compile Compiles application code, including code under app, app/assets, and views Depends on update
run Runs application in development mode, continuously recompiles on demand Depends on compile
console Starts an interactive Scala prompt Depends on compile
test:compile Compiles all unit tests Depends on compile
test Compiles and runs all unit tests Depends on test:compile
testOnly foo.Bar Compiles and runs unit tests defined in the class foo.Bar Depends on test:compile
stage Gathers all dependencies into a single stand-alone directory Depends on compile
dist Gathers staged files into a ZIP file Depends on stage
clean Deletes temporary build files under ${projecthome}/target No dependencies
eclipse Generates Eclipse project files No dependencies, requires the sbteclipse plugin

1.3.3 Compiling and Cleaning Code

The compile and test:compile commands compile our application and unit tests respectively. The clean command deletes the generated class files in case we want to rebuild from scratch (clean is not normally required as we shall see below).

Let’s clean the example project from the previous section and recompile the code as an example:

bash$ ./sbt.sh
[info] Loading project definition from ↩
       /Users/dave/dev/projects/essential-play-code/project
[info] Set current project to app (in build file:/.../essential-play-code/)

[app] $ clean
[success] Total time: 0 s, completed 13-Jan-2015 11:15:32

[app] $ compile
[info] Updating {file:/Users/dave/dev/projects/essential-play-code/}app...
[info] Resolving jline#jline;2.12 ...
[info] Done updating.
[info] Compiling 3 Scala sources and 1 Java source to ↩
       /Users/dave/dev/projects/essential-play-code/ ↩
       target/scala-2.11/classes...
[success] Total time: 7 s, completed 13-Jan-2015 11:15:39

[app] $

In the output from compile SBT tells us how many source files it compiled and how long compilation took—7 seconds in this case! Fortunately we normally don’t need to wait this long. The compile and test:compile commands are incremental—they automatically recompile only the files that have changed since the last time we compiled the code. We can see the effect of incremental compilation by changing our application and running compile again. Open app/controllers/AppController.scala in an editor and change the "Hello World!" line to greet you by name:

package controllers

import play.api.Logger
import play.api.Play.current
import play.api.mvc._

import models._

object AppController extends Controller {
  def index = Action { request =>
    Ok("Hello Dave!")
  }
}

Now re-run the compile command:

[app] $ compile
[info] Compiling 1 Scala source to ↩
       /Users/dave/dev/projects/essential-play-code/target/scala-2.11/classes...
[success] Total time: 1 s, completed 13-Jan-2015 12:26:16

[app] $

One Scala file compiled in one second. Much better! Incremental compilation means we can rely on compile and test:compile to do the right thing to recompile our code—we rarely need to use clean to rebuild from scratch.

Compiling in interactive mode

Another reason our first compile command was slow was because SBT spent a lot of time loading the Scala compiler for the first time. If we keep SBT open in interactive mode, subsequent compile commands become much faster.

1.3.4 Watch Mode

We can prefix any SBT command with a ~ to run the command in watch mode. SBT watches our codebase and reruns the specified task whenever we change a source file. Type ~compile at the prompt to see this in action:

[app] $ ~compile
[success] Total time: 0 s, completed 13-Jan-2015 12:31:09
1. Waiting for source changes... (press enter to interrupt)

SBT tells us it is “waiting for source changes”. Whenever we edit a source file it will trigger the compile task and incrementally recompile the changed code. Let’s see this by introducing a compilation error to AppController.scala. Open the source file again and delete the closing " from "Hello Name!". As soon as we save the file we see the following in SBT:

[info] Compiling 1 Scala source to ↩
       /Users/dave/dev/projects/essential-play-code/target/scala-2.11/classes...
[error] /Users/dave/dev/projects/essential-play-code/app/ ↩
        controllers/AppController.scala:11: unclosed string literal
[error]     Ok("Hello Dave!)
[error]        ^
[error] /Users/dave/dev/projects/essential-play-code/app/ ↩
        controllers/AppController.scala:12: ')' expected but '}' found.
[error]   }
[error]   ^
[error] two errors found
[error] (compile:compile) Compilation failed
[error] Total time: 0 s, completed 13-Jan-2015 12:32:45
2. Waiting for source changes... (press enter to interrupt)

The compiler has picked up the error and produced some error messages as a result. If we fix the error again and save the file, the error messages disappear:

[success] Total time: 0 s, completed 13-Jan-2015 12:33:55
3. Waiting for source changes... (press enter to interrupt)

Watch mode is extremely useful for getting instant feedback during development. Simply press Enter when you’re done to return to the SBT command prompt.

Watch mode and other tasks

We can use watch mode with any SBT command. For example:

  • ~compile watches our code and recompiles it whenever we change a file;

  • ~test watches our code and reruns the unit tests whenever we change a file; and

  • ~dist watches our code and builds a new distributable ZIP archive whenever we change a file.

This behaviour is built into SBT and works irrespective of whether we’re using Play.

1.3.5 Running a Development Web Server

We can use the run command to run our application in a development environment. This command starts a development web server, watches for incoming connections, and recompiles our code whenever an incoming request is received.

Let’s see this in action. First clean the codebase, then enter run at the SBT prompt:

[app] $ clean
[success] Total time: 0 s, completed 13-Jan-2015 12:44:07
[app] $ run
[info] Updating {file:/Users/dave/dev/projects/essential-play-code/}app...
[info] Resolving jline#jline;2.12 ...
[info] Done updating.

--- (Running the application from SBT, auto-reloading is enabled) ---

[info] play - Listening for HTTP on /0:0:0:0:0:0:0:0:9000

(Server started, use Ctrl+D to stop and go back to the console...)

SBT starts up a web server on /0:0:0:0:0:0:0:0:9000 (which means localhost:9000 in IPv6-speak) and waits for a browser to connect. Open up http://localhost:9000 in a web browser and watch the SBT console to see what happens. Play receives the incoming request and recompiles and runs the application to respond:

[info] Compiling 3 Scala sources and 1 Java source to ↩
       /Users/dave/dev/projects/essential-play-code/target/scala-2.11/classes...
[info] play - Application started (Dev)

If we reload the web page without changing any source code, Play simply serves up the response again. However, if we edit the code and reload the page, Play recompiles the application before responding.

Differences between run and watch mode

The run command is a great way to get instant feedback when developing an application. However, we have to send a request to the web browser to get Play to recompile the code. In contrast, watch mode recompiles the application as soon as we change a file.

Sometimes using ~compile or ~test can be a more efficient way of working. It depends on how much code we’re rewriting and how many compile errors we are likely to introduce during coding.

Running non-Play applications

SBT’s default run command is much simpler than the command provided by Play. It simply runs a command line or graphical application and exits when it terminates. Play provides the development web server and continuous compilation functionality.

1.3.5.1 Running Unit Tests

The test and testOnly commands are used to run unit tests. test runs all unit tests for the application; testOnly runs a single test suite. Let’s use test to test our sample application:

[app] $ test
[info] Compiling 1 Scala source to ↩
       /Users/dave/dev/projects/essential-play-code/target/scala-2.10/test-classes...
[info] ApplicationSpec:
[info] AppController
[info] - must respond with a friendly message
[info] ScalaTest
[info] Run completed in 934 milliseconds.
[info] Total number of tests run: 1
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[info] Passed: Total 1, Failed 0, Errors 0, Passed 1
[success] Total time: 2 s, completed 14-Jan-2015 14:02:45

[app] $

Because this is the first time we’ve run test, SBT starts by compiling the test suite. It then runs our sample code’s single test suite, controllers.AppControllerSpec. The suite contains a single test that checks whether our greeting starts with the word "Hello".

We don’t have many tests for our sample application so testing is fast. If we had lots of test suites we could focus on a single suite using the testOnly command. testOnly takes the fully qualified class name of the desired suite as an argument:

[app] $ testOnly controllers.AppControllerSpec
[info] ScalaTest
[info] Run completed in 44 milliseconds.
[info] Total number of tests run: 0
[info] Suites: completed 0, aborted 0
[info] Tests: succeeded 0, failed 0, canceled 0, ignored 0, pending 0
[info] No tests were executed.
[info] Passed: Total 0, Failed 0, Errors 0, Passed 0
[info] No tests to run for test:testOnly
[success] Total time: 1 s, completed 14-Jan-2015 14:06:42

[app] $

As with compile, both of these commands can run in watch mode by prefixing them with a ~. Whenever we change and save a file, SBT will recompile it and rerun our tests for us.

1.3.6 Packaging and Deploying the Application

The stage command bundles the compiled application and all of its dependencies into a single directory under the directory target/universal/stage. Let’s see this in action:

[app] $ stage
[info] Packaging /Users/dave/dev/projects/essential-play-code/ ↩
       target/scala-2.10/app_2.10-0.1-SNAPSHOT-sources.jar ...
[info] Done packaging.
[info] Packaging /Users/dave/dev/projects/essential-play-code/ ↩
       target/scala-2.10/app_2.10-0.1-SNAPSHOT.jar ...
[info] Main Scala API documentation to /Users/dave/dev/projects/ ↩
       essential-play-code/target/scala-2.10/api...
[info] Done packaging.
[info] Wrote /Users/dave/dev/projects/essential-play-code/ ↩
       target/scala-2.10/app_2.10-0.1-SNAPSHOT.pom
[info] Packaging /Users/dave/dev/projects/essential-play-code/ ↩
       target/app-0.1-SNAPSHOT-assets.jar ...
[info] Done packaging.
model contains 10 documentable templates
[info] Main Scala API documentation successful.
[info] Packaging /Users/dave/dev/projects/essential-play-code/ ↩
       target/scala-2.10/app_2.10-0.1-SNAPSHOT-javadoc.jar ...
[info] Done packaging.
[success] Total time: 1 s, completed 14-Jan-2015 14:08:14

[app] $

Now press Ctrl+D to quit SBT and take a look at the package created by the stage command:

bash$ ls -l target/universal/stage/
total 0
drwxr-xr-x   4 dave  staff   136 14 Jan 14:11 bin
drwxr-xr-x   3 dave  staff   102 14 Jan 14:11 conf
drwxr-xr-x  44 dave  staff  1496 14 Jan 14:11 lib
drwxr-xr-x   3 dave  staff   102 14 Jan 14:08 share

bash$ ls -l target/universal/stage/bin
total 40
-rwxr--r--  1 dave  staff  12210 14 Jan 14:11 app
-rw-r--r--  1 dave  staff   6823 14 Jan 14:11 app.bat

SBT has created a directory target/universal/stage containing all the dependencies we need to run the application. It has also created two executable scripts under target/universal/stage/bin to set an appropriate classpath and run the application from the command prompt. If we run one of these scripts, the app starts up and allows us to connect as usual:

bash$ target/universal/stage/bin/app
Play server process ID is 22594
[info] play - Application started (Prod)
[info] play - Listening for HTTP on /0:0:0:0:0:0:0:0:9000

The contents of target/universal/stage can be copied onto a remote web server and run as a standalone application. We can use standard Unix commands such as rsync and scp to achieve this. Sometimes, however, it is more convenient to have an archive to distribute. We can use the dist command to create a ZIP of target/universal/stage for easy distribution:

[app] $ dist
[info] Wrote /Users/dave/dev/projects/essential-play-code/ ↩
       target/scala-2.10/app_2.10-0.1-SNAPSHOT.pom
[info]
[info] Your package is ready in /Users/dave/dev/projects/ ↩
       essential-play-code/target/universal/app-0.1-SNAPSHOT.zip
[info]
[success] Total time: 2 s, completed 14-Jan-2015 14:15:50

Packaging non-Play applications

The stage and dist commands are specific to the Play plugin. SBT contains a built-in package command for building non-Play projects, but this functionality is beyond the scope of this book.

1.3.7 Working With Eclipse

The sample SBT project includes a plugin called sbteclipse that generates project files for Eclipse. Run the eclipse command to see this in action:

[app] $ eclipse
[info] About to create Eclipse project files for your project(s).
[info] Successfully created Eclipse project files for project(s):
[info] app

[app] $

Now start Eclipse and import your SBT project using File menu > Import… > General > Existing files into workspace and select the root directory of the project source tree in the Select root directory field. Click Finish to add a project called app to the Eclipse workspace.

1.3.8 Working With Intellij IDEA

Newer versions of the Scala plugin for Intellij IDEA support direct import of SBT projects from within the IDE. Choose File menu > Import… > SBT and select the root directory of the project source tree. The import wizard will do the rest automatically.

1.3.9 Configuring SBT

A full discussion of how to write SBT project configurations is beyond the scope of this book. For more information we recommend reading the tutorial on the SBT web site and the build documentation on the Play web site. The sample projects and exercises for this book will provide a good starting point for your own projects.

2 The Basics

In this chapter we will introduce five fundamental concepts used to process web requests in Play: actions, controllers, routes, requests, and results. With these concepts we will be able to read incoming HTTP requests, pass them to the correct module of the application code, extract appropriate information, and send a response back to the client.

2.1 Directory Structure

Play projects use the following directory structure, which is quite different to the standard structure of an SBT project:

root/
 +- app/        # Scala application code
 |   |
 |   +- assets/ # client assets for compilation by SBT
 |              # (Javascript, Coffeescript, Less CSS, and so on)
 |
 +- views/      # Twirl templates for compilation by SBT
 |
 +- public/     # static assets to be served by the application
 |              # (HTML, Javascript, CSS, and so on)
 |
 +- conf/       # runtime configuration files bundled with the
 |              # deployed application (route config, logs, DB config)
 |
 +- test/       # Scala unit tests
 |
 +- logs/       # logs generated by the development server
 |
 +- project/    # configuration files and temporary files
 |
 +- target/     # temporary directory used to store completed builds

Most of our time in this book will be spent editing Scala files in the app and test directories and the routes configuration file in the conf directory. You can find out more about the asset directories and configuration files in the Play documentation.

2.2 Actions, Controllers, and Routes

We create Play web applications from actions, controllers, and routes. In this section we will see what each part does and how to wire them together.

2.2.1 Hello, World!

Actions are objects that handle web requests. They have an apply method that accepts a play.api.mvc.Request and returns a play.api.mvc.Result. We create them using one of several apply methods on the play.api.mvc.Action companion:

import play.api.mvc.Action

Action { request =>
  Ok("Hello, world!")
}

We package actions inside Controllers. These are singleton objects that contain action-producing methods:

package controllers

import play.api.mvc.{ Action, Controller }

object HelloController extends Controller {
  def hello = Action { request =>
    Ok("Hello, world!")
  }

  def helloTo(name: String) = Action { request =>
    Ok(s"Hello, $name!")
  }
}

We use routes to dispatch incoming requests to Actions. Routes choose Actions based on the HTTP method and path of the request. We write routes in a Play-specific DSL that is compiled to Scala by SBT:

GET /      controllers.HelloController.hello
GET /:name controllers.HelloController.helloTo(name: String)

We’ll learn more about this DSL in the next section. By convention we place controllers in the controllers package in the app/controllers folder, and routes in a conf/routes configuration file.

The structure of our minimal Play application is as follows:

root/
 +- app/
 |   +- controllers/
 |       +- HelloController.scala # Controller and actions (Scala code)
 +- conf/
 |   +- routes                    # Routes (Play routing DSL)
 +- project/
 |   +- plugins.sbt               # Load the Play plugin (SBT code)
 +- build.sbt                     # Configure the build (SBT code)

2.2.2 The Anatomy of a Controller

Let’s take a closer look at the controller in the example above. The code in use comes from two places:

The controller, called HelloController, is a subtype of play.api.mvc.Controller. It defines two Action-producing methods, hello and helloTo. Our routes specify which of these methods to call when a request comes in.

Note that Actions and Controllers have different lifetimes. Controllers are created when our application boots and persist until it shuts down. Actions are created and executed in response to incoming Requests and have a much shorter lifespan. Play passes parameters from our routes to the method that creates the Action, not to the action itself.

Each of the example Actions creates an Ok response containing a simple message. Ok is a helper object inherited from Controller. It has an apply method that creates Results with HTTP status 200. The actual return type of Ok.apply is play.api.mvc.Result.

Play uses the type of the argument to Ok.apply to determine the Content-Type of the Result. The String arguments in the example create a Results of type text/plain. Later on we’ll see how to customise this behaviour and create results of different types.

2.2.3 Take Home Points

The backbone of a Play web application is made up of Actions, Controllers, and routes:

We typically place controllers in a Controllers package in the app/controllers folder. Routes go in the conf/routes file (no filename extension).

In the next section we will take a closer look at routes.

2.2.4 Exercise: Time is of the Essence

The chapter2-time directory in the exercises contains an unfinished Play application for telling the time.

Complete this application by filling in the missing actions and routes. Implement the three missing actions described in the comments in app/controllers/TimeController.scala and complete the conf/routes file to hook up the specified URLs.

We’ve written this project using the Joda Time library to handle time formatting and time zone conversion. Don’t worry if you haven’t used the library before—the TimeHelpers trait in TimeController.scala contains all of the functionality needed to complete the task at hand.

Test your code using curl if you’re using Linux or OS X or a browser if you’re using Windows:

bash$ curl -v 'http://localhost:9000/time'
# HTTP headers...
4:18 PM

bash$ curl -v 'http://localhost:9000/time/zones'
# HTTP headers...
Africa/Abidjan
Africa/Accra
Africa/Addis_Ababa
# etc...

bash$ curl -v 'http://localhost:9000/time/CET'
# HTTP headers...
5:21 PM

bash$

Be agile!

Complete the exercises by coding small units of end-to-end functionality. Start by implementing the simplest possible action that you can test on the command line:

// Action:
def time = Action { request =>
  Ok("TODO: Complete")
}

// Route:
GET /time controllers.TimeController.time

Write the route for this action and test it using curl before you move on. The faster you get to running your code, the faster you will learn from any mistakes.

Answer the following questions when you’re done:

  1. What happens when you connect to the application using the following URL? Why does this not work as expected and how can you work around the behaviour?

    bash$ curl -v 'http://localhost:9000/time/Africa/Abidjan'
  2. What happens when you send a POST request to the application?

    bash$ curl -v -X POST 'http://localhost:9000/time'`

The main task in the actions in TimeController.scala is to convert the output of the various methods in TimeHelpers to a String so we can wrap it in an Ok() response:

def time = Action { request =>
  Ok(timeToString(localTime))
}

def timeIn(zoneId: String) = Action { request =>
  val time = localTimeInZone(zoneId)
  Ok(time map timeToString getOrElse "Time zone not recognized.")
}

def zones = Action { request =>
  Ok(zoneIds mkString "\n")
}

Hooking up the routes would be straightforward, except we included one gotcha to trip you up. You must place the route for TimeController.zones above the route for TimeController.timeIn:

GET /time        controllers.TimeController.time
GET /time/zones  controllers.TimeController.zones
GET /time/:zone  controllers.TimeController.timeIn(zone: String)

If you put these two in the wrong order, Play will treat the word zones in /time/zones as the name of a time zone and route the request to TimeController.timeIn("zones") instead of TimeController.zones.

The answers to the questions are as follows:

  1. The mistake here is that we haven’t escaped the / in Africa/Abidjan. Play interprets this as a path with three segments but our route will only match two. The result is a 404 response.

    If we encode the value as Africa%2FAbidjan the application will respond as desired. The %2F is decoded by Play before the argument is passed to timeIn:

    bash$ curl 'http://localhost:9000/time/Africa%2FAbidjan'
    4:38 PM
  2. Our routes are only configured to match incoming GET requests so POST requests result in a 404 response.

2.3 Routes in Depth

The previous section introduced Actions, Controllers, and routes. Actions and Controllers are standard Scala code, but routes are something new and specific to Play.

We define Play routes using a special DSL that compiles to Scala code. The DSL provides both a convenient way of mapping URIs to method calls and a way of mapping method calls back to URIs. In this section we will take a deeper look at Play’s routing DSL including the various ways we can extract parameters from URIs.

2.3.1 Path Parameters

Routes associate URI patterns with action-producing method calls. We can specify parameters to extract from the URI and pass to our controllers. Here are some examples:

# Fixed route (no parameters):
GET /hello controllers.HelloController.hello

# Single parameter:
GET /hello/:name controllers.HelloController.helloTo(name: String)

# Multiple parameters:
GET /send/:msg/to/:user ↩
  controllers.ChatController.send(msg: String, user: String)

# Rest-style parameter:
GET /download/*filename ↩
  controllers.DownloadController.file(filename: String)

The first example assocates a single URI with a parameterless method. The match must be exact—only GET requests to /hello will be routed. Even a trailing slash in the URI (/hello/) will cause a mismatch.

The second example introduces a single-segment parameter written using a leading colon (‘:’). Single-segment parameters match any continuous set of characters excluding forward slashes (‘/’). The parameter is extracted and passed to the method call—the rest of the URI must match exactly.

The third example uses two single-segment parameters to extract two parts of the URI. Again, the rest of the URI must match exactly.

The final example uses a rest-parameter written using a leading asterisk (’*’). Rest-style parameters match all remaining characters in the URI, including forward slashes.

2.3.2 Matching Requests to Routes

When a request comes in, Play attempts to route it to an action. It examines each route in turn until it finds a match. If no routes match, it returns a 404 response.

Routes match if the HTTP method has the relevant value and the URI matches the shape of the pattern. Play supports all eight HTTP methods: OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, and CONNECT.

Routing examples—mappings from HTTP data to Scala code
HTTP method and URI Scala method call or result
GET /hello controllers.HelloController.hello
GET /hello/dave controllers.HelloController.helloTo("dave")
GET /send/hello/to/dave controllers.ChatController.send("hello", "dave")
GET /download/path/to/file.txt controllers.DownloadController.file("path/to/file.txt")
GET /hello/ 404 result (trailing slash)
POST /hello 404 result (POST request)
GET /send/to/dave 404 result (missing path segment)
GET /send/a/message/to/dave 404 result (extra path segment)

Play Routing is Strict

Play’s strict adherance to its routing rules can sometimes be problematic. Failing to match the URI /hello/, for example, may seem overzealous. We can work around this issue easily by mapping multiple routes to a single method call:

GET  /hello  controllers.HelloController.hello # no trailing slash
GET  /hello/ controllers.HelloController.hello # trailing slash
POST /hello/ controllers.HelloController.hello # POST request
# and so on...

2.3.3 Query Parameters

We can specify parameters in the method-call section of a route without declaring them in the URI. When we do this Play extracts the values from the query string instead:

# Extract `username` and `message` from the path:
GET /send/:message/to/:username ↩
  controllers.ChatController.send(message: String, username: String)

# Extract `username` and `message` from the query string:
GET /send ↩
  controllers.ChatController.send(message: String, username: String)

# Extract `username` from the path and `message` from the query string:
GET /send/to/:username ↩
  controllers.ChatController.send(message: String, username: String)

We sometimes want to make query string parameters optional. To do this, we just have to define them as Option types. Play will pass Some(value) if the URI contains the parameter and None if it does not.

For example, if we have the following Action:

object NotificationController {
  def notify(username: String, message: Option[String]) =
    Action { request => /* ... */ }
}

we can invoke it with the following route:

GET /notify controllers.NotificationController. ↩
  notify(username: String, message: Option[String])

We can mix and match required and optional query parameters as we see fit. In the example, username is required and message is optional. However, path parameters are always required—the following route fails to compile because the path parameter :message cannot be optional:

GET /notify/:username/:message controllers.NotificationController. ↩
  notify(username: String, message: Option[String])

# Fails to compile with the following error:
#     [error] conf/routes:1: No path binder found for Option[String].
#     Try to implement an implicit PathBindable for this type.
Query string parameter examples
HTTP method and URI Scala method call or result
GET /send/hello/to/dave ChatController.send("hello", "dave")
GET /send?message=hello&username=dave ChatController.send("hello", "dave")
GET /send/to/dave?message=hello ChatController.send("hello", "dave")

2.3.4 Typed Parameters

We can extract path and query parameters of types other than String. This allows us to define Actions using well-typed arguments without messy parsing code. Play has built-in support for Int, Double, Long, Boolean, and UUID parameters.

For example, given the following route and action definition:

GET /say/:msg/:n/times controllers.VerboseController.say(msg: String, n: Int)
object VerboseController extends Controller {
  def say(msg: String, n: Int) = Action { request =>
    Ok(List.fill(n)(msg) mkString "\n")
  }
}

We can send requests to URLs like /say/Hello/5/times and get back appropriate responses:

bash$ curl -v 'http://localhost:9000/say/Hello/5/times'
# HTTP headers...
Hello
Hello
Hello
Hello
Hello

bash$

Play also has built-in support for Option and List parameters in the query string (but not in the path):

GET /option-example controllers.MyController.optionExample(arg: Option[Int])
GET /list-example   controllers.MyController.listExample(arg: List[Int])

Optional parameters can be specified or omitted and List parameters can be specified any number of times:

/option-example             # => MyController.optionExample(None)
/option-example?arg=123     # => MyController.optionExample(Some(123))
/list-example               # => MyController.listExample(Nil)
/list-example?arg=123       # => MyController.listExample(List(123))
/list-example?arg=12&arg=34 # => MyController.listExample(List(12, 34))

If Play cannot extract values of the correct type for each parameter in a route, it returns a 400 Bad Request response to the client. It doesn’t consider any other routes lower in the file. This is standard behaviour for all types of path and query string parameter.

Custom Parameter Types

Play parses route parameters using instances of two different type classes:

We can implement custom parameter types by creating implicit values these type classes.

2.3.5 Reverse Routing

Reverse routes are objects that we can use to generate URIs. This allows us to create URIs from type-checked program code without having to concatenate Strings by hand.

Play generates reverse routes for us and places them in a controllers.routes package that we can access from our Scala code. Returning to our original routes for HelloController:

GET /hello       controllers.HelloController.hello
GET /hello/:name controllers.HelloController.helloTo(name: String)

The route compiler generates a controllers.routes.HelloController object with reverse routing methods as follows:

package routes

import play.api.mvc.Call

object HelloController {
  def hello: Call =
    Call("GET", "/hello")

  def helloTo(name: String): Call =
    Call("GET", "/hello/" + encodeURIComponent(name))
}

We can use reverse routes to reconstruct play.api.mvc.Call objects containing the information required to address hello and helloTo over HTTP:

import play.api.mvc.Call

val methodAndUri: Call =
  controllers.routes.HelloController.helloTo("dave")

methodAndUri.method // "GET"
methodAndUrl.url    // "/hello/dave"

Play’s HTML form templates, in particular, make use of Call objects when writing HTML for <form> tags. We’ll see these in more detail next chapter.

2.3.6 Take Home Points

Routes provide bi-directional mapping between URIs and Action-producing methods within Controllers.

We write routes using a Play-specific DSL that compiles to Scala code. Each route comprises an HTTP method, a URI pattern, and a corresponding method call. Patterns can contain path and query parameters that are extracted and used in the method call.

We can type the path and query parameters in routes to simplify the parsing code in our controllers and actions. Play supports many types out of the box, but we can also write code to map our own types.

Play also generates reverse routes that map method calls back to URIs. These are placed in a synthetic routes package that we can access from our Scala code.

2.3.7 Exercise: Calculator-as-a-Service

The chapter2-calc directory in the exercises contains an unfinished Play application for performing various mathematical calculations. This is similar to the last exercise, but the emphasis is on defining more complex routes.

Complete this application by filling in the missing actions and routes. Implement the missing actions marked TODO in app/controllers/CalcController.scala, and complete conf/routes to hook up the specified URLs:

Test your code using curl if you’re using Linux or OS X or a browser if you’re using Windows:

bash$ curl 'http://localhost:9000/add/123/to/234'
357

bash$ curl 'http://localhost:9000/and/true/with/true'
true

bash$ curl 'http://localhost:9000/concat/foo/bar/baz'
foobarbaz

bash$ curl 'http://localhost:9000/sort?num=1&num=3&num=2'
1 2 3

bash$ curl 'http://localhost:9000/howto/add/123/to/234'
GET /add/123/to/234

Answer the following questions when you’re done:

  1. What happens when you add a URL-encodeD forward slash (%2F) to the argument to concat? Is this the desired behaviour?

    bash$ curl 'http://localhost:9000/concat/one/thing%2Fthe/other'

    How does the URL-decoding behaviour of Play differ for normal parameters and rest-parameters?

  2. Do you need to use the same parameter name in conf/routes and in your actions? What happens if they are different?

  3. Is it possible to embed a parameter of type List or Option in the path part of the URL? If it is, what do the resulting URLs look like? If it is not, what error message do get?

As with the previous exercise the add, and, concat, and sort Actions simply involve manipulating types to build Results:

def add(a: Int, b: Int) = Action { request =>
  Ok((a + b).toString)
}

def and(a: Boolean, b: Boolean) = Action { request =>
  Ok((a && b).toString)
}

def concat(args: String) = Action { request =>
  Ok(args.split("/").map(decode).mkString)
}

def sort(numbers: List[Int]) = Action { request =>
  Ok(numbers.sorted mkString " ")
}

howToAdd is more interesting. We can avoid hard-coding the URL for add by using its reverse route:

def howToAdd(a: Int, b: Int) = Action { request =>
  val call = routes.CalcController.add(a, b)
  Ok(call.method + " " + call.url)
}

The routes file is straightforward if you follow the examples above:

GET /add/:a/to/:b       controllers.CalcController.add(a: Int, b: Int)
GET /and/:a/with/:b     controllers.CalcController.and(a: Boolean, b: Boolean)
GET /concat/*args       controllers.CalcController.concat(args: String)
GET /sort               controllers.CalcController.sort(num: List[Int])
GET /howto/add/:a/to/:b controllers.CalcController.howToAdd(a: Int, b: Int)

The answers to the questions are as follows:

  1. If we pass a %2F to the route here, we end up with the same undesirable %2F in the result.

    This happens because args is a rest-parameter. Play treats rest-parameters differently from regular path and query string parameters.

    Because regular parameters are always a single path segment, we know there will never be a reserved URL character such as a /, ?, & or = in the content. Play is able to reliably decode any URL encoded characters for us without fear of ambiguity, and does so automatically before calling our Action.

    Rest-parameters, on the other hand, can contain unencoded / characters. Play cannot decode the content without causing ambiguity so it passes the raw string captured from the URL without decoding.

    To correctly handle URL encoded characters, we have to split the rest parameter on instances of / and apply the urlDecode function to each segment:

    args.split("/").map(urlDecode)

    In example in the question, the controller should remove the / characters from the parameter and decode the %2F, yielding a response of onething/theother.

  2. Play matches parameters in routes by position rather than by name, so we don’t have to use the same names in our routes and our controllers.

    In certain circumstances this behaviour can be useful. In sort, for example, we want a singular parameter name in the URL:

    curl 'http://localhost:9000/sort?num=1&num=3&num=2'

    and a plural name in the action:

    def sort(numbers: List[Int]) = ???

    This can beome confusing when using named arguments on reverse routes. Reverse routes take their parameter names from the conf/routes file, not from our Actions. Calls to the action and the reverse route may therefore look different:

    // Direct call to the Action:
    controllers.CalcController.sort(numbers = List(1, 3, 2))
    
    // Call to the reverse route:
    routes.CalcController.sort(num = List(1, 3, 2))
  3. Play uses two different type classes for encoding and decoding URL parameters: PathBindable for path parameters and QueryStringBindable for query string parameters.

    Play provides default implementations of QueryStringBindable for Optional and List parameters, but it doesn’t provide PathBindables.

    If we attempt to create a path parameter of type List[...]:

    # We've added `:num` to the `sort` route from the solution
    # to change the required type class from QueryStringBindable to PathBindable:
    GET /sort/:num controllers.CalcController.sort(num: List[Int])

    we get a compile error because of the failure to find a PathBindable:

    [error] /Users/dave/dev/projects/essential-play-code/ ↩
            chapter2-calc/conf/routes:4: ↩
            No URL path binder found for type List[Int]. ↩
            Try to implement an implicit PathBindable for this type.

Now we have seen what we can do with routes, let’s look at the code we can write to handle Request and Result objects in our applications. This will arm us with all the knowledge we need to start working with HTML and forms in the next chapter.

2.4 Parsing Requests

So far we have seen how to create Actions and map them to URIs using routes. In the rest of this chapter we will take a closer look at the code we write in the actions themselves.

The first job of any Action is to extract data from the HTTP request and turn it into well-typed, validated Scala values. We have already seen how routes allow us to extract information from the URI. In this section we will see the other tools Play provides for the rest of the Request.

2.4.1 Request Bodies

The most important source of request data comes from the body. Clients can POST or PUT data in a huge range of formats, the most common being JSON, XML, and form data. Our first task is to identify the content type and parse the body.

Confession time. Up to this point we’ve been telling a white lie about Request. It is actually a generic type, Request[A]. The parameter A indicates the type of body, which we can retrieve via the body method:

def index = Action { request =>
  val body: ??? = request.body
  // ... what type is `body`? ...
}

Play contains an number of body parsers that we can use to parse the request and return a body of an appropriate Scala type.

So what type does request.body return in the examples we’ve seen so far? We haven’t chosen a body parser, nor have we indicated the type of body anywhere in our code. Play cannot know the Content-Type of a request at compile time, so how is this handled? The answer is quite clever—by default our actions handle requests of type Request[AnyContent].

play.api.mvc.AnyContent is a sealed trait with subtypes for several common content types and a set of convenience methods that return Some if the request matches the relevant type and None if it does not:

Body parser return types
Method of AnyContent Request content type Return type
asText text/plain Option[String]
asFormUrlEncoded application/x-www-form-urlencoded Option[Map[String, Seq[String]]]
asMultipartFormData multipart/form-data Option[MultipartFormData]
asJson application/json Option[JsValue]
asXml application/xml Option[NodeSeq]
asRaw any other content type Option[RawBuffer]

We can use any of these methods to read the body as a specific type and process it in our Action. The Optional return types force us to deal with the possibility that the client sent us the wrong content type:

def exampleAction = Action { request =>
  request.body.asXml match {
    case Some(xml) => // Handle XML
    case None      => BadRequest("That's no XML!")
  }
}

We can alternatively implement handlers for multiple content types and chain them together with calls to map, flatMap, orElse, and getOrElse:

def exampleAction2 = Action { request =>
  (request.body.asText map handleText) orElse
  (request.body.asJson map handleJson) orElse
  (request.body.asXml  map handleXml)  getOrElse
  BadRequest("You've got me stumped!")
}

def handleText(data: String): Result = ???

def handleJson(data: JsValue): Result = ???

def handleXml(data: NodeSeq): Result = ???

Custom Body Parsers

AnyContent is a convenient way to parse common types of request bodies. However, it suffers from two drawbacks:

  • it only caters for a fixed set of common data types;
  • with the exception of multipart form data, requests must be read entirely into memory before parsing.

If we are certain about the data type we want in a particular Action, we can specify a body parser to restrict it to a specific type. Play returns a 400 Bad Request response to the client if it cannot parse the request as the relevant type:

import play.api.mvc.BodyParsers.parse

def index = Action(parse.json) { request =>
  val body: JsValue = request.body
  // ... no need to call `body.asJson` ...
}

If the situation demands, we can even implement our own custom body parsers to parse exotic formats:

object myDataParser new BodyParser[MyData] {
  // ...
}

def action = Action(myDataParser) { request =>
  val body: MyData = request.body
  // ...
}

See Play’s documentation on body parsers for more information.

2.4.2 Headers and Cookies

Request contains two methods for inspecting HTTP headers:

These take care of common error scenarios: missing headers, upper- and lower-case names, and so on. Values are treated as Strings throughout. Play doesn’t attempt to parse headers as dedicated Scala types. Here is a synopsis:

object RequestDemo extends Controller {
  def headers = Action { request =>
    val headers: Headers = request.headers
    val ucType: Option[String] = headers.get("Content-Type")
    val lcType: Option[String] = headers.get("content-type")

    val cookies: Cookies = request.cookies
    val cookie: Option[Cookie] = cookies.get("DemoCookie")
    val value: Option[String] = cookie.map(_.value)

    Ok(Seq(
      s"Headers: $headers",
      s"Content-Type: $ucType",
      s"content-type: $lcType",
      s"Cookies: $cookies",
      s"Cookie value: $value"
    ) mkString "\n")
  }
}

Case sensitivity

The Headers.get method is case insensitive. We can grab the Content-Type using headers.get("Content-Type") or headers.get("content-type"). Cookie names, on the other hand, are case sensitive. Make sure you define your cookie names as constants to avoid case errors!

2.4.3 Methods and URIs

Routes are the recommended way of extracting information from a method or URI. However, the Request object also provides methods that are of occasional use:

// The HTTP method ("GET", "POST", etc):
val method: String = request.method

// The URI, including path and query string:
val uri: String = request.uri

// The path of the URI, without the query string:
val path: String = request.path

// The query string, split into name/value pairs:
val query: Map[String, Seq[String]] = request.queryString

2.4.4 Take Home Points

Incoming web requests are represented by objects of type Request[A]. The type parameter A indicates the type of the request body.

By default, Play represents bodies using a type called AnyContent that allows us to parse bodies a set of common data types.

Reading the body may succeed or fail depending on whether the content type matches the type we expect. The various body.asX methods such as body.asJson return Options to force us to deal with the possibility of failure.

If we’re only concerned with one type of data, we can choose or write custom body parsers to process the body as a specific type.

Request also contains methods to access HTTP headers, cookies, and various parts of the HTTP method and URI.

2.5 Constructing Results

In the previous section we saw how to extract well-typed Scala values from an incoming request. This should always be the first step in any Action. If we tame incoming data using the type system, we remove a lot of complexity and possibility of error from our business logic.

Once we have finished processing the request, the final step of any Action is to convert the result into a Result. In this section we will see how to create Results, populate them with content, and add headers and cookies.

2.5.1 Setting The Status Code

Play provides a convenient set of factory objects for creating Results. These are defined in the play.api.mvc.Results trait and inherited by play.api.mvc.Controller

Result codes
Constructor HTTP status code
Ok 200 Ok
NotFound 404 Not Found
InternalServerError 500 Internal Server Error
Unauthorized 401 Unauthorized
Status(number) number (an Int)—anything we want

Each factory has an apply method that creates a Result with a different HTTP status code. Ok.apply creates 200 responses, NotFound.apply creates 404 responses, and so on. The Status object is different: it allows us to specify the status as an Int parameter. The end result in each case is a Result that we can return from our Action:

val result1: Result = Ok("Success!")
val result2: Result = NotFound("Is it behind the fridge?")
val result3: Result = Status(401)("Access denied, Dave.")

2.5.2 Adding Content

Play adds Content-Type headers to our Results based on the type of data we provide. In the examples above we provide String data creating three results of Content-Type: text/plain.

We can create Results using values of other Scala types, provided Play understands how to serialize them. Play even sets the Content-Type header for us as a convenience. Here are some examples:

Result Content-Types
Using this Scala type… Yields this result type…
String text/plain
play.twirl.api.Html (see Chapter 2) text/html
play.api.libs.json.JsValue (see Chapter 3) application/json
scala.xml.NodeSeq application/xml
Array[Byte] application/octet-stream

The process of creating a Result is type-safe. Play determines the method of serialization based on the type we give it. If it understands what to do with our data, we get a working Result. If it doesn’t understand the type we give it, we get a compilation error. As a consequence the final steps in an Action tend to be as follows:

  1. Convert the result of action to a type that Play can serialize:
    • HTML using a Twirl template, or;
    • a JsValue to return the data as JSON, or;
    • a Scala NodeSeq to return the data as XML, or;
    • a String or Array[Byte].
  2. Use the serializable data to create a Result.

  3. Tweak HTTP headers and so on.

  4. Return the Result.

Custom Result Types

Play understands a limited set of result content types out-of-the-box. We can add support for our own types by defining instances of the play.api.http.Writeable type class. See the Scaladocs for more information:

// We have a custom library for manipulating iCal calendar files:
case class ICal(/* ... */)

// We implement an implicit `Writeable[ICal]`:
implicit object ICalWriteable extends Writeable[ICal] {
  // ...
}

// Now our actions can serialize `ICal` results:
def action = Action { request =>
  val myCal: ICal = ICal(/* ... */)

  Ok(myCal) // Play uses `ICalWriteable` to serialize `myCal`
}

The intention of Writeable is to support general data formats. We wouldn’t create a Writeable to serialize a specific class from our business model, for example, but we might write one to support a format such as XLS, Markdown, or iCal.

2.5.3 Tweaking the Result

Once we have created a Result, we have access to a variety of methods to alter its contents. The API documentation for play.api.mvc.Result shows this:

These methods can be chained, allowing us to create the Result, tweak it, and return it in a single expression:

def ohai = Action { request =>
  Ok("OHAI").
    as("text/lolspeak").
    withHeaders(
      "Cache-Control" -> "no-cache, no-store, must-revalidate",
      "Pragma"        -> "no-cache",
      "Expires"       -> "0",
      // etc...
    ).
    withCookies(
      Cookie(name = "DemoCookie", value = "DemoCookieValue"),
      Cookie(name = "OtherCookie", value = "OtherCookieValue"),
      // etc...
    )
}

2.5.4 Take Home Points

The final step of an Actions is to create and return a play.api.mvc.Result.

We create Results using factory objects provided by play.api.mvc.Controller. Each factory creates Results with a specific HTTP status code.

We can Results with a variety of data types. Play provides built-in support for String, JsValue, NodeSeq, and Html. We can add our own data types by writing instances of the play.api.http.Writeable type class.

Once we have created a Result, we can tweak headers and cookies before returning it.

2.5.5 Exercise: Comma Separated Values

The chapter2-csv directory in the exercises contains an unfinished Play application for converting various data formats to CSV. Complete the application by filling in the missing action in app/controllers/CsvController.scala.

The action is more complicated than in previous exercises. It must accept data POSTed to it by the client and convert it to CSV using the relevant helper method from CsvHelpers.

We have included several files to help you test the code: test.formdata and test.tsv are text files containing test data, and the various run- shell scripts make calls to curl with the correct command line parameters.

Your code should behave as follows:

Answer the following question when you are done:

Are your handlers for text/plain and text/tsv interchangeable? What happens when you remove one of the handlers and submit a file of the corresponding type? Does play compensate by running the other handler?

There are several parts to this solution: create handler functions for the various content types, ensure that the results have the correct status code and content type, and chain the handlers together to implement our Action. We will address each part in turn.

First let’s create handlers for each content type. We have three types to consider: application/x-www-form-url-encoded, text/plain, and text/tsv. Play has built-in body parsers for the first two. The methods in CsvHelpers do most of the rest of the work:

def formDataResult(request: Request[AnyContent]): Option[Result] =
  request.body.asFormUrlEncoded map formDataToCsv map csvResult

def plainTextResult(request: Request[AnyContent]): Option[Result] =
  request.body.asText map tsvToCsv map csvResult

The text/tsv conten type is trickier, however. We can’t use request.body.asText—it returns None because Play assumes the request content is binary. We have to use request.body.asRaw to get a RawBuffer, extract the Array[Byte] within, and create a String:

def rawBufferResult(request: Request[AnyContent]): Option[Result] =
  request.contentType flatMap {
    case "text/tsv" => request.body.asRaw map rawBufferToCsv map csvResult
    case _          => None
  }

Note the pass-through clause for content types other than "text/tsv". We have no control over the types of data the client may send our way, so we always have to provide a mechanism for dealing with the unexpected.

Also note that the conversion method in rawBufferToCsv assumes unicode character encoding—make sure you check for other encodings if you write code like this in your production applications!

Each of the handler functions uses a common csvResult method to convert the String CSV data to a Result with the correct status code and content type:

def csvResult(csvData: String): Result =
  Ok(csvData).withHeaders("Content-Type" -> "text/csv")

We also need a handler for the case where we don’t know how to parse the request. In this case we return a BadRequest result with a content type of "text/plain":

val failResult: Result =
    BadRequest("Expected application/x-www-form-url-encoded, " +
               "text/tsv, or text/plain")

Finally, we need to put these pieces together. Because each of our handlers returns an Option[Result], we can use the standard methods to chain them together:

def toCsv = Action { request =>
  formDataResult(request) orElse
    plainTextResult(request) orElse
    rawBufferResult(request) getOrElse
    failResult
}

The answer to the question is as follows. Although we are using "text/plain" and "text/tsv" interchangeably, Play treats the two content types differently:

  • "text/plain" is parsed as plain text. request.body.asText returns Some and request.body.asRaw returns None;

  • "text/tsv" is parsed as binary data. request.body.asText returns None and request.body.asRaw returns Some.

In lieu of writing a custom BodyParser for "text/tsv" requests, we have to work around Play’s (understandable) misinterpretation of the format. We read the data as a RawBuffer and convert it to a String. The example code for doing this is error-prone because it doesn’t deal with character encodings correctly. We would have to address this ourselves in a production application. However, the example demonstrates the principle of dispatching on content type and parsing the request appropriately.

2.6 Handling Failure

At this point we have covered all the basics for this chapter. We have learned how to set up routes, write Actions, handle Requests, and create Results.

In this final section of the chapter we will take a first look at a theme that runs throughout the course—failures and error handling. In future chapters we will look at how to generate good error messages for our users. In this section we will see what error messages Play provides for us.

2.6.1 Compilation Errors

Play reports compilation errors in two places: on the SBT console, and via 500 error pages. If you’ve been following the exercises so far, you will have seen this already. When we run a development web server using sbt run and make a mistake in our code, Play responds with an error page:

Internal error: Play’s compilation error 500 page
While this behaviour is useful, we should be aware of two drawbacks:

  1. The web page only reports the first error from the SBT console. A single typo in Scala code can create several compiler errors, so we often have to look at the complete output from SBT to trace down a bug.

  2. When we use sbt run, Play only recompiles our code when we refresh the web page. This sometimes slows down development because we have to constantly switch back and forth between editor and browser.

We can write and debug code faster if we use SBT’s continuous compilation mode instead of sbt run. To start continuous compilation, type ~compile on the SBT console:

[hello-world] $ ~compile
[success] Total time: 0 s, completed 11-Oct-2014 11:46:28
1. Waiting for source changes... (press enter to interrupt)

In continuous compilation mode, SBT recompiles our code every time we change a file. However, we have to go back to sbt run to see the changes in a browser.

2.6.2 Runtime Errors

If our code compiles but fails at runtime, we get a similar error page that points to the source of the exception. The exception is reported on the SBT console as well as on the page:

Internal error: Play’s default error 500 page
 

2.6.3 Routing Errors

Play generates a 404 page if it can’t find an appropriate route for an incoming request. This error doesn’t appear on the console: