指令如何在 Spray 中工作?

How do directives work in Spray?

我想了解 Spray 中的指令是如何工作的。 As per the documentation:

The general anatomy of a directive is as follows:

name(arguments) { extractions =>
  ... // inner Route
}

我的基本理解是,在下面的代码片段中,32 作为参数传递给方法 test

test {
  32
}

但是,在上面的指令name例子中,说的是参数传递给内部路由,这是一个匿名函数。

有人可以帮助我从如何提取参数并将其传递到内部路由开始,帮助我理解语法和流程吗?

你是对的,该语法将 32 传递给函数 test。你缺少的是 Directive 接受一个函数作为参数(记住,我们现在正在做函数式编程所以 函数是值).如果你想这样写:

path(IntNumber) {
  userId =>
    complete(s"Hello user $userId")
}

以一种不那么 DSL 的方式,你可以这样做:

val innerFunction: Int => Route = {userId => complete(s"Hello user $userId")}
(path(IntNumber))(innerFunction)

甚至这样:

def innerMethod(userId: Int): Route = complete(s"Hello user $userId")
(path(IntNumber))(innerMethod)

这实际上是如何实现的机制是……复杂的;此方法使 Directive 可隐式转换为函数:

implicit def pimpApply[L <: HList](directive: Directive[L])(implicit hac: ApplyConverter[L]): hac.In ⇒ Route = f ⇒ directive.happly(hac(f))

这是使用 "magnet pattern" 到 select 一个适当的 hac,这样它就可以在内部路径中使用一个函数(具有适当数量的参数)如果指令如果指令不提取参数,则提取参数或内部路径(普通路由)中的值。代码看起来比实际更复杂,因为 scala 不直接支持完全依赖类型,所以我们必须通过隐式来模拟它。请参阅 ApplyConverterInstances 以了解这需要的可怕代码:/.

当我们在特定指令的 happly 方法中获得实际路线时,实际提取全部发生。 (如果每个地方都使用 HList,我们几乎可以 avoid/ignore 前面的恐怖)。大多数 extract-ey 指令(例如 path)最终会调用 hextract:

def hextract[L <: HList](f: RequestContext ⇒ L): Directive[L] = new Directive[L] {
  def happly(inner: L ⇒ Route) = ctx ⇒ inner(f(ctx))(ctx)
}

记住 Route 实际上只是一个 RequestContext => Unit,所以这个 returns 一个 Route 当传递一个 RequestContext 时:

  1. 在其上运行 f,以提取需要提取的内容(例如 URL 路径组件)
  2. 那个上运行innerinner 是一个函数,例如内部路由的路径组件。
  3. 在上下文中运行该内部路由。

(以下由评论对话中的 mod 编辑):

从根本上说还是挺优雅的,能看到所有的喷码还是普通的scala代码真是太棒了(真心推荐看源码看不懂)。但是 "bridging" 与 ApplyConverter 的部分很复杂,而且真的没有办法解决这个问题;它来自于尝试使用一种并非真正为它们设计的语言来实现完全依赖类型。

你要记住spray routing DSL是DSL;这是几乎任何其他语言都必须作为外部配置文件的东西。我想不出有哪个 Web 框架能够提供与 spray 具有完全编译时类型安全性的路由定义相同的灵活性。所以,是的,spray 做的一些事情很复杂——但正如引述所说,简单的事情应该很容易,而困难的事情应该是可能的。所有 scala 级别的东西都很简单; spray 很复杂,但在另一种语言中它会更复杂(不可用)。