带HTTP接口的Akka actor系统

时间:2016-02-08 16:04:17

标签: scala akka akka-http

我正在尝试创建一个Akka系统,该系统会响应HTTP请求。我创建了一些交换信息的演员。我也可以使用akka-http来响应HTTP请求。问题在于连接这两个部分。

TL; DR:如何在akka-http请求处理期间与Akka演员交谈?

我创建了一个负责启动HTTP系统的角色:

class HttpActor extends Actor with ActorLogging  {
  /* implicits elided */

  private def initHttp() = {
    val route: Route =  path("status") { get { complete { "OK" } } }
    Http()(context.system).bindAndHandle(route, "localhost", 8080)
  }
  private var bound: Option[Future[Http.ServerBinding]] = None

  override def receive = {
    case HttpActor.Init =>
      bound match {
        case Some(x) => log.warning("Http already bootstrapping")
        case None =>
          bound = Some(initHttp(watcher))
      }

  }
}

object HttpActor {
  case object Init
}

正如您所看到的,actor在它收到的第一条消息上创建了akka-http服务(没有理由,实际上,它也可以在构造函数中执行)。

现在,在请求处理期间,我需要与其他一些演员进行沟通,但我无法让它发挥作用。

我的方法:

  private def initInteractiveHttp() = {
    val route: Route =  path("status") { 
      get { complete { "OK" } } 
    } ~ path("ask") {
      get { complete {
        // Here are the interesting two lines:
        val otherActorResponse = someOtherActor ? SomeMessage
        otherActorResponse.mapTo[String]
    } }
    Http()(context.system).bindAndHandle(route, "localhost", 8080)
  }

这将向SomeMessage发送someOtherActor并在完成请求 - 响应周期之前等待响应。但是,据我所知,这些消息将从根HttpActor发送,这很糟糕,在可扩展性方面无处可寻。理想情况下,我会为每个请求创建一个独特的专用actor实例,但由于akka-http输入而失败。请考虑以下示例:

class DisposableActor(httpContext: HttpContext) {
    override def preStart() = {
       // ... send some questions to other actors
    }
    override def receive = {
      case GotAllData(x) => httpContext.complete(x)
    }
}

class HttpActorWithDisposables {
  // there is a `context` value in scope - we're an actor, after all
  private def initHttpWithDisposableActors() = {
    val route: Route =  path("status") { 
      get { complete { "OK" } } 
    } ~ path("ask") {
      get { httpContext =>
        val props = Props(new DisposableActor(httpContext))
        val disposableActor = context.actorOf(props, "disposable-actor-name")
        // I have nothing to return here
      }
    }
    Http()(context.system).bindAndHandle(route, "localhost", 8080)
  }

通过这种方法,我可以强制DisposableActor在某个时间点调用httpContext.complete。这应该正确结束请求 - 响应处理周期。但是,Route DSL需要在get块内返回有效响应(或Future),因此这种方法不起作用。

1 个答案:

答案 0 :(得分:4)

实际上,你的第一种方法很不错。 ask模式为您创建一个轻量级的一次性actor,它等待结果(非阻塞)以完成未来。它基本上完成了你想用DisposableActor复制的所有东西,而你的主要HttpActor并没有因此而受到压力。

如果您仍想使用其他演员,则有completeWith

completeWith(instanceOf[String]) { complete =>
  // complete is of type String => Unit
  val props = Props(new DisposableActor(complete))
  val disposableActor = context.actorOf(props, "disposable-actor-name")

  // completeWith expects you to return unit
}

在你的演员中,当你得到结果时调用完整的函数

class DisposableActor(complete: String => Unit) {
  override def receive = {
    case GotAllData(x) => 
      complete(x)
      context stop self // don't for get to stop the actor
  }
}