Мне нужна помощь с Play Framework. У меня есть этот веб-сокет, который немедленно отвечает сообщением клиенту. Мой API должен иметь возможность получать сообщения от другого API, который отправляет их через запрос POST.
Он должен следовать такой логике:
Есть ли способ передать данные из почтового запроса в веб-сокет, чтобы он отправил их клиенту?
Я пробовал только тот же веб-сокет, но не смог зайти слишком далеко. Я оставил так, как написано в документации. На данный момент он отвечает только «Я получил ваше сообщение» клиенту, отправившему это сообщение.
Я оставлю здесь рассматриваемый код, функция post имеет комментарий «// подключаем WebSocket». Это то место, где мне нужно отправить это сообщение на выход WebSocket, чтобы оно достигло клиента в режиме реального времени. Я читал документацию много раз, но она не особо помогла. Ему не хватает многого.
(Я не думаю, что это должно работать так, но это то, что приказал мой начальник... По этой причине я не пробовал отправлять другому клиенту через тот же WebSocket)
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)
}
}
Я только что извлек часть кода из MarkCLewis — Play-Videos . Также есть плейлист на YouTube Play Framework с использованием Scala, в котором вы узнаете, как создать чат с использованием веб-сокетов.
Это всего лишь базовый и действительно глупый пример. В реальном проекте с большим трафиком это не сработает, но я надеюсь, что это вам поможет.
Не уверен, насколько хорошо вы знакомы с моделью актера и акка / пекко . Play Framework построен на основе play akka или play pekko в зависимости от версии Play.
Вы можете представить актера как очередь сообщений, которая будет последовательно обрабатывать каждое сообщение. Вы не можете создать актера как новый экземпляр объекта, вам нужен ActorSystem
, который позволит вам создавать актеров. Каждый раз, когда вы создаете новый объект с помощью ActorSystem, вы получаете ссылку на него. Единственный способ отправлять сообщения актеру — использовать эту ссылку.
val dummyActor = system.actorOf(Props[DummyActor], "unique-name-of-actor")
dummyActor ! SomeMessage(param1, param2, param3)
WebSocket
— это актер, который может получать сообщение от клиента и отправлять сообщение клиенту, открывшему соединение через веб-сокет. В примере называется ChatActor
. Нам понадобится второй актер (с именем ChatManager
), который позволит нам отправлять сообщения ChatActor
, используя его ссылку.
Нечего объяснять. Две конечные точки
/
: получить запрос GET и отправить сообщение на открытые веб-сокеты./ws
: конечная точка для открытия соединения через веб-сокет.GET / HelloController.index
GET /ws HelloController.socket
Просто базовый контроллер с двумя методами и одним полем.
manager
: ChatManager
, созданный с использованием ActorSystem
, предоставленного игрой.
index
: как только получен HTTP-запрос, мы отправляем сообщение ChatManager
, который отправит сообщение каждому ChatActor
, имеющему открытое соединение через веб-сокет.
socket
: обработчик открытия соединения с веб-скокетом. Вы также можете видеть, что ChatActor
получает параметр ActorManager
by в своем конструкторе.
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)
}
}
}
Как видите, как только актер создан, в первой строке мы отправили сообщение manager
о том, что у нас есть новый чат. В методе receive
мы рассматриваем два случая
String
мы отправили сообщение менеджеруSendMessage
мы просто отправили сообщение через веб-сокетimport 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)
}
У нас есть изменяемый список с именем chatters
, который будет содержать все открытые соединения webscoket. Тогда рассмотрим два случая
NewChatter
, мы просто добавляем новый элемент в списокMessage
получен, мы перебираем список chatters
и отправляем Message
каждому из нихimport 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)
}
Здесь сложно дать развернутый ответ. Stackoverflow предназначен для конкретных вопросов с конкретными ответами. Я бы посоветовал задать вопрос на другой платформе, например reddit - scala , или вы можете попробовать какое-нибудь из сообществ Discord
Большое спасибо за ваш ответ, он определенно решил эту проблему для меня! Честно говоря, я не знаком с Пекко, моя компания начала мигрировать с NodeJS на playframework пару недель назад. Мне нужно задать пару вопросов: во-первых, как вы упомянули, это не подойдет для среднего или большого проекта. Есть ли какое-либо решение, которое мне следует иметь в виду, чтобы позже сделать его пригодным для более крупного проекта? И во-вторых, есть ли реальная разница между ActorSystems Pekko и Akka? Я знаю, что это форк, но отсутствие документации Pekko затрудняет изучение. Приветствую вас и еще раз спасибо!