Unable to Materialise Request Body in Action Filter

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.

1 Like

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.

1 Like

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.