在谈论Directive之前,我们先要简单的解释一下Routes(路由), 它的类型是RequestContext => Unit
,在英文中Route是路径,路线的意思。在Spray中,承担了如何处理请求的任务,顺利完成请求requestContext.complete(...)
; 因为某种原因拒绝请求requestContext.reject(...)
;因为某些异常而导致500 Server Error。
在最开始接触Routes的时候的一个疑惑,Route函数(RequestContext => Unit
)的返回值类型为什么是Unit
。
Contrary to what you might initially expect a route does not return anything. Rather, all response processing (i.e. everything that needs to be done after the route itself has handled a request) is performed in “continuation-style” via the responder of the RequestContext
一般来说,在B/S的架构中请求是和响应相对应的,如果将RequestContext视为请求上下文,那么返回值类型就应该是Response之类的。后来阅读源码之后发现,这与Spray的设计理念是有关的,它是一个基于Actor的Rest框架,相应的响应是通过消息发送传递的而且采用的是Fire-And-Forget的模式,所以返回值是Unit
。
case class RequestContext(req: HttpRequest, responder: ActorRef,
unmatchedPath: Uri.Path) {
def complete[T](obj: T)(....): Unit = {
val ctx = new ToResponseMarshallingContext {
..
def marshallTo(response: HttpResponse) = responder ! response
..
}
}
}
而Directive从数据结构上来讲是基于Shapeless HList的高度抽象,读起来比较困难,但是理解之后会感受到类型系统的奥妙; 从功能上来讲,它提供了各种各样的指令来构建Routes; 它的一般结构是:
name(parameters) { L =>
innerRoute
}
PathDirective用于抽取部分URL
path(uid / "key") { uid =>
innerRoute
}
在Restful风格的编程中,我们通常会将资源的唯一标识放在URL中,通过上面的PathDirective我们可以从URL中抽取相应的数据。
SecurityDirective用于请求验证
path(uid / "key") { uid =>
authorize { ctx =>
// 获取URL参数 | 请求头等来进行权限验证
boolean
} {
innerRoute
}
}
接下来看看几种常见的Directive实现Route的实例。
(1) 最基本的Route实现
// get的类型是Directive0
get {
// complete的类型: ToResponseMarshallable => StandRoute
// Spray提供了很多隐式转换将不同类型转换成ToResponseMarshallable,String正是其中之一
// complete("")的类型是 RequestContext => Unit
complete("")
}
在Route实现过程,一个需要特别注意的点就是业务逻辑放置在不同的地方,所执行的次数也会有所不同。
val route: Route = get {
println("MARK")
path("/") {
complete("OK")
}
}
val route: Route = get {
path("/") {
complete {
println("MARK")
"OK"
}
}
}
上面两种路由从逻辑上来讲是一样的,但是后者的MARK每次响应GET请求的时候都会执行,而前者只会在第一次加载的时候执行。上面两种写法最终生成的都是函数字面量(function literal),不同的是前者在生成的过程MARK就已经执行; 而后者并没有执行,当请求进来的时候,complete
中的逻辑才会真正的执行。
HttpServiceBase
要求所有的子类调用runRoute
方法的时候提供Route
函数实例
// type Route = RequestContext => Unit
trait HttpServiceBase extends Directives {
def runRoute(route: Route)(...) {}
}
再来看一下抽象类Directive
的基本结构:
abstract class Diretive[L <: HList] {
// L的类型为HNil
def happly(func: L => Route): Route
// 组合操作
def &(magnet: ConjunctionMagnet): magnet.OUT = magnet(this)
...
}
object Directive {
/*
H的类型: HNil
hac.apply的类型: def apply(in: Route): HNil => Route
hac.In的类型 => Route: Route => Route
hac(f)的类型 HNil => Route
happly(HNil => Route)的类型: Route
*/
implicit def pimpApply[H <: HList](directive: Directive[H])
(implicit hac: ApplyConveter[H]): hac.In => Route = {
f => directive(happly(hac(f)))
}
}
Directiv0实例经由Directive中的隐式方法pimpApply被转换:
abstract class ApplyConverter[L] {
type In
// L的类型为HNil 所以apply的返回值: HNil => Route
// In的类型为Route
def apply(in: In): L => Route
}
// 这种模式在Spray中大量使用,伴生对象并没有直接继承伴生类
// 而是继承了另外一个提供了伴生类实例的类或特质
object ApplyConverter extends ApplyConverterInstances {
implicit val hac0 = new ApplyConverter[HNil] {
type In = Route
def apply(fn: In) = {
case HNil => fn
}
}
}
最终Get的类型为Route => Route
,而complete(““)类型是StandardRoute
,函数调用的结果是Route
,正好是runRoute
方法所需要的。
(2) 使用Directive组合操作来实现Route
path("xx") & authorize(true)
上述操作是一个简单的Directive组合操作,目的是为了在路径匹配之后增加额外的验证; 比如说为App提供API的话,每一次请求除了要验证Path,还需要验证相应的权限。
// ConjunctionMagnet
trait ConjunctionMagnet[L <: HList] {
type OUT
def apply(underlying: Directive[L]): OUT
}
// 这里伴生对象又没有直接继承伴生类(这种模式上面已经提到过了)
// prepender提供了进一步的拼接操作; prepender.Out拼接之后的结果(HList)
object ConjunctionMagnet {
implicit def fromDirecive[L <: HList, R <: HList](other: Directive[R])
(implicit prepender: Prepender[L, R]) = {
new Directive[prepender.Out] {
type Out = Directive[prepender.Out]
def apply(left: Directive[L]): Out = {
new Directive[prepender.Out] {
def happly(f: prepender.Out => Route): Route = {
left.happly { l =>
right.happly { r =>
f(prepender(l, r))
}
}
}
}
}
}
}
}
(3) 使用Future Directive来实现Route
val routes: spray.routing.Route = {
get {
// Normally T is a case class
onSuccess(.. Future[T]..) {
// T => Route => Route
(t: T) => complete("t")
}
}
}
trait OnSuccessFutureMagnet {
type Out <: HList
def get: Directive[Out]
}
object OnSuccessFutureMagnet {
implicit def apply[T](future: ⇒ Future[T])(implicit hl: HListable[T], ec: ExecutionContext) =
new Directive[hl.Out] with OnSuccessFutureMagnet {
...
type Out = hl.Out
...
}
}
上面HListable[T]
的实现还是挺有意思,基本规则如果T <: HList
,直接返回,否则 使用T :: HNil
构建新的HList
,下面模仿它的实现思想,实现一个Listable
。
trait Listable[T] {
type Out <: List[T]
def apply(t: T): Out
}
object Listable extends LowerpriorityListable {
implicit def fromList[T <: List[T]] = new Listable[T] {
override type Out = T
override def apply(t: T): Out = t
}
}
abstract class LowerpriorityListable {
implicit def fromAnyRef[T] = new Listable[T] {
import scala.collection.immutable.::
override type Out = ::[T]
override def apply(t: T): Out = ::(t, Nil)
}
}
在HListable实现过程中,有一个地方需要注意:
abstract class LowerpriorityHListable {
implicit def fromAnyRef[T] = new HListable[T] {
override type Out = T :: HNil
override def apply(t: T): Out = t :: HNil
}
}
final case class ::[+H, +T <: HList](head: H, tail: T) extends HList {}
Out类型的定义实际上用到了中置操作符(Infix Operations): T :: HNil => ::[T, Nil]
,实际上就是生成了::类型。