Currently I'm trying to implement the "actor-per-request" pattern proposed by NET-A-PORTER devs in Akka HTTP. The problem I'm facing is that this pattern is not documented anywhere in the docs. There doesn't seem to be a way to do the following:
IO(Http) ! Http.Bind(serviceActor, "localhost", port = 38080)
How can I use one Akka actor per request without using Spray?
The HttpExt class has a method bindAndHAndleAsync that can be used for this purpose. This method takes in a function with the following signature:
handler: (HttpRequest) ⇒ Future[HttpResponse]
So, suppose we have an Actor that will produce an HttpResponse when asked about an HttpRequest:
class HttpResponseHandlerActor extends Actor {
override def receive = {
case _ : HttpRequest =>
sender() ! HttpResponse(200, entity = "Response From Actor")
}
}
Inefficient Answer
Your question explicitly asks how to use 1 Actor per request, to do that we can now use our Actor class to create a handler function:
implicit val actorSystem = ActorSystem()
implicit val timeout = Timeout(5 seconds)
val handler : (HttpRequest) => Future[HttpResponse] = (httpRequest) = {
val actorHandlerRef =
system.actorOf(Props[HttpResponseHandlerActor], "responseActor")
(actorHandlerRef ask httpRequest).mapTo[HttpResponse]
}
We can now use this function to bind our server with:
val serverBinding : Future[ServerBinding] =
Http().bindAndHandleAsync(handler, "localhost", 8080)
Efficient Answer
It is usually not necessary to re-create a new Actor for each request, typically you want to create 1 Actor and use it for every request.
Therefore we can move the Actor creation outside of handler:
val handler : (ActorRef) => (HttpRequest) => Future[HttpResponse] =
(actorRef) => (httpRequest) =>
(actorRef ask httpRequest).mapTo[HttpResponse]
The server binding is now slightly modified to:
val singleResponseActorRef =
system.actorOf(Props[HttpResponseHandlerActor], "responseActor")
val serverBinding : Future[ServerBinding] =
Http().bindAndHandleAsync(handler(singleResponseActorRef),
"localhost",
8080)
Related
ActorRef actor = ...;
Flow<HttpRequest, HttpResponse, NotUsed> flow = Flow
.of(HttpRequest.class)
.via(Flow.fromSinkAndSource(
Sink
.actorRef(actor, "COMPLETED"),
Source
.actorRef(4, OverflowStrategy.fail())
.mapMaterializedValue(sourceActor -> {
actor.tell("REGISTER", sourceActor);
return sourceActor;
})
))
.map(info -> (HttpResponse) info);
connection.handleWith(flow, materializer);
I create an actor when a connection accepted, and handle the HttpRequest with this actor.
Question(1): Is there a better way to implement this?
Question(2): Now, requests from a connection is sent to the same actor, and I can retrieve a token from a HttpRequest. How can I send requests to a specific actor base on the token? The pseudocode as follow is what I try to do.
Flow<HttpRequest, HttpResponse, NotUsed> flow = Flow
.of(HttpRequest.class)
.via(
.........................................................
String token = retrieveTokenFromHttpRequest(HttpRequest);
ActorRef actor = actorContainer.get(token);
.........................................................
Flow.fromSinkAndSource(
Sink
.actorRef(actor, "COMPLETED"),
Source
.actorRef(4, OverflowStrategy.fail())
.mapMaterializedValue(sourceActor -> {
actor.tell("REGISTER", sourceActor);
return sourceActor;
})
)
)
.map(info -> (HttpResponse) info);
connection.handleWith(flow, materializer);
I want to send notifications to clients via websockets. This notifications are generated by actors, hence I'm trying to create a stream of actor's messages at server startup and subscribe websockects connections to this stream (sending only those notifications emitted since subscription)
With Source.actorRef we can create a Source of actor messages.
val ref = Source.actorRef[Weather](Int.MaxValue, fail)
.filter(!_.raining)
.to(Sink foreach println )
.run()
ref ! Weather("02139", 32.0, true)
But how can I subscribe (akka http*) websockets connections to this source if has been materialized already?
*WebSockets connections in Akka HTTP requires a Flow[Message, Message, Any]
What I'm trying to do is something like
// at server startup
val notifications: Source[Notification,ActorRef] = Source.actorRef[Notificacion](5,OverflowStrategy.fail)
val ref = notifications.to(Sink.foreach(println(_))).run()
val notificationActor = system.actorOf(NotificationActor.props(ref))
// on ws connection
val notificationsWS = path("notificationsWS") {
parameter('name) { name ⇒
get {
onComplete(flow(name)){
case Success(f) => handleWebSocketMessages(f)
case Failure(e) => throw e
}
}
}
}
def flow(name: String) = {
val messages = notifications filter { n => n.name equals name } map { n => TextMessage.Strict(n.data) }
Flow.fromSinkAndSource(Sink.ignore, notifications)
}
This doensn't work because the notifications source is not the one that is materialized, hence it doens't emit any element.
Note: I was using Source.actorPublisher and it was working but ktoso discourages his usage and also I was getting this error:
java.lang.IllegalStateException: onNext is not allowed when the stream has not requested elements, totalDemand was 0.
You could expose the materialised actorRef to some external router actor using mapMaterializedValue.
Flow.fromSinkAndSourceMat(Sink.ignore, notifications)(Keep.right)
.mapMaterializedValue(srcRef => router ! srcRef)
The router can keep track of your sources actorrefs (deathwatch can help tidying things up) and forward messages to them.
NB: you're probably already aware, but note that by using Source.actorRef to feed your flow, your flow will not be backpressure aware (with the strategy you chose it will just crash under load).
I'm an akka noob so apologies!
I'm playing around with a system that uses Spray and Akka.
I'm using the following code snippet to send a message to another actor.
It uses ask which, from what I understand will return a future which is resolved in "mapTo" and "map". I then return the result to the users using Sprays "complete" directive.
val response = (worker ? Create(json))
.mapTo[Ok]
.map(result => s"I got a response: ${result}")
.recover { case _ => "error" }
complete(response)
My question is, since this is a future, do I need to be worried about sending the correct response to the client? In some code samples I see examples where the actorRef to reply to is sent as part of the request...
// set reply to actor
val replyTo = sender() // important to not close over sender()
// create actor that collects replies from workers
val aggregator = context.actorOf(Props(
classOf[StatsAggregator], words.size, replyTo))
Then in the receiving actor...
replyTo ! SendResult
Should I be passing the "replyTo" actor as part of the request or is this all taken care of in the mapTo?
Thanks in advance!
The complete directive will send back a response to http/https client of your service. You don't need to do more than that. Please note that your code swallows errors by making recover on a future. Spray will treat it as a success and will return status code 200.
The last and most importantly, your worker has to reply with Ok message back like this.
class Worker extends Actor {
def receive: Receive = {
case Create(json) =>
//do some staff with json
sender() ! Ok // This Ok message will be passed in the future in spray route
}
}
The replyTo idiom is needed only when worker uses Future internally to process the work load. As it in the following example
class Worker extends Actor {
def recieve: Recieve = {
case Create(json) =>
val future = Future{
//do some staff with json
}
val replyTo = sender()
future.onComplete {
case scala.util.Success(result) =>
replyTo ! Ok
case scala.util.Failure(ex) =>
replyTo ! akka.actor.Status.Failure(ex)
}
}
}
The replyTo is needed to fix actual sender of the message since onComplete may be executed within a different actor context that can point to a different sender resulting in message being sent to a wrong actor.
I am trying to find out what message delivery guarantees Akka supports. I came to the following conclusion:
At-most-once : Supported by default
At-least-once : Supported with Akka Persistence
Exactly-once : ?
Does Akka support exactly-once? How would I be able to achieve this if it doesn't?
Akka out of the box provides At-Most-Once delivery, as you've discovered. At-Least-Once is available in some libraries such as Akka Persistence, and you can create it yourself fairly easily by creating an ACK-RETRY protocol in your actors. The Sender keeps periodically sending the message until the receiver acknowledges receipt of it.
Put simply, for At-Least-Once the responsibility is with the Sender. E.g in Scala:
class Sender(receiver: ActorRef) extends Actor {
var acknowledged = false
override def preStart() {
receiver ! "Do Work"
system.scheduler.scheduleOnce(50 milliseconds, self, "Retry")
}
def receive = {
case "Retry" =>
if(!acknowledged) {
receiver ! "Do Work"
system.scheduler.scheduleOnce(50 milliseconds, self, "Retry")
}
case "Ack" => acknowledged = true
}
}
class Receiver extends Actor {
def receive = {
case "Do Work" =>
doWork()
sender ! "Ack"
}
def doWork() = {...}
}
But with At-Most-Once processing, the receiver has to ensure that repeated instances of the same message only result in work being done once. This could be achieved through making the work done by the receiver idempotent so it can be repeatedly applied, or by having the receiver keep a record of what it has processed already. For At-Most-Once the responsibility is with the receiver:
class AtMostOnceReceiver extends Actor {
var workDone = false
def receive = {
case "Do Work" =>
if(!workDone) {
doWork()
workDone = true
}
sender ! Ack
}
}
i'm using Akka on one of my projects and i need to get the state of an actor, the way i'm doing it is as follows.
a REST request comes in
#GET
#Produces(Array(MediaType.APPLICATION_JSON))
def get() = {
try {
Await.result((getScanningActor ? WorkInfo), 5.second).asInstanceOf[ScanRequest]
}
catch{
case ex: TimeoutException => {
RequestTimedOut()
}
}
}
on the actor i respond with the current work state
case WorkInfo => sender ! currentWork
for some reason the first time i call this function i get the correct value, on the following requests i get the same value i received on the first call
I'm also using DCEVM if that makes any difference.