Scala and the Play Framework

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:

app\controllers\Application.scala

  1. package controllers
  2.  
  3. import play.api._
  4. import play.api.mvc._
  5.  
  6. object Application extends Controller {
  7.  
  8. def index = Action {
  9. Ok(views.html.index("Your new application is ready."))
  10. }
  11.  
  12. }

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:

app\views\index.scala.html

  1. @(message: String)
  2.  
  3. @main("Welcome to Play 2.1") {
  4.  
  5. @play20.welcome(message)
  6.  
  7. }

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:

app\views\main.scala.html

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

conf\routes

  1. # Routes
  2. # This file defines all application routes (Higher priority routes first)
  3. # ~~~~
  4.  
  5. # Home page
  6. GET / controllers.Application.index
  7.  
  8. # Map static resources from the /public folder to the /assets URL path
  9. 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.

test\ApplicationSpec.scala

  1. package test
  2.  
  3. import org.specs2.mutable._
  4.  
  5. import play.api.test._
  6. import play.api.test.Helpers._
  7.  
  8. /**
  9.  * Add your spec here.
  10.  * You can mock out a whole application including requests, plugins etc.
  11.  * For more information, consult the wiki.
  12.  */
  13. class ApplicationSpec extends Specification {
  14.  
  15. "Application" should {
  16.  
  17. "send 404 on a bad request" in {
  18. running(FakeApplication()) {
  19. route(FakeRequest(GET, "/boum")) must beNone
  20. }
  21. }
  22.  
  23. "render the index page" in {
  24. running(FakeApplication()) {
  25. val home = route(FakeRequest(GET, "/")).get
  26.  
  27. status(home) must equalTo(OK)
  28. contentType(home) must beSome.which(_ == "text/html")
  29. contentAsString(home) must contain ("Your new application is ready.")
  30. }
  31. }
  32. }
  33. }

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:

test\IntegrationSpec.scala

  1. package test
  2.  
  3. import org.specs2.mutable._
  4.  
  5. import play.api.test._
  6. import play.api.test.Helpers._
  7.  
  8. /**
  9.  * add your integration spec here.
  10.  * An integration test will fire up a whole play application in a real (or headless) browser
  11.  */
  12. class IntegrationSpec extends Specification {
  13.  
  14. "Application" should {
  15.  
  16. "work from within a browser" in {
  17. running(TestServer(3333), HTMLUNIT) { browser =>
  18.  
  19. browser.goTo("http://localhost:3333/")
  20.  
  21. browser.pageSource must contain("Your new application is ready.")
  22.  
  23. }
  24. }
  25.  
  26. }
  27.  
  28. }

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.

project\Build.scala

  1. import sbt._
  2. import Keys._
  3. import play.Project._
  4.  
  5. object ApplicationBuild extends Build {
  6.  
  7. val appName = "ajax"
  8. val appVersion = "1.0-SNAPSHOT"
  9.  
  10. val appDependencies = Seq(
  11. jdbc,
  12. anorm,
  13. cache,
  14.  
  15. // https://github.com/schaloner/deadbolt-2
  16. "be.objectify" %% "deadbolt-scala" % "2.2-RC2"
  17. )
  18.  
  19. val main = play.Project(appName, appVersion, appDependencies).settings(
  20. resolvers += Resolver.url("Objectify Play Repository", url("http://schaloner.github.io/releases/"))(Resolver.ivyStylePatterns),
  21. resolvers += Resolver.url("Objectify Play Snapshot Repository", url("http://schaloner.github.io/snapshots/"))(Resolver.ivyStylePatterns)
  22. )
  23.  
  24. }

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:

app\controllers\Application.scala

  1. package controllers
  2.  
  3. import play.api._
  4. import play.api.mvc._
  5. import play.api.data._
  6. import play.api.data.Forms._
  7. import be.objectify.deadbolt.scala.DeadboltActions
  8. import security.MyDeadboltHandler
  9.  
  10. 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:

  1. val loginForm = Form(
  2. tuple(
  3. "username" -> nonEmptyText,
  4. "password" -> text)
  5. verifying ("Invalid username or password", result => result match {
  6. case (username, password) =>
  7. models.User.authenticate(username, password).isDefined
  8. }))
  9.  
  10. def index = Action {
  11. Ok(views.html.index(loginForm))
  12. }

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.

app\models\User.scala

  1. package models
  2.  
  3. import be.objectify.deadbolt.core.models._
  4. import play.libs.Scala
  5.  
  6. class SecurityRole(val roleName: String) extends Role {
  7. def getName: String = roleName
  8. }
  9.  
  10. class UserPermission(val value: String) extends Permission {
  11. def getValue: String = value
  12. }

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.

  1. case class User(name: String = "") extends Subject {
  2.  
  3. def getRoles: java.util.List[SecurityRole] = {
  4. Scala.asJava(List[SecurityRole]()) // no roles needed
  5. }
  6.  
  7. def getPermissions: java.util.List[UserPermission] = {
  8. Scala.asJava(List[UserPermission]()) // no permissions needed
  9. }
  10.  
  11. def getIdentifier: String = name
  12. }

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.

  1. object User {
  2. import play.api.Play
  3. import play.api.Play.current
  4. private lazy val adminUsername = Play.current.configuration.getString("login.username").getOrElse("")
  5. private lazy val adminPassword = Play.current.configuration.getString("login.password").getOrElse("")
  6.  
  7. def authenticate(username: String, password: String): Option[User] =
  8. if ((adminUsername == username) && (adminPassword == password))
  9. Some(User(username))
  10. else
  11. None
  12. }

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.

app\views\index.scala.html

  1. @(frm: Form[(String,String)])
  2.  
  3. @import helper._
  4.  
  5. @main("Ajax") {
  6.  
  7. @form(action = routes.Application.login, args = 'id -> "loginform") {
  8.  
  9. @frm.globalError.map { error =>
  10. <p style="color: red">
  11. @error.message
  12. </p>
  13. }
  14.  
  15. @inputText(
  16. field = frm("username"),
  17. args = '_label -> "Username:"
  18. )
  19. @inputPassword(
  20. field = frm("password"),
  21. args = '_label -> "Password:"
  22. )
  23.  
  24. <input type="submit" id="submit" value="Log in">
  25. }
  26.  
  27. }

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.

conf\routes

  1. POST /login controllers.Application.login

We implement the login action as follows:

app\controllers\Application.scala

  1. def login = {
  2. Action { implicit request =>
  3. loginForm.bindFromRequest.fold(
  4. formWithErrors => { // binding failure
  5. Logger.error("Login failed for user " + formWithErrors.data("username"))
  6. BadRequest(views.html.index(formWithErrors))
  7. },
  8. user => {
  9. // update the session BEFORE the view is rendered
  10. val modifiedRequest = updateRequestSession(request, List(("user" -> user._1)))
  11. Ok(views.html.pageA(modifiedRequest)).withSession(modifiedRequest.session)
  12. })
  13. }
  14. }

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

  1. private def updateRequestSession(request: Request[Any], additionalSessionParams: List[(String, String)]): Request[Any] = {
  2. import scala.language.reflectiveCalls
  3.  
  4. val updatedSession = additionalSessionParams.foldLeft[Session](request.session) { _ + _ }
  5. val existingSession = request.headers.get(SET_COOKIE).map(cookies =>
  6. Session.decodeFromCookie(Cookies.decode(cookies).find(_.name == Session.COOKIE_NAME)))
  7. val newSession = if (existingSession.isDefined)
  8. existingSession.get.data.foldLeft(updatedSession) { _ + _ }
  9. else
  10. updatedSession
  11.  
  12. val cookies = Cookies(request.headers.get(COOKIE)).cookies +
  13. (Session.COOKIE_NAME -> Session.encodeAsCookie(newSession))
  14. val headerMap = request.headers.toMap +
  15. (COOKIE -> Seq(Cookies.encode(cookies.values.toSeq)))
  16. val theHeaders = new play.api.mvc.Headers {
  17. val data: Seq[(String, Seq[String])] = headerMap.toSeq
  18. }
  19.  
  20. Request[Any](request.copy(headers = theHeaders), request.body)
  21. }

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:

app\views\pageA.scala.html

  1. @(request:Request[Any])
  2.  
  3. @import helper._
  4.  
  5. @main("Page A") {
  6. <h1>Welcome @request.session.get("user")</h1>
  7.  
  8. <a id="logout" href=@routes.Application.logout>logout</a> <br />
  9. <a id="pageB" href=@routes.Application.pageB>page B</a>
  10. }

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:

app\views\pageB.scala.html

  1. @main("Page B") {
  2. <h1>Page B</h1>
  3. }

Routes are added for each of the actions in the routes file, and the actions implemented as:

app\controllers\Application.scala

  1. def logout =
  2. Action { implicit request =>
  3. Ok(views.html.index(loginForm)).withNewSession
  4. }
  5.  
  6. def pageB = SubjectPresent(new MyDeadboltHandler) {
  7. Action { implicit request =>
  8. Ok(views.html.pageB())
  9. }
  10. }

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:

app\security\MyDeadboltHandler.scala

  1. package security
  2.  
  3. import be.objectify.deadbolt.scala.{ DynamicResourceHandler, DeadboltHandler }
  4. import play.api.mvc.{ Request, Result, Results }
  5. import be.objectify.deadbolt.core.models.Subject
  6.  
  7. class MyDeadboltHandler(dynamicResourceHandler: Option[DynamicResourceHandler] = None) extends DeadboltHandler {
  8.  
  9.   def beforeAuthCheck[A](request: Request[A]) = None
  10.  
  11.   def getDynamicResourceHandler[A](request: Request[A]): Option[DynamicResourceHandler] = None
  12.  
  13.   def getSubject[A](request: Request[A]): Option[Subject] =
  14.     request.session.get("user").map(u => models.User(u))
  15.  
  16.   import scala.concurrent.Future
  17.   import scala.concurrent.ExecutionContext.Implicits.global
  18.   def onAuthFailure[A](request: Request[A]) = {
  19.     Future.successful(Results.Forbidden(views.html.authFailed()))
  20.   }
  21. }

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:

app\views\authFailed.scala.html

  1. @main("Authorization Failed") {
  2. <h1>Authorization Failed</h1>
  3. }

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.

test\IntegrationSpec.scala

  1. package test
  2.  
  3. import org.specs2.mutable._
  4.  
  5. import play.api.test._
  6. import play.api.test.Helpers._
  7.  
  8. class IntegrationSpec extends Specification {
  9.  
  10. def login(username: String, password: String)(implicit browser: TestBrowser) = {
  11. browser.goTo("http://localhost:3333/")
  12.  
  13. browser.title must beEqualTo("Ajax")
  14.  
  15. browser.$("#username").text(username)
  16. browser.$("#password").text(password)
  17. browser.$("#submit").click
  18. }

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.

  1. "Application" should {
  2.  
  3. "allow a correct login" in {
  4. running(TestServer(3333), HTMLUNIT) { implicit browser =>
  5.  
  6. login("bob", "bobpass")
  7.  
  8. browser.pageSource must contain("Welcome bob")
  9. }
  10. }

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.

  1. "reject an incorrect login" in {
  2. running(TestServer(3333), HTMLUNIT) { implicit browser =>
  3.  
  4. login("fred", "fredpass")
  5.  
  6. browser.title must beEqualTo("Ajax")
  7. browser.pageSource must contain("Invalid username or password")
  8. }
  9. }

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.

  1. "allow a logged-in user to view page B" in {
  2.   running(TestServer(3333), HTMLUNIT) { implicit browser =>
  3.  
  4.     login("bob", "bobpass")
  5.  
  6.     browser.pageSource must contain("Welcome bob")
  7.  
  8.     browser.$("#pageB").click
  9.  
  10.     browser.title must beEqualTo("Page B")
  11.   }
  12. }

The fourth test attempts to navigate to page B without logging in, and the request is expected to be rejected.

  1. "prevent unauthorized access to page B" in {
  2. running(TestServer(3333), HTMLUNIT) { browser =>
  3.  
  4. browser.goTo("http://localhost:3333/pageB")
  5.  
  6. browser.title must beEqualTo("Authorization Failed")
  7. }
  8. }
  9.  
  10. }
  11.  
  12. }

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:

app\views\pageA.scala.html

  1. <div id="div1">
  2. <button id="btn1">Press Me 1</button>
  3. </div>
  4.  
  5. <div id="div2">
  6. <button id="btn2">Press Me 2</button>
  7. </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.

  1. <script>
  2. function doAjax(buttonID) {
  3. var dataToSend, json;
  4.  
  5. dataToSend = {};
  6. dataToSend.buttonID = buttonID;
  7.  
  8. json = JSON.stringify(dataToSend);
  9.  
  10. $.ajax({
  11. url : "ajaxText",
  12. contentType : 'application/json',
  13. dataType : "json", // server return is expected to be JSON
  14. type : "post",
  15. data : json,
  16. success : function(data) {
  17. // "data" is the JSON response from the server
  18. $("#div" + data.divID).text(data.text);
  19. },
  20. error : function(jqXHR, textStatus, errorThrown) {
  21. console.log(errorThrown);
  22. }
  23. });
  24. }

In jQuery's document ready() handler, we associate click handlers with each of the buttons, calling doAjax() with the appropriate button ID.

  1. $(function() {
  2. $("#btn1").click(function() {
  3. doAjax(1);
  4. });
  5. $("#btn2").click(function() {
  6. doAjax(2);
  7. });
  8. });
  9. </script>

After adding a POST route for ajaxText to the conf\routes file, the action itself is implemented in the controller.

app\controllers\Application.scala

  1. import play.api.libs.json._
  2. import play.api.libs.functional.syntax._
  3.  
  4. val ajaxTextRds = (__ \ 'buttonID).read[Long]
  5.  
  6. def ajaxText = SubjectPresent(new MyDeadboltHandler) {
  7. Action(parse.json) { request =>
  8. request.body.validate[Long](ajaxTextRds).map {
  9. case (buttonID) => {
  10. val text = "Pressed " + buttonID + " at " + new java.util.Date()
  11. Ok(Json.obj("status" -> "OK", "divID" -> buttonID, "text" -> text))
  12. }
  13. }.recoverTotal {
  14. e => BadRequest(Json.obj("status" -> "KO", "message" -> JsError.toFlatJson(e)))
  15. }
  16. }
  17. }

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.

test\IntegrationSpec.scala

  1. "allow a logged-in user to make an ajax request" in {
  2. running(TestServer(3333), HTMLUNIT) { implicit browser =>
  3.  
  4. login("bob", "bobpass")
  5.  
  6. browser.pageSource must contain("Welcome bob")
  7.  
  8. browser.$("#btn1").click
  9.  
  10. browser.await.atMost(3, java.util.concurrent.TimeUnit.SECONDS).until("#div1").withText.startsWith("Pressed 1").isPresent
  11.  
  12. browser.$("#div1").first.getText must contain("Pressed 1")
  13.  
  14. }
  15. }

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.

test\ApplicationSpec.scala

  1. "allow an authorized Ajax request" in {
  2.   running(FakeApplication()) {
  3.     val reqJSON = Json.obj("buttonID" -> 1)
  4.  
  5.     val Some(rsp) = route(FakeRequest(POST, "/ajaxText").withSession("user" -> "bob").withBody(reqJSON))
  6.  
  7.     status(rsp) must equalTo(OK)
  8.     contentType(rsp) must beSome.which(_ == "application/json")
  9.      
  10.     val rspJSON = Json.parse(contentAsString(rsp))
  11.     val jsText = (rspJSON \ "text").asOpt[String]
  12.      
  13.     jsText must not beNone
  14.      
  15.     jsText.get must contain("Pressed 1")
  16.   }
  17. }

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.

 
  1. "reject a malformed Ajax request" in {
  2.   running(FakeApplication()) {
  3.     val reqJSON = Json.obj("thisIsBad" -> "thisIsBad")
  4.  
  5.     val Some(rsp) = route(FakeRequest(POST, "/ajaxText").withSession("user" -> "bob").withBody(reqJSON))
  6.  
  7.     status(rsp) must equalTo(BAD_REQUEST)
  8.     contentType(rsp) must beSome.which(_ == "application/json")
  9.      
  10.     val rspJSON = Json.parse(contentAsString(rsp))
  11.     // rspJSON == {"status":"KO","message":{"obj.buttonID":[{"msg":"error.path.missing","args":[]}]}}
  12.      
  13.     val jsStatus = (rspJSON \ "status").asOpt[String]
  14.      
  15.     jsStatus must not beNone
  16.      
  17.     jsStatus.get must contain("KO")
  18.      
  19.     val jsError = (rspJSON \ "message" \ "obj.buttonID" \\ "msg").map(_.as[String]).headOption
  20.  
  21.     jsError must not beNone
  22.    
  23.     jsError.get must beEqualTo("error.path.missing")
  24.   }
  25. }

Here, instead of sending a valid buttonIDthisIsBad 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.

  1. "prevent an unauthorized Ajax request" in {
  2. running(FakeApplication()) {
  3. val reqJSON = Json.obj("buttonID" -> 1)
  4.  
  5. val Some(rsp) = route(FakeRequest(POST, "/ajaxText").withBody(reqJSON))
  6.  
  7. status(rsp) must equalTo(FORBIDDEN)
  8. contentType(rsp) must beSome.which(_ == "text/html")
  9. contentAsString(rsp) must contain("Authorization Failed")
  10. }
  11. }
  12. }
  13. }

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 confdirectory, 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:

conf\messages

  1. welcomeMessage=Welcome {0}! You have {1} new messages.

conf\messages.fr

  1. 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:

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

conf\application.conf

  1. 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:

  1. 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:

  1. import play.api.libs.iteratee.Enumerator
  2. import java.io.ByteArrayInputStream
  3. import play.api.libs.concurrent.Execution.Implicits._
  4.  
  5. val ba: Array[Byte] = ... something ...
  6. val baos = new ByteArrayInputStream(ba)
  7. val fileContent: Enumerator[Array[Byte]] = Enumerator.fromStream(baos)
  8. SimpleResult(
  9. header = ResponseHeader(200,
  10. Map(
  11. CONTENT_LENGTH -> ba.length.toString,
  12. CONTENT_DISPOSITION -> ("""attachment; filename="file.dat""""))),
  13. 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.

  1. def insert(v: SomeType): Option[Long] =
  2. DB.withConnection { implicit connection =>
  3. SQL("insert into someTable(firstField, secondField) values ({myFirstField}, {mySecondField})")
  4. .on('myFirstField -> v.fieldA, 'mySecondField -> v.fieldB).executeInsert()
  5. }

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

Software Engineering Tech Trends (SETT) is a regular publication featuring emerging trends in software engineering.


secret