§アクション合成
この章では、アクションの一部機能を汎用的な形で切り出して定義する方法を紹介していきます。
§アクション合成の基本
アクションの呼び出しをロギングする、簡単なロギングデコーレータの例から始めてみましょう。
一つめの方法は、アクション自体を定義する代わりに、アクションを生成するためのヘルパーメソッドを提供することです。
def LoggingAction(f: Request[AnyContent] => Result): Action[AnyContent] = {
Action { request =>
Logger.info("Calling action")
f(request)
}
}
このヘルパーメソッドは次のように利用できます。
def index = LoggingAction { request =>
Ok("Hello World")
}
この方法はとても簡単ですが、ボディパーサーを外部から指定する方法がないため、デフォルトの parse.anyContent
ボディパーサーしか利用できません。そこで、次のようなヘルパーメソッドも定義してみます。
def LoggingAction[A](bp: BodyParser[A])(f: Request[A] => Result): Action[A] = {
Action(bp) { request =>
Logger.info("Calling action")
f(request)
}
}
これは次のように利用できます。
def index = LoggingAction(parse.text) { request =>
Ok("Hello World")
}
§既存のアクションをラップする
その他に、LoggingAction
を Action
のラッパーとして定義する方法もあります。
case class Logging[A](action: Action[A]) extends Action[A] {
def apply(request: Request[A]): Result = {
Logger.info("Calling action")
action(request)
}
lazy val parser = action.parser
}
これを利用すると、他のアクション値をラップすることができます。
def index = Logging {
Action {
Ok("Hello World")
}
}
この方法ではラップされたアクションのボディパーサーがそのまま再利用されるため、次のような記述が可能です。
def index = Logging {
Action(parse.text) {
Ok("Hello World")
}
}
次のように、
Logging
クラスを定義せずに全く同じ機能を持つラッパーを記述することもできます。def Logging[A](action: Action[A]): Action[A] = { Action(action.parser) { request => Logger.info("Calling action") action(request) } }
§さらに複雑な例
次は、認証を伴うアクションという、より複雑ですが一般的な例を見てみましょう。ここでの問題は、認証されたユーザだけをラップしたアクションへ通すこと、また認証を行うために元のボディパーサーをラップするという2点です。
def Authenticated[A](action: User => Action[A]): Action[A] = {
// Let's define an helper function to retrieve a User
def getUser(request: RequestHeader): Option[User] = {
request.session.get("user").flatMap(u => User.find(u))
}
// Wrap the original BodyParser with authentication
val authenticatedBodyParser = parse.using { request =>
getUser(request).map(u => action(u).parser).getOrElse {
parse.error(Unauthorized)
}
}
// Now let's define the new Action
Action(authenticatedBodyParser) { request =>
getUser(request).map(u => action(u)(request)).getOrElse {
Unauthorized
}
}
}
このヘルパーメソッドは次のように利用することができます。
def index = Authenticated { user =>
Action { request =>
Ok("Hello " + user.name)
}
}
注:
play.api.mvc.Security.Authenticated
にはここで説明した例よりもっと良いAuthenticated
アクションの実装が用意されています。
§認証されたアクションの別の実装方法
次は、先ほどの例を、アクションを丸ごとラップせずに、かつボディパーサーの認証なしで記述してみましょう。
def Authenticated(f: (User, Request[AnyContent]) => Result) = {
Action { request =>
request.session.get("user").flatMap(u => User.find(u)).map { user =>
f(user, request)
}.getOrElse(Unauthorized)
}
}
これは次のように利用します。
def index = Authenticated { (user, request) =>
Ok("Hello " + user.name)
}
ここでの問題は、もう request
という引数を implicit
指定することはできない、ということです。これはカリー化を使うと解決できます。
def Authenticated(f: User => Request[AnyContent] => Result) = {
Action { request =>
request.session.get("user").flatMap(u => User.find(u)).map { user =>
f(user)(request)
}.getOrElse(Unauthorized)
}
}
これは次のように利用することができます。
def index = Authenticated { user => implicit request =>
Ok("Hello " + user.name)
}
別の (たぶんより簡単な) 方法は、Request
のサブクラス AuthenticatedRequest
を定義することです (二つの引数をひとつにまとめる、とも言い換えられます) 。
case class AuthenticatedRequest(
val user: User, request: Request[AnyContent]
) extends WrappedRequest(request)
def Authenticated(f: AuthenticatedRequest => Result) = {
Action { request =>
request.session.get("user").flatMap(u => User.find(u)).map { user =>
f(AuthenticatedRequest(user, request))
}.getOrElse(Unauthorized)
}
}
これを利用すると、次のような記述ができます。
def index = Authenticated { implicit request =>
Ok("Hello " + request.user.name)
}
ボディパーサーを指定できるようにすると、この実装はもっと一般的な形に拡張することができます。
case class AuthenticatedRequest[A](
val user: User, request: Request[A]
) extends WrappedRequest(request)
def Authenticated[A](p: BodyParser[A])(f: AuthenticatedRequest[A] => Result) = {
Action(p) { request =>
request.session.get("user").flatMap(u => User.find(u)).map { user =>
f(AuthenticatedRequest(user, request))
}.getOrElse(Unauthorized)
}
}
// Overloaded method to use the default body parser
import play.api.mvc.BodyParsers._
def Authenticated(f: AuthenticatedRequest[AnyContent] => Result): Action[AnyContent] = {
Authenticated(parse.anyContent)(f)
}
次ページ: 非同期 HTTP プログラミング