Action composition[翻譯]

原文:Action composition

這章將介紹幾種定義常用Action函數的方式。

自定義Action構建器

我們在前面看到過,有幾種聲明Action的方式——使用Request參數,不使用request參數,使用Body解析器等等。事實上,正如我們將在異步編程章節看到的. 還有更多的方式。

實際上,這些構建Action的方法都是通過一個叫 ActionBuilder 的特質定義的,并且我們用來聲明我的Action的Action對象只是這個特質的實例。通過實現你自己的ActionBuilder, 你可以聲明一個可被重復使用的Action棧,然后這可以被用來構建Action。

讓我們從一個簡單的日志裝飾的例子開始,我想記錄每次對這個Action的調用。第一種方式是在invokeBlock 方法中實現這個功能,這個方法通過ActionBuilder構建每個Action時調用:

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 提供了構建Action的所有不同的方法,這也適用于,如聲明一個自定義的Body解析器:

def submit = LoggingAction(parse.text) { request =>
Ok("Got a body " + request.body.length + " bytes long")
}

組成Action

在大多數應用里,我想要有多種Action的構建器,一些做不同類型的驗證,一些提供不同類型的通用功能等等。這樣的話,我們不想為每一個Action構建器類型重寫我們的日志Action代碼,我們想把它定義成一個可以服用的方式。

可復用Action代碼可以通過封裝Action被實現:

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 構建器來構建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構建器:

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")
}

我們也可以不使用Action構建器混入封裝Action:

def index = Logging {
Action {
Ok("Hello World")
}
}

更復雜的Action

到目前為止,我們已經展示了根本不會影響請求的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已經內置了對 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"))
}

不同的請求類型

Action組合允許你在HTTP請求和響應級別上執行額外的處理,經常你想要構建數據轉換的管道來添加上下文或者執行驗證到請求自身。ActionFunction 可以被認為是做為請求的方法,參數化輸入請求類型和輸出請求類型傳遞個下一層。每一個Action方法可以表示模塊化處理,如驗證,數據庫查找對象,權限檢查,或者你希望跨Action組合和復用的其他的操作。
有幾個實現了ActionFunction 的預定義的特質,他們對一些不同類型的操作有用:

  • ActionTransformer 可以改變請求,例如添加額外的信息。
  • ActionFilter 可以選擇性了攔截請求,例如在不改變請求值的情況下產生錯誤。
  • ActionRefiner 是上述兩種的一般情況。
  • ActionBuilder 使用Request 做為輸入的函數的特例,因此可以構建Action。

你也可以通過實現invokeBlock方法定義你自己的想要的 ActionFunction。通常很容易讓輸入輸出類型成為Request (使用WrappedRequest)的實例。

身份驗證

Action方法最常用的案例之一是身份驗證。我們可以很容易的實現我們自己的從原始的請求確定用戶的身份驗證的Action轉換器,并把它添加到新的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也提供了一個內置的身份驗證的Action構建器。關于這個的信息和怎樣使用它可以在這里找到.

注意:內置的身份驗證Action構建器,只是一個在簡單情況下實現身份驗證的最少必要代碼的便利的助手,它的實現和上面的例子類似。
如果你有比內置身份驗證Action能滿足的需求還要復雜的需求,那么推薦不只是簡單的實現你自己的Action。

給請求增加信息

現在讓我們考慮一下與Item類型的對象一起工作的REST API。在/item/:itemId 路徑下也許有很多路由,他們中的每一個都要尋找Item。在這個案例中,把這個邏輯放到Action方法中也許是有用的。
首先,我們將創建一個增加了Item的請求對象到我們的UserRequest:

import play.api.mvc._

class ItemRequest[A](val item: Item, request: UserRequest[A]) extends WrappedRequest[A](request) {
def username = request.username
}

現在我們將創建一個尋找那些Item的Action細化器,并返回一個錯誤(Left)Either 或者一個新的 ItemRequest (Right).注意這個Action細化器被定義在一個攜帶Item的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)
}
}

驗證請求

最后,我們也許想要一個驗證請求是應該繼續的Action方法。例如,也許我們想檢查一下UserAction 的中的用戶是否有權限從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
}
}

所有都放在一起

現在我們可以使用andThen 把這些Action方法連接到一起(以ActionBuilder開頭)來創建一個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 ,這對全局橫切關注點有用。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,923評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,269評論 25 708
  • 國家電網公司企業標準(Q/GDW)- 面向對象的用電信息數據交換協議 - 報批稿:20170802 前言: 排版 ...
    庭說閱讀 11,145評論 6 13
  • 我不知道怎樣把生活中的種種情愫轉化為語言 心中默默淌過的...
    復合肥雞閱讀 322評論 1 1
  • 迷茫迷茫 在迷茫的日子里打發時間 無助無助 在無助的生活里追求光明 虛無虛無 在虛無的世界里浪費光陰 漫長漫長 ...
    BurningZone閱讀 278評論 0 0