Как отправить сообщение актеру через веб-сокет в Play! фреймворк?

Это довольно простой вопрос, но я не смог найти удовлетворительного ответа после нескольких часов поиска в Google. Из примера в здесь способ создания веб-сокета выглядит примерно так:

Код контроллера:

import play.api.mvc._
import play.api.libs.streams.ActorFlow
import javax.inject.Inject
import akka.actor.ActorSystem
import akka.stream.Materializer

class Application @Inject()(cc:ControllerComponents) (implicit system: ActorSystem, mat: Materializer) extends AbstractController(cc) {

  def socket = WebSocket.accept[String, String] { request =>
    ActorFlow.actorRef { out =>
      MyWebSocketActor.props(out)
    }
  }
}

Актерский код:

import akka.actor._

object MyWebSocketActor {
  def props(out: ActorRef) = Props(new MyWebSocketActor(out))
}

class MyWebSocketActor(out: ActorRef) extends Actor {
  def receive = {
    case msg: String =>
      out ! ("I received your message: " + msg)
  }
}

Но как именно отправить сообщение от контроллера актеру через веб-сокет? Скажем, в коде контроллера у меня есть код действия, который обрабатывает, когда кнопка нажата, он отправляет блок строки актеру. Как отправить эту строку указанному выше актеру из кода контроллера?

Удалось ли вам этого добиться? Я тоже столкнулся с той же проблемой.

Peter 05.11.2020 13:48

@ Питер, я не мог решить эту проблему. В итоге я просто использовал флягу с java-скриптом.

Algorithman 10.11.2020 22:42
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
2
844
4

Ответы 4

Я могу предоставить вам несколько примеров веб-сокетов в Play. По сути, они используют Flow (akka-streams) для обработки соединения через веб-сокет.

Есть официальный пример Play Websocket: Пример Lightbend Websocket

Исходя из этого, у меня есть несколько проектов, использующих веб-сокеты, например:

play-wsocket-scalajs

Это пример приложения, показывающий, как вы можете интегрировать проект Play с проектом Scala.js, Binding.scala - используя веб-сокеты.

Это довольно сложно, поэтому самый простой способ - проверить HomeController, UserParentActor, UserActor и АдаптерАктёр, как они работают вместе.

scala-адаптеры

Это фреймворк, основанный на приведенном выше примере, который также показывает, как регистрировать клиентов websocket.

Давайте сначала разберемся, что уже создано, а что нам нужно добавить. Тип socket - это WebSocket.

Этот WebSocket раскрывает единственный метод apply:

def apply(request: RequestHeader): Future[Either[Result, Flow[Message, Message, _]]]

Следовательно, пока вы не отправили сообщение, поток еще не создан. Теперь, когда сообщение отправлено, мы можем создать поток и отправить ему сообщение:

def index = Action.async(parse.json) { request =>
  socket(request).map {
    case Left(result) =>
      Ok("Done: Left: " + result.body)
    case Right(value) =>
      Source.single(TextMessage(Json.stringify(request.body))).via(value).to(Sink.ignore).run()
      Ok("Done: Right: ")
  }
}

Эти образец приложения и связанное обсуждение могут быть полезны. Вот фрагмент кода, вырезанный / обобщенный из связанного примера приложения:

Flow.futureFlow(futureUserActor.map { userActor =>
  val incomingMessages: Sink[Message, NotUsed] =
    Flow[Message]
      .map(...)
      .to(...)

  val outgoingMessages: Source[Message, NotUsed] =
    ActorSource
      .actorRef[User.OutgoingMessage](...)
      .mapMaterializedValue { outActor =>
        // give the user actor a way to send messages out
        userActor ! User.Connected(outActor)
        NotUsed
      }
      .map(...)

  // then combine both to a flow
  Flow.fromSinkAndSourceCoupled(incomingMessages, outgoingMessages)
})

Есть как минимум два подхода к этому:

  1. Настройте метод ActorFlow.actorRef play для возврата основного актера. Раньше был аналогичное обсуждение, вот суть. Если вы помещаете базовый субъект в карту (пользователь, веб-сокет), убедитесь, что используете поточно-ориентированную реализацию, такую ​​как TrieMap.
  2. То, что вы пытаетесь сделать, можно решить, создав шину событий и подписавшись на нее изнутри актера. Затем вы можете отфильтровать интересующие вас события и соответствующим образом отреагировать. Это решение лучше тем, что оно действительно масштабируется - у вас может быть более одной реплики вашего веб-приложения (1-й подход не будет работать в этом случае, потому что реплика, которая не содержит ссылку на пользовательский актор WS, может получить событие нажатия кнопки). В псевдокоде для иллюстрации идеи:
sealed trait AppEvent
final case class ButtonClicked(user: User.ID) extends AppEvent

// inside an action
system.eventStream.publish(ButtonClicked(request.identity.id))

// inside your actor
override def preStart =
  context.system.eventStream.subscribe(self, classOf[AppEvent])

Обратите внимание, что идея шины событий абстрактна. То, что я продемонстрировал выше, - это самый простой подход с использованием akka классический автобус событий, который работает локально. Для этого подхода к масштабированию вам потребуется реальная очередь сообщений за кулисами.

Другие вопросы по теме