Action composition

Defining an Action for a request is merely the act of using the Action helper object, which is defined as follows:

object Action extends ActionBuilder[Request] {
  def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = block(request) 
}

The code which we write within an action block goes on to be the invokeBlock method. This method is inherited from ActionBuilder. This is a trait that provides helper methods to generate an Action. All the different ways in which we define an Action, such as async, synchronous, with or without specifying a parser, and so on are declared in ActionBuilder.

We can also define our custom Actions by extending ActionBuilder and defining a custom invoke block.

The need for an Action composition

Let's take a case study. A lot of applications these days keep track of requests, such as the IP address of the machine it was instigated from, the time it was received, or even the whole request as is. It would be a crime to add the same code in almost every Action defined for such an application.

Now, assume that we need to persist a request using a persistReq method every time it is encountered for a specific module: the administrator user, for example. Then, in this case, we could define a custom Action to be used only within this module. Let's see how we can define a custom Action to persist a request before processing it:

import play.api.mvc._
import scala.concurrent.Future

object TrackAction extends ActionBuilder[Request] {
  override protected def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = { 
    persistReq(request) 
    block(request) 
  } 

  private def persistReq[A](request: Request[A]) = { 
    ...  
  } 
} 

Within our application, we could use it similar to the default Action:

def viewAdminProfile(id: Long) = TrackAction {
    request => 
      ... 
  } 

def updateAdminProfile(id: Long) = TrackAction(parse.json) { 
    request => 
      ... 
  } 

Another way to define a custom Action is by extending the Action trait. So, we can also define TrackAction as follows:

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

  def apply(request: Request[A]): Future[Result] = { 
    persistReq(request) 
    action(request) 
  } 

  private def persistReq(request: Request[A]) = { 
    …  
    } 
  
  lazy val parser = action.parser 
}

Its usage would be something similar to this:

def viewAdminProfile(id: Long) = TrackAction {
    Action {request => 
        … 
  } 
} 

def updateAdminProfile(id: Long) = TrackAction { 
    Action(parse.json) {	request => 
        … 
   } 
   } 

Notice that we need to wrap the Action definition again within the action object. We could remove this additional overhead of wrapping an action object every time by defining ActionBuilder, which uses the composeAction method:

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

Now, the usage will be:

def viewAdminProfile(id: Long) = TrackingAction {
    request => 
      ... 
  } 

def updateAdminProfile(id: Long) = TrackingAction(parse.json) { 
    request => 
      ... 
    }

Differentiating between Action composition and filters

Action composition is an Action that extends EssentialAction and returns a result. It is more suitable when we need to perform an operation on a few routes or Actions only. Action composition is more powerful than a filter and is more apt at handling specific concerns, such as authentication.

It provides support to read, modify, and even block a request. There is also a provision to define Actions for custom request types.

Customized requests

First, let's see how to define custom requests. We can also define custom requests using the WrappedRequest class. This is defined as follows:

class WrappedRequest[A](request: Request[A]) extends Request[A] { 
     def id = request.id 
    def tags = request.tags 
    def body = request.body 
    def headers = request.headers 
    def queryString = request.queryString 
    def path = request.path 
    def uri = request.uri 
    def method = request.method 
    def version = request.version 
    def remoteAddress = request.remoteAddress 
    def secure = request.secure 
   } 

Suppose we wish to pass the time at which a request was received with every request, we could define this as:

class TimedRequest[A](val time: DateTime, request: Request[A]) extends WrappedRequest[A](request) 

Now, let's see how we can manipulate the incoming requests and transform them into TimedRequest:

def timedAction[A](action: Action[A]) = Action.async(action.parser) {
  request => 
    val time = new DateTime()
    val newRequest = new AppRequest(time, request)
    action(newRequest)
}

Therefore, the timedAction Action can be used within controllers in this way:

def getUserList = timedAction {
    Action {
      request => 
        val users= User.getAll
        Ok(Json.toJson(users))
    }
  }

Now, suppose we wish to block all the requests from certain browsers; it can be done in this way:

def timedAction[A](action: Action[A]) = Action.async(action.parser) {
  request => 
    val time = new DateTime()
    val newRequest = new AppRequest[A](time, request)
    request.headers.get(USER_AGENT).collect {
      case agent if isCompatibleBrowser(agent) => 
        action(newRequest)
      }.getOrElse{
     Future.successful(Ok(views.html.main()))
  }
}

Here, the isCompatibleBrowser method checks if the browser is supported.

We can also manipulate the response; let's add the duration it took to process the request in the response headers:

def timedAction[A](action: Action[A]) = Action.async(action.parser) { 
   request => 
    val time = new DateTime() 
    val newRequest = new AppRequest(time, request) 
    action(newRequest).map(_.withHeaders("processTime" -> new DateTime().minus(time.getMillis).getMillis.toString())) 
}

Now, let's see how we define an Action for a custom request. You may wonder why we need a custom request. Take the example where our application has a facility for the users to use e-mail, chat, block, upload, share, and so on. In this case, we could tie these so that we can have a user object as part of the request internally.

The need for a user object

Our REST API only sends userId, which is a number. For all these operations, we need the user's emailId, userName, and profile picture, if any. Let's define UserRequest in the following way:

class UserRequest[A](val user: User, request: Request[A]) extends WrappedRequest[A](request) 

Now, let's define an Action, which uses this request:

def UserAction(userId: Long) = new ActionBuilder[UserRequest] { 
  def invokeBlock[A](request: Request[A], block: (UserRequest[A]) => Future[Result]) = { 
      User.findById(userId).map { user:User => 
         block(new UserRequest(user, request)) 
      } getOrElse { 
        Future.successful(Redirect(views.html.login)) 
      } 
    } 
  } 

So, in our Action, we find the user corresponding to the given userId, else we redirect to the login page.

Here, we can see how to use UserAction:

def initiateChat(userId:Long,chatWith:Long) = UserRequest{ 
    request=> 
      val status:Boolean = ChatClient.initiate(request.user,chatWith) 
       if(status){ 
        Ok 
      }else{ 
         Unauthorized 
      } 
  } 

The chat client initiates a method and sends a message to the user with userId.chatWith that a user, whose profile is request.user, wants to chat. It returns true if the other user agrees, else it returns false.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.225.57.126