spray.routing.HttpService 如何发送请求?
How does spray.routing.HttpService dispatch requests?
免责声明:我目前没有 scala 经验,所以我的问题与非常基础的知识有关。
考虑以下示例(可能不完整):
import akka.actor.{ActorSystem, Props}
import akka.io.IO
import spray.can.Http
import akka.pattern.ask
import akka.util.Timeout
import scala.concurrent.duration._
import akka.actor.Actor
import spray.routing._
import spray.http._
object Boot extends App {
implicit val system = ActorSystem("my-actor-system")
val service = system.actorOf(Props[MyActor], "my")
implicit val timeout = Timeout(5.seconds)
IO(Http) ? Http.Bind(service, interface = "localhost", port = 8080)
}
class MyActor extends Actor with MyService {
def actorRefFactory = context
def receive = runRoute(myRoute)
}
trait MyService extends HttpService {
val myRoute =
path("my") {
post {
complete {
"PONG"
}
}
}
}
我的问题是:当控制到达 complete
块时实际发生了什么?这个问题好像太笼统了,我拆分一下吧。
- 我在示例中看到了单个演员的创建。这是否意味着该应用程序是单线程的并且只使用一个 cpu 内核?
- 如果我在
complete
中阻塞调用会怎样?
- 如果第1 为真,p。 2 将阻塞,我如何发送请求以利用所有 cpus?我看到两种方式:每个请求的演员和每个连接的演员。第二种似乎是合理的,但我找不到使用spray库的方法。
- 如果前面的问题无关紧要,
detach
指令可以吗?那么将返回 Future 的函数传递给 complete
指令呢?分离和传递函数返回 Future 有什么区别?
- 配置工作线程数和平衡的正确方法是什么requests/connections?
如果您能在官方文档中指出我的解释,那就太好了。它非常广泛,我相信我遗漏了一些东西。
谢谢。
Spray 的作者之一 Mathias 回答了 here。复制他的回复以供参考:
In the end the only thing that really completes the request is a call
to requestContext.complete
. Thereby it doesn't matter which thread
or Actor context this call is made from. All that matters is that it
does happen within the configured "request-timeout" period. You can of
course issue this call yourself in some way or another, but spray
gives you a number of pre-defined constructs that maybe fit your
architecture better than passing the actual RequestContext around.
Mainly these are:
- The
complete
directive, which simply provides some sugar on top of the "raw" ctx => ctx.complete(…)
function literal.
- The Future Marshaller, which calls
ctx.complete
from an future.onComplete
handler.
- The
produce
directive, which extracts a function T => Unit
that can later be used to complete the request with an instance of a custom
type.
Architecturally, in most cases, it's a good idea to not have the API
layer "leak into" the core of your application. I.e. the application
should not know anything about the API layer or HTTP. It should only
deal with objects of its own domain model. Therefore passing the
RequestContext directly to the application core is mostly not the best
solution.
Resorting to the "ask" and relying on the Future Marshaller is an
obvious, well understood and rather easy alternative. It comes with
the (small) drawback that an ask comes with a mandatory timeout check
itself which logically isn't required (since the spray-can layer
already takes care of request timeouts). The timeout on the ask is
required for technical reasons (so the underlying PromiseActorRef can
be cleaned up if the expected reply never comes).
Another alternative to passing the RequestContext around is the
produce
directive (e.g. produce(instanceOf[Foo]) { completer =>
…
). It extracts a function that you can pass on to the application
core. When your core logic calls complete(foo)
the completion logic
is run and the request completed. Thereby the application core remains
decoupled from the API layer and the overhead is minimal. The
drawbacks of this approach are twofold: first the completer function
is not serializable, so you cannot use this approach across JVM
boundaries. And secondly the completion logic is now running directly
in an actor context of the application core, which might change
runtime behavior in unwanted ways if the Marshaller[Foo] has to do
non-trivial tasks.
A third alternative is to spawn a per-request actor in the API layer
and have it handle the response coming back from the application core.
Then you do not have to use an ask. Still, you end up with the same
problem that the PromiseActorRef underlying an ask has: how to clean
up if no response ever comes back from the application core? With a
re-request actor you have full freedom to implement a solution for
this question. However, if you decide to rely on a timeout (e.g. via
context.setReceiveTimeout
) the benefits over an "ask" might be
non-existent.
Which of the described solutions best fits you architecture you need
to decide yourself. However, as I hopefully was able to show, you do
have a couple of alternatives to choose from.
回答您的一些具体问题:只有一个 actor/handler 为该路线提供服务,因此如果您阻止它,Spray 将被阻止。这意味着您想要立即完成路线或使用上述 3 个选项之一分派工作。
网上有很多关于这 3 个选项的示例。最简单的方法是将您的代码包装在 Future
中。还要检查 "actor per request" option/example。最后,您的架构将定义最合适的方式。
最后,Spray 在 Akka 之上运行,因此所有 Akka 配置仍然适用。有关 Actor 线程设置,请参阅 HOCON reference.conf
和 application.conf
。
免责声明:我目前没有 scala 经验,所以我的问题与非常基础的知识有关。
考虑以下示例(可能不完整):
import akka.actor.{ActorSystem, Props}
import akka.io.IO
import spray.can.Http
import akka.pattern.ask
import akka.util.Timeout
import scala.concurrent.duration._
import akka.actor.Actor
import spray.routing._
import spray.http._
object Boot extends App {
implicit val system = ActorSystem("my-actor-system")
val service = system.actorOf(Props[MyActor], "my")
implicit val timeout = Timeout(5.seconds)
IO(Http) ? Http.Bind(service, interface = "localhost", port = 8080)
}
class MyActor extends Actor with MyService {
def actorRefFactory = context
def receive = runRoute(myRoute)
}
trait MyService extends HttpService {
val myRoute =
path("my") {
post {
complete {
"PONG"
}
}
}
}
我的问题是:当控制到达 complete
块时实际发生了什么?这个问题好像太笼统了,我拆分一下吧。
- 我在示例中看到了单个演员的创建。这是否意味着该应用程序是单线程的并且只使用一个 cpu 内核?
- 如果我在
complete
中阻塞调用会怎样? - 如果第1 为真,p。 2 将阻塞,我如何发送请求以利用所有 cpus?我看到两种方式:每个请求的演员和每个连接的演员。第二种似乎是合理的,但我找不到使用spray库的方法。
- 如果前面的问题无关紧要,
detach
指令可以吗?那么将返回 Future 的函数传递给complete
指令呢?分离和传递函数返回 Future 有什么区别? - 配置工作线程数和平衡的正确方法是什么requests/connections?
如果您能在官方文档中指出我的解释,那就太好了。它非常广泛,我相信我遗漏了一些东西。
谢谢。
Spray 的作者之一 Mathias 回答了 here。复制他的回复以供参考:
In the end the only thing that really completes the request is a call to
requestContext.complete
. Thereby it doesn't matter which thread or Actor context this call is made from. All that matters is that it does happen within the configured "request-timeout" period. You can of course issue this call yourself in some way or another, but spray gives you a number of pre-defined constructs that maybe fit your architecture better than passing the actual RequestContext around. Mainly these are:
- The
complete
directive, which simply provides some sugar on top of the "raw"ctx => ctx.complete(…)
function literal.- The Future Marshaller, which calls
ctx.complete
from anfuture.onComplete
handler.- The
produce
directive, which extracts a functionT => Unit
that can later be used to complete the request with an instance of a custom type.Architecturally, in most cases, it's a good idea to not have the API layer "leak into" the core of your application. I.e. the application should not know anything about the API layer or HTTP. It should only deal with objects of its own domain model. Therefore passing the RequestContext directly to the application core is mostly not the best solution.
Resorting to the "ask" and relying on the Future Marshaller is an obvious, well understood and rather easy alternative. It comes with the (small) drawback that an ask comes with a mandatory timeout check itself which logically isn't required (since the spray-can layer already takes care of request timeouts). The timeout on the ask is required for technical reasons (so the underlying PromiseActorRef can be cleaned up if the expected reply never comes).
Another alternative to passing the RequestContext around is the
produce
directive (e.g.produce(instanceOf[Foo]) { completer => …
). It extracts a function that you can pass on to the application core. When your core logic callscomplete(foo)
the completion logic is run and the request completed. Thereby the application core remains decoupled from the API layer and the overhead is minimal. The drawbacks of this approach are twofold: first the completer function is not serializable, so you cannot use this approach across JVM boundaries. And secondly the completion logic is now running directly in an actor context of the application core, which might change runtime behavior in unwanted ways if the Marshaller[Foo] has to do non-trivial tasks.A third alternative is to spawn a per-request actor in the API layer and have it handle the response coming back from the application core. Then you do not have to use an ask. Still, you end up with the same problem that the PromiseActorRef underlying an ask has: how to clean up if no response ever comes back from the application core? With a re-request actor you have full freedom to implement a solution for this question. However, if you decide to rely on a timeout (e.g. via
context.setReceiveTimeout
) the benefits over an "ask" might be non-existent.Which of the described solutions best fits you architecture you need to decide yourself. However, as I hopefully was able to show, you do have a couple of alternatives to choose from.
回答您的一些具体问题:只有一个 actor/handler 为该路线提供服务,因此如果您阻止它,Spray 将被阻止。这意味着您想要立即完成路线或使用上述 3 个选项之一分派工作。
网上有很多关于这 3 个选项的示例。最简单的方法是将您的代码包装在 Future
中。还要检查 "actor per request" option/example。最后,您的架构将定义最合适的方式。
最后,Spray 在 Akka 之上运行,因此所有 Akka 配置仍然适用。有关 Actor 线程设置,请参阅 HOCON reference.conf
和 application.conf
。