Our courses, workshops, and other products can help you and your team create better software and have more fun. For more information, as well as the latest Underscore titles, please visit http://underscore.io/training.

Disclaimer: Every precaution was taken in the preparation of this book. However, the author and Underscore Consulting LLP assume no responsibility for errors or omissions, or for damages that may result from the use of information (including program listings) contained herein.

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:

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:

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.

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:

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:

change to the relevant exercise directory;

start SBT;

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:

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

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:

System-wide vanilla SBT—We can install a system-wide SBT launcher using the instructions on the SBT web site. Linux and OS X users can download copies via package managers such as Apt, MacPorts, and Homebrew.

Project-local vanilla SBT—We can bundle the SBT launcher JAR with a project and create shell scripts to start it with the correct command line arguments. This is the approach used in the exercises and solutions for this book. ZIP downloads of the required files are available from the SBT web site.

Typesafe Activator—Activator, available from Typesafe’s web site, is a tool for getting started with the Typesafe Stack. The activator command is actually just an alias for SBT, although the activator distribution comes pre-bundled with a global plugin for generating new projects from templates (the activator new command).

“SBT Extras” script—Paul Philips released an excellent shell script that acts as a front-end for SBT. The script does the bootstrapping process of detecting Scala and SBT versions without requiring a launcher JAR. Linux and OS X users can download the script from Paul’s Github page.

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 promptbash$

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

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:

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:

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:

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:

~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:

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:

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:

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:

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:

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.

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:

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.2 The Anatomy of a Controller

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:

Hooking up the routes would be straightforward, except we included one gotcha to trip you up. You must place the route for TimeController.zonesabove 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:

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

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:

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:

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.

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.

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:

2.3.6 Take Home Points

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:

CalcController.add and CalcController.and are examples of Actions involving typed parameters;

CalcController.concat is an example involving a rest-parameter;

CalcController.sort is an example involving a parameter with a parameterized type;

CalcController.howToAdd is an example of reverse routing.

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

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.

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:

defsort(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:

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:

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:

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:

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:

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:

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.

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:

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:

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].

Use the serializable data to create a Result.

Tweak HTTP headers and so on.

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:

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:

we can change the Content-Type header (without changing the content) using the as method;

we can add and/or alter HTTP headers using withHeaders;

we can add and/or alter cookies using withCookies.

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

2.5.4 Take Home Points

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:

Form data (content type application/x-url-form-url-encoded) should be converted to CSV in columnar orientation and returned with text/csv content type:

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:

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:

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:

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:

While this behaviour is useful, we should be aware of two drawbacks:

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.

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: