I need some help with the Play Framework. I have this Websocket that immediately responds with a message to a client. My API has to be able to receive messages from another API, that sends them via POST request.
It has to follow this logic:
Is there any way I could pass the data from the Post Request to the Websocket, so it sends it to the client?
I've only tried with the same web socket, but I couldn't make it too far. I left it the same way it is written in the documentation. As for now, it only responds with "I've received your message" to the client who sent that message.
I'll leave here the code in question, the post function has a comment that says "// connect the WebSocket". That's the place where I need to send that message to the Output of the WebSocket, so it reaches the client in real-time. I've read the documentation a lot of times but it's not really that helpful. It lacks a lot of things.
(I don't think this is the way it should work, but that's what my boss ordered... I haven't tried sending to another client via the same WebSocket due to that reason)
package controllers
import org.apache.pekko.actor._
import org.apache.pekko.stream.Materializer
import play.api.libs.streams.ActorFlow
import javax.inject._
import play.api.mvc._
@Singleton
class HomeController @Inject()(val controllerComponents: ControllerComponents) (implicit system: ActorSystem, mat: Materializer) extends BaseController {
def index(): Action[AnyContent] = Action { implicit request: Request[AnyContent] =>
Ok(views.html.index())
}
def getMessage: Action[AnyContent] = Action { request: Request[AnyContent] =>
println(request.body.toString)
//connect the websocket here
Ok("It works!")
}
def socket: WebSocket = WebSocket.accept[String, String] { request =>
ActorFlow.actorRef { out => MyWebSocketActor.props(out) }
}
}
object MyWebSocketActor {
def props(out: ActorRef): Props = Props(new MyWebSocketActor(out))
}
class MyWebSocketActor(out: ActorRef) extends Actor {
def receive: Receive = {
case msg: String =>
out ! ("I received your message: " + msg)
}
}
I just extracted part of the code from MarkCLewis - Play-Videos. There is also a youtube playlist Play Framework using Scala which teaches you how to build a chat room using websockets.
This is just a basic and really dummy example. It will not work in a real project with lot of traffic, but I hope it helps you.
Not sure how well you are familiarized with the actor model and akka/pekko. Play Framework is built on top of play akka or play pekko depends on the version of Play.
You can think of an actor as a message queue that will process each message sequentially. You can not create an actor as a new instance of an object, you need an ActorSystem
that let you spawns actors. Each time you create a new one using the ActorSystem, you get a reference to it. The only way to send messages to an actor is through that reference
val dummyActor = system.actorOf(Props[DummyActor], "unique-name-of-actor")
dummyActor ! SomeMessage(param1, param2, param3)
WebSocket
is an actor that can receive message from a client and send a message to the client that opened the websocket connection. In the example is called ChatActor
. We will need a second actor (named ChatManager
) that let us send messages to the ChatActor
using its reference
Not much to explain. Two endpoints
/
: receive a GET request and send a message to opened the websockets/ws
: the endpoint to open the websocket connectionGET / HelloController.index
GET /ws HelloController.socket
Just a basic controller that has two methods and one field
manager
: the ChatManager
created using the ActorSystem
provided by play
index
: once a http request is received, we send a message to the ChatManager
who will send a message to each ChatActor
that has an open websocket connection
socket
: the handler to open a webscoket connection. You can also see that the ChatActor
receives the ActorManager
by parameter in its constructor
import org.apache.pekko.actor.{ActorSystem, Props}
import org.apache.pekko.stream.Materializer
import play.api.libs.streams.ActorFlow
import play.api.mvc._
import javax.inject.Inject
class HelloController @Inject() (cc: ControllerComponents)(implicit
system: ActorSystem,
mat: Materializer
) extends AbstractController(cc) {
private val manager = system.actorOf(Props[ChatManager], "Manager")
def index: Action[AnyContent] =
Action { _ =>
// this is the place where we sent a message to the manager.
// Similar to what you need. You receive a POST (in this example
// is a GET) and you send a message to the client connected to the
// websocket
manager ! ChatManager.Message("a hello message sent from index")
Ok("hello")
}
def socket = WebSocket.accept[String, String] { _ =>
ActorFlow.actorRef { out =>
ChatActor.props(out, manager)
}
}
}
As you can see, once the actor is created, in the first line we sent a message to the manager
saying that we have a new chatter. In the receive
method we consider two cases
String
, we sent a message to the managerSendMessage
received, we just sent the message through the websocketimport org.apache.pekko.actor.{Actor, ActorRef, Props}
class ChatActor(out: ActorRef, manager: ActorRef) extends Actor {
manager ! ChatManager.NewChatter(self)
import ChatActor._
def receive = {
case s: String => manager ! ChatManager.Message(s)
case SendMessage(msg) => out ! msg
}
}
object ChatActor {
def props(out: ActorRef, manager: ActorRef) = Props(
new ChatActor(out, manager)
)
case class SendMessage(msg: String)
}
We have a mutable list named chatters
that will contain all the open webscoket connections. Then we consider two cases
NewChatter
is receieved, we just add a new element to the listMessage
is received, we iterate the list of chatters
and send a Message
to each oneimport org.apache.pekko.actor.{Actor, ActorRef}
import scala.collection.mutable.ListBuffer
class ChatManager extends Actor {
private val chatters = ListBuffer.empty[ActorRef]
import ChatManager._
def receive = {
case NewChatter(chatter) => chatters += chatter
case Message(msg) => for (c <- chatters) c ! ChatActor.SendMessage(msg)
}
}
object ChatManager {
case class NewChatter(chatter: ActorRef)
case class Message(msg: String)
}