這章將介紹幾種定義常用Action函數(shù)的方式哼蛆。
自定義Action構(gòu)建器
我們在前面看到過,有幾種聲明Action的方式——使用Request參數(shù)荷逞,不使用request參數(shù),使用Body解析器等等。事實上玄捕,正如我們將在異步編程章節(jié)看到的. 還有更多的方式喂柒。
實際上不瓶,這些構(gòu)建Action的方法都是通過一個叫 ActionBuilder 的特質(zhì)定義的,并且我們用來聲明我的Action的Action對象只是這個特質(zhì)的實例灾杰。通過實現(xiàn)你自己的ActionBuilder, 你可以聲明一個可被重復使用的Action棧蚊丐,然后這可以被用來構(gòu)建Action。
讓我們從一個簡單的日志裝飾的例子開始艳吠,我想記錄每次對這個Action的調(diào)用麦备。第一種方式是在invokeBlock 方法中實現(xiàn)這個功能,這個方法通過ActionBuilder構(gòu)建每個Action時調(diào)用:
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)
}
}
現(xiàn)在我們可以用相同的方式使用 Action:
def index = LoggingAction {
Ok("Hello World")
}
由于 ActionBuilder 提供了構(gòu)建Action的所有不同的方法,這也適用于凛篙,如聲明一個自定義的Body解析器:
def submit = LoggingAction(parse.text) { request =>
Ok("Got a body " + request.body.length + " bytes long")
}
組成Action
在大多數(shù)應用里黍匾,我想要有多種Action的構(gòu)建器,一些做不同類型的驗證鞋诗,一些提供不同類型的通用功能等等膀捷。這樣的話,我們不想為每一個Action構(gòu)建器類型重寫我們的日志Action代碼削彬,我們想把它定義成一個可以服用的方式全庸。
可復用Action代碼可以通過封裝Action被實現(xiàn):
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 構(gòu)建器來構(gòu)建Action而不用定義我們自己的Action類:
import play.api.mvc._
def logging[A](action: Action[A])= Action.async(action.parser) { request =>
Logger.info("Calling action")
action(request)
}
可以使用composeAction 方法把Action混合進Action構(gòu)建器:
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)
}
現(xiàn)在構(gòu)建者可以像前面那樣被使用:
def index = LoggingAction {
Ok("Hello World")
}
我們也可以不使用Action構(gòu)建器混入封裝Action:
def index = Logging {
Action {
Ok("Hello World")
}
}
更復雜的Action
到目前為止,我們已經(jīng)展示了根本不會影響請求的Action融痛。當然壶笼,我們也可以讀和修改傳入的請求對象:
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已經(jīng)內(nèi)置了對 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"))
}
}
最后我們也可以修改返回結(jié)果:
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"))
}
不同的請求類型
Action組合允許你在HTTP請求和響應級別上執(zhí)行額外的處理,經(jīng)常你想要構(gòu)建數(shù)據(jù)轉(zhuǎn)換的管道來添加上下文或者執(zhí)行驗證到請求自身雁刷。ActionFunction 可以被認為是做為請求的方法覆劈,參數(shù)化輸入請求類型和輸出請求類型傳遞個下一層。每一個Action方法可以表示模塊化處理沛励,如驗證责语,數(shù)據(jù)庫查找對象,權(quán)限檢查目派,或者你希望跨Action組合和復用的其他的操作坤候。
有幾個實現(xiàn)了ActionFunction 的預定義的特質(zhì),他們對一些不同類型的操作有用:
- ActionTransformer 可以改變請求企蹭,例如添加額外的信息白筹。
- ActionFilter 可以選擇性了攔截請求,例如在不改變請求值的情況下產(chǎn)生錯誤谅摄。
- ActionRefiner 是上述兩種的一般情況徒河。
- ActionBuilder 使用Request 做為輸入的函數(shù)的特例,因此可以構(gòu)建Action送漠。
你也可以通過實現(xiàn)invokeBlock方法定義你自己的想要的 ActionFunction顽照。通常很容易讓輸入輸出類型成為Request (使用WrappedRequest)的實例。
身份驗證
Action方法最常用的案例之一是身份驗證螺男。我們可以很容易的實現(xiàn)我們自己的從原始的請求確定用戶的身份驗證的Action轉(zhuǎn)換器棒厘,并把它添加到新的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也提供了一個內(nèi)置的身份驗證的Action構(gòu)建器。關(guān)于這個的信息和怎樣使用它可以在這里找到.
注意:內(nèi)置的身份驗證Action構(gòu)建器谓媒,只是一個在簡單情況下實現(xiàn)身份驗證的最少必要代碼的便利的助手淆院,它的實現(xiàn)和上面的例子類似。
如果你有比內(nèi)置身份驗證Action能滿足的需求還要復雜的需求,那么推薦不只是簡單的實現(xiàn)你自己的Action土辩。
給請求增加信息
現(xiàn)在讓我們考慮一下與Item類型的對象一起工作的REST API支救。在/item/:itemId 路徑下也許有很多路由,他們中的每一個都要尋找Item拷淘。在這個案例中各墨,把這個邏輯放到Action方法中也許是有用的。
首先启涯,我們將創(chuàng)建一個增加了Item的請求對象到我們的UserRequest:
import play.api.mvc._
class ItemRequest[A](val item: Item, request: UserRequest[A]) extends WrappedRequest[A](request) {
def username = request.username
}
現(xiàn)在我們將創(chuàng)建一個尋找那些Item的Action細化器贬堵,并返回一個錯誤(Left)Either 或者一個新的 ItemRequest (Right).注意這個Action細化器被定義在一個攜帶Item的ID的方法內(nèi):
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)
}
}
驗證請求
最后,我們也許想要一個驗證請求是應該繼續(xù)的Action方法结洼。例如黎做,也許我們想檢查一下UserAction 的中的用戶是否有權(quán)限從ItemAction獲取Item,如果沒有返回一個錯誤:
object PermissionCheckAction extends ActionFilter[ItemRequest] {
def filter[A](input: ItemRequest[A]) = Future.successful {
if (!input.item.accessibleByUser(input.username))
Some(Forbidden)
else
None
}
}
所有都放在一起
現(xiàn)在我們可以使用andThen 把這些Action方法連接到一起(以ActionBuilder開頭)來創(chuàng)建一個Action:
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 松忍,這對全局橫切關(guān)注點有用蒸殿。