Documentation

Action composition

This chapter introduces several ways of defining generic action functionality.

Custom action builders

We saw previously that there are multiple ways to declare an action - with a request parameter, without a request parameter, with a body parser etc. In fact there are more than this, as we’ll see in the chapter on asynchronous programming.

These methods for building actions are actually all defined by a trait called ActionBuilder, and the Action object that we use to declare our actions is just an instance of this trait. By implementing your own ActionBuilder, you can declare reusable action stacks, that can then be used to build actions.

Let’s start with the simple example of a logging decorator, we want to log each call to this action.

The first way is to implement this functionality in the invokeBlock method, which is called for every action built by the ActionBuilder:

import play.api.mvc._

object LoggingAction extends ActionBuilder[Request] {
  def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[SimpleResult]) = {
    Logger.info("Calling action")
    block(request)
  }
}

Now we can use it the same way we use Action:

def index = LoggingAction {
  Ok("Hello World")
}

Since ActionBuilder provides all the different methods of building actions, this also works with, for example, declaring a custom body parser:

def submit = LoggingAction(parse.text) { request =>
  Ok("Got a body " + request.body.length + " bytes long")
}

Composing actions

In most applications, we will want to have multiple action builders, some that do different types of authentication, some that provide different types of generic functionality, etc. In which case, we won’t want to rewrite our logging action code for each type of action builder, we will want to define it in a reuseable way.

Reusable action code can be implemented by wrapping actions:

import play.api.mvc._

case class Logging[A](action: Action[A]) extends Action[A] {

  def apply(request: Request[A]): Future[SimpleResult] = {
    Logger.info("Calling action")
    action(request)
  }

  lazy val parser = action.parser
}

We can also use the Action action builder to build actions without defining our own action class:

import play.api.mvc._

def logging[A](action: Action[A])= Action.async(action.parser) { request =>
  Logger.info("Calling action")
  action(request)
}

Actions can be mixed in to action builders using the composeAction method:

object LoggingAction extends ActionBuilder[Request] {
  def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[SimpleResult]) = {
    block(request)
  }
  override def composeAction[A](action: Action[A]) = new Logging(action)
}

Now the builder can be used in the same way as before:

def index = LoggingAction {
  Ok("Hello World")
}

We can also mix in wrapping actions without the action builder:

def index = Logging {
  Action {
    Ok("Hello World")
  }
}

More complicated actions

So far we’ve only shown actions that don’t impact the request at all. Of course, we can also read and modify the incoming request object:

import play.api.mvc._

def xForwardedFor[A](action: Action[A]) = Action.async(action.parser) { request =>
  val newRequest = request.headers.get("X-Forwarded-For").map { xff =>
    new WrappedRequest[A](request) {
      override def remoteAddress = xff
    }
  } getOrElse request
  action(newRequest)
}

Note: Play already has built in support for X-Forwarded-For headers.

We could block the request:

import play.api.mvc._

def onlyHttps[A](action: Action[A]) = Action.async(action.parser) { request =>
  request.headers.get("X-Forwarded-Proto").collect {
    case "https" => action(request)
  } getOrElse {
    Future.successful(Forbidden("Only HTTPS requests allowed"))
  }
}

And finally we can also modify the returned result:

import play.api.mvc._
import play.api.libs.concurrent.Execution.Implicits._

def addUaHeader[A](action: Action[A]) = Action.async(action.parser) { request =>
  action(request).map(_.withHeaders("X-UA-Compatible" -> "Chrome=1"))
}

Different request types

The ActionBuilder trait is parameterised to allow building actions using different request types. The invokeBlock method can translate the incoming request to whatever request type it wants. This is useful for many things, for example, authentication, to attach a user object to the current request, or to share logic to load objects from a database.

Let’s consider a REST API that works with objects of type Item. There may be many routes under the /item/:itemId path, and each of these need to look up the item. They may also share the same authorisation properties. In this case, it may be useful to put this logic into an action builder.

First of all, we’ll create a request object that adds an Item:

import play.api.mvc._

class RequestWithItem[A](val item: Item, request: Request[A]) extends WrappedRequest[A](request)

Now we’ll create an action builder that looks up that item when the request is made. Note that this action builder is defined inside a method that takes the id of the item:

def ItemAction(itemId: String) = new ActionBuilder[RequestWithItem] {
  def invokeBlock[A](request: Request[A], block: (RequestWithItem[A]) => Future[SimpleResult]) = {
    ItemDao.findById(itemId).map { item =>
      block(new RequestWithItem(item, request))
    } getOrElse {
      Future.successful(NotFound)
    }
  }
}

Now we can use this action builder for each item:

def tagItem(itemId: String, tag: String) = ItemAction(itemId) { request =>
  request.item.addTag(tag)
  Ok
}

Authentication

One of the most common use cases for action composition is authentication. We can easily implement our own authentication action builder like this:

import play.api.mvc._

class AuthenticatedRequest[A](val username: String, request: Request[A]) extends WrappedRequest[A](request)

object Authenticated extends ActionBuilder[AuthenticatedRequest] {
  def invokeBlock[A](request: Request[A], block: (AuthenticatedRequest[A]) => Future[SimpleResult]) = {
    request.session.get("username").map { username =>
      block(new AuthenticatedRequest(username, request))
    } getOrElse {
      Future.successful(Forbidden)
    }
  }
}

def currentUser = Authenticated { request =>
  Ok("The current user is " + request.username)
}

Play also provides a built in authentication action builder. Information on this and how to use it can be found here.

Note: The built in authentication action builder is just a convenience helper to minimise the code necessary to implement authentication for simple cases, its implementation is very similar to the example above.

If you have more complex requirements than can be met by the built in authentication action, then implementing your own is not only simple, it is recommended.

Play also provides a global filter API , which is useful for global cross cutting concerns.

Next: Content negotiation


Found an error in this documentation? The source code for this page can be found here. After reading the documentation guidelines, please feel free to contribute a pull request.