Akka Actor hotswapping using rest api

1.6k views Asked by At

Upon calling a rest api, I would want to switch the actor's routing to another routes. Please see the below code.

Couple of questions:

  1. The code compiles fine but when the app is started and a http call is made, I get Configured registration timeout of 1 second expired, stopping message and i dont get any response from server.
  2. I want to be able to switch routing to another set of routes through api.

made

package com.example
import akka.actor.Actor
import akka.io.IO
import spray.httpx.RequestBuilding._
import spray.http.MediaTypes._
import spray.routing.{RoutingSettings, RejectionHandler, ExceptionHandler, HttpService}
import spray.util.LoggingContext
import scala.concurrent.Future
import spray.can.Http
import spray.http._
import akka.util.Timeout
import HttpMethods._
import akka.pattern.ask
import akka.event.Logging
import scala.concurrent.duration._

case object Swap
class MyServiceActor extends Actor with MyService with akka.actor.ActorLogging {

  implicit def actorRefFactory = context
  import context._

  def receive = {
      case Swap =>
           become {
             case Swap => unbecome()
             case _    => runRoute(otherRoutes)
           }
      case _ =>   runRoute(myRoute)
  } 
}


trait MyService extends HttpService { this: MyServiceActor =>

  implicit val timeout: Timeout = Timeout(15.seconds)

  implicit val system = context.system

  val myRoute =
  {
    path("") {
      get {
          complete("MyRoute")
      }
    } ~ path("swap") {

        get{
            self ! Swap
            complete("Swapped")
        }
    }
  }

  val otherRoutes =path("") {
  get {
      complete("OtherRoutes")
     }
   } ~ path("swap") {
        get{
        self ! Swap
        complete("Reverted")
     }
}

}
2

There are 2 answers

10
dk14 On BEST ANSWER

runRoute is a partially applied function, so you can't just write runRoute(routeName) to call it - it will just return another function (which handles routes) but without calling it; you should pass the request object explicitly:

def receive = {
      case Swap =>
           become {
             case Swap => unbecome()
             case x    => val f = runRoute(otherRoutes); f(x)
           }
      case x => val f = runRoute(myRoute); f(x)
  } 

runRoute(route) returns function which handle "Connected" message. So that's why you're getting "registration timeout" error - you don't return this function from receive method. When you write def receive = runRoute(route) this function is used as handler and everything is fine. But when you write def receive = {case _ => runRoute(route)} nothing happens - receive function do nothing because function returned by runRoute(route) goes nowhere.

See, https://github.com/spray/spray/blob/master/spray-routing/src/main/scala/spray/routing/HttpService.scala

And also you can call become/unbecome right from your route because you already have MyServiceActor as self-type. When you use separate Swap message - actor may change its role a little bit after you receive successful "Swapped" response (role changing will occur asynchronously)

case object Swap
class MyServiceActor extends Actor with MyService with akka.actor.ActorLogging {

  implicit def actorRefFactory = context
  import context._

  def swapped = {
      case x => val f = runRoute(otherRoutes); f(x)
  }

  def receive = {
      case x => val f = runRoute(myRoute); f(x)
  } 
}


trait MyService extends HttpService { this: MyServiceActor =>

  implicit val timeout: Timeout = Timeout(15.seconds)

  implicit val system = context.system

  val myRoute = {
    pathSingleSlash {
      get {
            complete("MyRoute")
      }
    } ~ path("swap") {
        get {
            become(swapped)
            complete("Swapped")
        }
    }
  }

  val otherRoutes = { 
   pathSingleSlash {
     get {
      complete("OtherRoutes")
     }
   } ~ path("swap") {
     get{
        unbecome()
        complete("Reverted")
     }
   }
  }
}

Updated: your path matchers are also incorrect. Use:

 pathSingleSlash {
   ...
 } ~ path("swap") {
     ...
 }

or

 path("swap") {
     ...
 } ~ path("") { //matches everything else
     ...
 } 

Updated2:

Make sure that your actor registered as singleton in your Main:

import akka.io.IO
import spray.can.Http

implicit val system = ActorSystem()

val myListener: ActorRef = Props[MyServiceActor]

IO(Http) ! Http.Bind(myListener, interface = "localhost", port = 8080)

http://spray.io/documentation/1.1-SNAPSHOT/spray-can/http-server/#http-server

0
徐礼林 On

I also face the same problem, then you can set the server paras larger to reslove the problem

enter code here

val backlog = 50000
val standardConfig = ServerSettings("spray.can.server.ssl-encryption = off, spray.can.server.registration-timeout = 5s")
val config = standardConfig.copy(serverHeader = "REST API", sslEncryption = false, remoteAddressHeader = true)
val serviceActor = system.actorOf(Props[ApiServiceActor], "apiServiceActor")
IO(Http) ? Http.Bind(serviceActor, interface = "0.0.0.0", ConfigurationHelper.Port, settings = Some(config), backlog = backlog)