Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 370
Jas Avatar asked Jan 02 '23 22:01

Jas


1 Answers

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()
  }
}
like image 75
Jeffrey Chung Avatar answered Apr 27 '23 19:04

Jeffrey Chung