はじめに
本記事はPlay Frameworkのソースコードリーディング Action周り の続きとなる。
BodyParserとAnyContent
object Actionのapplyの中で以下の定義があった。
final def apply(block: R[AnyContent] => Result): Action[AnyContent] = apply(BodyParsers.parse.anyContent)(block)
BodyParsers.parse.anyContentメソッド定義は以下となる。
/**
* 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)
をピックアップして追いかける。
case Some("text/plain") => {
Play.logger.trace("Parsing AnyContent as text")
text(request).map(_.right.map(s => AnyContentAsText(s)))
}
一見すると上記はdef text(request: RequestHeader)
というメソッドが呼ばれているように見えるがそうではない。
実際には以下が呼ばれており、
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)は以下。
def text(maxLength: Int): BodyParser[String] = when(
_.contentType.exists(_.equalsIgnoreCase("text/plain")),
tolerantText(maxLength),
createBadResult("Expecting text/plain body")
)
リクエストヘッダのcontent-type部分をチェックしているのが分かる。
チェーンして、
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の生成に使っているコンパニオンオブジェクト部を見てみる。
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トレイト宣言部は以下。
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部分が終わりとなる。
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の定義は以下なので、
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型のインスタンスに変換している。
case class AnyContentAsText(txt: String) extends AnyContent
これで晴れて、object Actionのapplyメソッド内のBodyParsers.parse.anyContent
関数の処理が終わってBodyParser[AnyContent]が渡っていく事となる。
final def apply(block: R[AnyContent] => Result): Action[AnyContent] = apply(BodyParsers.parse.anyContent)(block)
なお、先ほどスキップしたGET時のanyContentメソッドのBodyParser部分を改めて。
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関数。
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部が実行される。
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)
以上、終わり!