如何在不升级到 Akka HTTP 的情况下停止使用路由 DSL 的 Spray 服务器?

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

我有这条路线:

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

当我试图绑定它以停止它时(根据 https://doc.akka.io/docs/akka-http/current/routing-dsl/index.html),我得到一个编译错误:

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)

如何停止 HTTP 服务器?目前我可以启动 HTTP 服务器:

startServer("0.0.0.0", port)

但是,我不知道如何使用 startServer 函数停止它。

更新:我无法按照下面的建议从 Spray 升级到 Akka HTTP(管理,不受我控制)。

查看 Http().bindAndHandle,它来自 akka-http-core_2.11-2.4.11.1.jar。我看到 here 我需要 RouteResult 才能将其转换为 Flow。但是我在 akka-http-core_2.11-2.4.11.1.jar.

中找不到任何 RouteResult

首先,您的路线看起来是 spray.routing.Route 类型。尝试删除您的 Spray 依赖项并改用 Akka HTTP Route。看起来您的依赖项中已经有 Akka HTTP。

其次,您需要在范围内隐式 ActorMaterializer(和 ActorSystem),以便能够将您的 Route 隐式转换为 Flow[akka.http.scaladsl.model.HttpRequest,akka.http.scaladsl.model.HttpResponse,Any]bindAndHandle 方法需要。

Akka HTTP 是 Spray 的继承者。一旦您按照 Stefano Bonetti 在他的回答中建议的那样删除了 Spray 导入并导入了正确的 Akka 包,停止服务器的一种方法如下(此示例取自 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

正如其他答案已经指出的那样,您将 Spray 和 Akka HTTP 混为一谈。这两个库是不同的,它们各自的服务器端组件并不意味着在同一个应用程序中共存。如果您无法迁移到取代 Spray 的 Akka HTTP,那么 从您的项目中删除 Akka HTTP 依赖项 并查看 Spray documentation 以获取有关停止 Spray 服务器的信息:

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.

显然您正在使用 SimpleRoutingApp,这是定义 startServer 方法的地方。此方法未公开获取对 HttpListener 参与者的引用的方法。正如引用的文档所述,您必须向该参与者发送 Http.Unbind 消息才能停止服务器。

一个想法是定义您自己的 actor,它可以发送对 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
}

然后使用此 actor 代替 SimpleRoutingApp 来启动服务器:

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.")
    }
}

所有这些都假定您想要在(或不)关闭 actor 系统之前明确关闭服务器。如果不是这种情况,您当然可以在不显式停止服务器的情况下关闭 actor 系统。例如,您可以在处理此问题的路由中添加一个路径(以下代码改编自 Spray 存储库中的 sample applications 之一):

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