有3種建立自定義指令的基本方法:html
建立自定義指令最簡便的方法就是將一個或多個已有指令經過配置的方式分配一個新的名字來定義。事實上Akka HTTP預約義的大多數指令都由以較低級別指令命名配置的方式來定義的。如:git
val getPut = get & put def postEntity[T](um: FromRequestUnmarshaller[T]): Directive1[T] = post & entity(um) def completeOk: Route = complete(HttpEntity.Empty) def completeNotImplemented: Route = complete(StatusCodes.NotImplemented)
第二種方式是經過「轉換方法」來轉換現有指令,這是在Directive
類上定義的方法:github
map/tmap
flatMap/tflatMap
require/trequire
recover/recoverPF
map、tmap
就和Scala集合庫上的map
轉換相似,它能夠將值映射轉換成另外一個值。map
用於Directive1
類型的指令(單值指令),而tmap
用於值爲其它元組的狀況,它的簽名以下:web
def tmap[R](f: L => R): Directive[Out]
tmap
能夠用來將提取的元組轉換成另外一個元組,提取的數量和類型均可以改變,而map
只用改變變換後的類型。以下是一個虛構的例子:api
val twoIntParameters: Directive[(Int, Int)] = parameters(("a".as[Int], "b".as[Int])) val myDirective: Directive1[String] = twoIntParameters.tmap { case (a, b) => (a + b).toString } // tests: Get("/?a=2&b=5") ~> myDirective(x => complete(x)) ~> check { responseAs[String] mustBe "7" }
經過map、tmap
能夠將指令抽取的值轉換成其它值,但不能改變其「抽取」的性質。當須要抽取一個對它作一些轉換操做,並將結果交給一個嵌套的指令使用時,map、tmap
就無能爲力了。同map、tmap
相似,flatMap
也是用於單值指令,而tflatMap
用於其它元組值。tflatMap
的函數簽名以下:app
def tflatMap[R: Tuple](f: L => Directive[R]): Directive[R]
能夠看一個例子,預約義的method
指令,它的定義以下:函數
def method(httpMethod: HttpMethod): Directive0 = extractMethod.flatMap[Unit] { case `httpMethod` => pass case _ => reject(MethodRejection(httpMethod)) } & cancelRejections(classOf[MethodRejection]) val get: Directive0 = method(HttpMethods.GET) val post: Directive0 = method(HttpMethods.POST)
extractMethod
指令獲取請求的HTTP方法,再經過flatMap[Unit]
轉換方法對它進行處理。由於extractMethod
是一個單值指令且轉換後值爲Unit
(也是個單值),這裏調用flatMap
方法。httpMethod
匹配時,調用pass
指令使其經過,不然調用reject(MethodRejection(httpMethod))
拒絕。require方法將單個指令轉換爲沒有抽取值的指令,該指令根據謂詞函數過濾請求,全部謂詞函數調用後爲false的請求都被拒絕,其它請求保持不變。它的定義以下:post
def require(predicate: T => Boolean, rejections: Rejection*): Directive0 = underlying.filter(predicate, rejections: _*).tflatMap(_ => Empty)
從定義能夠看出,它其實是先經過謂詞函數調用filter
方法對請求進行過濾,而後再調用tflatMap
函數將指令抽取的值去掉。ui
recover方法容許「捕獲」由底層指令向上冒泡產生的rejections,並生成且有相同抽取類型的替代指令。這樣就能夠恢復指令來經過而不是拒絕它。它們的定義分別以下:scala
def recover[R >: L: Tuple](recovery: immutable.Seq[Rejection] => Directive[R]): Directive[R] = Directive[R] { inner => ctx => import ctx.executionContext @volatile var rejectedFromInnerRoute = false tapply({ list => c => rejectedFromInnerRoute = true; inner(list)(c) })(ctx).fast.flatMap { case RouteResult.Rejected(rejections) if !rejectedFromInnerRoute => recovery(rejections).tapply(inner)(ctx) case x => FastFuture.successful(x) } } def recoverPF[R >: L: Tuple](recovery: PartialFunction[immutable.Seq[Rejection], Directive[R]]): Directive[R] = recover { rejections => recovery.applyOrElse(rejections, (rejs: Seq[Rejection]) => RouteDirectives.reject(rejs: _*)) }
能夠經過調用Directive.apply
或它的子類型來從頭開始定義一個指令,Directive
的簡化定義看起來像下面這樣:
abstract class Directive[L](implicit val ev: Tuple[L]) { def tapply(f: L => Route): Route } object Directive { /** * Constructs a directive from a function literal. */ def apply[T: Tuple](f: (T => Route) => Route): Directive[T] = new Directive[T] { def tapply(inner: T => Route) = f(inner) } }
Directive
類型有一個抽象方法tapply
,參數f
是一個函數類型,將類型L
傳入並返回Route
。Directive
的伴身對象提供了apply
來實現自定義指令。它的參數是一個高階函數(T => Route) => Route
,就像小括號那樣,咱們應把(T => Route)
當作一個總體,它是函數參數,返回類型爲Route
。
f
爲咱們自定義指令用於從RequestContext
裏抽取值(值的類型爲Tuple[L]
),而inner
就是f
抽取值後調用的嵌套路由,在調用inner
時將抽取出的值做爲參數傳入。
對於一個抽取訪問host和port的指令,能夠這樣實現:
def hostnameAndPort: Directive[(String, Int)] = Directive[(String, Int)] { inner => ctx => // inner: (String, Int) => Route // ctx: RequestContext val authority: Uri.Authority = ctx.request.uri.authority val tupleValue: (String, Int) = (authority.host.address(), authority.port) val route: Route = inner(tupleValue) route(ctx) // Future[RouteResult] }
讓咱們來分析下這個例子:
hostnameAndPort
指令的類型Directive[(String, Int)]
,它從請求上下文(RequestContext
)中抽取出的值是Tuple2[String, Int]
。apply
方法執行的代碼參數是:inner => ctx => ....
其實能夠當作:inner => ((ctx: RequestContext) => Future[RouteResult])
,inner
就是f
函數參數(T => Route)
部分。inner(tupleValue)
執行後結果route
的類型是Route
,這時這段代碼爲的類型就爲inner => ctx => Route
,而實際上Directive.apply
須要的參數類型爲inner => Route
。以前咱們知道,Route
是一個類型別名RequestContext => Future[RouteResult]
,因此咱們須要將ctx => Route
轉換爲Route
。而將tupleValue
做爲參數調用route
後將獲取結果類型Future[RouteResult]
,這段代碼的類型就是inner => ctx => Future[RouteResult]
-> inner => Route
。本文節選自《Scala Web開發》,更多內容請訪問:https://www.yangbajing.me/scala-web-development/server-api/routing-dsl/custom-directive.html 。