I'm trying to create a websocket using Play ScalaWebsockets. I referred to their documentation which is located here. And as of the moment, I am using these SBT plugins which is required to use Play's websockets.
"com.typesafe.akka" %% "akka-actor" % "2.4.7",
"com.typesafe.akka" %% "akka-testkit" % "2.4.7" % "test",
This is my Actor:
package actors.chat
import akka.actor._
import akka.stream._
object ChatSocketActor {
def props(out: ActorRef) = Props(new ChatSocketActor(out))
}
class ChatSocketActor(out: ActorRef) extends Actor {
def receive = {
case msg: String =>
out ! ("I received your message: " + msg)
}
}
In my Controller, I have this socket function:
#Singleton
class ChatController #Inject()(
val messagesApi: MessagesApi,
val pageMetaApi: PageMetaApi,
implicit val materializer: Materializer,
implicit val system: ActorSystem,
implicit val wja: WebJarAssets
) extends Controller with I18nSupport with PageMetaSupport {
import actors.chat.ChatSocketActor
def index = Action { implicit request =>
Ok(views.html.chat.index());
}
def socket = WebSocket.accept[String, String] { request =>
ActorFlow.actorRef(out => ChatSocketActor.props(out))
}
}
After I compile and refresh the browser, I get this 426 response.
Upgrade to WebSocket required
Is there something I missed?
PROBLEM SOLVED:
I'm new to websockets. Problem was I was accessing the socket on a standard HTTP. I changed my routes to
# Chat
GET /chat controllers.ChatController.index
GET /websocket controllers.ChatController.socket
And a JS script in the /chat page which calls the websocket connection.
var ws = new WebSocket("ws://127.0.0.1:9000/websocket");
console.log(ws)
Related
I am trying to write a simple integration test for a scala application that uses the AKKA framework.
I want to
have the application start on my local host
write test cases that hit the application directly
I have done similar things things using springboottest but i can't seem to find anything remotely similar to this. I have been trying to read up on testkit routes and what not but it seems more like a unit test then it is a full application integration testing.
Any pointers or recommendations on what I am looking for would be great.
Thanks!
First import
val akkaVersion = "2.6.10"
libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-actor" % akkaVersion,
"com.typesafe.akka" %% "akka-testkit" % akkaVersion)
Create the actors
import akka.actor.{Actor, ActorSystem, Props}
object ActorsIntro extends App {
run()
def run() = {
val actorSystem = ActorSystem("ActorsIntro")
println(actorSystem.name)
val worldCounter = actorSystem.actorOf(Props[WordCountActor], "WordCounter")
val anotherWorldCounter = actorSystem.actorOf(Props[WordCountActor], "AnotherWordCounter")
worldCounter ! "I am reviewing Akka using Scala and it is pretty damn awesome !"
worldCounter ! "asynchronous message Akka Scala"
anotherWorldCounter ! "asynchronous message Akka Scala"
val person = actorSystem.actorOf(Person.props("Bob"))
person ! "hi"
}
class WordCountActor extends Actor {
var totalWords = 0
override def receive: PartialFunction[Any, Unit] = {
case message: String =>
println(s"Message received[ $message ]")
totalWords += message.split(" ").length
println(s"Total words counted: $totalWords")
case msg => println(s"word count. I cannot understand ${msg.toString}")
}
}
object Person {
def props(name: String) = Props(new Person(name))
val propsPersonActor = {
Props(new Person(""))
}
}
class Person(name: String) extends Actor {
override def receive: Receive = {
case "hi" =>
val reply = s"Hi, my name is $name"
println(reply)
sender() ! reply
case message => sender() ! message
}
}
}
And its test case
import akka.actor.ActorSystem
import akka.testkit.{ImplicitSender, TestKit}
import org.scalatest.BeforeAndAfterAll
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike
class ActorsIntroTest extends TestKit(ActorSystem("ActorsIntroSpec"))
with ImplicitSender
with AnyWordSpecLike
with Matchers
with BeforeAndAfterAll {
override def afterAll(): Unit = {
TestKit.shutdownActorSystem(system)
}
"The ActorsIntro actor" should {
"send back hi replay" in {
val name = "Bob"
val actorPerson = system.actorOf(ActorsIntro.Person.props(name))
val hi = "hi"
val hiReply = s"Hi, my name is $name"
actorPerson ! hi
expectMsg(hiReply)
}
"or send back the same message" in {
val name = "Bob"
val actorPerson = system.actorOf(ActorsIntro.Person.props(name))
val message = "hello, test"
actorPerson ! message
expectMsg(message)
}
}
}
If you want the application to run on localhost, I recommend you consider using akka http, so that you can bind a localhost server and test your app.
Akka Testkit AutoPilot documentation examples show that we can send messages to a TestProbe right after invoking setAutoPilot:
probe.setAutoPilot(new TestActor.AutoPilot {
def run(sender: ActorRef, msg: Any): TestActor.AutoPilot =
msg match {
case "stop" ⇒ TestActor.NoAutoPilot
case x ⇒ testActor.tell(x, sender); TestActor.KeepRunning
}
})
//#autopilot
probe.ref ! "hallo"
On the other handsetAutoPilot has been implemented as sending a message to the testActor:
def setAutoPilot(pilot: TestActor.AutoPilot): Unit = testActor ! TestActor.SetAutoPilot(pilot)
According to Akka message receive order guarantees, there is no way for testActor (probe.ref) to receive "hallo" before TestActor.SetAutoPilot(pilot) because both are being sent from the same origin.
However, if we used a third actor (created using system.actorOf(...)) to send a "hello" to probe.ref,
wouldn't it be possible that, under some circumstances, it got received by probe.ref before TestActor.SetAutoPilot(pilot) thus ending up being ignored?
In theory - yes, definitely - and that's basically your own answer to the question. In practice, this is unlikely, since the other message travels the longer path, so something very unusual needs to happen so that it arrives earlier.
Since theorizing around this wouldn't give any actionable answer, I've written a test to have an empirical observation:
Build.sbt:
libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-actor" % "2.5.17"
)
libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-testkit" % "2.5.17",
"org.scalactic" %% "scalactic" % "3.0.5",
"org.scalatest" %% "scalatest" % "3.0.5",
"org.scalacheck" %% "scalacheck" % "1.14.0"
) map (_ % "test")
Test:
import scala.concurrent.duration.DurationInt
import akka.actor.{Actor, ActorRef, ActorSystem, Props}
import akka.pattern.ask
import akka.testkit.{TestActor, TestKit, TestProbe}
import akka.util.Timeout
import org.scalatest.{Matchers, PropSpecLike}
import org.scalatest.concurrent.ScalaFutures
import org.scalatest.prop.PropertyChecks
class AutopilotTest extends TestKit(ActorSystem("test"))
with PropSpecLike with PropertyChecks with ScalaFutures with Matchers {
private implicit val askTimeout: Timeout = Timeout(100.millis)
property("Test probe receives autopilot before any other message from same origin") {
forAll(minSuccessful(1000)) { msg: String =>
val probe = TestProbe()
probe.setAutoPilot((sender: ActorRef, msg: Any) => msg match {
case x => sender ! x; TestActor.KeepRunning
})
whenReady((probe.ref ? msg).mapTo[String]) {_ shouldBe msg}
}
}
private class ProxyActor(target: ActorRef) extends Actor {
override def receive: Receive = { case msg: Any => target forward msg }
}
private object ProxyActor { def props(target: ActorRef): Props = Props(new ProxyActor(target)) }
property("Test probe receives autopilot before any other message from other origin") {
// set minSuccessuful to as high as you want, but note that current version takes ~38 seconds on my laptop to run
forAll(minSuccessful(1000)) { msg: String =>
val probe = TestProbe()
val proxy = system.actorOf(ProxyActor.props(probe.ref))
val result = (proxy ? msg).mapTo[String]
probe.setAutoPilot((sender: ActorRef, msg: Any) => msg match {
case x => sender ! x; TestActor.KeepRunning
})
whenReady(result) {_ shouldBe msg}
}
}
}
Practically, I went up all they way to 10000 repeats for the second test, and the tests always passed - also I've made sure it fails if autopilot is set after sending a message to proxy, or if testProbe does not respond.
So, I would say the potential issue you're describing either does not happen at all, or is highly unlikely. This test is a very simplistic one of course, so under other conditions (i.e. blocking, parallel test execution, CI, etc.) observations might be different, but at least it provides some evidence for a most common/simple case.
first of all thank you all for taking some time to review my problem. I'm a newbie in scala ecosystem so I think I'm confussing some concepts.
I'm introducing Guice in a Play 2.4 project and it's working in some REST controllers. I've edited build.sbt to append routesGenerator := InjectedRoutesGenerator as Play official doc recomends and edited my routes as you can see in the following example:
GET /socket #com.letgo.chat.controllers.ChatController.socket
My module for injecting actors looks like:
class ChatModule extends AbstractModule with ScalaModule with AkkaGuiceSupport with GuiceAkkaActorRefProvider {
override def configure() {
bindActor[TalkerProviderActor](TalkerProviderActor.name)
...
And all of this seems to be working.
But another endpoint handles a websocket with Play method WebSocket.acceptWithActor. I need to create an actor which also needs some dependencies.
The controller creates a ConnectionActor:
class ConnectionActor(
#Assisted() websocket: ActorRef,
monitoring: Monitoring,
#Named(BouncerActor.name) bouncer: ActorRef,
) extends Actor
...
class ChatController #Inject() extends Controller {
def socket(): WebSocket[String, JsValue] = WebSocket.acceptWithActor[String, JsValue] { request => out =>
// I had the following statement in order to build the actor before introducing Guice:
// ConnectionActor.props()
// Now, I need some magic here
}
}
So as you can see I need a websocket: ActorRef for the websocket output.
I've created some actors in other parts of the application using bindActorFactory method provided by AkkaGuiceSupport:
bindActorFactory[TalkerActor, TalkerActor.Factory]
But I don't know how I should create an actor for handling the websocket in this case. Can you guys help me?
Thank you
I believe this can do the trick:
package controllers
import javax.inject._
import akka.actor.{Actor, ActorRef, ActorSystem, Props}
import akka.stream.Materializer
import play.api.libs.streams.ActorFlow
import play.api.libs.ws.WSClient
import play.api.mvc._
import scala.concurrent.ExecutionContext.Implicits.global
#Singleton
class SocketActorProvider #Inject() (ws: WSClient) {
def get(out: ActorRef) = Props(new SocketActor(out, ws))
}
class SocketActor(out: ActorRef, ws: WSClient) extends Actor {
override def receive: Receive = {
case "ping" => ws.url("http://localhost:9000/pong").get().foreach(pong => out ! pong.body)
}
}
#Singleton
class HomeController #Inject() (implicit system: ActorSystem, ws: WSClient, materializer: Materializer, provider: SocketActorProvider) extends Controller {
// route '/ws'
def socket = WebSocket.accept[String, String] { request =>
ActorFlow.actorRef(out => provider.get(out))
}
// route '/pong'
def pong = Action {
Ok("PONG!")
}
// route '/'
def index = Action {
Ok("""
<script>
var ws = new WebSocket('ws://localhost:9000/ws');
ws.onopen = () => console.log('I am open!');
ws.onmessage = m => console.log(m.data);
setInterval(() => {console.log('ping'); ws.send('ping')}, 1000)
</script>
""").as("text/html")
}
}
As you can see there is a provider injected by Guice which had WSClient injected and when building an actor it passes in the dependency
I am new to scala, so I am quite prepared to accept that I am doing something wrong!
I am playing around with Akka, and have a test using scalatest and the akka-testkit. Here is my build.sbt config
name := """EventHub"""
version := "1.0"
scalaVersion := "2.10.3"
libraryDependencies ++= Seq(
"com.typesafe.akka" % "akka-actor_2.10" % "2.2.3",
"com.typesafe.akka" % "akka-testKit_2.10" % "2.2.3" % "test",
"org.scalatest" % "scalatest_2.10.0-M4" % "1.9-2.10.0-M4-B2" % "test",
"com.ning" % "async-http-client" % "1.8.1"
)
When I compile, I get a message that I don't understand. I have google for this and have found related scala compiler issues and bugs. I have no idea if that is what I am seeing or if I am making a basic mistake somewhere. Here is a summary of the output (I have removed alot of "noise" for brevity; can add more detail if required!):
scalac:
while compiling: /Users/robert/Documents/Programming/Scala/Projects/EventHub/src/test/scala/Hub/Subscription/SubscriberSpec.scala
during phase: typer
library version: version 2.10.3
compiler version: version 2.10.3
...
...
== Expanded type of tree ==
TypeRef(
TypeSymbol(
class SubscriberSpec extends TestKit with WordSpec with BeforeAndAfterAll with ImplicitSender
)
)
uncaught exception during compilation: scala.reflect.internal.FatalError
And:
scalac: Error: package api does not have a member materializeWeakTypeTag
scala.reflect.internal.FatalError: package api does not have a member materializeWeakTypeTag
at scala.reflect.internal.Definitions$DefinitionsClass.scala$reflect$internal$Definitions$DefinitionsClass$$fatalMissingSymbol(Definitions.scala:1037)
at scala.reflect.internal.Definitions$DefinitionsClass.getMember(Definitions.scala:1055)
at scala.reflect.internal.Definitions$DefinitionsClass.getMemberMethod(Definitions.scala:1090)
at scala.reflect.internal.Definitions$DefinitionsClass.materializeWeakTypeTag(Definitions.scala:518)
at scala.tools.reflect.FastTrack$class.fastTrack(FastTrack.scala:34)
at scala.tools.nsc.Global$$anon$1.fastTrack$lzycompute(Global.scala:493)
at scala.tools.nsc.Global$$anon$1.fastTrack(Global.scala:493)
at scala.tools.nsc.typechecker.Namers$Namer.methodSig(Namers.scala:1144)
at scala.tools.nsc.typechecker.Namers$Namer.getSig$1(Namers.scala:1454)
at scala.tools.nsc.typechecker.Namers$Namer.typeSig(Namers.scala:1466)
at scala.tools.nsc.typechecker.Namers$Namer$$anonfun$monoTypeCompleter$1$$anonfun$apply$1.apply$mcV$sp(Namers.scala:731)
at scala.tools.nsc.typechecker.Namers$Namer$$anonfun$monoTypeCompleter$1$$anonfun$apply$1.apply(Namers.scala:730)
at scala.tools.nsc.typechecker.Namers$Namer$$anonfun$monoTypeCompleter$1$$anonfun$apply$1.apply(Namers.scala:730)
at scala.tools.nsc.typechecker.Namers$Namer.scala$tools$nsc$typechecker$Namers$Namer$$logAndValidate(Namers.scala:1499)
at scala.tools.nsc.typechecker.Namers$Namer$$anonfun$monoTypeCompleter$1.apply(Namers.scala:730)
at scala.tools.nsc.typechecker.Namers$Namer$$anonfun$monoTypeCompleter$1.apply(Namers.scala:729)
at scala.tools.nsc.typechecker.Namers$$anon$1.completeImpl(Namers.scala:1614)
...
...
I am using IntelliJ as the ide. There are a couple scala files; one contains an actor, the other a webclient:
package Hub.Subscription
import scala.concurrent.{Promise, Future}
import com.ning.http.client.{AsyncCompletionHandler, AsyncHttpClient, Response}
trait WebClient {
def postUpdate(url: String, payload: Any, topic: String): Future[Int]
def postUnSubscribe(url: String, topic: String): Future[Int]
}
case class PostUpdateFailed(status: Int) extends RuntimeException
object AsyncWebClient extends WebClient{
private val client = new AsyncHttpClient
override def postUpdate(url: String, payload: Any, topic: String): Future[Int] = {
val request = client.preparePost(url).build()
val result = Promise[Int]()
client.executeRequest(request, new AsyncCompletionHandler[Response]() {
override def onCompleted(response: Response) = {
if (response.getStatusCode / 100 < 4)
result.success(response.getStatusCode)
else
result.failure(PostUpdateFailed(response.getStatusCode))
response
}
override def onThrowable(t: Throwable) {
result.failure(t)
}
})
result.future
}
override def postUnSubscribe(url: String, topic: String): Future[Int] = {
val request = client.preparePost(url).build()
val result = Promise[Int]
client.executeRequest(request, new AsyncCompletionHandler[Response] {
override def onCompleted(response: Response) = {
if (response.getStatusCode / 100 < 4)
result.success(response.getStatusCode)
else
result.failure(PostUpdateFailed(response.getStatusCode))
response
}
override def onThrowable(t: Throwable) {
result.failure(t)
}
})
result.future
}
def shutdown(): Unit = client.close()
}
And my actor:
package Hub.Subscription
import akka.actor.Actor
import Hub.Subscription.Subscriber.{Failed, Update, UnSubscribe}
import scala.concurrent.ExecutionContext
import java.util.concurrent.Executor
object Subscriber {
object UnSubscribe
case class Update(payload: Any)
case class Failed(callbackUrl: String)
}
class Subscriber(callbackUrl: String, unSubscribeUrl: String, topic: String) extends Actor{
implicit val executor = context.dispatcher.asInstanceOf[Executor with ExecutionContext]
def client: WebClient = AsyncWebClient
def receive = {
case Update(payload) => doUpdate(payload)
case UnSubscribe => doUnSubscribe
case Failed(clientUrl) => //log?
}
def doUpdate(payload: Any): Unit = {
val future = client.postUpdate(callbackUrl, payload, topic)
future onFailure {
case err: Throwable => sender ! Failed(callbackUrl)
}
}
def doUnSubscribe: Unit = {
//tell the client that they have been un-subscribed
val future = client.postUnSubscribe(unSubscribeUrl, topic)
future onFailure {
case err: Throwable => //log
}
}
}
And finally my test spec:
package Hub.Subscription
import akka.testkit.{ImplicitSender, TestKit}
import akka.actor.{ActorRef, Props, ActorSystem}
import org.scalatest.{WordSpec, BeforeAndAfterAll}
import scala.concurrent.Future
import scala.concurrent.duration._
object SubscriberSpec {
def buildTestSubscriber(url: String, unSubscribeUrl: String, topic: String, webClient: WebClient): Props =
Props(new Subscriber(url, unSubscribeUrl, topic) {
override def client = webClient
})
object FakeWebClient extends WebClient {
override def postUpdate(url: String, payload: Any, topic: String): Future[Int] = Future.successful(201)
override def postUnSubscribe(url: String, topic: String): Future[Int] = Future.failed(PostUpdateFailed(500))
}
}
class SubscriberSpec extends TestKit(ActorSystem("SubscriberSpec"))
with WordSpec
with BeforeAndAfterAll
with ImplicitSender {
import SubscriberSpec._
"A subscriber" must {
"forward the update to the callback url" in {
val fakeClient = FakeWebClient
val callbackUrl = "http://localhost:9000/UserEvents"
val subscriber: ActorRef = system.actorOf(buildTestSubscriber(callbackUrl, "unSubscribeUrl", "aTopic", fakeClient))
subscriber ! Subscriber.Update(Nil)
within(200 millis) {
expectNoMsg
}
}
}
override def afterAll(): Unit = {
system.shutdown()
}
}
Thanks in advance for any help / pointers!
Update: I should have noted that if I do not include the test spec, then all is well. But when I add the test spec, I get the errors above.
Btw I just realized that you're using scalatest compiled for 2.10.0-M4.
Scala's final releases aren't supposed to be binary compatible with corresponding milestone releases, so weird things might happen, including crashes.
If you change scalatest's version to "org.scalatest" %% "scalatest" % "1.9.1", everything is going to work just fine.
For learning purposes I'm trying to implement a simple play application that gets data from a remote actor. The code for the actor is as follows:
import akka.actor.{Props, ActorSystem, Actor}
class NumbersServer extends Actor {
var number = 0
protected def receive = {
case 'next => {
number += 1
number
}
case 'reset => number = 0
case 'exit => context.stop(self)
case 'get => sender ! number
}
}
object Server {
def main(args: Array[String]) {
val system = ActorSystem("ServerSystem")
val server = system.actorOf(Props[NumbersServer], "server")
}
}
I package it into a jar and start it from the command line. If I try to send messages to this actor from a Scala console opened from another window, all works fine. Now I want to get the actor from the Play framework. In the Application object I define the following method:
def numbers = Action {
Ok(views.html.numbers(Client.actor.path.name))
}
Then in the models package I define the Client object:
object Client {
import play.api.Play.current
val actor = Akka.system.actorFor("akka://ServerSystem#127.0.0.1:2552/user/server")
}
The numbers.html.scala file:
#(message: String)
#main("Header") {
<h1>#message</h1>
}
So I expect that when I go to 127.0.0.1:9000/numbers, I'd get a page with the path to the server actor. Instead of this, I get <h1>deadLetters</h1>. What do I do wrong and how this should be done correctly?
Please follow the configuration given in
https://groups.google.com/forum/#!topic/akka-user/Vw-B8nQeagk
And also add akka-remote dependency
val appDependencies = Seq(
"com.typesafe.akka" % "akka-remote" % "2.0.2"
)