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.
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) }
def viewAdminProfile(id: Long) = TrackingAction { request => ... } def updateAdminProfile(id: Long) = TrackingAction(parse.json) { request => ... }
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.
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.
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
.
18.225.57.126