Play Frameworkのソースコードリーディング Action周り(BodyParserとAnyContent)

# はじめに
本記事は[Play Frameworkのソースコードリーディング Action周り](http://qiita.com/mtoyoshi/items/0679618fdfa710ded645) の続きとなる。


# BodyParserとAnyContent
object Actionのapplyの中で以下の定義があった。

```scala
final def apply(block: R[AnyContent] => Result): Action[AnyContent] = apply(BodyParsers.parse.anyContent)(block)
```

[BodyParsers.parse.anyContent](https://github.com/playframework/playframework/blob/master/framework/src/play/src/main/scala/play/api/mvc/ContentTypes.scala#L545-L579)メソッド定義は以下となる。

```scala
/**
 * Guess the body content by checking the Content-Type header.
 */
def anyContent: BodyParser[AnyContent] = BodyParser("anyContent") { request =>
  import play.api.libs.iteratee.Execution.Implicits.trampoline
  if (request.method == HttpVerbs.GET || request.method == HttpVerbs.HEAD) {
    Play.logger.trace("Parsing AnyContent as empty")
    Done(Right(AnyContentAsEmpty), Empty)
  } else {
    val contentType: Option[String] = request.contentType.map(_.toLowerCase(Locale.ENGLISH))
    contentType match {
      case Some("text/plain") => {
        Play.logger.trace("Parsing AnyContent as text")
        text(request).map(_.right.map(s => AnyContentAsText(s)))
      }
      case Some("text/xml") | Some("application/xml") | Some(ApplicationXmlMatcher()) => {
        Play.logger.trace("Parsing AnyContent as xml")
        xml(request).map(_.right.map(x => AnyContentAsXml(x)))
      }
      case Some("text/json") | Some("application/json") => {
        Play.logger.trace("Parsing AnyContent as json")
        json(request).map(_.right.map(j => AnyContentAsJson(j)))
      }
      case Some("application/x-www-form-urlencoded") => {
        Play.logger.trace("Parsing AnyContent as urlFormEncoded")
        urlFormEncoded(request).map(_.right.map(d => AnyContentAsFormUrlEncoded(d)))
      }
      case Some("multipart/form-data") => {
        Play.logger.trace("Parsing AnyContent as multipartFormData")
        multipartFormData(request).map(_.right.map(m => AnyContentAsMultipartFormData(m)))
      }
      case _ => {
        Play.logger.trace("Parsing AnyContent as raw")
        raw(request).map(_.right.map(r => AnyContentAsRaw(r)))
      }
    }
  }
}
```

要はAPI利用者から特に指定がなくてもcontent-type毎にリクエストボディのパースの仕方およびその結果(AnyContentAsTextやAnyContentAsRaw等の型)を自動で算出している。

本記事ではGET呼び出しを想定している```Ok("hello")```を例に進んできているが、GETだとすぐ終わってしまうのでPOSTリクエストがあったことを想定し進める。特に```text(request)```をピックアップして追いかける。

```scala
case Some("text/plain") => {
  Play.logger.trace("Parsing AnyContent as text")
  text(request).map(_.right.map(s => AnyContentAsText(s)))
}
```

一見すると上記は```def text(request: RequestHeader)```というメソッドが呼ばれているように見えるがそうではない。
実際には以下が呼ばれており、

```scala
def text: BodyParser[String] = text(DEFAULT_MAX_TEXT_LENGTH)
```

返ってきた（生成された）BodyParserインスタンスにRequestを渡している。つまり、apply(request: RequestHeader)メソッドである。

順を追ってtext関数のその先を見ていくことにする。
なお、DEFAULT_MAX_TEXT_LENGTHは、デフォルト設定では100kb。
application.confで```parsers.text.maxLength = 512k```と設定することで上限を書き換え可能。

text(DEFAULT_MAX_TEXT_LENGTH)は以下。

```scala
def text(maxLength: Int): BodyParser[String] = when(
  _.contentType.exists(_.equalsIgnoreCase("text/plain")),
  tolerantText(maxLength),
  createBadResult("Expecting text/plain body")
)
```

リクエストヘッダのcontent-type部分をチェックしているのが分かる。
チェーンして、

```scala
def tolerantText(maxLength: Int): BodyParser[String] = BodyParser("text, maxLength=" + maxLength) { request =>
  // Encoding notes: RFC-2616 section 3.7.1 mandates ISO-8859-1 as the default charset if none is specified.

  import Execution.Implicits.trampoline
  Traversable.takeUpTo[Array[Byte]](maxLength)
    .transform(Iteratee.consume[Array[Byte]]().map(c => new String(c, request.charset.getOrElse("ISO-8859-1"))))
    .flatMap(Iteratee.eofOrElse(Results.EntityTooLarge))
}
```

となり、object BodyParserを使用してBodyParserインスタンスを生成して返している。
ちなみに「tolerant」とは「寛大な、懐の深い」という意味。要はcontent-typeチェックを行わないメソッドであることを示している。

では実際にBodyParserの生成に使っているコンパニオンオブジェクト部を見てみる。

```scala
object BodyParser {
  def apply[T](debugName: String)(f: RequestHeader => Iteratee[Array[Byte], Either[Result, T]]): BodyParser[T] = new BodyParser[T] {
    def apply(rh: RequestHeader) = f(rh)
    override def toString = "BodyParser(" + debugName + ")"
  }
```

newしているのが分かる。

なお、BodyParserトレイト宣言部は以下。

```scala
trait BodyParser[+A] extends Function1[RequestHeader, Iteratee[Array[Byte], Either[Result, A]]] {
```

実体は**RequestHeaderを受け取ってIteratee[Array[Byte], Either[Result, A]]を返すFunction1である**ことが分かる。

HTTPリクエストのボディ部も複数回のArray[Byte]で来るのでそれを処理しやすいようIterateeとなっている。
inputはArray[Byte]、outputはうまくいかなかった時がResultで、うまくパース出来た時がAとなる、Either。

これでようやく、以下のコードのtext部分が終わりとなる。

```scala
case Some("text/plain") => {
  Play.logger.trace("Parsing AnyContent as text")
  text(request).map(_.right.map(s => AnyContentAsText(s)))
}
```

BodyParser#apply(request: RequestHeader)、これは先ほど```def apply(rh: RequestHeader) = f(rh)```と定義していることを確認した。単に関数を実行しているだけで、Iteratee[Array[Byte], Either[Result, String]]が返ってきている。
Iteratee#mapの定義は以下なので、

```scala
trait Iteratee[E, +A] {
  def map[B](f: A => B)(implicit ec: ExecutionContext): Iteratee[E, B] = this.flatMap(a => Done(f(a), Input.Empty))(ec)
```

map関数に渡す高階関数部ではoutput側のEitherが渡ってくることが分かる。
EitherのrightのStringインスタンスをAnyContentAsText型のインスタンスに変換している。

```scala
case class AnyContentAsText(txt: String) extends AnyContent
```

これで晴れて、object Actionのapplyメソッド内の```BodyParsers.parse.anyContent```関数の処理が終わってBodyParser[AnyContent]が渡っていく事となる。

```scala
final def apply(block: R[AnyContent] => Result): Action[AnyContent] = apply(BodyParsers.parse.anyContent)(block)
```

なお、先ほどスキップしたGET時のanyContentメソッドのBodyParser部分を改めて。

```scala
def anyContent: BodyParser[AnyContent] = BodyParser("anyContent") { request =>
  import play.api.libs.iteratee.Execution.Implicits.trampoline
  if (request.method == HttpVerbs.GET || request.method == HttpVerbs.HEAD) {
    Play.logger.trace("Parsing AnyContent as empty")
    Done(Right(AnyContentAsEmpty), Empty)
  } else {
```

GET時はrequestを受け取って```Done(Right(AnyContentAsEmpty), Empty)```なIterateeとなり、BodyParser[AnyContent(実際はAnyContentAsEmpty)]が返っている。

# Action
改めてActionインスタンスの[apply](https://github.com/playframework/playframework/blob/master/framework/src/play/src/main/scala/play/api/mvc/Action.scala#L121-L135)関数。

```scala
def apply(rh: RequestHeader): Iteratee[Array[Byte], Result] = parser(rh).mapM {
  case Left(r) =>
    Play.logger.trace("Got direct result from the BodyParser: " + r)
    Future.successful(r)
  case Right(a) =>
    val request = Request(rh, a)
    Play.logger.trace("Invoking action with request: " + request)
    Play.maybeApplication.map { app =>
      play.utils.Threads.withContextClassLoader(app.classloader) {
        apply(request)
      }
    }.getOrElse {
      apply(request)
    }
}(executionContext)
```

BodyParserはRequestHeaderを受け取り、Iteratee[Array[Byte], Either[Result, A]]を返すFunction1であった。よって```parser(rh)```の結果はパースが終わったIteratee[Array[Byte], Either[Result, A]]。IterateeのmapM関数を実行すると高階関数にはEither[Result, A]が渡ってくる(この高階関数はFuture[Result]を返す必要あり)。
Left、つまりボディ部のパース失敗時はLeftにBadRequestが格納されているのでそれを返す。
Right、つまりパース成功時はAの部分が利用できるようになっている。RequestHeaderとbodyを使ってRequestを生成、その後ActionインスタンスのapplyにRequestを渡し、処理実行を行う。

このapplyは、object Actionのasync内で生成していたActionインスタンスのapply(Request)であり、ここで自分で定義したblock部が実行される。

```scala
final def async[A](bodyParser: BodyParser[A])(block: R[A] => Future[Result]): Action[A] = composeAction(new Action[A] {
  def parser = composeParser(bodyParser)
  def apply(request: Request[A]) = try {
    invokeBlock(request, block)
```

以上、終わり！