Akka actor system with HTTP interface - scala

I'm trying to create an Akka system which would among other things respond to HTTP requests. I created a few actors who exchange messages fine. I can also use akka-http to respond to HTTP requests. The problem is in connecting those two parts.
TL;DR: How to talk to Akka actors during akka-http request processing?
I created a single actor responsible for bringing up the HTTP system:
class HttpActor extends Actor with ActorLogging {
/* implicits elided */
private def initHttp() = {
val route: Route = path("status") { get { complete { "OK" } } }
Http()(context.system).bindAndHandle(route, "localhost", 8080)
}
private var bound: Option[Future[Http.ServerBinding]] = None
override def receive = {
case HttpActor.Init =>
bound match {
case Some(x) => log.warning("Http already bootstrapping")
case None =>
bound = Some(initHttp(watcher))
}
}
}
object HttpActor {
case object Init
}
As you may see, the actor creates the akka-http service on the first message it receives (no reason, really, it could do it in the constructor as well).
Now, during the request processing I need to communicate with some other actors, and I can't get it working.
My approach:
private def initInteractiveHttp() = {
val route: Route = path("status") {
get { complete { "OK" } }
} ~ path("ask") {
get { complete {
// Here are the interesting two lines:
val otherActorResponse = someOtherActor ? SomeMessage
otherActorResponse.mapTo[String]
} }
Http()(context.system).bindAndHandle(route, "localhost", 8080)
}
This would send a SomeMessage to someOtherActor and wait for response prior to completing the Request-Response cycle. As I understand, however, that messages would be sent from the root HttpActor, which is bad and leads nowhere in terms of scalability. Ideally I would create a distinct instance of specialized actor for every request, but this fails due to akka-http typing. Consider the following example:
class DisposableActor(httpContext: HttpContext) {
override def preStart() = {
// ... send some questions to other actors
}
override def receive = {
case GotAllData(x) => httpContext.complete(x)
}
}
class HttpActorWithDisposables {
// there is a `context` value in scope - we're an actor, after all
private def initHttpWithDisposableActors() = {
val route: Route = path("status") {
get { complete { "OK" } }
} ~ path("ask") {
get { httpContext =>
val props = Props(new DisposableActor(httpContext))
val disposableActor = context.actorOf(props, "disposable-actor-name")
// I have nothing to return here
}
}
Http()(context.system).bindAndHandle(route, "localhost", 8080)
}
With this approach, I can force the DisposableActor to call httpContext.complete at some point of time. This should correctly end the Request-Response processing cycle. The Route DSL, however, requires to return a valid response (or Future) inside the get block, so this approach doesn't work.

Your first approach is quite alright, actually.
The ask pattern creates a lightweight, disposable actor for you that waits on the result (non-blockingly) to complete the future. It basically does all the things you want to replicate with the DisposableActor and your main HttpActor is not stressed by this.
If you still want to use a different actor, there is completeWith:
completeWith(instanceOf[String]) { complete =>
// complete is of type String => Unit
val props = Props(new DisposableActor(complete))
val disposableActor = context.actorOf(props, "disposable-actor-name")
// completeWith expects you to return unit
}
And in you actor, call the complete function when you have the result
class DisposableActor(complete: String => Unit) {
override def receive = {
case GotAllData(x) =>
complete(x)
context stop self // don't for get to stop the actor
}
}

Related

Get or create child actor by ID

I have two actors in my system. Talker and Conversation. Conversation consists in two talkers (by now). When a Talker wants to join a conversation I should check if conversation exists (another talker has created it) and if it not, create it. I have this code in a method of my Talker actor:
def getOrCreateConversation(conversationId: UUID): ActorRef = {
// #TODO try to get conversation actor by conversationId
context.actorSelection("user/conversation/" + conversationId.toString)
// #TODO if it not exists... create it
context.actorOf(Conversation.props(conversationId), conversationId.toString)
}
As you can see, when I create my converastion actor with actorOf I'm passing as a second argument the conversationId. I do this for easy searching this actor... Is it the correct way to do this?
Thank you
edited
Thanks to #Arne I've finally did this:
class ConversationRouter extends Actor with ActorLogging {
def receive = {
case ConversationEnv(conversationId, msg) =>
val conversation = findConversation(conversationId) match {
case None => createNewConversation(conversationId)
case Some(x) => x
}
conversation forward msg
}
def findConversation(conversationId: UUID): Option[ActorRef] = context.child(conversationId.toString)
def createNewConversation(conversationId: UUID): ActorRef = {
context.actorOf(Conversation.props(conversationId), conversationId.toString)
}
}
And the test:
class ConversationRouterSpec extends ChatUnitTestCase("ConversationRouterSpec") {
trait ConversationRouterSpecHelper {
val conversationId = UUID.randomUUID()
var newConversationCreated = false
def conversationRouterWithConversation(existingConversation: Option[ActorRef]) = {
val conversationRouterRef = TestActorRef(new ConversationRouter {
override def findConversation(conversationId: UUID) = existingConversation
override def createNewConversation(conversationId: UUID) = {
newConversationCreated = true
TestProbe().ref
}
})
conversationRouterRef
}
}
"ConversationRouter" should {
"create a new conversation when a talker join it" in new ConversationRouterSpecHelper {
val nonExistingConversationOption = None
val conversationRouterRef = conversationRouterWithConversation(nonExistingConversationOption)
conversationRouterRef ! ConversationEnv(conversationId, Join(conversationId))
newConversationCreated should be(right = true)
}
"not create a new conversation if it already exists" in new ConversationRouterSpecHelper {
val existingConversation = Option(TestProbe().ref)
val conversationRouterRef = conversationRouterWithConversation(existingConversation)
conversationRouterRef ! ConversationEnv(conversationId, Join(conversationId))
newConversationCreated should be(right = false)
}
}
}
Determining the existence of an actor cannot be done synchronously. So you have a couple of choices. The first two are more conceptual in nature to illustrate doing asynchronous lookups, but I offer them more for reference about the asynchronous nature of actors. The third is likely the correct way of doing things:
1. Make the function return a Future[ActorRef]
def getOrCreateConversation(conversationId: UUID): Unit {
context.actorSelection(s"user/conversation/$conversationId")
.resolveOne()
.recover { case _:Exception =>
context.actorOf(Conversation.props(conversationId),conversationId.toString)
}
}
2. Make it Unit and have it send the ActorRef back to your current actor
Pretty much the same as the above, but now you we pipe the future back the current actor, so that the resolved actor can be dealt with in the context of the calling actor's receive loop:
def getOrCreateConversation(conversationId: UUID): Unit {
context.actorSelection(s"user/conversation/$conversationId")
.resolveOne()
.recover { case _:Exception =>
context.actorOf(Conversation.props(conversationId),conversationId.toString)
}.pipeTo(self)
}
3. Create a router actor that you send your Id'ed messages to and it creates/resolves the child and forwards the message
I say that this is likely the correct way, since your goal seems to be cheap lookup at a specific named path. The example you give makes the assumption that the function is always called from within the actor at path /user/conversation otherwise the context.actorOf would not create the child at /user/conversation/{id}/.
Which is to say that you have a router pattern on your hands and the child you create is already known to the router in its child collection. This pattern assumes you have an envelope around any conversation message, something like this:
case class ConversationEnv(id: UUID, msg: Any)
Now all conversation messages get sent to the router instead of to the conversation child directly. The router can now look up the child in its child collection:
def receive = {
case ConversationEnv(id,msg) =>
val conversation = context.child(id.toString) match {
case None => context.actorOf(Conversation.props(id),id.toString)
case Some(x) => x
}
conversation forward msg
}
The additional benefit is that your router is also the conversation supervisor, so if the conversation child dies, it can deal with it. Not exposing the child ActorRef to the outside world also has the benefit that you could have it die when idle and have it get re-created on the next message receipt, etc.

schedule and cancel an unknown number of message sendings

I want an Actor to be able to scheduleOnce() a message sending - any number of times - through message receipt (receive()). The cancellable thus created can be cancel()led through message receipt.
I encounter great difficulties to think about an architecture to handle this. Anyone'd have a clue on how to build this, with multiple Actors, functions, etc...?
Can I just cancel() the id defined as follows?
class myActor extends Actor {
def Receive = {
case msg(t) => {
createTrigger(t)
}
case _ => //do something
}
def createTrigger(t) => {
val id/*cancellable*/ = context.system.scheduler.schedule(t milliseconds,
someActor,
someMessage)
}
def cancelSchedule(cancellable) => {
cancellable.cancel()
}
}

Attach a callback to run after a scala spray server successfully sends a response

I want to do something like the following:
object SprayTest extends App with SimpleRoutingApp {
implicit val system = ActorSystem("my-system")
import system.dispatcher
startServer(interface = "0.0.0.0", port = 8080) {
post {
path("configNetwork") {
entity(as[Config]) { config =>
complete {
// has a response indicating "OK"
// also, restarts the network interface
handleConfig(config)
}
}
}
}
}
}
The problem is that handleConfig reinitializes the network interface, so remote hosts accessing this endpoint never receive their response.
One way to solve this is to run handleConfig in a separate thread and complete the request immediately with some response like "OK". This isn't a good solution however because it introduces a race condition between the future and the request completion (also, it always fails if the future is executed in a "same thread" execution context).
Therefore, an ideal solution would be to attach a callback to a "write response" future and perform the network re-initialization there, after the response has been successfully sent. Is there a way to achieve this in the spray framework?
As a simple example of the race condition, consider the following two examples:
object SprayTest extends App with SimpleRoutingApp {
implicit val system = ActorSystem("my-system")
import system.dispatcher
startServer(interface = "0.0.0.0", port = 8080) {
post {
path("configNetwork") {
entity(as[Config]) { config =>
ctx =>
ctx.complete("OK")
System.exit(0) // empty response due to this executing before response is sent
}
}
}
}
}
object SprayTest extends App with SimpleRoutingApp {
implicit val system = ActorSystem("my-system")
import system.dispatcher
startServer(interface = "0.0.0.0", port = 8080) {
post {
path("configNetwork") {
entity(as[Config]) { config =>
ctx =>
ctx.complete("OK")
Thread.sleep(1000)
System.exit(0) // response is "OK" because of the sleep
}
}
}
}
}
You can use the withAck method on HttpResponse to receive notification when the response is sent on the wire. Here is a sketch of what that would look like in code, though I suspect if you're reconfiguring the low-level network interface then you will need to actually close the http listener and rebind.
case object NetworkReady
class ApiManager extends HttpServiceActor with Directives {
override def receive: Receive = networkReady
private def networkReady: Receive = runRoute(routes) orElse networkManagementEvents
private def networkManagementEvents: Receive = {
case Config =>
context.become(reconfiguringNetwork)
magicallyReconfigureNetwork pipeTo self
}
private def magicallyReconfigureNetwork: Future[NetworkReady] = ???
private def reconfiguringNetwork: Receive = {
case NetworkReady => context.become(networkReady)
case _: HttpRequest => sender() ! HttpResponse(ServiceUnavailable)
case _: Tcp.Connected => sender() ! Tcp.Close
}
private def routes: Route = {
(post & path("configNetwork") & entity(as[Config])) { config =>
complete(HttpResponse(OK).withAck(config))
}
}
}

Initializing an actor before being able to handle some other messages

I have an actor which creates another one:
class MyActor1 extends Actor {
val a2 = system actorOf Props(new MyActor(123))
}
The second actor must initialize (bootstrap) itself once it created and only after that it must be able to do other job.
class MyActor2(a: Int) extends Actor {
//initialized (bootstrapped) itself, potentially a long operation
//how?
val initValue = // get from a server
//handle incoming messages
def receive = {
case "job1" => // do some job but after it's initialized (bootstrapped) itself
}
}
So the very first thing MyActor2 must do is do some job of initializing itself. It might take some time because it's request to a server. Only after it finishes successfully, it must become able to handle incoming messages through receive. Before that - it must not do that.
Of course, a request to a server must be asynchronous (preferably, using Future, not async, await or other high level stuff like AsyncHttpClient). I know how to use Future, it's not a problem, though.
How do I ensure that?
p.s. My guess is that it must send a message to itself first.
You could use become method to change actor's behavior after initialization:
class MyActor2(a: Int) extends Actor {
server ! GetInitializationData
def initialize(d: InitializationData) = ???
//handle incoming messages
val initialized: Receive = {
case "job1" => // do some job but after it's initialized (bootstrapped) itself
}
def receive = {
case d # InitializationData =>
initialize(d)
context become initialized
}
}
Note that such actor will drop all messages before initialization. You'll have to preserve these messages manually, for instance using Stash:
class MyActor2(a: Int) extends Actor with Stash {
...
def receive = {
case d # InitializationData =>
initialize(d)
unstashAll()
context become initialized
case _ => stash()
}
}
If you don't want to use var for initialization you could create initialized behavior using InitializationData like this:
class MyActor2(a: Int) extends Actor {
server ! GetInitializationData
//handle incoming messages
def initialized(intValue: Int, strValue: String): Receive = {
case "job1" => // use `intValue` and `strValue` here
}
def receive = {
case InitializationData(intValue, strValue) =>
context become initialized(intValue, strValue)
}
}
I don't know wether the proposed solution is a good idea. It seems awkward to me to send a Initialization message. Actors have a lifecycle and offer some hooks. When you have a look at the API, you will discover the prestart hook.
Therefore i propose the following:
When the actor is created, its preStart hook is run, where you do your server request which returns a future.
While the future is not completed all incoming messages are stashed.
When the future completes it uses context.become to use your real/normal receive method.
After the become you unstash everything.
Here is a rough sketch of the code (bad solution, see real solution below):
class MyActor2(a: Int) extends Actor with Stash{
def preStart = {
val future = // do your necessary server request (should return a future)
future onSuccess {
context.become(normalReceive)
unstash()
}
}
def receive = initialReceive
def initialReceive = {
case _ => stash()
}
def normalReceive = {
// your normal Receive Logic
}
}
UPDATE: Improved solution according to Senias feedback
class MyActor2(a: Int) extends Actor with Stash{
def preStart = {
val future = // do your necessary server request (should return a future)
future onSuccess {
self ! InitializationDone
}
}
def receive = initialReceive
def initialReceive = {
case InitializationDone =>
context.become(normalReceive)
unstash()
case _ => stash()
}
def normalReceive = {
// your normal Receive Logic
}
case class InitializationDone
}

how to correctly setup Akka.Actors compared to Scala.Actors

How do I run 2 Akka actors with the caller sending the consumer a message every n seconds?
As there are no loop-react methods as in the Scala.Actors library I am stuck
somehow, the following will not compile and produces:
overriding method receive in trait Actor of type =>
Caller.this.Receive; method receive has incompatible type
object Foo {
def init() {
actorOf[Caller].start()
actorOf[Consumer].start()
}
}
class Caller extends Actor {
def receive {
while (true) {
self ! "msg"
Thread.sleep(1000)
}
}
}
class Consumer extends Actor {
def receive = {
case msg:String => doStuff()
case e => _
}
}
You're missing the equals sign after receive in Caller. Without it, the method is defined as returning Unit (i.e. no useful value), and akka needs you to return a PartialFunction[Any,Unit] from receive.
Now, to actually implement your logic in the idiomatic way, you probably want to use a ReceiveTimeout, like so:
class Caller extends Actor {
self.receiveTimeout = Some(1000)
def receive = {
case ReceiveTimeout =>
self ! "msg"
}
}