Writing Play controllers in Scala
Play controllers are the most important part of any Play applications. A Play Scala application share the same concepts than a classical Play application but use a more functional way to describe actions.
Scala controllers are Objects
A Controller is a Scala singleton object, hosted by the controllers
package, and subclassing play.mvc.Controller
. In Scala you can declare as many controllers you want in the same file.
This a classical controller definition:
package controllers {
import play._
import play.mvc._
object Users extends Controller {
def show(id:Long) = Template("user" -> User.findById(id))
def edit(id:Long, email:String) = {
User.changeEmail(id, email)
Action(show(id))
}
}
}
Because Scala provides the native notion of Singleton objects we don’t need anymore to deal with Java static methods while keeping to ability to reference statically any action like show(id)
.
Action methods return values
A Play controller usually uses imperative orders like render(…)
or forbidden()
to trigger the response generation. On the contrary an action methods written in Scala is seen as functions and must return a value. This value will be used by the framework to generate the HTTP response resulting of the request.
An action method can of course return several kind of values depending of the request (like for example a Template
or an Forbidden
value).
Here are listed the typical return types:
Ok
Returning the Ok
value will generate an empty 200 OK response.
def index = Ok
Html
Returning an Html
value will generated a 200 OK response filled with the HTML content. The response content type will be automatically set to text/html.
def index = Html("<h1>Hello world!</h1>")
You can also generate Html by calling a template.
Xml
Returning an Xml
value will generated a 200 OK response filled with the XML content. The response content type will be automatically set to text/xml.
def index = Xml(<message>Hello world!</message>)
Text
Returning an Text
value will generated a 200 OK response filled with the text content. The response content type will be automatically set to text/plain.
def index = Text("Hello world!")
Json
Returning an Json
value will generated a 200 OK response filled with the text content. The response content type will be automatically set to application/json.
def index = Json("{message: 'Hello world'}")
You can also try to pass any Scala object and Play will try to serialize it to JSON:
def index = Json(users)
However currently the JSON serialization mechanism comes from Java and can not work as exepected with complex Scala structures.
A workaround is to use a Scala dedicated JSON serialization library, for example Lift JSON, and use it as Json(JsonAST.render(users))
Created
Returning the Created
value will generate an empty 201 Created response.
def index = Created
Accepted
Returning the Accepted
value will generate an empty 202 Accepted response.
def index = Accepted
NoContent
Returning the NoContent
value will generate an empty 204 No Content response.
def index = NoContent
Action
If an action method return a Action
value, Play will redirect the Browser to the corresponding action, using the action method arguments to properly resolve the proper URL.
def index = Action(show(3))
Note that here show(3)
is a by-name parameter, and the corresponding methid will not been invoked. Play will resolve this call as an URL (typically something like users/3), and will issue an HTTP redirect to this URL. The action will then be invoked in a new request context.
In a Java controller you achieve the same result by calling directly the corresponding action method. Using Scala call by name concept allow to keep the compiler checked and typesafe redirection without any language hack.
Redirect
Returning the Redirect
value will generate an empty 301 Moved Permanently response.
def index = Redirect("http://www.google.com")
You can optionnaly specify a second argument to switch between 301 and 302 response status code.
def index = Redirect("http://www.google.com", false)
NotModified
Returning the NotModified
value will generate an empty 304 Not Modified response.
def index = NotModified
You can also specify an ETag to the response:
def index = NotModified("123456")
BadRequest
Returning the BadRequest
value will generate an empty 400 Bad Request response.
def index = BadRequest
Unauthorized
Returning the Unauthorized
value will generate an empty 401 Unauthorized response.
def index = Unauthorized
You can optionnaly specify a realm name:
def index = Unauthorized("Administration area")
Forbidden
Returning the Forbidden
value will generate an empty 403 Forbidden response.
def index = Forbidden
You can optionnaly specify an error message:
def index = Forbidden("Unsufficient permissions")
NotFound
Returning the NotFound
value will generate an empty 404 Not Found response.
def index = NotFound
You can optionnaly specify a resource name:
def index = NotFound("Article not found")
Or use a more classical HTTP method, resource Path combination:
def index = NotFound("GET", "/toto")
Error
Returning the Error
value will generate an empty 500 Internal Server Error response.
def index = Error
You can optionnaly specify an error message:
def index = Error("Oops…")
Or specify a more specific error code:
def index = Error(503, "Not ready yet…")
Return type inference
You can also directly use the inferred return type to send the action result. For example using a String:
def index = "<h1>Hello world</h1>"
Or you can even use the built-in XML support to write XHTML in a literal way:
def index = <h1>Hello world</h1>
If the return type looks like a binary stream, play will automatically render the response as binary. So generating a captcha image using the built-in Captcha helper can be written as:
def index = Images.captcha
Controller interceptors
Controller interceptors work almost the same way than for Java controller. You simply have to annotate any controller method with the corresponding interceptor annotation:
@Before def logRequests {
println("New request…")
}
You see that here, the logRequests
method does not return any value. So the request execution will continue by invoking the next interceptors and eventually the action method.
But you can also write some interceptor that return a value:
@Before def protectActions = {
Forbidden
}
Here the execution will stop, and the Forbidden
value will be used to generate the HTTP response.
If you want to continue the request execution, just make your interceptor return Continue
:
@Before def protectActions = {
session("isAdmin") match {
case Some("yes") => Continue
case _ => Forbidden("Restricted to administrators")
}
}
Mixing controllers using Traits
Scala Traits can be used to compose controller more effeciently by mixing several aspects. You can define both action methods and interceptors in a controller Trait.
For example the following Secure trait add a seucrity interceptor to any controller applying the Trait:
trait Secure {
self:Controller =>
@Before checkSecurity = {
session("username") match {
case Some(username) => renderArgs += "user" -> User(username)
Continue
case None => Action(Authentication.login)
}
}
def connectedUser = renderArgs("user").get
}
Note that here we use the self:Controller =>
notation to indicate that this Trait can only be mixed with a Controller
type.
And you can use it to create a secured controller:
object Application extends Controller with Secure {
def index = <h1>Hello {connectedUser.name}!</h1>
}
There is also small differences about Data binding