Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pass a variable to an Action from an intercepted request in PlayFramework?

I am intercepting all the requests to my play application by overriding onRouteRequest method of GlobalSettings. Now, I need to send some data to the despatched action from here so that I dont perform all those calculations in all the actions. How do I set an attribute to the request (play.api.mvc.RequestHeader) object that I pass to the super onRouteRequest method ?

like image 504
Aman Avatar asked Jun 10 '12 03:06

Aman


1 Answers

For your need I don't thing using the onRouteRequest will work (elegantly at least).

But let's try to use a dedicated structure to intercept.

Here is how you could intercept the request, compute some generic stuff and pass it to Action

First of all, here is an Interceptor object that has a method intercept and a convenient method username :

object Interceptor {

  def intercept[A, B](f: RequestHeader => Option[B], e: RequestHeader => Result)(action: B => Action[A]): Action[(Action[A], A)] = {

    val bodyParser = BodyParser {
      request =>
        f(request) map {
          b =>
            val innerAction = action(b)
            innerAction.parser(request).mapDone {
              body => body.right.map(innerBody => (innerAction, innerBody))
            }
        } getOrElse {
          Done(Left(e(request)), Input.Empty)
        }
    }

    Action(bodyParser) {
      request =>
        val (innerAction, innerBody) = request.body
        innerAction(request.map(_ => innerBody))
    }
  }

  def username[A](check: RequestHeader => Option[String]): ((String) => Action[A]) => Action[(Action[A], A)] = intercept(check, r => Results.Unauthorized("not logged in"))

}

As you can see, the worker function intercept gives you the opportunity to compute some stuff based on the request content. Which computation result of type B might failed (Option), in that case a handler is there to tell what to do.

Having define what to compute, you can define your action using a function that takes a B and gives an Action[A].

The username method is just a simple predefined interceptor that can enables us to define how to retrieve the logged in username, just to illustrate.

Now here is how we can use both of them in your Controller

  //index is defined for both GET and POST in routes, but fails on POST
  //  thanks to the interceptor that checks at first the used method
  //  the case mustn't be handled in the Action definition
  def index = Interceptor.intercept(
    /*check the method*/
    request => if (request.method == "GET") Some(request.method) else None,

    /*not a GET => bad request*/
    request => BadRequest(request.method + " not allowed")

  ) { /*the computation result*/method => Action {
      Ok("The method : " + method)
    }
  }

  //this controller retrieve the username in the session and renders it in a OK response
  def secured = Interceptor.username(r => r.session.get("username")) { username => Action {
      Ok("You're logged in as " + username)
    }
  }

  //this enables you to logged in => store in session
  def login(u:String) = Action { request => {
      Ok("Logged in as " + u) withSession(("username" -> u))
    }
  }

Now if you have a generic computation you can create your preconfigured interceptor (here I'm using a case class but simply defining a function that partially applies the interceptor is enough)

  case class Intercept[B] (f: RequestHeader => Option[B], e: RequestHeader => Result) {

    def apply[A](action: B => Action[A]) = Interceptor.intercept[A,B](f, e)(action)

  }


  val getInterceptor = Intercept[String](
    request => if (request.method == "GET") Some(request.method) else None,
    request => BadRequest(request.method + " not allowed")
  )


  def index2 = getInterceptor { method => Action {
      Ok("Da method : " + method)
    }
  }

EDIT related to the comment:

Accordingly to your comment, here is how you could do using an interceptor (note that I've mocked up the host retrieval and checking)

Using hosted and anotherHosted, you'll be able to test this workflow:

  • /hosted/false?host=myhost => 404 because at first myhost is not cached and I provided false to the checked mockup
  • /hosted/true?host=myhost => not in cache but it will add it, and then no 404
  • /hosted/anotherHosted/false?host=myhost => in cache because it is hosted => no 404
  • /hosted/anotherHosted/false?host=notMyhost => 404

Here is the code

def getHost(request:RequestHeader) = request.queryString.get("host").get.head
def checkHost(host:String, b: Boolean) = b

val checkHosted = (b: Boolean) => Intercept[String](
  request => {
    val host = getHost(request)
    Cache.getAs[String](host) match {
      case x@Some(_) => x
      case None => if (checkHost(host, b)) {
        Cache.set(host, host)
        Some(host)
      } else None
    }

  },
  request => NotFound(getHost(request) + "not hosted")
)

def hosted(b:String) = checkHosted(b.toBoolean) {
  host => Action {
    Ok("this host is ok : " + host)
  }
}
def anotherHosted(b:String) = checkHosted(b.toBoolean) {
  host => Action {
    Ok("this host is ok : " + host)
  }
}
like image 104
Andy Petrella Avatar answered Nov 01 '22 08:11

Andy Petrella