Echo simple HTTP server with Akka Http in Scala - scala

I am developing a simple HTTP server using Akka-Http in Scala.
My code is as given below:
object HttpServer extends App {
override def main(args: Array[String]): Unit = {
implicit val system = ActorSystem("my-system")
implicit val materializer = ActorMaterializer()
implicit val executionContext = system.dispatcher
val route : Route = post {
path("echo") {
val json = ???
complete((StatusCodes.OK, json))
}
}
val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)
println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
StdIn.readLine()
bindingFuture.flatMap(_.unbind())
port.onComplete(_ => system.terminate())
}
}
I do not know Scala enough yet. For that, I need some help.
I do not know how I can get JSON from Http POST body to give back that json to client.

You only need to add an extractor to your route definition:
val route : Route = post {
path("echo") {
entity(as[String]) { json =>
complete(json)
}
}
Note that you don't need to set the status code explicitly, as akka-http will automatically set status 200 OK for you when you pass a value to complete

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

akka http mock (authorization) directive

I'm trying to write unit testing to an akka-http server (route), but having trouble with the authorization directive.
I simply want to ignore (mock) the authorization directive.
A minimal version of my code:
object App {
val logger: Logger = Logger[App]
def main(args: Array[String]): Unit = {
implicit val system: ActorSystem = ActorSystem("my-system")
implicit val executionContext: ExecutionContextExecutor = system.dispatcher
val service = new TestRoute()
val bindingFuture = Http().bindAndHandle(service.route, "0.0.0.0", 8080)
bindingFuture.onComplete {
case Success(_) => println(s"listening on localhost:8080")
case Failure(error) => println(s"failed: ${error.getMessage}")
}
}
object TestRoute {
def authorize: Directive1[JsObject] = extractCredentials.flatMap {
case Some(OAuth2BearerToken(token)) => verifyToken(token) match {
case Success(t) =>
logger.debug("token verified successfully")
provide(t)
case Failure(t) =>
logger.warn(s"token is not valid: ${t.getMessage}")
reject(Rejections.validationRejection(t.getMessage))
}
case _ =>
logger.warn("no token present in request")
reject(AuthorizationFailedRejection)
}
def verifyToken(token: String): Try[JsObject] = {
val source = Source.fromResource("public_production.pem")
val fileData = try source.mkString finally source.close()
JwtSprayJson.decodeJson(token, fileData, Seq(JwtAlgorithm.RS256))
}
}
class TestRoute {
val route: Route =
TestRoute.authorize { jwtClaims =>
println(jwtClaims)
complete("sucess")
}
}
}
I want to test the Route in TestRoute. So authorize is the directive I want to mock. I simply want to control what authorize will return, without having to actually getting in the function.
My colleague suggested to wrap the Route in some kind of class that will take the authorize method as an argument, such that in the unit-test I can control it very easily. But I'm looking for the mock answer.
I saw some examples with Mockito, but none of them were compatible with my scalatest version, which is 3.1.0.

Testing an Akka Stream containing a Akka Http cachedHostConnectionPool flow

Any ideas on how to best test an Akka Stream containing an Akka Http Flow? I'm struggling with the following method in particular:
def akkaHttpFlow(server: String)(implicit actorSystem: ActorSystem, actorMaterializer: ActorMaterializer) = {
val uri = new java.net.URI(server)
val port: Int = if( uri.getPort != -1) { uri.getPort } else { 80 }
Http().cachedHostConnectionPool[Seq[String]](uri.getHost, port)
.withAttributes(ActorAttributes.supervisionStrategy(decider))
}
This is the test code
val emails = Set("tonymurphy#example.com")
val source: Source[String, NotUsed] = Source(emails)
val f = source
.grouped(10)
.via(requestBuilderFlow)
.via(akkaHttpFlow)
.map(responseHandler)
.runForeach(println)
f.futureValue.shouldBe(Done)
It fails with the following error (not unexpected tbh) >>>
The future returned an exception of type: akka.stream.StreamTcpException, with message: Tcp command [Connect(localhost:9001,None,List(),Some(10 seconds),true)] failed because of Connection refused.
Would it be possible to embed akka http server in the test? Or how best could I structure the code to be able to mock it?
The supporting code
object MyOperations extends StrictLogging {
val requestBuilderFunc : Seq[String] => (HttpRequest, Seq[String]) = { emails : Seq[String] =>
HttpRequest(method = HttpMethods.POST, uri = "/subscribers").withEntity(ContentTypes.`application/json`, ByteString(Json.toJson(emails).toString())) -> emails.toVector
}
val requestBuilderFlow : Flow[Seq[String],(HttpRequest, Seq[String]),NotUsed] = Flow[Seq[String]] map requestBuilderFunc
val responseHandler: ((Try[HttpResponse], Seq[String])) => (HttpResponse, Seq[String]) = {
case (responseTry, context) =>
logger.debug(s"Response: $responseTry")
(responseTry.get, context.asInstanceOf[Seq[String]])
}
}
I have to admit I'm struggling with how to organise my scala applications into objects, traits, classes, higher order functions etc and test them
What you'll want to do is use something like dependency injection to inject a Flow[(HttpRequest, Seq[String]), (Try[HttpResponse], Seq[String]), Any].
In production that flow will be from akka http, but in test you can mock it yourself to return whatever you need.

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.

500 Internal Server Error in Akka Scala server

This is my code for the server written using Akka framework:
case class Sentence(data: String)
case class RawTriples(triples: List[String])
trait Protocols extends DefaultJsonProtocol {
implicit val sentenceRequestFormat = jsonFormat1(Sentence)
implicit val rawTriplesFormat = jsonFormat1(RawTriples)
}
trait Service extends Protocols {
implicit val system: ActorSystem
implicit def executor: ExecutionContextExecutor
implicit val materializer: Materializer
val openie = new OpenIE
def config: Config
val logger: LoggingAdapter
lazy val ipApiConnectionFlow: Flow[HttpRequest, HttpResponse, Any] =
Http().outgoingConnection(config.getString("services.ip-api.host"), config.getInt("services.ip-api.port"))
def ipApiRequest(request: HttpRequest): Future[HttpResponse] = Source.single(request).via(ipApiConnectionFlow).runWith(Sink.head)
val routes = {
logRequestResult("akka-http-microservice") {
pathPrefix("openie") {
post {
decodeRequest{
entity(as[Sentence]){ sentence =>
complete {
var rawTriples = openie.extract(sentence.data)
val resp: MutableList[String] = MutableList()
for(rtrip <- rawTriples){
resp += (rtrip.toString())
}
val response: List[String] = resp.toList
println(response)
response
}
}
}
}
}
}
}
}
object AkkaHttpMicroservice extends App with Service {
override implicit val system = ActorSystem()
override implicit val executor = system.dispatcher
override implicit val materializer = ActorMaterializer()
override val config = ConfigFactory.load()
override val logger = Logging(system, getClass)
Http().bindAndHandle(routes, config.getString("http.interface"), config.getInt("http.port"))
}
The server accepts a POST request containing a sentence and returns a json array in return. It works fine but if I am making requests to it too frequently using parallelized code, then it gives 500 Internal server error. I wanted to know is there any parameter which I can set in the server to avoid that (number of ready threads for accepting requests etc).
In log files, the error is logged as:
[ERROR] [05/31/2017 11:48:38.110]
[default-akka.actor.default-dispatcher-6]
[akka.actor.ActorSystemImpl(default)] Error during processing of
request: 'null'. Completing with 500 Internal Server Error response.
The doc on the bindAndHandle method shows what you want:
/**
* Convenience method which starts a new HTTP server at the given endpoint and uses the given `handler`
* [[akka.stream.scaladsl.Flow]] for processing all incoming connections.
*
* The number of concurrently accepted connections can be configured by overriding
* the `akka.http.server.max-connections` setting. Please see the documentation in the reference.conf for more
* information about what kind of guarantees to expect.
*
* To configure additional settings for a server started using this method,
* use the `akka.http.server` config section or pass in a [[akka.http.scaladsl.settings.ServerSettings]] explicitly.
*/
akka.http.server.max-connections is probably what you want. As the doc suggests, you can also dig deeper into the akka.http.server config section.
Add following in application.conf file
akka.http {
server {
server-header = akka-http/${akka.http.version}
idle-timeout = infinite
request-timeout = infinite
}
}