了解播放过滤器API
隨著Play 2.1的熱銷,很多人開始詢問(wèn)新的Play過(guò)濾器API。 實(shí)際上,API非常簡(jiǎn)單:
本質(zhì)上,過(guò)濾器只是一個(gè)執(zhí)行一個(gè)動(dòng)作并返回另一個(gè)動(dòng)作的函數(shù)。 過(guò)濾器通常會(huì)執(zhí)行的操作是包裝操作,并將其作為委托進(jìn)行調(diào)用。 要將過(guò)濾器添加到應(yīng)用程序中,只需將其添加到Global doFilter方法中即可。 我們提供了一個(gè)幫助類來(lái)幫助您:
object Global extends WithFilters(MyFilter) {... }容易吧? 包裝動(dòng)作,在全局中進(jìn)行注冊(cè)。 好吧,這很容易,但前提是您了解Play架構(gòu)。 這非常重要,因?yàn)橐坏┠私饬薖lay的體系結(jié)構(gòu),就可以使用Play進(jìn)行更多的工作。 我們這里有一些文檔,從較高的層次解釋了Play的體系結(jié)構(gòu)。 在這篇博客中,我將在過(guò)濾器的上下文中解釋Play的體系結(jié)構(gòu),并附帶代碼片段和用例。
Plays架構(gòu)簡(jiǎn)介
我不需要在這里進(jìn)行深入介紹,因?yàn)槲乙呀?jīng)提供了指向我們的體系結(jié)構(gòu)文檔的鏈接,但是總而言之,Play的體系結(jié)構(gòu)非常適合HTTP請(qǐng)求的流程。 發(fā)出HTTP請(qǐng)求時(shí)到達(dá)的第一件事是請(qǐng)求標(biāo)頭。 因此,Play中的動(dòng)作必須是接受請(qǐng)求標(biāo)頭的函數(shù)。 HTTP請(qǐng)求中接下來(lái)會(huì)發(fā)生什么? 身體被接收。 因此,接收請(qǐng)求的函數(shù)必須返回消耗主體的東西。 這是一個(gè)迭代器,它是一個(gè)反應(yīng)式流處理程序,在使用流后最終產(chǎn)生單個(gè)結(jié)果。 您不必為了了解過(guò)濾器而需要了解有關(guān)迭代操作方式的詳細(xì)信息,需要了解的重要一點(diǎn)是,迭代最終會(huì)產(chǎn)生結(jié)果,您可以像使用將來(lái)那樣使用其map函數(shù)進(jìn)行map 。 有關(guān)編寫迭代對(duì)象的詳細(xì)信息,請(qǐng)閱讀我的博客文章 。 HTTP請(qǐng)求中發(fā)生的下一件事是必須發(fā)送http響應(yīng)。 那么iteratee的結(jié)果是什么? HTTP響應(yīng)。 HTTP響應(yīng)是一組響應(yīng)標(biāo)頭,后跟一個(gè)響應(yīng)正文。 響應(yīng)主體是一個(gè)枚舉器,它是一個(gè)反應(yīng)流生成器。 所有這些都是在Plays EssentialAction特性中捕獲的:
trait EssentialAction extends (RequestHeader => Iteratee[Array[Byte], Result])這表明基本動(dòng)作是一個(gè)函數(shù),該函數(shù)采用請(qǐng)求標(biāo)頭并返回迭代器,該迭代器消耗字節(jié)數(shù)組主體塊并最終產(chǎn)生結(jié)果。
更簡(jiǎn)單的方法
在繼續(xù)之前,我想指出Play提供了一個(gè)名為Filter的輔助特性,它比使用EssentialFilter時(shí)使編寫過(guò)濾器更容易。 這類似于Action特質(zhì),因?yàn)锳ction無(wú)需擔(dān)心迭代和如何解析主體,從而簡(jiǎn)化了編寫EssentialAction的過(guò)程,而只是提供了一個(gè)函數(shù),該函數(shù)接受具有解析主體的請(qǐng)求,并返回結(jié)果。 Filter特質(zhì)以類似的方式簡(jiǎn)化了事情,但是我將一直講到最后,因?yàn)槲艺J(rèn)為最好在開始使用助手類之前先了解過(guò)濾器的工作原理。
Noop過(guò)濾器
為了演示過(guò)濾器的外觀,我將展示的第一件事是noop過(guò)濾器:
class NoopFilter extends EssentialFilter {def apply(next: EssentialAction) = new EssentialAction {def apply(request: RequestHeader) = {next(request)}} }每次執(zhí)行過(guò)濾器時(shí),我們都會(huì)創(chuàng)建一個(gè)包裝它的新EssentialAction 。 由于EssentialAction只是一個(gè)函數(shù),我們可以調(diào)用它,傳遞傳入的請(qǐng)求。 因此,以上是我們實(shí)現(xiàn)EssentialFilter基本模式。
處理請(qǐng)求頭
假設(shè)我們要查看請(qǐng)求標(biāo)頭,然后根據(jù)我們檢查的內(nèi)容有條件地調(diào)用包裝的操作。 可以執(zhí)行此操作的過(guò)濾器示例可能是網(wǎng)站/admin區(qū)域的全面安全策略。 可能看起來(lái)像這樣:
class AdminFilter extends EssentialFilter {def apply(next: EssentialAction) = new EssentialAction {def apply(request: RequestHeader) = {if (request.path.startsWith('/admin') && request.session.get('user').isEmpty) {Iteratee.ignore[Array[Byte]].map(_ => Results.Forbidden())} else {next(request)}}} }您可以在此處看到,由于我們是在解析正文之前攔截動(dòng)作,因此在阻止動(dòng)作時(shí)我們?nèi)匀恍枰峁┮粋€(gè)正文解析器。 在這種情況下,我們將返回一個(gè)將忽略整個(gè)正文的正文解析器,并將其映射為禁止的結(jié)果。
處理身體
在某些情況下,您可能想對(duì)過(guò)濾器中的主體進(jìn)行處理。 在某些情況下,您可能想解析正文。 如果是這種情況,請(qǐng)考慮改用動(dòng)作合成 ,因?yàn)檫@樣可以在動(dòng)作解析正文之后掛接到動(dòng)作處理。 如果要在過(guò)濾器級(jí)別解析主體,則必須對(duì)其進(jìn)行緩沖,解析,然后再次對(duì)其進(jìn)行流傳輸以使動(dòng)作再次解析。 但是,有些事情可以在過(guò)濾器級(jí)別輕松完成。 一個(gè)示例是gzip解壓縮。 Play框架已經(jīng)提供了開箱即用的gzip解壓縮功能,但是如果沒(méi)有的話,它可能是這樣(使用我的play extra iteratees項(xiàng)目中的gunzip枚舉):
class GunzipFilter extends EssentialFilter {def apply(next: EssentialAction) = new EssentialAction {def apply(request: RequestHeader) = {if (request.headers.get('Content-Encoding').exists(_ == 'gzip')) {Gzip.gunzip() &>> next(request)} else {next(request)}}} }在這里,我們使用iteratee組成將人體分析器iteratee包裹在gunzip枚舉對(duì)象中。
處理響應(yīng)頭
當(dāng)您進(jìn)行過(guò)濾時(shí),您通常會(huì)希望對(duì)正在發(fā)送的響應(yīng)進(jìn)行處理。 如果您只想添加標(biāo)題,或向會(huì)話中添加內(nèi)容,或?qū)憫?yīng)進(jìn)行任何寫操作,而無(wú)需實(shí)際讀取它,那么這很簡(jiǎn)單。 例如,假設(shè)您要向每個(gè)響應(yīng)添加一個(gè)自定義標(biāo)頭:
class SosFilter extends EssentialFilter {def apply(next: EssentialAction) = new EssentialAction {def apply(request: RequestHeader) = {next(request).map(result => result.withHeaders('X-Sos-Message' -> 'I'm trapped inside Play Framework please send help'))}} }使用處理身體的iteratee上的map函數(shù),我們可以訪問(wèn)該動(dòng)作產(chǎn)生的結(jié)果,然后可以按照演示進(jìn)行修改。 但是,如果您想讀取結(jié)果,則需要解開包裝。 播放結(jié)果是AsyncResult或PlainResult 。 一個(gè)AsyncResult是一個(gè)Result ,其中包含一個(gè)Future[Result] 。 它具有允許最終的PlainResult transform方法。 PlainResult具有標(biāo)題和正文。 因此,假設(shè)您要向每個(gè)新創(chuàng)建的會(huì)話添加時(shí)間戳以記錄創(chuàng)建時(shí)間。 可以這樣完成:
class SessionTimestampFilter extends EssentialFilter {def apply(next: EssentialAction) = new EssentialAction {def apply(request: RequestHeader) = {def addTimestamp(result: PlainResult): Result = {val session = Session.decodeFromCookie(Cookies(result.header.headers.get(HeaderNames.COOKIE)).get(Session.COOKIE_NAME))if (!session.isEmpty) {result.withSession(session + ('timestamp' -> System.currentTimeMillis.toString))} else {result}}next(request).map {case plain: PlainResult => addTimestamp(plain)case async: AsyncResult => async.transform(addTimestamp)}}} }處理響應(yīng)主體
您可能要做的最后一件事是轉(zhuǎn)換響應(yīng)主體。 PlainResult有兩個(gè)實(shí)現(xiàn), SimpleResult (用于沒(méi)有傳輸編碼的主體)和ChunkedResult (用于分塊傳輸編碼的主體)。 SimpleResult包含一個(gè)枚舉器,而ChunkedResult包含一個(gè)接受迭代器以將結(jié)果寫出的函數(shù)。 您可能要執(zhí)行的操作示例是實(shí)現(xiàn)gzip過(guò)濾器。 一個(gè)非常幼稚的實(shí)現(xiàn)(例如,不要使用它,而是使用我的play iteratees項(xiàng)目中的完整實(shí)現(xiàn)),如下所示:
class GzipFilter extends EssentialFilter {def apply(next: EssentialAction) = new EssentialAction {def apply(request: RequestHeader) = {def gzipResult(result: PlainResult): Result = result match {case simple @ SimpleResult(header, content) => SimpleResult(header.copy(headers = (header.headers - 'Content-Length') + ('Content-Encoding' -> 'gzip')), content &> Enumeratee.map(a => simple.writeable.transform(a)) &> Gzip.gzip())}next(request).map {case plain: PlainResult => gzipResult(plain)case async: AsyncResult => async.transform(gzipResult)}}} }使用更簡(jiǎn)單的API
現(xiàn)在,您已經(jīng)了解了如何使用基本的EssentialFilter API來(lái)實(shí)現(xiàn)所有目標(biāo),并希望因此了解了過(guò)濾器如何適應(yīng)Play的體系結(jié)構(gòu)以及如何利用它們來(lái)滿足您的要求。 現(xiàn)在讓我們看一下更簡(jiǎn)單的API:
trait Filter extends EssentialFilter {def apply(f: RequestHeader => Result)(rh: RequestHeader): Resultdef apply(next: EssentialAction): EssentialAction = {...} }object Filter {def apply(filter: (RequestHeader => Result, RequestHeader) => Result): Filter = new Filter {def apply(f: RequestHeader => Result)(rh: RequestHeader): Result = filter(f,rh)} }簡(jiǎn)而言之,該API允許您編寫過(guò)濾器而不必?fù)?dān)心正文解析器。 它看起來(lái)好像動(dòng)作只是結(jié)果的請(qǐng)求標(biāo)頭的功能。 這限制了過(guò)濾器的全部功能,但是對(duì)于許多用例而言,您根本不需要此功能,因此使用此API提供了一種簡(jiǎn)單的替代方法。 為了演示,noop過(guò)濾器類如下所示:
class NoopFilter extends Filter {def apply(f: (RequestHeader) => Result)(rh: RequestHeader) = {f(rh)} }或者,使用Filter隨播對(duì)象:
val noopFilter = Filter { (next, req) =>next(req) }請(qǐng)求計(jì)時(shí)過(guò)濾器可能如下所示:
val timingFilter = Filter { (next, req) =>val start = System.currentTimeMillisdef logTime(result: PlainResult): Result = {Logger.info('Request took ' + (System.currentTimeMillis - start))result}next(req) match {case plain: PlainResult => logTime(plain)case async: AsyncResult => async.transform(logTime)} }參考: James and Beth Roper的博客博客中的JCG合作伙伴 James Roper 了解了Play Filter API 。
翻譯自: https://www.javacodegeeks.com/2013/02/understanding-the-play-filter-api.html
總結(jié)
以上是生活随笔為你收集整理的了解播放过滤器API的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Java 7:Fork / Join框架
- 下一篇: ddos如何防御(ddos十大防御方法)