我试图编写一个调用HTTP REST API的actor。其余的API需要一个将从调用Actor传递的查询参数。 official documentation有一个例子来使用preStart方法实现上述目的,该方法将消息传递给自己:
import akka.actor.{ Actor, ActorLogging }
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.stream.{ ActorMaterializer, ActorMaterializerSettings }
import akka.util.ByteString
class Myself extends Actor
with ActorLogging {
import akka.pattern.pipe
import context.dispatcher
final implicit val materializer: ActorMaterializer = ActorMaterializer(ActorMaterializerSettings(context.system))
val http = Http(context.system)
override def preStart() = {
http.singleRequest(HttpRequest(uri = "http://akka.io"))
.pipeTo(self)
}
def receive = {
case HttpResponse(StatusCodes.OK, headers, entity, _) =>
entity.dataBytes.runFold(ByteString(""))(_ ++ _).foreach { body =>
log.info("Got response, body: " + body.utf8String)
}
case resp @ HttpResponse(code, _, _, _) =>
log.info("Request failed, response code: " + code)
resp.discardEntityBytes()
}
}
以上工作但URL是硬编码的。我想要实现的是一个REST客户端actor,我可以将参数作为消息发送到并获取调用结果。我修改了上面的代码以接收参数作为消息(伪代码):
def receive = {
case param: RESTAPIParameter => {
http.singleRequest(HttpRequest(URI("http://my-rest-url").withQuery("name", "value"))
.pipeTo(self)
}
case HttpResponse(StatusCodes.OK, headers, entity, _) =>
entity.dataBytes.runFold(ByteString(""))(_ ++ _).foreach { body =>
log.info("Got response, body: " + body.utf8String)
sender! body.utf8String //Will not work
}
case resp @ HttpResponse(code, _, _, _) =>
log.info("Request failed, response code: " + code)
resp.discardEntityBytes()
}
以上内容应该可以使用,但是当REST调用的结果通过管道传送回sender
时,self
引用会丢失,因此无法真正用于将响应发送回客户端。
我想我可以尝试将发件人本地存储在变量中并使用它来传回响应,但我认为这不是一个好主意。
那么,处理这种情况的正确方法是什么?
修改:@ PH88下面建议的解决方案有效,但我希望在外部循环中保持HttpResponse
上的模式匹配。
编辑2 :我想将响应传回self
的原因是因为我想实现一个状态机.kind of。状态根据actor接收的消息类型而改变。举个例子:
becomes
awaitingResult。数据通过管道进行进一步处理。becomes
dataRecevied
。数据再次通过管道传输到self
以进行更多处理。希望澄清意图。欢迎任何其他建议/设计实现干净/简单的设计: - )
答案 0 :(得分:1)
您可以使用自己的案例类封装HttpResponse
并将发件人与其捆绑在一起:
case class ServerResponse(requester: ActorRef, resp: HttpResponse)
然后:
def receive = {
case param: RESTAPIParameter => {
val requester = sender
http.singleRequest(HttpRequest(URI("http://my-rest-url").withQuery("name", "value"))
.map(httpResp =>
// This will execute in some other thread, thus
// it's important to NOT use sender directly
ServerResponse(requester, httpResp)
)
.pipeTo(self)
}
case ServerResponse(requester, HttpResponse(...)) =>
val result = ...
requester ! result
...
}
答案 1 :(得分:0)
pipeTo
方法将发送方作为隐式参数:
def pipeTo(recipient: ActorRef)(implicit sender: ActorRef = Actor.noSender): Future[T]
Within an Actor self
被定义为隐式ActorRef
:
implicit final val self: ActorRef
因此self
是在pipeTo中指定的发件人。
您只需将发件人明确指定为原始发件人即可:
def receive = {
case param: RESTAPIParameter =>
http.singleRequest(HttpRequest(URI("http://my-rest-url").withQuery("name", "value"))
.pipeTo(self)(sender) //specify original sender
case HttpResponse(StatusCodes.OK, headers, entity, _) =>
entity.dataBytes.runFold(ByteString(""))(_ ++ _).foreach { body =>
log.info("Got response, body: " + body.utf8String)
sender! body.utf8String //Works now!
}
case resp @ HttpResponse(code, _, _, _) => {
log.info("Request failed, response code: " + code)
resp.discardEntityBytes()
}
}