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).
Related
I have AkkaHttp client and cats library. I'd like to escape throwing exception, so I wrote this code:
class AkkaHttpJokeClient(url: String)(implicit system: ActorSystem) extends JokeClient[IO] {
override def getJoke(): IO[Either[Throwable, Joke]] = {
implicit val materializer: ActorMaterializer = ActorMaterializer()
implicit val ec: ExecutionContext = system.dispatcher
IO.fromFuture(IO {
Http()
.singleRequest(HttpRequest(uri = url))
.flatMap(Unmarshal(_).to[String])
.map(x => Try{x.parseJson.convertTo[Joke]}.toEither)
})
}
}
It works, but I have a few problems with it. The first one is connection problem isn't solved. The second: I think it could be done easier, isn't it? I can't find right methods to do it better.
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)
}
}
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 am passing from SprayJsonSupport to argonaut based on this example.
After some code modification :
object ElevationJsonProtocol extends DefaultJsonProtocol {
implicit val locationCodec: CodecJson[Elevation] = casecodec2(Elevation, Elevation.unapply)("location", "elevation")
implicit val elevationCodec: CodecJson[Location] = casecodec2(Location, Location.unapply)("lat", "lng")
implicit def googleApiResultCodec: CodecJson[GoogleApiResult] = casecodec2(GoogleApiResult, GoogleApiResult.unapply)("status", "results")
}
I got this error
Error:(41, 42) not enough arguments for method unmarshal: (implicit evidence$1: spray.httpx.unmarshalling.FromResponseUnmarshaller[GoogleApiResult])spray.http.HttpResponse => GoogleApiResult.
Unspecified value parameter evidence$1.
val pipeline = sendReceive ~> unmarshal[GoogleApiResult]
^
I take a look at the unmarshall method:
def unmarshal[T](implicit evidence$1 : spray.httpx.unmarshalling.FromResponseUnmarshaller[T]) : scala.Function1[spray.http.HttpResponse, T]
How can I add the implicit parameter? and why I did not got such error whith the sprayJsonSupport ?
The hole code :
import spray.httpx.unmarshalling.FromResponseUnmarshaller
import scala.util.{Success, Failure}
import scala.concurrent.duration._
import akka.actor.ActorSystem
import akka.pattern.ask
import akka.event.Logging
import akka.io.IO
import spray.json.{JsonFormat, DefaultJsonProtocol}
import spray.can.Http
import spray.httpx.SprayJsonSupport
import spray.client.pipelining._
import spray.util._
import argonaut._, Argonaut._
case class Elevation(location: Location, elevation: Double)
case class Location(lat: Double, lng: Double)
case class GoogleApiResult(status: String, results: List[Elevation])
object ElevationJsonProtocol extends DefaultJsonProtocol {
implicit val locationCodec: CodecJson[Elevation] = casecodec2(Elevation, Elevation.unapply)("location", "elevation")
implicit val elevationCodec: CodecJson[Location] = casecodec2(Location, Location.unapply)("lat", "lng")
implicit def googleApiResultCodec: CodecJson[GoogleApiResult] = casecodec2(GoogleApiResult, GoogleApiResult.unapply)("status", "results")
}
object Main extends App {
// we need an ActorSystem to host our application in
implicit val system = ActorSystem("simple-spray-client")
import system.dispatcher // execution context for futures below
val log = Logging(system, getClass)
log.info("Requesting the elevation of Mt. Everest from Googles Elevation API...")
import ElevationJsonProtocol._
val pipeline = sendReceive ~> unmarshal[GoogleApiResult]
val responseFuture = pipeline (
Get("http://maps.googleapis.com/maps/api/elevation/json?locations=27.988056,86.925278&sensor=false")
)
responseFuture onComplete {
case Success(GoogleApiResult(_, Elevation(_, elevation) :: _)) =>
log.info("The elevation of Mt. Everest is: {} m", elevation)
shutdown()
case Success(somethingUnexpected) =>
log.warning("The Google API call was successful but returned something unexpected: '{}'.", somethingUnexpected)
shutdown()
case Failure(error) =>
log.error(error, "Couldn't get elevation")
shutdown()
}
def shutdown(): Unit = {
IO(Http).ask(Http.CloseAll)(1.second).await
system.shutdown()
}
}
I don't really use argonaut, I use play json with spray. But at a glance it seems like there needs to be an argonaut support trait/import pulled in for your implicit codecs to convert to spray's unmarshaller (similar thing is required for play json).
https://github.com/dwhjames/argonaut-spray
this library seems to be what you want. Your implicits and imports look fine, pulling in the library should solve your problem.