Jas
Jas

Reputation: 15123

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.

Upvotes: 4

Views: 543

Answers (3)

Jeffrey Chung
Jeffrey Chung

Reputation: 19527

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()
  }
}

Upvotes: 6

Jeffrey Chung
Jeffrey Chung

Reputation: 19527

Akka HTTP is the successor to Spray. Once you've removed the Spray imports and imported the correct Akka packages as Stefano Bonetti suggests in his answer, one way to stop the server is the following (this example is taken from the documentation):

val route = ???

val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)

println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
StdIn.readLine() // let it run until user presses return

bindingFuture
  .flatMap(_.unbind()) // trigger unbinding from the port
  .onComplete(_ => system.terminate()) // and shutdown when done

Upvotes: 2

Stefano Bonetti
Stefano Bonetti

Reputation: 9023

First of all, it looks like your route is of type spray.routing.Route. Try removing your Spray dependency and use Akka HTTP Route instead. It looks like you already have Akka HTTP in your dependencies.

Second, you'll need and implicit ActorMaterializer (and ActorSystem) in scope to be able to implicitly convert your Route to a Flow[akka.http.scaladsl.model.HttpRequest,akka.http.scaladsl.model.HttpResponse,Any], which the bindAndHandle method expects.

Upvotes: 1

Related Questions