Search code examples
scalaakkasprayakka-http

How to stop Spray server with routing DSL without upgrading to Akka HTTP?


I have this route:

val route = pathPrefix("es") {
  path("se") {
    post {
      entity(as[JsValue]) {
        t =>
          complete("ok")
      }
    }
  } ~ path("q" / "show") {
    get {
      complete(q)
    }
  }
}

When I try to bind it in order to stop it (according to https://doc.akka.io/docs/akka-http/current/routing-dsl/index.html), I get a compilation error:

val bindingFuture = Http().bindAndHandle(route, "0.0.0.0", 9100)

Error:(54, 46) type mismatch; found : spray.routing.Route (which expands to) spray.routing.RequestContext => Unit required: akka.stream.scaladsl.Flow[akka.http.scaladsl.model.HttpRequest,akka.http.scaladsl.model.HttpResponse,Any] val bindingFuture = Http().bindAndHandle(route, "0.0.0.0", 9100)

How can I stop the HTTP server? Currently I'm able to start the HTTP server with:

startServer("0.0.0.0", port)

However, I don't see how to stop it with the startServer function.

UPDATE: I cannot upgrade from Spray to Akka HTTP as suggested below (administrative, not in my control).

Looking at Http().bindAndHandle, it's coming from akka-http-core_2.11-2.4.11.1.jar. I saw here that I need a RouteResult to convert it to a Flow. But I cannot find any RouteResult in akka-http-core_2.11-2.4.11.1.jar.


Solution

  • As the other answers have already indicated, you're conflating Spray and Akka HTTP. The two libraries are distinct and their respective server-side components are not meant to coexist in the same application. If you're unable to migrate to Akka HTTP, which supersedes Spray, then remove the Akka HTTP dependencies from your project and look to the Spray documentation for information on stopping a Spray server:

    To explicitly stop the server, send an Http.Unbind command to the HttpListener instance (the ActorRef for this instance is available as the sender of the Http.Bound confirmation event from when the server was started).

    The listener will reply with an Http.Unbound event after successfully unbinding from the port (or with an Http.CommandFailed in the case of error). At that point no further requests will be accepted by the server.

    Apparently you're using SimpleRoutingApp, which is where the startServer method is defined. This method doesn't expose a way to obtain a reference to the HttpListener actor. As the quoted documentation states, you have to send an Http.Unbind message to this actor in order to stop the server.

    One idea is to define your own actor that can send a reference to the HttpListener:

    import akka.actor._
    import spray.can.Http
    import spray.routing._
    
    object MyActor {
      case object GetListener
      def props(route: => Route): Props = Props(new MyActor(route))
    }
    
    class MyActor(route: => Route) extends HttpServiceActor {
      import MyActor._
    
      var httpListener: Option[ActorRef] = None
    
      def routeReceive: Receive = runRoute(route)
    
      def serverLifecycleReceive: Receive = {
        case b: Http.Bound =>
          println(s"Successfully bound to ${b.localAddress}")
          val listener = sender()
          httpListener = Some(listener)
        case GetListener =>
          httpListener.foreach(sender ! _)
      }
    
      def receive = routeReceive orElse serverLifecycleReceive
    }
    

    Then use this actor instead of SimpleRoutingApp to start the server:

    import scala.concurrent.Future
    import scala.concurrent.duration._
    import scala.util.Success
    import akka.actor._
    import akka.io.IO
    import akka.pattern.ask
    import akka.util.Timeout
    import spray.can.Http
    import spray.http._
    import spray.routing._
    import MyActor
    
    object Main extends App {
    
      implicit val system = ActorSystem()
      import system.dispatcher
      implicit val timeout = Timeout(5.seconds)
    
      val route = ???
    
      val handler = system.actorOf(MyActor.props(route), name = "handler")
    
      IO(Http) ! Http.Bind(handler, interface = "0.0.0.0", port = 9100)
    
      // run the below code to shut down the server before shutting down the actor system
      (handler ? MyActor.GetListener)
        .flatMap { case actor: ActorRef => (actor ? Http.Unbind) }
        .onComplete {
          case Success(u: Http.Unbound) =>
            println("Unbinding from the port is done.")
            // system.shutdown()
          case _ =>
            println("Unbinding failed.")
        }
    }
    

    All of this assumes that you want to explicitly shut down the server before (or without) shutting down the actor system. If this is not the case, you could of course just shut down the actor system without explicitly stopping the server. For example, you could add a path to your route that handles this (the below code is adapted from one of the sample applications in the Spray repository):

    object Main extends App with SimpleRoutingApp {
      implicit val system = ActorSystem("simple-routing-app")
      import system.dispatcher
    
      val route = ...
        ~ (post | parameter('method ! "post")) {
          path("stop") {
            complete {
              system.scheduler.scheduleOnce(1.second)(system.shutdown())(system.dispatcher)
              "Shutting down in 1 second..."
            }
          }
        }
    
      startServer("0.0.0.0", port = 9100) {
        route
      }.onComplete {
        case Success(b) =>
          println(s"Successfully bound to ${b.localAddress}")
        case Failure(ex) =>
          println(ex.getMessage)
          system.shutdown()
      }
    }