Scala and the Play Framework
by Charles Calkins, Principal Software Engineer
January 2014
Introduction
The Play Framework is framework for developing web applications in Java or Scala. This article will show how easy it can be to write a secured, dynamic, Ajax-enabled site, with this framework. The code associated with this article is available here.
Play Framework
The Play Framework is a web application framework, inspired by lighter-weight frameworks such as Django and Rails, rather than following the philosophy of Java Enterprise Edition. Play follows the Model-View-Controller pattern, where controllers and models can be written in Java or Scala, but views are written as HTML with Scala (Java is not permitted) interspersed as needed.
Play was created by Guillaume Bort in 2007, with a 1.0 release in October 2009. Version 2.0 made significant changes to Play, and was released in March 2012. As of this writing, Play has reached version 2.2.1, and this is the version used in this article. Play runs on the Java Virtual Machine, so a Play application can not only use the multitudes of existing Java libraries, but can run under Windows, Linux, and other systems that support the JVM, without modification.
Play includes its own Netty-based web server, but, with an appropriate plugin, a Play application can be packaged as a WAR file for deployment under Servlet 2.5- or 3.0-based containers, such as recent versions of Tomcat, Jetty and JBoss.
In Play, a URL is mapped to an action, via a routes
file. The action receives the HTTP request, and returns an HTTP response, which frequently is HTML as produced by the evaluation of a Play view template, or can be a file download, a JSON response, an image, or other appropriate return. Since view templates only allow Scala (and not Java), it is convenient to write the entire application in Scala as well.
Scala
The design of Scala was begun in 2001 by Martin Odersky, with an initial release in late 2003/early 2004 on the JVM. Early versions of Scala had been available on other platforms, such as the .NET Framework, but those platforms are now virtually abandoned. Version 2.0 followed in March 2006, and is 2.10.3 as of this writing. Odersky started Typesafe, Inc. in 2011 to promote Scala, Play, and Akka (an actor framework). Scala, an abbreviation for "scalable language," is a blend of object-oriented and functional programming concepts, where classes, objects and inheritance are mixed with anonymous functions, list comprehensions, map()
, fold()
, and the like. Scala is statically-typed, but a philosophy of Scala is to eliminate syntactic sugar when not needed, so types often do not need to be explicitly stated when they can be inferred. Semicolons, parentheses, the return
keyword and such can also often be eliminated as well.
Scala has been influenced by other languages. Since it runs on the JVM, it uses existing Java classes and libraries as needed, and is impacted by JVM design decisions, such as type erasure of generics, for which Scala has a workaround. It is similar to Smalltalk in that every value is an object and every function a method call (+
is a method of Int
, for example), and everything is a subclass of Any
, through two branches, AnyVal
and AnyRef
, for value and reference types, respectively. Additionally, the actor support and pattern matching in Scala is reminiscent of Erlang.
Installation
Installation of the Play Framework is simple. The Play Framework is distributed as a ZIP file. Once it is downloaded, it only needs to be extracted into a directory, and that directory added to the system path.
Creating and Running the Default Play Application
After Play has been installed, type the following command at a system prompt:
play new ajax
Two questions will be asked. Confirm that ajax
is the name we will use for the application, and select Scala (option 1) as the development language. When the process completes, a basic project will have been created in the ajax
subdirectory.
Next, change directory into ajax
, and type:
play run
The code will now compile, and download dependencies as needed. When complete, a message indicating that the server has started is displayed.
Open a web browser, and navigate to http://localhost:9000. The command play run
starts the server running in development mode — as code is changed, it will be automatically compiled when the browser is refreshed and the server receives an HTTP request.
If there are any compilation errors, they will be displayed in the browser window, but the generated application will compile successfully. When the application is ready and running, the web browser's request will be served, and the browser will show a default Play Framework welcome page.
A play application can also be executed in production mode, by executing the play start
command. The application will run faster overall, and will not monitor code for changes. A distribution of the created Play application can be made with the play dist
command. This command will create a single ZIP file containing the application, its dependencies, and Play itself. This allows for easy installation of a Play application as the one file provides a self-contained archive, without requiring Play to be installed separately.
A Look at the Generated Code
In the ajax
directory that was created, the file app\controllers\Application.scala
contains the action that executed when the page was loaded. The entire file is:
- package controllers
-
- import play.api._
- import play.api.mvc._
-
- object Application extends Controller {
-
- def index = Action {
- Ok(views.html.index("Your new application is ready."))
- }
-
- }
Actions are methods of objects that inherit from class Controller
. In Scala, a method of anobject
is similar to a static method in Java, in that a specific instantiation of a class is not needed in order to execute the method. The keyword def
defines a method, named index
here, implemented as a Play Action
. The Action
executes a code block containing a call to the helper method Ok()
. Ok()
returns an HTTP response with code 200, indicating success. The response is the evaluation of a view template named views.html.index
, which takes a string as a parameter.
The view template is as follows:
- @(message: String)
-
- @main("Welcome to Play 2.1") {
-
- @play20.welcome(message)
-
- }
View templates, named with the .scala.html
extension, contain HTML and Scala, and are compiled into Scala functions behind the scenes. As such, they can take parameters (message
, in this case, of type String
), and can invoke Scala functions, including other templates. Scala invocations are preceded by the @ sign to differentiate them from literal text for the page. Here, a template located in main.scala.html
is invoked with two parameters – a string and a block of HTML. In the HTML block is a call to a built-in function which generates the welcome page.
The called template is:
@(title: String)(content: Html)
<!DOCTYPE html>
<html>
<head>
<title>@title</title>
<link rel="stylesheet" media="screen" href="@routes.Assets.at("stylesheets/main.css")">
<link rel="shortcut icon" type="image/png" href="@routes.Assets.at("images/favicon.png")">
<script src="@routes.Assets.at("javascripts/jquery-1.9.0.min.js")" type="text/javascript"></script>
</head>
<body>
@content
</body>
</html>
The two parameters are received as the parameters title
and content
, where title
is a Scala string, and content
is an object of type Html
.
This template provides the traditional structure of an HTML page (doctype, head, and body), with the parameters evaluated at appropriate points. The title
variable is evaluated (an @
preceding a template, method name, or Scala keyword invokes the template, method, or language element, while @
preceding a variable evaluates the variable and displays its contents) in the page in the <title>
, and the <content>
parameter becomes the page <body>
.
The generated code includes references to a style sheet, favicon, and the jQuery library, all located below the public
subdirectory. Loading resources using the rotues.Assets.at()
syntax allows caching of them by Play.
An entry in the conf\routes
file maps the URL to the index
action. The generated routes
file is as follows:
- # Routes
- # This file defines all application routes (Higher priority routes first)
- # ~~~~
-
- # Home page
- GET / controllers.Application.index
-
- # Map static resources from the /public folder to the /assets URL path
- GET /assets/*file controllers.Assets.at(path="/public", file)
Each entry begins with the HTTP verb to map, such as GET, POST, PUT, DELETE, or HEAD, followed by a URL pattern, and lastly the action to invoke. The URL pattern can include wildcards, as with the assets URL, which are then mapped to action parameters. In this instance, the asterisk beforefile
causes the entire path following /assets
in a URL to be bound to the file
variable, which is passed as the second parameter to the at()
action.
Developing a web application with Play can be that simple — develop a view template, write an action to invoke it, and add an entry to the routes
file to associate a URL with the action.
Testing
The Play Framework integrates the Specs2 testing framework. When the project is created, two test specifications are created. The first, in class ApplicationSpec
, shows one style of test, that of direct route evaluation.
- package test
-
- import org.specs2.mutable._
-
- import play.api.test._
- import play.api.test.Helpers._
-
- /**
- * Add your spec here.
- * You can mock out a whole application including requests, plugins etc.
- * For more information, consult the wiki.
- */
- class ApplicationSpec extends Specification {
-
- "Application" should {
-
- "send 404 on a bad request" in {
- running(FakeApplication()) {
- route(FakeRequest(GET, "/boum")) must beNone
- }
- }
-
- "render the index page" in {
- running(FakeApplication()) {
- val home = route(FakeRequest(GET, "/")).get
-
- status(home) must equalTo(OK)
- contentType(home) must beSome.which(_ == "text/html")
- contentAsString(home) must contain ("Your new application is ready.")
- }
- }
- }
- }
A Specs2 test is written in a declarative, behavior-driven manner, and is placed in the test
package. Specs2 supports two forms of tests, Unit and Acceptance, with the structure above in the unit test form. The keyword in
creates an Example, which generates a Result, such as success
or failure
as determined by, for example, the comparison of a calculated quantity with a known result. The keyword should
collects a list of Examples into a test Fragment, and a list of Fragments composes a test specification.
In these Examples, the running(FakeApplication())
lets the test run with an active application context, which is often necessary for full system functionality. For instance, if a database connection is referenced, this allows the database to be opened and made ready. Unit tests that test behavior of internal objects, however, may not require an application context, so for those tests this may be omitted.
Both of these tests use the Play Router
object to invoke an action, rather than calling the action directly. In the first Example, a request made to an invalid route is tested to ensure that it does not return a page. In the second Example, a request for the main page of the site is tested to determine if it successfully returns an HTML page containing the specified text.
A second test specification that Play creates tests the application within the context of a browser, using the Selenium browser automation framework:
- package test
-
- import org.specs2.mutable._
-
- import play.api.test._
- import play.api.test.Helpers._
-
- /**
- * add your integration spec here.
- * An integration test will fire up a whole play application in a real (or headless) browser
- */
- class IntegrationSpec extends Specification {
-
- "Application" should {
-
- "work from within a browser" in {
- running(TestServer(3333), HTMLUNIT) { browser =>
-
- browser.goTo("http://localhost:3333/")
-
- browser.pageSource must contain("Your new application is ready.")
-
- }
- }
-
- }
-
- }
In this Example, the basic headless HTMLUNIT
driver is used (FIREFOX
is also available, as well as others, once additional browser connectors are installed), and a web server is started on port 3333. The actions in the Example use commands provided by FluentLenium. FluentLenium provides commands to automate the browser by directing it to goTo()
URLs or to click()
on links, as a user would.
The command play test
will execute all tests, although a single test specification can be run via the play "test-only test.spec_name"
command.
Add User Authentication
We will first modify the application to require a user login. Development can be done strictly with a text editor, although a Scala development environment for Eclipse is available which also is able to display the Play project structure in an intelligent way, as well as providing context-sensitive help on Scala methods, having support for refactoring, and including other useful tools. To use Eclipse, invoke the play eclipse
command at the prompt, which will generate project files which can later be imported into an Eclipse workspace and viewed within the Scala perspective. Similarly, IntelliJ is supported via the play idea
command.
The Play Framework supports plugins, and we will use the Deadbolt 2 plugin for authorization. We begin by adding a reference to it in Build.scala
, so it can be automatically retrieved as part of the build process. Both the dependency itself must be added, as well as the repository from where it can be obtained. It also requires the cache
plugin, which must also be specified. The updated file is as follows, with the highlighted lines showing what must be added to the automatically generated file.
- import sbt._
- import Keys._
- import play.Project._
-
- object ApplicationBuild extends Build {
-
- val appName = "ajax"
- val appVersion = "1.0-SNAPSHOT"
-
- val appDependencies = Seq(
- jdbc,
- anorm,
- cache,
-
- // https://github.com/schaloner/deadbolt-2
- "be.objectify" %% "deadbolt-scala" % "2.2-RC2"
- )
-
- val main = play.Project(appName, appVersion, appDependencies).settings(
- resolvers += Resolver.url("Objectify Play Repository", url("http://schaloner.github.io/releases/"))(Resolver.ivyStylePatterns),
- resolvers += Resolver.url("Objectify Play Snapshot Repository", url("http://schaloner.github.io/snapshots/"))(Resolver.ivyStylePatterns)
- )
-
- }
If Eclipse is used for development, play eclipse
should be executed again to update the project files to reflect the added dependency.
To secure a controller with Deadbolt 2, the controller must inherit from DeadboltActions
. We update Application.scala
as follows:
- package controllers
-
- import play.api._
- import play.api.mvc._
- import play.api.data._
- import play.api.data.Forms._
- import be.objectify.deadbolt.scala.DeadboltActions
- import security.MyDeadboltHandler
-
- object Application extends Controller with DeadboltActions {
Prompting the user to log in requires a form to be filled out containing username and password fields. One way to express a form in Play is:
- val loginForm = Form(
- tuple(
- "username" -> nonEmptyText,
- "password" -> text)
- verifying ("Invalid username or password", result => result match {
- case (username, password) =>
- models.User.authenticate(username, password).isDefined
- }))
-
- def index = Action {
- Ok(views.html.index(loginForm))
- }
A form is an instance of class Form
, containing a mapping of field names to their types. A simple form such as this can use a direct mapping to a tuple of two strings, but a more complicated form should use a case class, rather than an unnamed tuple, for easier manipulation.
Validation, implemented as a boolean expression on individual fields or the form as a whole, can be applied so conditions can be automatically checked before the form is successfully submitted. As this form consists of a tuple of two strings, a pattern match is applied to populate the variables username
and password
with the corresponding form fields. The authenticate()
method on the User
object is called to determine if the user is valid. The index
action is updated to pass the form to the view template.
The User
is implemented in the models
package, continuing the Model-View-Controller pattern. Deadbolt 2 provides three forms of security: subject-based (whether or not a user is logged in), role-based (control based on the user's status), and permission-based (control based on assigned capabilities). Although we will not be using role or permission-based security, the pattern is to subclass the Deadbolt 2 Role
and Permission
classes with string identifiers to represent the user's assigned roles and permissions, respectively. For example, a SecurityRole
could contain the strings Admin
or RegularUser
, with tests made against these strings to implement restrictions.
- package models
-
- import be.objectify.deadbolt.core.models._
- import play.libs.Scala
-
- class SecurityRole(val roleName: String) extends Role {
- def getName: String = roleName
- }
-
- class UserPermission(val value: String) extends Permission {
- def getValue: String = value
- }
Class User
is a subclass of a Deadbolt 2 Subject
. Three methods must be overridden to provide a list of roles the user has, a list of permissions the user has been granted, and a string to identify the user. Subject
is implemented in Java, so as the User
class is implemented in Scala, each Scala List
must be returned as a Java java.util.List
. In our case, as no roles or permissions are needed, the lists that are returned are empty.
- case class User(name: String = "") extends Subject {
-
- def getRoles: java.util.List[SecurityRole] = {
- Scala.asJava(List[SecurityRole]()) // no roles needed
- }
-
- def getPermissions: java.util.List[UserPermission] = {
- Scala.asJava(List[UserPermission]()) // no permissions needed
- }
-
- def getIdentifier: String = name
- }
A Scala class can have an associated companion object which can implement variables and methods that are not specific to a given instantiation of the class (as was done with the Controller
above), and we will implement the authenticate()
method here.
A typical application would read user information from a database, but we will read a user from the application's associated conf\application.conf
configuration file instead. The application.conf
file is managed by the Typesafe configuration library, included with Play.
- object User {
- import play.api.Play
- import play.api.Play.current
- private lazy val adminUsername = Play.current.configuration.getString("login.username").getOrElse("")
- private lazy val adminPassword = Play.current.configuration.getString("login.password").getOrElse("")
-
- def authenticate(username: String, password: String): Option[User] =
- if ((adminUsername == username) && (adminPassword == password))
- Some(User(username))
- else
- None
- }
The authenticate()
method returns an Option[User]
. An Option[T]
in Scala is a way to avoid the use of a null reference. An Option[T]
always contains a value, equal to Some(x)
for an x
of type T
if the value exists, or None
if it does not. The method isDefined
on Option[T]
, as used in the form validation predicate, is true if the Option[T]
is Some(x)
, or false if it is None
.
We return an Option[User]
, which is either Some(user)
if the username and password match the entry read from the conf\application.conf
file, or None
otherwise. Note that, in Scala, ==
is the equivalent of .equals()
in Java.
The API of Play.current.configuration
provides a set of methods to read values from theconf\application.conf
file. The getString()
method returns an Option[String]
, as the requested key may not be present in the file. The call to getOrElse("")
is used to return a blank string if the given property is not found (the Option[String]
was None
). The value is obtained in a lazy
manner, where it is only read if needed.
Now that the user is defined, we can turn to the view template, and display the form to collect the login information. The Play Framework provides helpers for the creation of forms and form fields. These helpers also show validation errors when the contents of a particular field are not as expected. Pure HTML can still be written, though, as is done with the submit button.
- @(frm: Form[(String,String)])
-
- @import helper._
-
- @main("Ajax") {
-
- @form(action = routes.Application.login, args = 'id -> "loginform") {
-
- @frm.globalError.map { error =>
- <p style="color: red">
- @error.message
- </p>
- }
-
- @inputText(
- field = frm("username"),
- args = '_label -> "Username:"
- )
- @inputPassword(
- field = frm("password"),
- args = '_label -> "Password:"
- )
-
- <input type="submit" id="submit" value="Log in">
- }
-
- }
The parameter passed into the template is a Form[(String, String)]
, matching the type of the form as defined in the controller, a form containing a tuple of two strings. The two fields, labelled username
and password
in the form definition in the controller, are used here as parameters to the form object frm
, when referencing the form fields in the form helper methods. The inputText()
helper generates an input field of type text
, while the inputPassword()
helper generates an input field of type password
, causing text entry to be masked.
When the form is submitted, it invokes the login
action. We add a route in the routes
file for the action, mapping it to the login
method of the Application
controller.
- POST /login controllers.Application.login
We implement the login
action as follows:
- def login = {
- Action { implicit request =>
- loginForm.bindFromRequest.fold(
- formWithErrors => { // binding failure
- Logger.error("Login failed for user " + formWithErrors.data("username"))
- BadRequest(views.html.index(formWithErrors))
- },
- user => {
- // update the session BEFORE the view is rendered
- val modifiedRequest = updateRequestSession(request, List(("user" -> user._1)))
- Ok(views.html.pageA(modifiedRequest)).withSession(modifiedRequest.session)
- })
- }
- }
This action is a bit more complex than the index
action. This action binds the result of the form submission to the variables of the form via the call to bindFromRequest()
. If the binding was not successful, the variable formWithErrors
is populated with the data the user submitted, along with validation error messages indicating what particular field or fields did not have valid contents. The BadRequest()
helper sets the HTTP status code to 400, and the index
view template is re-rendered with the form and its errors, to present those errors to the user. Next, the Play Framework logger is invoked to log an error message, to record that a login failure has occurred. Messages logged through the Logger
object are appended to the file application.log
in the logs
subdirectory.
If the form binding was successful, the form values are bound to the variable user
. As the form data is a tuple of two strings, the type of user
is the same, where user._1
is the username, anduser._2
is the password. If execution has reached this point, the user has been successfully authenticated (otherwise, the call to authenticate()
in the form validation would have caused the form binding to fail, and the formWithErrors
branch would have been taken), so the username can be added to the HTTP session to indicate that a user has successfully logged in.
The call to withSession()
updates the session object, but, unfortunately, the view template is rendered before the request is modified. If the view template layout depends upon the logged in user, the request must be updated with the modified session before the template is rendered. These two discussions show how the session can be manipulated manually. The method updateRequestSession()
performs that manipulation.
- private def updateRequestSession(request: Request[Any], additionalSessionParams: List[(String, String)]): Request[Any] = {
- import scala.language.reflectiveCalls
-
- val updatedSession = additionalSessionParams.foldLeft[Session](request.session) { _ + _ }
- val existingSession = request.headers.get(SET_COOKIE).map(cookies =>
- Session.decodeFromCookie(Cookies.decode(cookies).find(_.name == Session.COOKIE_NAME)))
- val newSession = if (existingSession.isDefined)
- existingSession.get.data.foldLeft(updatedSession) { _ + _ }
- else
- updatedSession
-
- val cookies = Cookies(request.headers.get(COOKIE)).cookies +
- (Session.COOKIE_NAME -> Session.encodeAsCookie(newSession))
- val headerMap = request.headers.toMap +
- (COOKIE -> Seq(Cookies.encode(cookies.values.toSeq)))
- val theHeaders = new play.api.mvc.Headers {
- val data: Seq[(String, Seq[String])] = headerMap.toSeq
- }
-
- Request[Any](request.copy(headers = theHeaders), request.body)
- }
The foldLeft()
method starts with the existing session from the request, and adds the new session parameters to it, by acting as an accumulation operation. The underscores used in the call to foldLeft()
are a Scala shortcut for parameters when they can be unambiguously determined.
The map()
method on a list produces a new list, by transforming each element of the first list. Here, each cookie is enumerated to find the cookie corresponding to the session, and, once found, is decoded into a session object.
Next, the updated session is either the existing session merged with the new session (via another call to foldLeft()
) or just the new session, if there was no existing session.
The remaining half of the method adds the session back to the list of cookies, and updates the request header with the new cookie list. Finally, an updated request is returned that has the same body as the original request, but with modified headers.
The last operation of the login
action is to render the pageA
view template. This template is as follows:
- @(request:Request[Any])
-
- @import helper._
-
- @main("Page A") {
- <h1>Welcome @request.session.get("user")</h1>
-
- <a id="logout" href=@routes.Application.logout>logout</a> <br />
- <a id="pageB" href=@routes.Application.pageB>page B</a>
- }
The request
object is passed into the template, so the username can be extracted from the session. If the session had not been updated before the template was invoked, no username would be available to be retrieved, even though a user had successfully logged in.
This template includes two anchors. The first invokes an action to log out the user, and the second to display page B. The page B template is simply:
- @main("Page B") {
- <h1>Page B</h1>
- }
Routes are added for each of the actions in the routes
file, and the actions implemented as:
- def logout =
- Action { implicit request =>
- Ok(views.html.index(loginForm)).withNewSession
- }
-
- def pageB = SubjectPresent(new MyDeadboltHandler) {
- Action { implicit request =>
- Ok(views.html.pageB())
- }
- }
The logout
action displays the index
page, and clears the session via the call towithNewSession()
. As a user is considered logged in because a user
key is present in the session, the user is logged out by clearing the session.
The pageB
action demonstrates how an action can be secured to ensure it can only be invoked when a user is logged in. Deadbolt 2 provides other tests to secure actions, such asRestrict(Array("Admin"), new MyDeadboltHandler)
, which would only allow the action to execute if the user is in the Admin
role.
The instantiation of MyDeadboltHandler
allows Deadbolt 2's behavior to be customized when security decisions are being made. The implementation used in this example is as follows:
- package security
-
- import be.objectify.deadbolt.scala.{ DynamicResourceHandler, DeadboltHandler }
- import play.api.mvc.{ Request, Result, Results }
- import be.objectify.deadbolt.core.models.Subject
-
- class MyDeadboltHandler(dynamicResourceHandler: Option[DynamicResourceHandler] = None) extends DeadboltHandler {
-
- def beforeAuthCheck[A](request: Request[A]) = None
-
- def getDynamicResourceHandler[A](request: Request[A]): Option[DynamicResourceHandler] = None
-
- def getSubject[A](request: Request[A]): Option[Subject] =
- request.session.get("user").map(u => models.User(u))
-
- import scala.concurrent.Future
- import scala.concurrent.ExecutionContext.Implicits.global
- def onAuthFailure[A](request: Request[A]) = {
- Future.successful(Results.Forbidden(views.html.authFailed()))
- }
- }
Class MyDeadboltHandler
has the DeadboltHandler
trait, and must override four methods.
1. def onAuthFailure[A](request: Request[A]): Future[SimpleResult]
This method is called when a Deadbolt 2 authorization test fails, and the SimpleResult
returned by this method is executed instead of the SimpleResult
that would have been returned by the action that was secured by the Deadbolt 2 authorization check. In this example, the authFailed
view template is rendered, and is returned to the browser with an HTTP status code of 403 (forbidden).
This template is simply:
- @main("Authorization Failed") {
- <h1>Authorization Failed</h1>
- }
2. def beforeAuthCheck[A](request: Request[A]): Option[Future[SimpleResult]]
The normal behavior of Deadbolt 2 is to return the result of an action — either the result of executing the action that is wrapped by the authorization test if the test is successful, or the result of calling onAuthFailure()
if the test failed. If beforeAuthCheck()
returns None
, this is the process that is followed. Otherwise, the authorization check is skipped (as is the action that was to be tested), and the result returned by beforeAuthCheck()
is used instead. Here, we return None
as we desire the normal Deadbolt 2 behavior in all cases.
3. def getSubject[A](request: Request[A]): Option[Subject]
This method returns the subject (such as the currently logged-in user) to use for the authorization test. We create an instance of class User
directly from the entry in the session, although a more typical application may require a database or cache lookup.
4. def getDynamicResourceHandler[A](request: Request[A]): Option[DynamicResourceHandler]
This method returns an instantiation of a subclass of DynamicResourceHandler
, used for special handling of more advanced role and permission checks. We return None
as no handler is necessary.
Testing
With user authorization complete, we can write tests to confirm the expected behavior — that a correct login displays page A, and that page B can be viewed, but an incorrect login redisplays the login page, and a request to display page B is rejected. We replace the generated IntegrationSpec
with the code below.
We first create a helper method, login()
, which navigates to the main page of the application, confirms that its page title is really that of the login page, populates the username and password fields to match the method arguments, and then clicks the submit button. As several tests require this functionality, it can be separated into its own method and reused.
- package test
-
- import org.specs2.mutable._
-
- import play.api.test._
- import play.api.test.Helpers._
-
- class IntegrationSpec extends Specification {
-
- def login(username: String, password: String)(implicit browser: TestBrowser) = {
- browser.goTo("http://localhost:3333/")
-
- browser.title must beEqualTo("Ajax")
-
- browser.$("#username").text(username)
- browser.$("#password").text(password)
- browser.$("#submit").click
- }
For the first test, the user is logged in using the same credentials as appear in the application.conf
file, and the resulting page confirmed to contain a welcome message for the user.
- "Application" should {
-
- "allow a correct login" in {
- running(TestServer(3333), HTMLUNIT) { implicit browser =>
-
- login("bob", "bobpass")
-
- browser.pageSource must contain("Welcome bob")
- }
- }
The second test is very similar, although incorrect credentials are entered. After the form is submitted, the page must remain on the index ("Ajax"-titled) page, and the page must contain an error message indicating that the username or password are incorrect.
- "reject an incorrect login" in {
- running(TestServer(3333), HTMLUNIT) { implicit browser =>
-
- login("fred", "fredpass")
-
- browser.title must beEqualTo("Ajax")
- browser.pageSource must contain("Invalid username or password")
- }
- }
The third test logs the user in successfully, and then clicks the link for page B. As a user successfully has logged in, the navigation to page B is tested to be successful.
- "allow a logged-in user to view page B" in {
- running(TestServer(3333), HTMLUNIT) { implicit browser =>
-
- login("bob", "bobpass")
-
- browser.pageSource must contain("Welcome bob")
-
- browser.$("#pageB").click
-
- browser.title must beEqualTo("Page B")
- }
- }
The fourth test attempts to navigate to page B without logging in, and the request is expected to be rejected.
- "prevent unauthorized access to page B" in {
- running(TestServer(3333), HTMLUNIT) { browser =>
-
- browser.goTo("http://localhost:3333/pageB")
-
- browser.title must beEqualTo("Authorization Failed")
- }
- }
-
- }
-
- }
To run only the integration tests, type the command play "test-only test.IntegrationSpec"
at the prompt.
Add Dynamism with Ajax
Dynamic and responsive web pages can be created by the use of Ajax, allowing regions of the page to be updated on demand. The Play Framework supports Ajax in a straightforward way. In our example, we will create two buttons. When a button is pressed, it is replaced with text indicating which button, and when, it was pressed.
Below the anchors, we update page A as follows, to contain the buttons:
- <div id="div1">
- <button id="btn1">Press Me 1</button>
- </div>
-
- <div id="div2">
- <button id="btn2">Press Me 2</button>
- </div>
The main view template automatically includes jQuery, so it is available for use on any page that references that template. We can use jQuery to both make the Ajax request, as well as associating a click handler with each of the buttons. We define the doAjax()
function to perform the Ajax request to the server. A JavaScript object is created containing one field set to the value of the button being pressed, and that object is converted to a JSON string. That is posted to the server, and a response, also formatted as JSON, is expected. The response contains two fields: divID
, indicating the particular DIV on the page to update, and text
, the text to update it with.
- <script>
- function doAjax(buttonID) {
- var dataToSend, json;
-
- dataToSend = {};
- dataToSend.buttonID = buttonID;
-
- json = JSON.stringify(dataToSend);
-
- $.ajax({
- url : "ajaxText",
- contentType : 'application/json',
- dataType : "json", // server return is expected to be JSON
- type : "post",
- data : json,
- success : function(data) {
- // "data" is the JSON response from the server
- $("#div" + data.divID).text(data.text);
- },
- error : function(jqXHR, textStatus, errorThrown) {
- console.log(errorThrown);
- }
- });
- }
In jQuery's document ready()
handler, we associate click handlers with each of the buttons, calling doAjax()
with the appropriate button ID.
- $(function() {
- $("#btn1").click(function() {
- doAjax(1);
- });
- $("#btn2").click(function() {
- doAjax(2);
- });
- });
- </script>
After adding a POST route for ajaxText
to the conf\routes
file, the action itself is implemented in the controller.
- import play.api.libs.json._
- import play.api.libs.functional.syntax._
-
- val ajaxTextRds = (__ \ 'buttonID).read[Long]
-
- def ajaxText = SubjectPresent(new MyDeadboltHandler) {
- Action(parse.json) { request =>
- request.body.validate[Long](ajaxTextRds).map {
- case (buttonID) => {
- val text = "Pressed " + buttonID + " at " + new java.util.Date()
- Ok(Json.obj("status" -> "OK", "divID" -> buttonID, "text" -> text))
- }
- }.recoverTotal {
- e => BadRequest(Json.obj("status" -> "KO", "message" -> JsError.toFlatJson(e)))
- }
- }
- }
As before, the action is secured by Deadbolt 2 by testing for a logged-in user via the call to SubjectPresent()
. As the request in this case is formatted as JSON, the action is directed to use the parse.json
body parser, instead of the default, to properly interpret the request. The call to request.body.validate()
uses a json.Reads
structure (ajaxTextRds
) to map the JSON key names (buttonID
, in this case) to their appropriate types. If the validation was successful, thecase
statement binds the parameters that have been validated to Scala variables, here just the one Long
being mapped to the buttonID
variable. A response formatted as JSON is returned with an HTTP status code of 200, indicating the DIV to update, and the text to update it with.
If the validation of the incoming JSON failed, a response is returned with HTTP status 400, also formatted as JSON, containing a failure status and an error message.
Testing
There are two ways that an Ajax action can be tested. The first is, as was done above, via an integration test that automates the browser to perform the action. As the page is dynamic, the call to await
is used to wait (for at most 3 seconds) until the appropriate DIV has changed.
- "allow a logged-in user to make an ajax request" in {
- running(TestServer(3333), HTMLUNIT) { implicit browser =>
-
- login("bob", "bobpass")
-
- browser.pageSource must contain("Welcome bob")
-
- browser.$("#btn1").click
-
- browser.await.atMost(3, java.util.concurrent.TimeUnit.SECONDS).until("#div1").withText.startsWith("Pressed 1").isPresent
-
- browser.$("#div1").first.getText must contain("Pressed 1")
-
- }
- }
The second way that an Ajax action can be tested is to create requests by hand, route them through Play's router, and analyze the response. We return to the ApplicationSpec
, remove the auto-generated tests, and add a test to confirm that a logged-in user can make a valid request.
- "allow an authorized Ajax request" in {
- running(FakeApplication()) {
- val reqJSON = Json.obj("buttonID" -> 1)
-
- val Some(rsp) = route(FakeRequest(POST, "/ajaxText").withSession("user" -> "bob").withBody(reqJSON))
-
- status(rsp) must equalTo(OK)
- contentType(rsp) must beSome.which(_ == "application/json")
-
- val rspJSON = Json.parse(contentAsString(rsp))
- val jsText = (rspJSON \ "text").asOpt[String]
-
- jsText must not beNone
-
- jsText.get must contain("Pressed 1")
- }
- }
In this test, a FakeRequest()
is created, with the body of the request a valid JSON string, containing a buttonID
of 1. As a logged-in user is represented as a user
key in the session, a mapping is added for the user bob
. The FakeRequest
is evaluated by Play's router, and a response is returned. The response must be an HTTP OK
(code 200), of type JSON, and the text
key of the returned JSON contains a reference to the button that was pressed. The \
provides a path syntax for retrieving fields from a JSON object.
A second test confirms that a valid user, sending an invalid request (no buttonID
field), has the request rejected.
- "reject a malformed Ajax request" in {
- running(FakeApplication()) {
- val reqJSON = Json.obj("thisIsBad" -> "thisIsBad")
-
- val Some(rsp) = route(FakeRequest(POST, "/ajaxText").withSession("user" -> "bob").withBody(reqJSON))
-
- status(rsp) must equalTo(BAD_REQUEST)
- contentType(rsp) must beSome.which(_ == "application/json")
-
- val rspJSON = Json.parse(contentAsString(rsp))
- // rspJSON == {"status":"KO","message":{"obj.buttonID":[{"msg":"error.path.missing","args":[]}]}}
-
- val jsStatus = (rspJSON \ "status").asOpt[String]
-
- jsStatus must not beNone
-
- jsStatus.get must contain("KO")
-
- val jsError = (rspJSON \ "message" \ "obj.buttonID" \\ "msg").map(_.as[String]).headOption
-
- jsError must not beNone
-
- jsError.get must beEqualTo("error.path.missing")
- }
- }
Here, instead of sending a valid buttonID
, thisIsBad
is sent instead. The server replies with a response with the status of BAD_REQUEST
(400), a bad status, and an error message indicating that an expected field was missing.
Lastly, a third test attempts the request when a user is not logged in.
- "prevent an unauthorized Ajax request" in {
- running(FakeApplication()) {
- val reqJSON = Json.obj("buttonID" -> 1)
-
- val Some(rsp) = route(FakeRequest(POST, "/ajaxText").withBody(reqJSON))
-
- status(rsp) must equalTo(FORBIDDEN)
- contentType(rsp) must beSome.which(_ == "text/html")
- contentAsString(rsp) must contain("Authorization Failed")
- }
- }
- }
- }
Here, the response is FORBIDDEN
(403), and instead of JSON, Deadbolt 2 has caused the "authorization failed" HTML page to be returned.
Other Features of the Play Framework
The example presented above shows some of the capabilities of the Play Framework, but there is much more that is possible. A few other areas are described below.
Internationalization
To internationalize an application, files named messages.lang_code
are created in the conf
directory, one per language translated, with the file messages
containing the default translation, or strings that are culture-neutral and not internationalized. Each file contains a key, representing the string that is translated, followed by an equals sign and then the translated string. Values that are replaced within a string are identified by position indices in braces. For example:
- welcomeMessage=Welcome {0}! You have {1} new messages.
- welcomeMessage=Bienvenue {0}! Vous avez {1} nouveaux messages.
The method play.api.i18n.Messages()
is used to select a string by its identifier, and to pass any values for replacement, as in:
- Messages("welcomeMessage", "bob", 17)
Unless explicitly specified, the language requested in the Accept-Language
request header will be used as the language of choice. The application.conf
must also be updated to include a comma-delimited list of the languages that are supported.
- application.langs="fr"
File Transfer
The Play Framework provides several methods for file download. A static file can be returned by an action as simply as:
- Ok.sendFile(new java.io.File("file.jpg"))
Files that are in-memory can be sent via the use of an Enumerator
. For example, data represented as a byte array can be downloaded as follows:
- import play.api.libs.iteratee.Enumerator
- import java.io.ByteArrayInputStream
- import play.api.libs.concurrent.Execution.Implicits._
-
- val ba: Array[Byte] = ... something ...
- val baos = new ByteArrayInputStream(ba)
- val fileContent: Enumerator[Array[Byte]] = Enumerator.fromStream(baos)
- SimpleResult(
- header = ResponseHeader(200,
- Map(
- CONTENT_LENGTH -> ba.length.toString,
- CONTENT_DISPOSITION -> ("""attachment; filename="file.dat""""))),
- body = fileContent)
Rather than using a helper, such as Ok()
, a SimpleResult
is constructed manually, and the CONTENT_LENGTH
and CONTENT_DISPOSITION
headers set appropriately. Play supports file uploading as well via the multipartFormData
body parser.
Caching
A server-side cache is supported via methods of play.api.cache.Cache
. The method set(key, v)
adds an item to the cache with the specified key, getAs[T](key)
retrieves an item from the cache as the specified type, and remove(key)
removes the item from the cache. Cached items can also be set to auto-expire.
Database Access
Play does not require a particular database access mechanism to be used — the application author can include their own by referencing external Java or Scala libraries — but Play already includes the Anorm framework. A reference to a JDBC provider is added toconf\application.conf
, and the associated database is automatically opened when the Play application starts. Anorm is not an object relational mapper, but instead supports direct SQL statements. For example, a database INSERT statement could be represented as follows, where the Anorm method executeInsert()
executes the statement, and returns the value of an autoincremented ID column, if available.
- def insert(v: SomeType): Option[Long] =
- DB.withConnection { implicit connection =>
- SQL("insert into someTable(firstField, secondField) values ({myFirstField}, {mySecondField})")
- .on('myFirstField -> v.fieldA, 'mySecondField -> v.fieldB).executeInsert()
- }
SSL and Port Configuration
Play supports SSL, with the SSL certificate stored in a Java keystore that can be password-protected. The ports used for HTTP and HTTPS can also be specified. For example, the play start
command could be expressed as:
play -Dhttp.port=80 -Dhttps.port=443 -Dhttps.keyStore="conf/keystore.jks" -Dhttps.keyStorePassword="somePassword" start
Summary
This article has shown the basics of the Play Framework, and some of its capabilities. It is a popular framework, used by businesses such as LinkedIn and The Guardian (UK), and was even part of a transition of one company's transition from one based around Microsoft's .NET Framework to Scala and Play.
References
- [1] Play Framework
http://www.playframework.com/ - [2] WAR plugin
https://github.com/dlecan/play2-war-plugin - [3] Scala
http://www.scala-lang.org/ - [4] Typesafe, Inc.
http://www.typesafe.com/ - [5] Eclipse
http://www.eclipse.org/ - [6] WAR Plugin for Play
https://github.com/dlecan/play2-war-plugin - [7] Scala IDE for Eclipse
http://scala-ide.org - [8] Typesafe configuration library
https://github.com/typesafehub/config - [9] Specs2
http://etorreborre.github.io/specs2/ - [10] Selenium
http://code.google.com/p/selenium/ - [11] FluentLenium
https://github.com/FluentLenium/FluentLenium - [12] Deadolt 2
https://github.com/schaloner/deadbolt-2 - [13] Session discussion 1
https://gist.github.com/manuelbernhardt/1868295 - [14] Session discussion 2
http://stackoverflow.com/questions/18088134/can-i-set-cookies-before-returning-an-action-in-play-framework-2 - [15] Technology Change: .NET to Scala
http://randonom.com/blog/2013/10/technology-change-net-to-scala/
Software Engineering Tech Trends (SETT) is a regular publication featuring emerging trends in software engineering.