Please refer to this SO post which describes the problem and this GitHub post on latest conversation on this issue with the Playframework developers
Hmm… I guess you can use
request.body.asInstanceOf[Request[AnyContent]].body.asFormUrlEncoded
(you need to import play.api.mvc.AnyContent
)
Seems like the Request[A]
passed is still too generic and casting should help. Of course only when the body parser you use gives you AnyContent
, but that is the default anyway.
This was one of the first solutions I tried but it doesn’t work as well. I always get back a AnyContentEmpty result.
Actually, this results in an exception:
play.api.http.HttpErrorHandlerExceptions$$anon$1: Execution exception[[ClassCastException: play.api.mvc.AnyContentAsFormUrlEncoded cannot be cast to play.api.mvc.Request]]
at play.api.http.HttpErrorHandlerExceptions$.throwableToUsefulException(HttpErrorHandler.scala:351)
at play.api.http.DefaultHttpErrorHandler.onServerError(HttpErrorHandler.scala:267)
at play.core.server.AkkaHttpServer$$anonfun$1.applyOrElse(AkkaHttpServer.scala:448)
at play.core.server.AkkaHttpServer$$anonfun$1.applyOrElse(AkkaHttpServer.scala:446)
at scala.concurrent.Future.$anonfun$recoverWith$1(Future.scala:417)
at scala.concurrent.impl.Promise.$anonfun$transformWith$1(Promise.scala:41)
at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:64)
at akka.dispatch.BatchingExecutor$AbstractBatch.processBatch(BatchingExecutor.scala:55)
at akka.dispatch.BatchingExecutor$BlockableBatch.$anonfun$run$1(BatchingExecutor.scala:92)
I actually think this is a bug which is why I opened the issue in GitHub.
Even though the Content-Length indicates there is content in the request body, that request body cannot be accessed in any shape or form inside the filter method of the Action Filter. I also conjecture it is the same for all the other types of Action composition types.
Sorry, actually I meant:
request.asInstanceOf[Request[AnyContent]].body.asFormUrlEncoded
Please try this.
Yes, this works! From what I remember last time, I was trying to cast directly to AnyContentAsFormUrlEncoded and I kept getting back AnyContentAsEmpty.
Even though this does work and unblocks me, is there a better solution than casting?
Doesn’t looks like there is a better solution. However I primarily use the Java API myself, so maybe I am wrong. Maybe someone else will comes up with a better solution.
Any idea why my attempt does not work?
override protected def filter[A](request: Request[A]): Future[Option[Result]] =
parse.formUrlEncoded.apply(request).run().map {
case Left(result) => Some(result)
case Right(formUrl) =>
logger.info(formUrl.isEmpty.toString)
None
}
}
It depends on which body parser the action is using, but usually, you are either using the default one (which produces a body of type play.api.mvc.AnyContent
) or something more specific, for example, play.api.mvc.AnyContentAsFormUrlEncoded
. I assume your action is NOT using play.api.mvc.PlayBodyParsers.formUrlEncoded
since the cast failed before. The docs have more information about it, but you need to do:
// This is your action
def action = Action(parse.tolerantFormUrlEncoded) { req: Request[Map[String, Seq[String]]] =>
Ok(s"Request body: ${req.body}")
}
And ActionFilter
can look like this:
class SomeActionFilter @Inject()(implicit val executionContext: ExecutionContext) extends ActionFilter[Request] {
override protected def filter[A](request: Request[A]): Future[Option[Result]] = Future.successful {
request.body match {
case AnyContentAsFormUrlEncoded(data) =>
println(s"Checking request data: $data")
// Suppose checking data failed, so return a BadRequest
Some(Results.BadRequest)
case _ =>
// Decide what to do if it is not the expected body type
None
}
}
}
Best.
Thanks @marcospereira.
You’re right, in my main Action I was not using any body parser. I was simply composing the action as follows:
Action.andThen(exampleFilter)
I still can’t it to work with your new proposed solution, see here:
import javax.inject.Inject
import play.api.Logging
import play.api.mvc._
import scala.concurrent.{ExecutionContext, Future}
class ExampleActionFilter @Inject()(
implicit val executionContext: ExecutionContext
) extends ActionFilter[Request]
with Logging {
override protected def filter[A](request: Request[A]): Future[Option[Result]] = Future.successful {
request.body match {
case AnyContentAsFormUrlEncoded(data) =>
logger.info(s"Checking request data: $data")
// Suppose checking data failed, so return a BadRequest
Some(Results.BadRequest)
case _ =>
logger.info(s"No data")
// Decide what to do if it is not the expected body type
None
}
}
}
With a controller method which looks like this:
def status = Action(parse.tolerantFormUrlEncoded).andThen(exampleActionFilter) {
req: Request[Map[String, Seq[String]]] =>
Ok(s"Request body: ${req.body}")
}
This does print out:
Request body: ListMap(param1 -> List(value1), param2 -> List(value2))
But it does not print out “Checking request data: …” because it doesn’t match on AnyContentAsFormUrlEncoded.