您不一定需要了解此答案中的所有内容才能有效地使用 akka-http,但我向您保证,有时(可能迟早)您将与编译器发生争执,并且只想要所有花哨的语法糖要消失了,好消息是有工具可以做到这一点(坏消息是,一旦你摆脱了花哨的语法,现实可能会变得一团糟)。
首先要注意的是,虽然这里的花括号可能看起来很像 Java 或其他语言的范围或定义分隔符,但它们实际上只是将方法应用于参数。你可以用括号做同样的事情:
scala> import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Directives._
scala> val route = path("hello")(get(complete("Hello, World!")))
route: akka.http.scaladsl.server.Route = ...
虽然这些 get 和 complete 可能看起来像关键字或其他东西,但它们实际上只是 Directives 上的静态方法(大约 - 阅读全文以获得完整故事),因此以下内容也是等效:
scala> import akka.http.scaladsl.server.Directives
import akka.http.scaladsl.server.Directives
scala> val route = Directives.path("hello")(
| Directives.get(Directives.complete("Hello, World!"))
| )
route: akka.http.scaladsl.server.Route = ...
这有望解释一些语法,但这里仍然有很多不可见的东西。如果你在 REPL 中,你可以使用 scala-reflect 的 reify 作为一个非常有用的工具来帮助使这些东西可见。
从一个简单(不相关的)示例开始,您可能想知道当您看到像 "a" * 3 这样的 Scala 代码时发生了什么,尤其是如果您知道 Java 字符串没有 * 运算符,因此您打开了 REPL :
scala> import scala.reflect.runtime.universe.reify
import scala.reflect.runtime.universe.reify
scala> reify("a" * 3).tree
res6: reflect.runtime.universe.Tree = Predef.augmentString("a").$times(3)
还有一个脱糖版本,显示了应用于字符串的隐式方法,以赋予它* 运算符。
在你的情况下,你可以这样写:
scala> import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Directives._
scala> import scala.reflect.runtime.universe.reify
import scala.reflect.runtime.universe.reify
scala> reify(path("hello")(get(complete("Hello, World!")))).tree
res0: reflect.runtime.universe.Tree = Directive.addByNameNullaryApply(Directives.path(Directives._segmentStringToPathMatcher("hello"))).apply(Directive.addByNameNullaryApply(Directives.get).apply(Directives.complete(ToResponseMarshallable.apply("Hello, World!")(Marshaller.liftMarshaller(Marshaller.StringMarshaller)))))
我们可以重新格式化具体化的表达式以提高可读性:
Directive.addByNameNullaryApply(
Directives.path(
Directives._segmentStringToPathMatcher("hello")
)
).apply(
Directive.addByNameNullaryApply(Directives.get).apply(
Directives.complete(
ToResponseMarshallable.apply("Hello, World!")(
Marshaller.liftMarshaller(Marshaller.StringMarshaller)
)
)
)
)
如果添加几个导入,这也是完全合法的 Scala 代码:
scala> import akka.http.scaladsl.server.{ Directive, Directives }
import akka.http.scaladsl.server.{Directive, Directives}
scala> import akka.http.scaladsl.marshalling.{ Marshaller, ToResponseMarshaller }
import akka.http.scaladsl.marshalling.{Marshaller, ToResponseMarshaller}
scala> val route = Directive.addByNameNullaryApply(
| Directives.path(
| Directives._segmentStringToPathMatcher("hello")
| )
| ).apply(
| Directive.addByNameNullaryApply(Directives.get).apply(
| Directives.complete(
| ToResponseMarshallable.apply("Hello, World!")(
| Marshaller.liftMarshaller(Marshaller.StringMarshaller)
| )
| )
| )
| )
route: akka.http.scaladsl.server.Route = ...
要逐步解释这一点,我们可以从path("hello")开始。我们可以从the API docs 看到Directives.path 不使用字符串,而是使用PathMatcher,因此我们知道从String 到PathMatcher 的隐式转换正在启动,并且在我们完全脱糖的版本中,我们可以在这里看到:
Directives.path(
Directives._segmentStringToPathMatcher("hello")
)
如果我们查看文档,可以肯定,_segmentStringToPathMatcher 是适当类型的隐式转换。
complete("Hello, World!") 也发生了类似的事情。 Directives.complete 采用 ToMarshallableResponse,而不是 String,因此必须进行隐式转换。在这种情况下,它是 ToResponseMarshallable.apply,它还需要隐式 Marshaller 实例,在这种情况下它通过从ToEntityMarshaller 到ToResponseMarshallable 的隐式转换,其中ToEntityMarshaller 实例为Marshaller.StringMarshaller,转换器为Marshaller.liftMarshaller 部分:
Directives.complete(
ToResponseMarshallable.apply("Hello, World!")(
Marshaller.liftMarshaller(Marshaller.StringMarshaller)
)
)
还记得上面我说过get 只是Directives 上的静态方法吗?这是一个谎言,虽然它是Directives 上的静态方法,但我们在编写get(...) 时并没有调用它。相反,这个get 实际上是一个返回Directive0 的无参数方法。 Directive0 是Directive[Unit] 的类型别名,虽然Directive[Unit] 没有apply 方法,但它可以通过Directive 上的addByNameNullaryApply 方法隐式转换为可以使用的东西。因此,当您编写get(...) 时,Scala 会将其脱糖为get.apply(...),然后将get 值转换为Route => Route 函数,该函数具有适当的apply 方法。 path("hello")(...) 部分也发生了完全相同的事情。
这种事情可能看起来像是一场噩梦,作为一个长期使用 Scala 的用户,我可以告诉你,它肯定经常发生。不过,reify 和 API 文档之类的工具可以让它变得不那么可怕。