Action composition[翻譯]

原文:Action composition

這章將介紹幾種定義常用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)注點有用蒸殿。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市鸣峭,隨后出現(xiàn)的幾起案子宏所,更是在濱河造成了極大的恐慌,老刑警劉巖摊溶,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件爬骤,死亡現(xiàn)場離奇詭異,居然都是意外死亡更扁,警方通過查閱死者的電腦和手機盖腕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來浓镜,“玉大人溃列,你說我怎么就攤上這事√叛Γ” “怎么了听隐?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長哄啄。 經(jīng)常有香客問我雅任,道長,這世上最難降的妖魔是什么咨跌? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任沪么,我火速辦了婚禮,結(jié)果婚禮上锌半,老公的妹妹穿的比我還像新娘禽车。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布殉摔。 她就那樣靜靜地躺著州胳,像睡著了一般。 火紅的嫁衣襯著肌膚如雪逸月。 梳的紋絲不亂的頭發(fā)上栓撞,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天,我揣著相機與錄音碗硬,去河邊找鬼瓤湘。 笑死,一個胖子當著我的面吹牛肛响,可吹牛的內(nèi)容都是我干的岭粤。 我是一名探鬼主播,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼特笋,長吁一口氣:“原來是場噩夢啊……” “哼剃浇!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起猎物,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤虎囚,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后蔫磨,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體淘讥,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年堤如,在試婚紗的時候發(fā)現(xiàn)自己被綠了蒲列。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡搀罢,死狀恐怖蝗岖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情榔至,我是刑警寧澤抵赢,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站唧取,受9級特大地震影響铅鲤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜枫弟,卻給世界環(huán)境...
    茶點故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一邢享、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧淡诗,春花似錦驼仪、人聲如沸掸犬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至宙攻,卻和暖如春奠货,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背座掘。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工递惋, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人溢陪。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓萍虽,卻偏偏與公主長得像,于是被迫代替她去往敵國和親形真。 傳聞我的和親對象是個殘疾皇子杉编,可洞房花燭夜當晚...
    茶點故事閱讀 44,884評論 2 354

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn)咆霜,斷路器邓馒,智...
    卡卡羅2017閱讀 134,656評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,127評論 25 707
  • 國家電網(wǎng)公司企業(yè)標準(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報批稿:20170802 前言: 排版 ...
    庭說閱讀 10,970評論 6 13
  • 我不知道怎樣把生活中的種種情愫轉(zhuǎn)化為語言 心中默默淌過的...
    復合肥雞閱讀 243評論 1 1
  • 迷茫迷茫 在迷茫的日子里打發(fā)時間 無助無助 在無助的生活里追求光明 虛無虛無 在虛無的世界里浪費光陰 漫長漫長 ...
    BurningZone閱讀 271評論 0 0