Hello I am trying to learn akka-http and make a simple client for a rest api. I would like to know how to send a request to a rest server at regular interval.
I have my simple client:
object RestClientApp extends App {
// set up ActorSystem and other dependencies here
//#main-class
//#server-bootstrapping
implicit val system: ActorSystem = ActorSystem()
implicit val materializer: ActorMaterializer = ActorMaterializer()
//#server-bootstrapping
// needed for the future flatMap/onComplete in the end
implicit val executionContext = system.dispatcher
val responseFuture: Future[HttpResponse] = Http().singleRequest(HttpRequest(uri = "http://akka.io"))
responseFuture
.onComplete {
case Success(res) => println(res)
case Failure(_) => sys.error("something wrong")
}
}
How can I send a request and process the response every x time unit ?
A natural fit would be Akka Streams, on which Akka HTTP is built. The example below uses a repeating Source to make an HTTP request to a given URI every five seconds. The materialized stream prints the responses.
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.Source
import scala.concurrent.duration._
object RestClient {
def main(args: Array[String]): Unit = {
implicit val materializer = ActorMaterializer()
Source
.repeat(HttpRequest(uri = "http://akka.io"))
.throttle(1, 5.seconds)
.mapAsync(1)(Http().singleRequest(_))
.runForeach(println)
}
}
you can define RestClient as an actor and use actorSystem.schedule to schedule the actor.
final def schedule(
initialDelay: FiniteDuration,
interval: FiniteDuration,
receiver: ActorRef,
message: Any)(implicit
executor: ExecutionContext,
sender: ActorRef = Actor.noSender): Cancellable =
Example,
import akka.actor.{Actor, ActorSystem, Props}
class RestClientActor extends Actor {
import RestClientActor._
import scala.util.Success
import scala.util.Failure
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.HttpRequest
implicit val actorSystem: ActorSystem = context.system
import context.dispatcher
override def receive: Receive = {
case InitiateRequest =>
Http().singleRequest(HttpRequest(uri = "http://jsonplaceholder.typicode.com/posts")).onComplete {
case Success(s) => println(s._3)
case Failure(f) => println(f.getMessage)
}
}
}
object RestClientActor {
case object InitiateRequest
}
object RestClientApp {
def main(args: Array[String]): Unit = {
import akka.actor.ActorSystem
import scala.concurrent.duration._
import RestClientActor._
import scala.concurrent.ExecutionContextExecutor
implicit val system: ActorSystem = ActorSystem()
implicit val executionContext: ExecutionContextExecutor = system.dispatcher
val actor = system.actorOf(Props[RestClientActor], "RestClientActor")
system.scheduler.schedule(1 seconds, 3 seconds, actor, InitiateRequest)
}
}
Related
I'm just starting out with Akka and Scala and I'm trying to connect to a WebSocket using Akka Streams. I've created my SocketActor below and I try to instantiate from the main method.
Here is my SocketActor:
package com.lightbend.akka.sample
import akka.actor.{Actor, Props}
import akka.Done
import akka.http.scaladsl.Http
import akka.stream.scaladsl._
import akka.http.scaladsl.model.ws._
import scala.concurrent.Future
object SocketActor {
def props(coinApiIdentifier: String): Props = Props(new SocketActor(coinApiIdentifier))
case object Start
case object Stop
}
class SocketActor(val ticker: String) extends Actor {
import SocketActor._
// Future[Done] is the materialized value of Sink.foreach,
// emitted when the stream completes
private val incoming: Sink[Message, Future[Done]] =
Sink.foreach[Message] {
case message: TextMessage.Strict =>
println(message.text)
}
// send this as a message over the WebSocket
private val outgoing = Source.single(TextMessage("hello world!"))
// flow to use (note: not re-usable!)
private val webSocketFlow = Http().webSocketClientFlow(WebSocketRequest("wss://api.com/v1/"))
// the materialized value is a tuple with
// upgradeResponse is a Future[WebSocketUpgradeResponse] that
// completes or fails when the connection succeeds or fails
// and closed is a Future[Done] with the stream completion from the incoming sink
private val graph =
outgoing
.viaMat(webSocketFlow)(Keep.right) // keep the materialized Future[WebSocketUpgradeResponse]
.toMat(incoming)(Keep.both) // also keep the Future[Done]
override def receive: PartialFunction[Any, Unit] = {
case Start =>
println("Start message received.")
graph.run()
}
}
And my main method:
object AkkaQuickstart extends App {
// Create the 'helloAkka' actor system
val system: ActorSystem = ActorSystem("test")
val materializer: ActorMaterializer = ActorMaterializer()
val socketActor: ActorRef =
system.actorOf(SocketActor.props("hello"), "socket-actor")
socketActor ! Start
}
Unfortunately, I get the error:
Error:(38, 35) could not find implicit value for parameter system:
akka.actor.ActorSystem private val webSocketFlow =
Http().webSocketClientFlow(WebSocketRequest("wss://api.com/v1/"))
I've tried passing some implicit parameters to the constructor of the SocketActor but that didn't work too well. It seems like the ActorSystem is not in scope for some reason. How can I get my system in scope for the Http() function in SocketActor?
Define an implicit val:
class SocketActor(val ticker: String) extends Actor {
implicit val sys = context.system
// ...
}
This will provide the implicit ActorSystem that the Http object expects.
There is another issue with your code: the stream in your actor will not run because there is no materializer in scope. One way to address this is to create the materializer inside the actor:
class SocketActor(val ticker: String) extends Actor {
implicit val sys = context.system
implicit val mat = ActorMaterializer()(context)
// ...
}
Note that if the materializer were defined as implicit val mat = ActorMaterializer(), it would implicitly use context.system because of the implicit val sys = context.system. Instead, the materializer is created with the actor's context explicitly. This is done because of the warning in the documentation:
Do not create new actor materializers inside actors by passing the context.system to it. This will cause a new ActorMaterializer to be created and potentially leaked (unless you shut it down explicitly) for each such actor. It is instead recommended to either pass-in the Materializer or create one using the actor’s context.
The recommended approach, which allows the creator of the actor to reuse a materializer, is to pass the materializer to the actor as an implicit parameter:
class SocketActor(val ticker: String)(implicit val mat: ActorMaterializer) extends Actor {
implicit val sys = context.system
// ...
}
Then you could pass the materializer in the main program to this actor:
object AkkaQuickstart extends App {
implicit val system: ActorSystem = ActorSystem("test")
implicit val materializer: ActorMaterializer = ActorMaterializer()
val socketActor: ActorRef =
system.actorOf(Props(classOf[SocketActor], "hello", materializer), "socket-actor")
socketActor ! Start
}
I have an akka actor, and I would like to use a simple service inside that actor. That service should use the client side api's singleRequest method to fetch something from the local network.
My Actor:
package actor
import actor.WorkerActor._
import akka.actor.Actor
import service.HealthCheckService
import scala.concurrent.ExecutionContext
object WorkerActor {
case object HealthCheck
}
class WorkerActor extends Actor {
implicit val system = context.system
implicit val ec: ExecutionContext = context.system.dispatcher
val healthCheckService = new HealthCheckService()
override def receive: Receive = {
case HealthCheck => sender ! healthCheckService.execute()
}
}
Here I created an ActorSystem and an ExecutionContext as well, so that my service can use it:
package service
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.{HttpRequest, HttpResponse}
import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Success}
class HealthCheckService(implicit ec: ExecutionContext, implicit val system: ActorSystem) {
def execute() = {
val responseFuture: Future[HttpResponse] = Http().singleRequest(HttpRequest(uri = "http://someRandom.url"))
and do something with the response....
}
}
If I don't pass an executionContext into the service I get the error:
[error] Cannot find an implicit ExecutionContext. You might pass
[error] an (implicit ec: ExecutionContext) parameter to your method
[error] or import scala.concurrent.ExecutionContext.Implicits.global.
And if I don't pass an actorsystem into the service I get the error:
[error] could not find implicit value for parameter system: akka.actor.ActorSystem
[error] val responseFuture: Future[HttpResponse] = Http().singleRequest(HttpRequest(uri = "http://someRandom.url"))
Questions:
How should services be correctly used from an Actor?
Is it correct to pass around the ActorSystem and ExecutionContext, why doesn't it happen
under the hood?
Http.apply signature is
def apply()(implicit system: ActorSystem): HttpExt
so you need to pass ActorSystem to it implicitly or explicitly.
I do not see anything that requires ExecutionContext inside service, so suppose you do something with Future you get from singleRequest call. Any method on Future will require execution context.
So code is correct, but could be simplified a little:
class WorkerActor extends Actor {
// you do not need to declare implicit val if you just need to pass it once
val healthCheckService = new HealthCheckService(context.system)
override def receive: Receive = {
case HealthCheck => sender ! healthCheckService.execute()
}
}
class HealthCheckService(implicit val system: ActorSystem) {
// dispatcher is already implicit so you need just import it, not declare another val
import system.dispatcher
def execute() = {
val responseFuture: Future[HttpResponse] = Http().singleRequest(HttpRequest(uri = "http://someRandom.url"))
and do something with the response....
}
}
Also, from your code for service I have an impression that you manipulate somehow with future, probably adding await (as soon as your service per your question does not really need ActorSystem), instead using akka and piping messages to actors from Future. Probably (as answer on you first question), you need to check later example of using singleRequest with actors from link to akka-http you added in question .
I'm trying to make sense of the error(s) I'm seeing below, and to learn how to fix it.
could not find implicit value for parameter materializer: akka.Stream.Materializer
val fut: Future[Result] = action.apply(fakeRequest).run
^
not enough arguments for method run (implicit materializer: akka.stream.Materializer)scala.concurrent.Future[play.api.mvc.Result].
Unspecified value parameter materializer.
val fut: Future[Result] = action.apply(fakeRequest).run
^
Here is the test code that produced the error(s):
package com.foo.test
import com.foo.{Api, BoundingBox}
import org.scalatest.{FlatSpec, Matchers}
import play.api.libs.json._
import play.api.mvc._
import play.api.test.{FakeHeaders, FakeRequest}
import scala.concurrent.duration._
import scala.concurrent.{Await, Future}
class TestJmlPlay extends FlatSpec with Matchers {
val bbox = new BoundingBox(-76.778154438007732F, 39.239828198015971F, -76.501003519894326F, 39.354663763993926F)
"latitudes" should "be between swLat and neLat" in {
val action: Action[AnyContent] = (new Api).getForPlay(bbox)
val jsonStr = getStringFromAction(action)
areLatitudesOk(jsonStr, bbox) shouldBe true
}
private def getStringFromAction(action:Action[AnyContent]):String = {
val fakeRequest: Request[String] = new FakeRequest("fakeMethod", "fakeUrl", new FakeHeaders, "fakeBody")
val fut: Future[Result] = action.apply(fakeRequest).run // <== ERROR!
val result = Await.result(fut, 5000 milliseconds)
result.body.toString
}
private def areLatitudesOk(jsonStr: String, bbox: BoundingBox): Boolean = ...
}
You can create an implicit ActorMaterializer within your test class which will use testkit's ActorSystem:
import akka.testkit.TestKit
import akka.actor.ActorSystem
class TestJmlPlay(_system : ActorSystem) extends TestKit(_system) ... {
implicit val materializer: ActorMaterializer = ActorMaterializer()
val bbox = ...
You don't need Materializer.
I believe you are calling not the right action.apply method.
You want def apply(request: Request[A]): Future[Result]
To call the right, you need FakeRequest[AnyContent], same parametrized type as action:Action[AnyContent].This type is forced by PlayBodyParser I believe you set for your action.
After that you don't need .run call
I am using akka-http 1.0 and I would like to use a route defined as
def route: Route = path("") {
// start actor with requestContext
// call requestContext.complete(...) in actor with the result
}
How do I accomplish this?
Elaborating on #jrudolph's comment, the below code satisfies your requirements of dispatching RequestContext values to an Actor. Your question indicated that you wanted a new Actor for each request; however, the below code uses the same Actor for all requests which I think is a more efficient/likely use case. The Actor creation can always be moved inside handleRequest if needed.
First we need an Actor for processing a request to a response:
import akka.actor.Actor
import akka.http.scaladsl.server.{RequestContext, RouteResult}
import akka.http.scaladsl.model.HttpResponse
class RequestActor extends Actor {
//business logic - returns empty HttpResponse
def handleRequestMessage(requestContext : RequestContext) =
RouteResult.Complete(new HttpResponse())
override def receive = {
case reqContext : RequestContext =>
sender ! handleRequestMessage(reqContext)
}
}//end class RequestActor
Now create a utility function for querying the Actor:
import akka.actor.ActorRef
import scala.concurrent.Future
import akka.pattern.ask
object RequestActor {
val handleRequest : ActorRef => RequestContext => Future[RouteResult] =
(actorRef) =>
(requestContext) =>
ask(actorRef,reqContext).mapTo[RouteResult]
}
And all that is left to do is wire everything together into a service:
import akka.actor.{ActorSystem, Props}
import akka.stream.ActorMaterializer
import akka.http.scaladsl.Http
import akka.http.scaladsl.server.Directives.{get,path}
import akka.util.Timeout
object RouteActorTest extends App {
implicit val as = ActorSystem("RouteActorTest")
implicit val timeout = new Timeout(1000)
val sendRequestToActor : RequestContext => Future[RouteResult] =
RequestActor handleRequest (as actorOf Props[RequestActor])
val route = path("")(get(sendRequestToActor))
//rest of application...
}//end object RouteActorTest
you may try even better like:
package controllers
import akka.actor.{Actor, ActorSystem, Props}
import akka.stream.ActorMaterializer
import scala.concurrent.{Await, Future}
import akka.http.scaladsl.server.Directives._
import akka.stream.ActorMaterializer
import akka.util.Timeout
import akka.pattern.ask
import scala.concurrent.Await
import scala.concurrent.duration._
import scala.io.StdIn
import akka.actor._
import akka.util.Timeout
case object Message
class TestActor(name:String) extends Actor {
def receive = {
case Message =>
sender ! "Testing Ask pattern Approach"
println(s"hello from $name")
case _ =>
println("that was unexpected")
}
}
object AskTest extends App {
implicit val system= ActorSystem("myactor")
implicit val material=ActorMaterializer()
// implicit val props=Props.empty
implicit val timeout = Timeout(5 seconds)
implicit val result =system.actorOf(Props(new TestActor("TestingName")),name = "Scala")
val future3:Future[String]= ask(result ,Message).mapTo[String]
val results = Await.result(future3, 2 seconds)
println(results)
}
I'm trying to write a simple HTTP client using Scala and spray-client. I'm basing my client on the examples given on Spray docs.
My issue is that the example is creating a new implicit ActorSystem i.e.
implicit val system = ActorSystem()
but I want my client to be reusable and not create a new ActorSystem.
Here's the gist of my code.
trait WebClient {
def get(url: String)(implicit system: ActorSystem): Future[String]
}
object SprayWebClient extends WebClient {
val pipeline: HttpRequest => Future[HttpResponse] = sendReceive
def get(url: String): Future[String] = {
val r = pipeline (Get("http://some.url/"))
r.map(_.entity.asString)
}
}
But I am getting two compiler errors regarding implicits
implicit ActorRefFactory required: if outside of an Actor you need an implicit ActorSystem, inside of an actor this should be the implicit ActorContext WebClient.scala ...
and
not enough arguments for method sendReceive: (implicit refFactory: akka.actor.ActorRefFactory, implicit executionContext: scala.concurrent.ExecutionContext, implicit futureTimeout: akka.util.Timeout)spray.http.HttpRequest => scala.concurrent.Future[spray.http.HttpResponse]. Unspecified value parameters refFactory, executionContext. WebClient.scala...
How should I change the API definitions?
Here's one solution:
import akka.actor.ActorSystem
import spray.http.{HttpRequest, HttpResponse}
import scala.concurrent.Future
import spray.client.pipelining._
trait WebClient {
def get(url: String): Future[String]
}
class SprayWebClient(implicit system: ActorSystem) extends WebClient {
import system.dispatcher
val pipeline: HttpRequest => Future[HttpResponse] = sendReceive
def get(url: String): Future[String] = {
val r = pipeline (Get("http://some.url/"))
r.map(_.entity.asString)
}
}
and here's another that keeps the original WebClient.get signature:
import akka.actor.ActorSystem
import spray.http.{HttpRequest, HttpResponse}
import scala.concurrent.Future
import spray.client.pipelining._
trait WebClient {
def get(url: String)(implicit system: ActorSystem): Future[String]
}
object SprayWebClient extends WebClient {
def get(url: String)(implicit system: ActorSystem): Future[String] = {
import system.dispatcher
val pipeline: HttpRequest => Future[HttpResponse] = sendReceive
val r = pipeline (Get("http://some.url/"))
r.map(_.entity.asString)
}
}
The second one is a bit more expensive because the pipeline is created anew every time even if it is theoretically static per ActorSystem. I would prefer the first solution and try to find a way to propagate the WebClient through your application (by using the cake pattern, by passing it around explicitly, or by using other dependency injection techniques).