Handle Akka HttpRequest with actors? - akka

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);

Related

akka http client follow redirect

The akka-http documentation provides an example to query a http service:
http://doc.akka.io/docs/akka-http/current/scala/http/client-side/request-level.html
how can I tell akka-http to automatically follow redirects, instead of receiving a HttpResponse with code == 302?
akka 2.5.3, akka-http 10.0.9
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()
}
}
You can't tell Akka-http client to do this automatically. It is an open issue for the Akka project: https://github.com/akka/akka-http/issues/195
You could handle this manually with something like:
case resp # HttpResponse(StatusCodes.Redirection(_, _, _, _), headers, _, _) =>
//Extract Location from headers and retry request with another pipeTo
You probably would want to maintain a count of the number of times you get redirected to avoid an infinite loop.

How to subscribe websockets to actor's messages using Akka Streams and Akka HTTP?

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

Akka HTTP "actor per request" pattern

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)

Akka Ask & Futures

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.

Akka TcpPipeLine: how can I send message to a client/server without receiving a init.Event first?

folks!
I'm using akka 2.2.3 and developing simple tcp server application.
The work flow is:
1. client connects to server
2. server accepts connection,
3. server sends to client message "Hello!"
On page http://doc.akka.io/docs/akka/2.2.3/scala/io-tcp.html I can see how I can send response message to request. But, how can I send message before some data was received?
How can I send message to a client without receiving a init.Event first?
Code from documentation page:
class AkkaSslHandler(init: Init[WithinActorContext, String, String])
extends Actor with ActorLogging {
def receive = {
case init.Event(data) ⇒
val input = data.dropRight(1)
log.debug("akka-io Server received {} from {}", input, sender)
val response = serverResponse(input)
sender ! init.Command(response)
log.debug("akka-io Server sent: {}", response.dropRight(1))
case _: Tcp.ConnectionClosed ⇒ context.stop(self)
}
}
You use the init for creating the TcpPipelineHandler as well, and you can of course always send commands to that actor. For this you will need to pass its ActorRef to your handler actor besides the Init.