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.
Related
The code below does not compile, it says that ActorMaterializer is missing an implicit ActorRefFactory. How should I provide one?
val guardian: Behavior[Done] = Behaviors.setup(_ => {
Behaviors.receiveMessage{
case Done => Behaviors.stopped
}
})
implicit val sys = ActorSystem(guardian, "sys")
implicit val materializer: Materializer = ActorMaterializer()
Previous answers are halfways to the indended new API.
The typed ActorSystem can be implicitly transformed to provide the system materialiser as well, so just having it available as an implicit should be enough.
For example:
Behaviors.setup { ctx =>
implicit val system = ctx.system
Source(1 to 10).runForeach(println)
...
}
The inner working of this is an implicit conversion available from the Materializer companion object that extracts the system materializer of a ClassicActorSystemProvider into a Materializer. Both the typed and the classic ActorSystem implements ClassicActorSystemProvider.
Akka Streams at this point requires a "classic" (untyped) ActorSystem which can be implicitly converted into a materializer.
So if materializing a stream inside an Akka Typed Behavior, one would
implicit val materializer = context.classicActorContext.system
And if materializing a stream outside of an actor but where you have a typed ActorSystem:
implicit val materializer = typedActorSystem.classicSystem
As mentioned by #johanandren, one can also put the Typed ActorSystem in the implicit scope, which will allow the implicit conversion to Materializer to take effect.
implicit val system = context.system
The Akka documentation for 2.6.x indicates that ActorMaterializer is deprectated: "Use the system wide materializer or Materializer.apply(actorContext) with stream attributes or configuration settings to change defaults."
Use this instead:
import akka.Done
import akka.actor.ActorSystem
import akka.actor.typed.Behavior
import akka.actor.typed.scaladsl.Behaviors
import akka.stream.Materializer
object Main {
def main(Args: Array[String]) : Unit = {
val guardian: Behavior[Done] = Behaviors.setup(_ => {
Behaviors.receiveMessage{
case Done => Behaviors.stopped
}
})
val as = ActorSystem()
val materializer = Materializer(as)
}
}
I am observing a strange behavior. In on of my test cases, I am using contentAsJson. In that test case, the compiler is not complaining that I have to provide an implicit value for Timeout and Materializer
class UserControllerUnitSpec extends PlaySpec with BeforeAndAfterAll with BeforeAndAfterEach with OneAppPerSuiteWithComponents{
..
"User signup request with body but with incorrect profile data " should {
"return error message " in {
...val resultFuture: Future[Result] = testEnv.controller.signupUser(request)
val responseBodyAsJsValue: JsValue = contentAsJson(resultFuture)//works
...
}
}
But in another test case, the compiler gives error that I need to provide the value
class QuestionsControllerUnitSpec extends PlaySpec with BeforeAndAfterAll with BeforeAndAfterEach with OneAppPerSuiteWithComponents{
...
"newQuestion" should {
"should return error if the size of the body in the request is more than the maximum allowed size" in {
...
val response:Accumulator[ByteString,Result] = questionController.newQuestion(request)
val responseBody = contentAsJson(response)//(Timeout(Duration(5000,"millis")),testEnv.testEnv.mat).
...
}
I get error
Error:(1485, 39) could not find implicit value for parameter mat: akka.stream.Materializer
val responseBody = contentAsJson(response)//(Timeout(Duration(5000,"millis")),testEnv.testEnv.mat)
How can I debug why one is working but the other isn't?
UPDATE - added return types after Mario's answer.
Try providing implicit Materializer like so
import play.api.test.Helpers._
implicit val actorSystem = ActorSystem("test")
implicit val materializer = ActorMaterializer()
val responseBody = contentAsJson(response)
instead of explicit testEnv.testEnv.mat
contentAsJson(response)(Timeout(Duration(5000,"millis")),testEnv.testEnv.mat)
Regarding the difference between the two tests, note there are two overloaded versions of contentAsJson
def contentAsJson(of: Future[Result])(implicit timeout: Timeout, mat: Materializer = NoMaterializer): JsValue
def contentAsJson(of: Accumulator[ByteString, Result])(implicit timeout: Timeout, mat: Materializer): JsValue
where in the first case we see default Materializer argument is provided
mat: Materializer = NoMaterializer
while in the second case we have to provided our own. Therefore it is likely that in the first test the type of resultFuture is Future[Result] whilst in the second test the return type of response is Accumulator.
Regarding finding out where the implicit is provided from, personally I use IntelliJ's View | Show Implicit Hints feature.
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 a class A(httpClient: HttpExt)(implicit val system: ActorSystem, materializer: ActorMaterializer) that has a method called extractInfo (res: Future[HttpResponse]): Int that takes a future response and extracts an int value from the response entity.
I want to write a unit test for this method and I have the following in my unit spec class:
class ASpec extends UnitSpec with MockFactory {
"An A" - {
implicit val as = mock[ActorSystem]
implicit val mat = mock[ActorMaterializer]
val stubHttpClient = stub[HttpExt]
val a = new A(stubHttpClient)
"return auth token from response" in {
val futureResponse: Future[HttpResponse] = <code that returns a Future[HttpResponse]>
val info: Future[Option[Int]] = A.extractInfo(futureResponse)
val result = Await.result(info, 10.seconds)
result shouldBe Some(<a int value>)
}
}
}
However, on the line mock[ActorMaterializer], I got the following compilation error:
Error:(19, 28) object creation impossible, since:
it has 3 unimplemented members.
/** As seen from <$anon: akka.stream.ActorMaterializer>, the missing .
signatures are as follows.
* For convenience, these are usable as stub implementations.
*/
private[package akka] def actorOf(context: akka.stream.MaterializationContext,props: akka.actor.Props): akka.actor.ActorRef = ???
private[package akka] def logger: akka.event.LoggingAdapter = ???
private[package akka] def supervisor: akka.actor.ActorRef = ???
implicit val mat = mock[ActorMaterializer]
I would love some suggestions on how to solve this problem. Thank you enormously in advance!
I would suggest not mocking the ActorMaterializer but instead using a default implementation for example ActorMaterializer() or the one you use for your production code. At least from your example it doesn't look that you are making assertions on the ActorMaterializer itself.
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).