How to run an Akka-HTTP server inside an Akka cluster? - scala

I am building an Akka cluster and want to use Akka-HTTP server as an API server inside. How does one do that?
I would imagine it will be a cluster singleton, as it is an access point to the cluster, but making it an actor seems weird, as it will need to have a receive() method, which will do nothing (i guess).

Simplest example:
implicit val system = ... // your ActorSystem goes here
implicit val materializer = ActorMaterializer()
implicit val executionContext = system.dispatcher
val route =
path("hello") {
get {
complete(HttpEntity(ContentTypes.`text/plain(UTF-8)`, "Hello World !"))
}
}
val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)
To stop:
bindingFuture
.flatMap(_.unbind()) // trigger unbinding from the port
.onComplete(_ => system.terminate()) // and shutdown when done

Related

How to implement a REST API with akka typed actor system

Im trying to start a REST API using scala with akka-http. Im new with akka and actor model paradigm so i want to implement a typed actor system but im getting this sbt compiling error:
could not find implicit value for parameter system: akka.actor.ActorSystem (implicit ActorRefFactory required: if outside of an Actor you need an implicit ActorSystem, inside of an actor this should be the implicit ActorContext)
[error] val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)
Any idea what is going on here?, this is my code based on an example from the akka documentation:
object FeedAggregatorServer {
def apply(): Behavior[Nothing] =
Behaviors.setup[Nothing](context => new FeedAggregatorServer(context))
}
class FeedAggregatorServer(context: ActorContext[Nothing]) extends AbstractBehavior[Nothing](context) {
context.log.info("Application started")
override def onMessage(msg: Nothing): Behavior[Nothing] = {
// No need to handle any messages
Behaviors.unhandled
}
override def onSignal: PartialFunction[Signal, Behavior[Nothing]] = {
case PostStop =>
context.log.info("Application stopped")
this
}
}
object FeedAggregatorApp {
def main(args: Array[String]): Unit = {
ActorSystem[Nothing](FeedAggregatorServer(), "FeedAggregatorServer")
val route =
concat (
path("") {
complete("Hello, World!")
},
path("feed") {
complete("Hello!")
}
)
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
}
}
Routing should be in FeedAggregatorServer maybe or ...?
You need to define an implicit akka.actor.ActorSystem (note, a classic actor system is required, though this will change in future versions of akka-http) when creating your akka-http server. You can learn more about interoperation between classic and typed actor systems in the akka docs (as mentioned by https://stackoverflow.com/users/4268228/shankar-shastri).
Based on your code you'll want to implicitly provide the classic actor system like so:
// you could move this to your other imports
import akka.actor.typed.scaladsl.adapter._
val typedSystem = ActorSystem[Nothing](FeedAggregatorServer(), "FeedAggregatorServer")
implicit val classicSystem = typedSystem.toClassic
...

how to read tcp stream using scala

I have a java jar that generates the tcp stream on particular port.
I cam run the using java command like java -jar runner.jar and this start producing the stream of message on port 8888.
When I do nc -l 8888 I can see the messages.
I want to read this stream using scala and another framework or tool like akka, akka-stream.
Can anyone help me understand the best tool, framework or any other technique to read this tcp stream.
I tried using akka stream with following code :-
implicit val system = ActorSystem()
implicit val mater = ActorMaterializer() val ss = Tcp().outgoingConnection("127.0.0.1", 8888)
.to(Sink.foreach(println(_)))
Source.empty.to(ss).run()
I also tried
Tcp().outgoingConnection(new InetSocketAddress("127.0.0.1", 8888))
.runWith(Source.maybe[ByteString], Sink.foreach(bs => println(bs.utf8String)))
This doesn't work.
I only need to read the messages and process on my own.
Thanks
As I understand you want to setup TCP server, here is example of TCP Echo using akka streams
def server(system: ActorSystem, address: String, port: Int): Unit = {
implicit val sys = system
import system.dispatcher
implicit val materializer = ActorMaterializer()
val handler = Sink.foreach[Tcp.IncomingConnection] { conn =>
println("Client connected from: " + conn.remoteAddress)
conn handleWith Flow[ByteString]
}
val connections = Tcp().bind(address, port)
val binding = connections.to(handler).run()
binding.onComplete {
case Success(b) =>
println("Server started, listening on: " + b.localAddress)
case Failure(e) =>
println(s"Server could not bind to $address:$port: ${e.getMessage}")
system.terminate()
}
}

Does Akka HTTP work with Akka Typed?

val behavior: Behavior[Message] = Behaviors.setup {
actorContext ⇒
logger.info("starting...")
implicit val actorSystem = actorContext.system.asInstanceOf[ActorSystem]
implicit val materializer = ActorMaterializer()(actorContext.asInstanceOf[ActorContext])
implicit val executionContext = actorContext.executionContext
val route =
path("hello") {
get {
complete(HttpEntity(ContentTypes.`text/html(UTF-8)`, "<h1>Say hello to akka-http</h1>"))
}
}
val bindingFuture = Http().bindAndHandle(route, "localhost", 8888)
compiles okay, but when I run my code I get
20:44:22.586 [Leaderboard-akka.actor.default-dispatcher-3] INFO net.kolotyluk.leaderboard.service.REST$ - starting...
[ERROR] [04/02/2018 20:44:22.592] [Leaderboard-akka.actor.default-dispatcher-2] [akka://Leaderboard/user/Http]
akka.actor.typed.internal.adapter.ActorSystemAdapter cannot be cast to akka.actor.ActorSystem
akka.actor.ActorInitializationException: akka://Leaderboard/user/Http: exception during creation
Is there some simple solution to this? How can I give Http() what it needs for an ActorSystem?
Akka Typed does not seem to play well yet with other Akka libraries
Your code seems to be casting a typed ActorSystem to an untyped ActorSystem, hence the error. Instead use the adapter:
import akka.actor.typed.scaladsl.adapter._
val bindingFuture = Http(actorContext.system.toUntyped).bindAndHandle(route, "localhost", 8888)
Here's an example that tests that a similar thing works:
https://github.com/akka/akka-http/blob/bb682d39b0eb570e74f837829d0f9c13eeea2299/akka-http-tests/src/test/scala/akka/http/scaladsl/TypedActorSystemSpec.scala#L17

Akka-http: connect to websocket on localhost

I am trying to connect to some server through websocket on localhost. When I try to do it in JS by
ws = new WebSocket('ws://localhost:8137');
it succeeds. However, when I use akka-http and akka-streams I get "connection failed" error.
object Transmitter {
implicit val system: ActorSystem = ActorSystem()
implicit val materializer: ActorMaterializer = ActorMaterializer()
import system.dispatcher
object Rec extends Actor {
override def receive: Receive = {
case TextMessage.Strict(msg) =>
Log.info("Recevied signal " + msg)
}
}
// val host = "ws://echo.websocket.org"
val host = "ws://localhost:8137"
val sink: Sink[Message, NotUsed] = Sink.actorRef[Message](system.actorOf(Props(Rec)), PoisonPill)
val source: Source[Message, NotUsed] = Source(List("test1", "test2") map (TextMessage(_)))
val flow: Flow[Message, Message, Future[WebSocketUpgradeResponse]] =
Http().webSocketClientFlow(WebSocketRequest(host))
val (upgradeResponse, closed) =
source
.viaMat(flow)(Keep.right) // keep the materialized Future[WebSocketUpgradeResponse]
.toMat(sink)(Keep.both) // also keep the Future[Done]
.run()
val connected: Future[Done.type] = upgradeResponse.flatMap { upgrade =>
if (upgrade.response.status == StatusCodes.SwitchingProtocols) {
Future.successful(Done)
} else {
Future.failed(new Exception(s"Connection failed: ${upgrade.response.status}")
}
}
def test(): Unit = {
connected.onComplete(Log.info)
}
}
It works completely OK with ws://echo.websocket.org.
I think attaching code of my server is reasonless, because it works with JavaScript client and problem is only with connection, however if you would like to look at it I may show it.
What am I doing wrong?
I have tested your client implementation with a websocket server from akka documentation,
and I did not get any connection error. Your websocket client connects successfully. That is why I am guessing the problem is with your server implementation.
object WebSocketServer extends App {
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
import Directives._
val greeterWebSocketService = Flow[Message].collect {
case tm: TextMessage => TextMessage(Source.single("Hello ") ++ tm.textStream)
}
val route =
get {
handleWebSocketMessages(greeterWebSocketService)
}
val bindingFuture = Http().bindAndHandle(route, "localhost", 8137)
println(s"Server online at http://localhost:8137/\nPress RETURN to stop...")
StdIn.readLine()
import system.dispatcher // for the future transformations
bindingFuture
.flatMap(_.unbind()) // trigger unbinding from the port
.onComplete(_ => system.terminate()) // and shutdown when done
}
By the way, I noticed that your actor's receive method does not cover all possible messages. According to that akka issue,
every message, even very small, can end up as Streamed. If you want to print all text messages a better implementation of the actor would be:
object Rec extends Actor {
override def receive: Receive = {
case TextMessage.Strict(text) ⇒ println(s"Received signal $text")
case TextMessage.Streamed(textStream) ⇒ textStream.runFold("")(_ + _).foreach(msg => println(s"Received streamed signal: $msg"))
}
}
Please find a working project on my github.
I found the solution: the server I used was running on IPv6 (as ::1), but akka-http treats localhost as 127.0.0.1 and ignores ::1. I had to rewrite server to force it to use IPv4 and it worked.

Shut down Akka HTTP app

I have a running Akka HTTP application and I want to shut it down.
Pressing Ctrl + C in SBT does not work for me (my shell is currently Git Bash for Windows).
What's the recommended way of shutting down an Akka app gracefully?
Taking inspiration from this thread, I added a route to my application that shuts down the application:
def shutdownRoute: Route = path("shutdown") {
Http().shutdownAllConnectionPools() andThen { case _ => system.terminate() }
complete("Shutting down app")
}
where system is the app's ActorSystem.
Given this route, I can now shut down my application with
curl http://localhost:5000/shutdown
Edit:
Being able to shut down a server remotely is not a good idea for production code. In the comments, Henrik pointed to a different way that shuts down the server by hitting Enter in the SBT console:
StdIn.readLine()
// Unbind from the port and shut down when done
bindingFuture
.flatMap(_.unbind())
.onComplete(_ => system.terminate())
For context, I put the above code at the end of server initialization:
// Gets the host and a port from the configuration
val host = system.settings.config.getString("http.host")
val port = system.settings.config.getInt("http.port")
implicit val materializer = ActorMaterializer()
// bindAndHandle requires an implicit ExecutionContext
implicit val ec = system.dispatcher
import akka.http.scaladsl.server.Directives._
val route = path("hi") {
complete("How's it going?")
}
// Starts the HTTP server
val bindingFuture: Future[ServerBinding] =
Http().bindAndHandle(route, host, port)
val log = Logging(system.eventStream, "my-application")
bindingFuture.onComplete {
case Success(serverBinding) =>
log.info(s"Server bound to ${serverBinding.localAddress}")
case Failure(ex) =>
log.error(ex, "Failed to bind to {}:{}!", host, port)
system.terminate()
}
log.info("Press enter key to stop...")
// Let the application run until we press the enter key
StdIn.readLine()
// Unbind from the port and shut down when done
bindingFuture
.flatMap(_.unbind())
.onComplete(_ => system.terminate())