§アクションの構成
この章では、汎用的なアクション機能を定義するいくつかの方法を紹介します。
§独自のアクションビルダー
以前のページ で、リクエストパラメータを使用する、リクエストパラメータを使用しない、ボディパーサーを使用するなど アクションを宣言する複数の方法を見てきました。実際のところ、非同期プログラミング の章にもあるとおり、ほかにも方法があります。
アクションを構築するためのこれらのメソッドは、実際は ActionBuilder
と呼ばれるトレイトに全て定義されています。また、これまでに定義してきた Action
オブジェクトはこのトレイトの単なるインスタンスです。独自の ActionBuilder
を実装することで、アクションを作るために使用できる、再利用可能なアクションスタックを宣言することができます。
ロギングデコレータの簡単な例から始めましょう。このアクションの各呼び出しをログに記録します。
最初の方法は、invokeBlock
メソッドでこの機能を実装するやり方です。このメソッドは、ActionBuilder
によって構築されたすべてのアクションに対して呼び出されます。
import play.api.mvc._
object LoggingAction extends ActionBuilder[Request] {
def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = {
Logger.info("Calling action")
block(request)
}
}
これで Action
と同じように使用できます。
def index = LoggingAction {
Ok("Hello World")
}
ActionBuilder
はアクションを構築するためのさまざまなメソッドをすべて提供するので、例えば、独自のボディパーサーを宣言することもできます。
def submit = LoggingAction(parse.text) { request =>
Ok("Got a body " + request.body.length + " bytes long")
}
§アクションの作成
ほとんどのアプリケーションでは、 様々な種類の認証を行いたい、様々な種類の一般的な機能を提供したいなど、複数のアクションビルダーが必要となるでしょう。 このような場合、それぞれの種類に応じたアクションビルダーに対するロギングアクションのコードを書き直すのではなく、再利用が可能な方法で定義したくなります。
アクションをラッピングして実装することで、再利用が可能なアクションのコードを書くことができます。
import play.api.mvc._
case class Logging[A](action: Action[A]) extends Action[A] {
def apply(request: Request[A]): Future[Result] = {
Logger.info("Calling action")
action(request)
}
lazy val parser = action.parser
}
Action
アクションビルダーを使って、独自のアクションクラスを定義せずにアクションを構築することもできます。
import play.api.mvc._
def logging[A](action: Action[A])= Action.async(action.parser) { request =>
Logger.info("Calling action")
action(request)
}
アクションは composeAction
メソッドを使ってアクションビルダーにミックスインさせることができます。
object LoggingAction extends ActionBuilder[Request] {
def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = {
block(request)
}
override def composeAction[A](action: Action[A]) = new Logging(action)
}
これで、ビルダーを先ほどと同じ方法で使用することができます。
def index = LoggingAction {
Ok("Hello World")
}
ラッピングしたアクションはアクションビルダーなしでミックスインさせることができます。
def index = Logging {
Action {
Ok("Hello World")
}
}
§より複雑なアクション
これまでは、リクエストに全く影響しないアクションしか示していませんでした。もちろん、入力されてくるリクエストオブジェクトに対して、読み込みと変更も可能です。
import play.api.mvc._
def xForwardedFor[A](action: Action[A]) = Action.async(action.parser) { request =>
val newRequest = request.headers.get("X-Forwarded-For").map { xff =>
new WrappedRequest[A](request) {
override def remoteAddress = xff
}
} getOrElse request
action(newRequest)
}
メモ: Play は ‘X-Forwarded-For’ ヘッダを組み込みですでにサポートしています。
リクエストをブロックすることができます。
import play.api.mvc._
def onlyHttps[A](action: Action[A]) = Action.async(action.parser) { request =>
request.headers.get("X-Forwarded-Proto").collect {
case "https" => action(request)
} getOrElse {
Future.successful(Forbidden("Only HTTPS requests allowed"))
}
}
そして、返された結果を変更することもできます。
import play.api.mvc._
import play.api.libs.concurrent.Execution.Implicits._
def addUaHeader[A](action: Action[A]) = Action.async(action.parser) { request =>
action(request).map(_.withHeaders("X-UA-Compatible" -> "Chrome=1"))
}
§様々なリクエストタイプ
アクションの構成では、HTTPリクエストとレスポンス・レベルで追加の処理を実行できますが、リクエスト自体に対してコンテキストを追加したりバリデーションを実行したりするデータ変換のパイプラインを構築することがよくあります。ActionFunction
は、入力リクエストタイプと次のレイヤーに渡される出力タイプの両方でパラメータ化された、リクエストの関数と考えることができます。各アクション関数は、認証、オブジェクトのデータベース検索、権限チェックなどのような、アクション全体で作成および再利用したいモジュール的な処理を表すことができます。
さまざまなタイプの処理に役立つ ActionFunction
を実装する、いくつかの事前定義されたトレイトがあります。
ActionTransformer
は、追加情報を加えるなど、リクエストを変更することができます。ActionFilter
は、リクエストの値を変更することなくエラーを生成するなど、リクエストを選択的に受け取ることができます。ActionRefiner
は、上記の両方の一般的なケースです。ActionBuilder
は、Request
を入力として受け取り、アクションを構築する関数の特殊なケースです。
invokeBlock
メソッドを実装することによって、任意の ActionFunction
を定義することもできます。多くの場合、(WrappedRequest
を使って) Request
の入力と出力タイプのインスタンスを作るのが便利ですが、これは厳密には必要ではありません。
§認証
アクション関数の最も一般的な使用例のひとつが認証です。元のリクエストからユーザーを判断し、それを新しい UserRequest
に追加する独自の認証アクションを簡単に実装することができます。シンプルな Request
を入力として受け取るので、これもActionBuilder
です。
import play.api.mvc._
class UserRequest[A](val username: Option[String], request: Request[A]) extends WrappedRequest[A](request)
object UserAction extends
ActionBuilder[UserRequest] with ActionTransformer[Request, UserRequest] {
def transform[A](request: Request[A]) = Future.successful {
new UserRequest(request.session.get("username"), request)
}
}
Play はまた、組み込みの認証アクションビルダーを提供しています。 詳細と使用方法については こちら を参照してください。
メモ: 組み込みの認証アクションビルダーは、シンプルなケースの認証を実装するために必要なコードを最小限に抑える便利なヘルパーであり、その実装は上記の例に非常に似ています。
組み込みの認証アクションでは満たされない、より複雑な要件がある場合の独自の実装は、シンプルであるだけではなく、推奨もされています。
§リクエストへの情報の追加
Item
型というオブジェクトについて動作する REST API について考えてみましょう。 /item/:itemId
というパスの下に多くのルートがあり、それらのルートでは Item を見つける必要があるとします。この場合、このロジックをアクション関数に入れると便利です。
まず、Item
を UserRequest
に追加するリクエストオブジェクトを作成します。
import play.api.mvc._
class ItemRequest[A](val item: Item, request: UserRequest[A]) extends WrappedRequest[A](request) {
def username = request.username
}
次に、そのアイテムを調べ、エラー (Left
) または新しい ItemRequest
(Right
) を返すアクションリファイナーを作成します。このアクションリファイナーは、アイテムの ID を取得するメソッド内で定義されています。
def ItemAction(itemId: String) = new ActionRefiner[UserRequest, ItemRequest] {
def refine[A](input: UserRequest[A]) = Future.successful {
ItemDao.findById(itemId)
.map(new ItemRequest(_, input))
.toRight(NotFound)
}
}
§リクエストの検証
最後に、リクエストを続行するかどうかを検証するアクション関数が必要な場合があります。例えば、UserAction
のユーザが ItemAction
からアイテムにアクセスする権限を持っているかどうかをチェックし、そうでない場合はエラーを返します。
object PermissionCheckAction extends ActionFilter[ItemRequest] {
def filter[A](input: ItemRequest[A]) = Future.successful {
if (!input.item.accessibleByUser(input.username))
Some(Forbidden)
else
None
}
}
§ひとまとめにする
andThen
を使ってこれらのアクション関数 (ActionBuilder
で始まる) をつなぎ、1つのアクションを作成することができます。
def tagItem(itemId: String, tag: String) =
(UserAction andThen ItemAction(itemId) andThen PermissionCheckAction) { request =>
request.item.addTag(tag)
Ok("User " + request.username + " tagged " + request.item.id)
}
Play は全体的な横断的関心事に便利な グローバルなフィルター API も提供しています。
Next: コンテンツネゴシエーション
このドキュメントの翻訳は Play チームによってメンテナンスされているものではありません。 間違いを見つけた場合、このページのソースコードを ここ で確認することができます。 ドキュメントガイドライン を読んで、お気軽にプルリクエストを送ってください。