Play2(Scala)のFilter機能で事前・事後処理を記述する
はじめに
BounscaleというオートスケールするHeroku Addonを作っています。
Bounscaleでは実験的にPlay2(Scala)への対応を実施中です。
オートスケールするにあたり、各種リソースの監視が必要なのですが、リソース値の取得をするためにPlay2のFilterの仕組みを利用しています。
Filterの使い方について下記にまとめます。
Filter?
FilterというとJavaのSevletFilterやRuby on Railsのfilterなど、Webアプリケーション開発では比較的浸透した用語だと思います。
処理としてはある特定のURL/コントローラへのリクエストについて、共通的な事前/事後処理を提供する機構です。
J2EEパターン(懐)のIntercepting Filterが語源なのかな?
典型的には、認証処理などを統一的に記述して漏れなくアクセス制御を実現したりします。
バージョン
探した限りにはPlay1にはFilterの機構を見つけられませんでした。
Play2.1.1でFilterの機構が導入されたようなので、それ以降でしか動かないと思います。
流れ
基本的にはappディレクトリの下などにFilterクラスを作成し、Global#applyで登録するイメージです。
Filterの作成
appの下にこんな感じで書きます。
package yourfilter
import play.api.mvc._
import play.api.libs.concurrent.Execution.Implicits._
class YourFilter extends Filter {
def apply(next: (RequestHeader) => Result)(rh: RequestHeader) = {
println("BEFORE")
def after(result: PlainResult): Result = {
println("AFTER")
result
}
next(rh) match {
case plain: PlainResult => after(plain)
case async: AsyncResult => async.transform(after)
}
}
}
ポイントとしては、
- Filterクラスを継承し、applyメソッドを実装する
- next(req)の前が事前処理
- 事後処理はメソッドとして定義しておく(非同期呼び出しに対応するため)
- next(req)の戻り値の型がPlainResultなら同期的に、AsyncResultなら非同期的に事後処理メソッドを呼ぶ
Globalへの登録
下の感じで先ほどのFilterをGlobalに登録します。
import play.api.GlobalSettings
import play.api.mvc.WithFilters
import yourfilter.YourFilter
object Global extends WithFilters(
new YourFilter) with GlobalSettings
実行
こんなサンプルアクションを定義して
package controllers
import play.api._
import play.api.mvc._
object Application extends Controller {
def index = Action {
println("action!")
Ok(views.html.index("Your new application is ready."))
}
}
このアドレスにアクセスしたら下記のログが出るようになりました。
BEFORE
action!
AFTER
所感
非同期処理を考慮する必要があるので記述がちょっと煩雑。もっとなんとかならないものかと思いました。
終わりに
このような事前・事後処理の多くはRubyではRack Middleware、node.jsではExpress Connect Middlewareの仕組みでも記述できます。
上記Middlwareの場合はフレームワークに到達する前に通過するので、より根っこの部分を抑えられます。
なので、例えばあるアクセスのCPU時間を取得するなどの場合は、そちらの方がフレームワークの処理時間も含めた正確な値が取れます。
Filterもいいのですが、PlayもMiddlewareの機構を提供してくれるとうれしい今日この頃です。