Вам не обязательно понимать все в этом ответе, чтобы эффективно использовать 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-refle reify
как чрезвычайно полезный инструмент, чтобы помочь сделать этот материал видимым.
Для начала с простого (несвязанного) примера, вы можете задаться вопросом, что происходиткогда вы видите Scala-код, такой как "a" * 3
, особенно если вы знаете, что строки 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)))))
Мы можем переформатировать выражение reified для удобства чтения:
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")
.Из API-документов видно, что Directives.path
принимает не строку, а PathMatcher
, поэтому мы знаем, что начинается неявное преобразование из String
в PathMatcher
,и в нашей полностью отлаженной версии мы можем видеть это здесь:
Directives.path(
Directives._segmentStringToPathMatcher("hello")
)
И, конечно же, если мы проверим документы, _segmentStringToPathMatcher
- неявное преобразование соответствующего типа.
Aподобное происходит в 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
, он может быть неявно преобразован в вещь, которая делает это, с помощью метода addByNameNullaryApply
в Directive
.Поэтому, когда вы пишете get(...)
, Scala обрабатывает это значение как get.apply(...)
, а затем преобразует значение get
в функцию Route => Route
, которая имеет соответствующий метод apply
.И точно так же происходит с партией path("hello")(...)
.
Подобные вещи могут показаться кошмаром, и как давний пользователь Scala я могу вам сказать, что это определенно часто происходит.Такие инструменты, как reify
и API-документы, могут сделать его немного менее ужасным.