Scala/Implicit/Playframework/Action: Wrapping Action Function - scala

It's exactly a Scala question, but it is in the context of the PlayFramework Action:
An Action takes something in the form (play.api.mvc.Request => play.api.mvc.Result). And then you'll pass a block like:
{ request =>
Ok(views.html.index("Your new application is ready."))
}
Now I have defined a function with the same signature. One with and one without implicit for request.
def WithUser2(request: Request[AnyContent])(block: (Request[AnyContent]) => Result):Result = {
block(request)
}
def WithUser3(block: (Request[AnyContent]) => Result)(implicit request: Request[AnyContent]):Result = {
block(request)
}
I'm calling it this way now (2 versions):
def index2 = Action { implicit request => WithUser2(request) { request =>
Ok(views.html.index("Your new application is ready. " ))
}}
def index3 = Action ( implicit request => WithUser3 { request =>
Ok(views.html.index("Your new application is ready. " ))
})
Is there a way to directly attach my function to Action? I mean without placing implicit request => before WithUserX so that my function replaces the complete block after Action directly? Now my function is inside the block and I think this is the problem why I need to pass request forward. So I need a way to get rid of the block around my function.

It's not 100% clear what you're trying to do, because WithUserX essentially does nothing special. It seems like you're trying to make your own Action, and for that we have Action Composition.
If you were to define a new Action like this:
object WithUser extends ActionBuilder[Request] {
def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = {
// Do something special maybe?
block(request)
}
}
You could then use it like this:
def index = WithUser { request =>
...
Ok("Ok!")
}
This would be much better than trying to stuff it within the stock Action, when it doesn't need to be.

Related

Play 2.4: intercept and modify response body

According to play documentation this is what a custom action should look like:
object CustomAction extends ActionBuilder[Request] {
def invokeBlock[A](request: Request[A], block: Request[A] => Future[Result]): Future[Result] = {
block(request)
}
}
But say if I wanted to append "foo" to every response body, how do I do that? Obviously below doesn't work:
block.andThen(result => result.map(r => r.body.toString + "foo")).apply(request)
Any ideas?
UPDATE: Something worth mentioning is that this action would be mostly used as asynchronous in the controller:
def test = CustomAction.async {
//...
}
You'll need to take the Enumerator[Array[Byte]] from the Result body and feed it to an iteratee to actually consume the result body before you can modify it. So a simple iteratee that consumes the result body and converts to a String might look like:
block.apply(request).flatMap { res =>
Iteratee.flatten(res.body |>> Iteratee.consume[Array[Byte]]()).run.map { byteArray =>
val bodyStr = new String(byteArray.map(_.toChar))
Ok(bodyStr + "foo")
}
}
I used flatMap as the result of running Iteratee.flatten is a Future[T]. Check out https://www.playframework.com/documentation/2.4.x/Enumerators for more details on how to work with Enumerators/Iteratees.

Play and Scala, type match and succesful compile, but I think it's a mismatch

I was implementing some PlayFramework custom Actions when I came up with a question, let me explain it a little bit:
This is the code for my custom action:
object AuthenticatedAction extends ActionBuilder[AuthenticatedRequest] with Controller {
def invokeBlock[A](request: Request[A], block: (AuthenticatedRequest[A]) => Future[Result]) = {
request.session.get("username").fold
{
Future.successful(Forbidden(views.html.forbidden()(request)))
} {
username => block(new AuthenticatedRequest(username, request))
}
}
}
And this is the code for my controller action:
def index = AuthenticatedAction { implicit request =>
Ok(views.html.index("Your new application is ready."))
}
The thing is that AuthenticatedAction needs a function that returns a Future[Result].
In the "forbidden" part of the invokeBlock[A]... I wrap the result in a Future
But in the "authenticated" part, the block function does not return a Future[Result] and instead returns a Result (see def index).
The code above compiles and I see the desired page, but I don't understand why it works. Also, if I wrap the result of the block function in a Future, the Intelli IDEA inspector tells me "type mismatch" specifying that wants a Result instead of a Future[Result].
I'm sure that I'm missing something, but I don't know what it is. Can anyone give some light to my mind?
If you want to return a future you must do this:
def index = AuthenticatedAction.async { implicit request =>
Future.successful(Ok)
}
If you look in the API (link below), there is the apply method which takes in a "=> Result" where as .async method takes in a "=> Future[Result]".
https://www.playframework.com/documentation/2.4.x/api/scala/index.html#play.api.mvc.ActionBuilder
block: (AuthenticatedRequest[A]) => Future[Result]
block is function that takes (AuthenticatedRequest[A]) and returns Future[Result].
If you wrap block then result type is Future[Future[]]

How to send a parameter to a custom action?

I am using action composition of play framework 2.3 and I would like to send parameters to the custom action.
For example, if you have a custom action that adds a cache how the custom action can receive the cache key and the desired cache time. Example code inside a play controller:
def myAction(p1: String) = CachedAction(key="myAction1"+p1, time = 300.seconds) {
implicit request =>
... do an expensive calculation …
Ok(views.html.example.template())
}
I have tested an ActionBuilder combined with a custom request but I haven't found a solution.
I am aware that play offers a cache for actions, unfortunately that cache does not meet all the requirements.
I am not sure about solution with ActionBuilder or ActionRefiner but that may work for you:
def CachedAction(key: String, time: Duration)(f: Request[AnyContent] => Result) = {
Action{ request =>
if(cache.contains(key)){
...
} else{
...
}
f(request)
}
}
and then:
def myAction(p1: String) = CachedAction("hello" + p1, 100 millis){ request =>
Ok("cached action")
}
Edit:
Since you need Action.async you can write something like that:
case class Caching[A](key: String, time: Duration)(action: Action[A]) extends Action[A] {
def apply(request: Request[A]): Future[Result] = {
if(cache.contains(key)){
Logger.info("Found key: " + key)
}
action(request)
}
lazy val parser = action.parser
}
def cached(p1: String) = Caching(key = p1, time = 100 millis){
Action.async { Future(Ok("hello")) }
//or
Action { Ok("hello") }
}
Case class with two parameter lists looks weird but it works.
Docs:https://www.playframework.com/documentation/2.3.x/ScalaActionsComposition#Composing-actions

Extend SecureActionBuilder to Validate Request Before Parsing the Body

I am working on writing a web application that takes multipart files as input and uploads them to an S3 instance. Since some of the files can be very large, I am using a custom Body Parser to send the chunks to S3 as they come in.
I want to do validation on the request prior to uploading the file (to check that the user has permission/enough space, etc). From reading the Play docs, it seems like extending ActionBuilder is the right way to do this. I noticed in SecureSocial, there is a SecureActionBuilder and I believe I should extend this in order to build a secure action (which is what I want).
I tried this simple test to see if I could print out the userId, therefore being able to perform actions based on the user.
object FileValidationAction extends SecuredActionBuilder {
def invokeBlock[A](request: SecuredRequest[A], block: SecuredRequest[A] => Future[SimpleResult]) = {
Logger.info("User id is " + request.user.userProfile.userId)
block(request)
}
}
However, the method never got called.
Next, I tried overriding the method from the SecuredActionBuilder object:
object FileValidationAction extends SecuredActionBuilder {
override def invokeBlock[A](request: Request[A], block: SecuredRequest[A] => Future[SimpleResult]) = {
val securedResult: Option[SecuredRequest[A]] = request match {
case r: SecuredRequest[A] => Option(r)
case _ => None
}
Logger.info("Calling action ------- WOO!")
securedResult match {
case Some(r) =>
block(r)
case _ =>
Future.successful(Forbidden)
}
}
}
That method gets called but the request coming in is not a SecuredRequest as I was hoping.
How do I build a SecuredAction, using a custom body parser, that I can do validation on before it completes (or even starts) the upload?
EDIT:
To clarify, I will be calling the Action with the following method signature:
def upload = FileValidationAction(streamingBodyParser(streamConstructor)) { request =>
The problem is that you are not invoking the original code in SecuredActionBuilder that actually checks if the user is there and constructs the SecuredRequest instance.
Something like this should work:
// A sample usage of the action
def checkFile = FileValidationAction { request =>
Ok("")
}
// The builder implementation
object FileValidationAction extends FileValidationActionBuilder {
def apply[A]() = new FileValidationActionBuilder()
}
class FileValidationActionBuilder(authorize: Option[Authorization[DemoUser]] = None) extends SecuredActionBuilder(authorize) {
def validateFile[A](block: SecuredRequest[A] => Future[SimpleResult]): SecuredRequest[A] => Future[SimpleResult] = { securedRequest =>
Logger.info(s"User id is ${securedRequest.user.main.userId}")
block(securedRequest)
}
override def invokeBlock[A](request: Request[A], block: (SecuredRequest[A]) => Future[SimpleResult]): Future[SimpleResult] = {
invokeSecuredBlock(authorize, request, validateFile(block))
}
}
You would need to add this inside the controller where you plan to use this actions. If you need to use it in multiple controllers then create a trait that you can extend with this.
Also note that in this sample code I am using the DemoUser type I have in the samples. You will need to change that to the type you are using in your app to represent users.

Play Framework 2.2 action composition returning a custom object

I am trying to create a custom play.api.mvc.Action which can be used to populate a CustomerAccount based on the request and pass the CustomerAccount into the controller.
Following the documentation for Play 2.2.x I've created an Action and ActionBuilder but I cannot seem to return the CustomerAccount from within the action.
My current code is:
case class AccountWrappedRequest[A](account: CustomerAccount, request: Request[A]) extends WrappedRequest[A](request)
case class Account[A](action: Action[A]) extends Action[A] {
lazy val parser = action.parser
def apply(request: Request[A]): Future[SimpleResult] = {
AccountService.getBySubdomain(request.host).map { account =>
// Do something to return the account like return a new AccountWrappedRequest?
action(AccountWrappedRequest(account, request))
} getOrElse {
Future.successful(NotFound(views.html.account_not_found()))
}
}
}
object AccountAction extends ActionBuilder[AccountWrappedRequest] {
def invokeBlock[A](request: Request[A], block: (AccountWrappedRequest[A]) => Future[SimpleResult]) = {
// Or here to pass it to the next request?
block(request) // block(AccountWrappedRequest(account??, request))
}
override def composeAction[A](action: Action[A]) = Account(action)
}
Note: This will not compile because the block(request) function is expecting a type of AccountWrappedRequest which I cannot populate. It will compile when using a straight Request
Additionally...
Ultimately I want to be able to combine this Account action with an Authentication action so that the CustomerAccount can be passed into the Authentication action and user authentication can be provided based on that customer's account. I would then want to pass the customer account and user into the controller.
For example:
Account(Authenticated(Action))) { request => request.account; request.user ... } or better yet as individual objects not requiring a custom request object.
I'm not sure if this is the best way to do it but I have managed to come up with a solution that seems to work pretty well.
The key was to match on the request converting it into an AccountWrappedRequest inside invokeBlock before passing it on to the next request. If another Action in the chain is expecting a value from an earlier action in the chain you can then similarly match the request converting it into the type you need to access the request parameters.
Updating the example from the original question:
case class AccountWrappedRequest[A](account: CustomerAccount, request: Request[A]) extends WrappedRequest[A](request)
case class Account[A](action: Action[A]) extends Action[A] {
lazy val parser = action.parser
def apply(request: Request[A]): Future[SimpleResult] = {
AccountService.getBySubdomain(request.host).map { account =>
action(AccountWrappedRequest(account, request))
} getOrElse {
Future.successful(NotFound(views.html.account_not_found()))
}
}
}
object AccountAction extends ActionBuilder[AccountWrappedRequest] {
def invokeBlock[A](request: Request[A], block: (AccountWrappedRequest[A]) => Future[SimpleResult]) = {
request match {
case req: AccountRequest[A] => block(req)
case _ => Future.successful(BadRequest("400 Invalid Request"))
}
}
override def composeAction[A](action: Action[A]) = Account(action)
}
Then inside the apply() method of another Action (the Authenticated action in my case) you can similarly do:
def apply(request: Request[A]): Future[SimpleResult] = {
request match {
case req: AccountRequest[A] => {
// Do something that requires req.account
val user = User(1, "New User")
action(AuthenticatedWrappedRequest(req.account, user, request))
}
case _ => Future.successful(BadRequest("400 Invalid Request"))
}
}
And you can chain the actions together in the ActionBuilder
override def composeAction[A](action: Action[A]) = Account(Authenticated(action))
If AuthenticatedWrappedRequest is then passed into the controller you would have access to request.account, request.user and all the usual request parameters.
As you can see there are a couple of cases where the response is unknown which would generate a BadRequest. In reality these should never get called as far as I can tell but they are in there just incase.
I would love to have some feedback on this solution as I'm still fairly new to Scala and I'm not sure if there might be a better way to do it with the same result but I hope this is of use to someone too.
I wrote a standalone small (ish) example that does what you're looking for:
https://github.com/aellerton/play-login-example
I gave up trying to use the Security classes that exist in the play framework proper. I'm sure they're good, but I just couldn't understand them.
Brief guide...
In the example code, a controller is declared as using the AuthenticatedRequests trait:
object UserSpecificController extends Controller with AuthenticatedRequests {
...
}
Forcing any page to require authentication (or redirect to get it) is done with the RequireAuthentication action:
def authenticatedIndex = RequireAuthentication { implicit request: AuthenticatedRequest[AnyContent] =>
Ok("This content will be accessible only after logging in)
}
Signing out is done by using the AbandonAuthentication action:
def signOut = AbandonAuthentication { implicit request =>
Ok("You're logged out.").withNewSession
}
Note that for this to work, you must override methods from the AuthenticatedRequests trait, e.g.:
override def authenticationRequired[A](request: Request[A]): Future[SimpleResult] = {
Future.successful(
Redirect(routes.LoginController.showLoginForm).withSession("goto" -> request.path)
)
}
There's more to it; best to see the code.
HTH
Andrew