How is a .HttpService spray request executed?

Disclaimer: I do not have scala experience at the moment, so my question is related to very basics.

Consider the following example (it may be incomplete):

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" } } } } 

My question is: what really happens when control reaches the complete block? The question seems too general, so let me split it up.

  • I see the creation of a single actor in the example. Does this mean that the application is single-threaded and uses only one cpu core?
  • What happens if I block the call inside complete ?
  • If p. 1 is true and p. 2 will block, how do I send requests to use the entire processor? I see two ways: the actor for the request and the actor for the connection. The second seems reasonable, but I cannot find a way to do this using the spray library.
  • If the previous question does not matter, will there be a detach directive? What about passing a function returning Future to the complete directive? What is the difference between the disconnect and transfer function returning the future?
  • What is the correct way to adjust the number of workflows and balance requests / connections?

It would be great if you would give me explanations in the official documentation. This is very extensive, and I believe that something is missing.

Thanks.

+1
source share
1 answer

He answered here Matthias - one of the authors of Spray. Copy answer for reference:

In the end, the only thing that really completes the request is the call to requestContext.complete . Thus, it does not matter which thread or context of the actor this call is made from. All that matters is that this happens during a given period of waiting for a request. You can. Of course, this question will call itself one way or another, but the spray gives you a few predefined constructs that might be better for yours than passing the actual RequestContext. Mainly:

  • The complete directive, which simply provides some sugar on top of the "raw" < ctx => ctx.complete(โ€ฆ) .
  • The future marshaller that calls ctx.complete from the future.onComplete handler.
  • The produce directive, which extracts the function T => Unit , which can subsequently be used to complete the request with an instance of a custom type.

Architecturally, in most cases, itโ€™s nice to have an API layer โ€œleakโ€ into the core of your application. That is, the application should not know anything about the API or HTTP level. It must deal with objects of its own domain model. Therefore, RequestContext directly in the application core is basically not the best solution.

By resorting to "demand" and relying on the future of Marshaller, an obvious, well-understood and fairly simple alternative. It comes with a (small) flaw, which is requested with a mandatory timeout check by itself, which is not logically required (since the spray layer already executes the request timeouts). A timeout on request is required for technical reasons (therefore, the underlying PromiseActorRef may be cleared if the expected response never arrives).

Another alternative to passing RequestContext is produce (for example, produce(instanceOf[Foo]) { completer => โ€ฆ ). It extracts a function that you can pass to the kernel application. When your main logic calls complete(foo) completion logic is executed and the request is complete. Thus, the application core remains separated from the API level, and the overhead is minimal. The disadvantages of this approach are twofold: at first, the complementary function is not serializable, so you cannot use this approach in the JVM boundary. And secondly, the completion logic is now executed directly in the context of the application core actor, which may change behavior during operation in an undesirable way, if it is necessary for Marshaller [Foo] non-trivial tasks.

The third alternative is to spawn an actor for each request in the API level and process the response returned from the application core. Then you do not need to use the request. However, you find yourself in the same problem in that the PromiseActorRef underlying the request has: how to clear if no response is ever returned from the application kernel? With a second request, you have complete freedom to implement a solution to this issue. However, if you decide to rely on a timeout (for example, through context.setReceiveTimeout ), there may be no advantage over โ€œdemandโ€.

Which of the described solutions is best suited for your architecture is up to you to decide. However, as I hope was able to show, you are doing there are several alternatives to choose from.

To answer some of your specific questions: there is only one actor / handler that serves the route this way, if you block it, the spray will be blocked. This means that you want to either complete the route immediately or send the job using one of the three options above.

There are many examples on the Internet for these 3 options. The easiest way is to wrap your code in Future . Check also the "actor per request" / example. Ultimately, your architecture will determine the most appropriate way.

Finally, Spray runs on top of Akka, so all of the Akka settings are still applied. See HOCON reference.conf and application.conf for Actor streaming settings.

+1
source

Source: https://habr.com/ru/post/1246661/


All Articles