All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.ensime.server.WebSocketBoilerplate.scala Maven / Gradle / Ivy

// Copyright: 2010 - 2016 https://github.com/ensime/ensime-server/graphs
// Licence: http://www.gnu.org/licenses/gpl-3.0.en.html
package org.ensime.server

import akka.actor._
import scala.reflect.ClassTag
import spray.json._

import akka.stream._
import akka.stream.scaladsl._
import akka.http.scaladsl.server._
import akka.http.scaladsl.model.ws._

/**
 * This crazy amount of indecipherable boilerplate is how we turn a
 * simple Actor into a Flow, without any backpressure, specifically
 * for use in akka-http WebSocket (but could be used elsewhere with
 * caution).
 *
 * If you want backpressure, you need to implement `Publisher` /
 * `Subscriber` or wait for:
 *
 *  - Source backpressure: https://github.com/akka/akka/issues/17610
 *  - Sink backpressure: https://github.com/akka/akka/issues/17967
 *
 * and then add appropriate Akka Streams buffering.
 *
 * Some more things that you might want:
 *
 *  - marshalling https://github.com/akka/akka/issues/17969
 *  - ping rate: https://github.com/akka/akka/issues/17968
 *  - compression: https://github.com/akka/akka/issues/17725
 *  - testing https://github.com/akka/akka/issues/17914
 *
 * And also note that WebSockets would almost certainly be better
 * modelled as a BidiFlow:
 *
 *  - https://github.com/akka/akka/issues/18008
 *
 * Some basic docs:
 *  http://doc.akka.io/docs/akka-stream-and-http-experimental/1.0-RC4/scala/http/routing-dsl/websocket-support.html
 */
object WebSocketBoilerplate {
  import Directives._

  /**
   * @param actor see `actorRefAsFlow`
   * @return a `Flow` suitable for use in a `Route`.
   */
  def jsonWebsocket[Incoming, Outgoing](
    actor: ActorRef => ActorRef
  )(
    implicit
    m1: RootJsonFormat[Incoming],
    m2: RootJsonFormat[Outgoing],
    mat: Materializer,
    oc: ClassTag[Outgoing],
    printer: JsonPrinter = PrettyPrinter
  ): Route = {
    val underlying = actorRefAsFlow[Incoming, Outgoing](actor)
    val marshalled = jsonMarshalledMessageFlow(underlying)
    handleWebsocketMessages(marshalled)
  }

  /**
   * @param actor constructor for an actor that accepts `Incoming` messages and
   *              sends (potentially async) `Outgoing` messages to
   *              `target` (the parameter).
   * @return the actor represented as a `Flow`, suitable for use in akka-stream
   *         with caution.
   */
  def actorRefAsFlow[Incoming, Outgoing](
    actor: ActorRef => ActorRef
  )(
    implicit
    mat: Materializer
  ): Flow[Incoming, Outgoing, Unit] = {
    val (target, pub) = Source.actorRef[Outgoing](
      0, OverflowStrategy.fail
    ).toMat(Sink.asPublisher(false))(Keep.both).run()
    val source = Source.fromPublisher(pub)

    val handler = actor(target)
    val sink = Sink.actorRef[Incoming](handler, PoisonPill)

    Flow.fromSinkAndSourceMat(sink, source)((_, _) => ())
  }

  /**
   * @param flow using user domain objects
   * @return a `Flow` using WebSocket `Message`s
   */
  def jsonMarshalledMessageFlow[Incoming, Outgoing](
    flow: Flow[Incoming, Outgoing, Unit]
  )(
    implicit
    m1: RootJsonFormat[Incoming],
    m2: RootJsonFormat[Outgoing],
    //mat: Materializer,
    oc: ClassTag[Outgoing],
    printer: JsonPrinter = PrettyPrinter
  ): Flow[Message, Message, Unit] = {
    Flow[Message].collect {
      case TextMessage.Strict(msg) =>
        msg.parseJson.convertTo[Incoming]
      case _ =>
        throw new IllegalArgumentException("not a valid message")
    }.via(flow).map {
      case e: Outgoing =>
        TextMessage.Strict(e.toJson.toString(printer)): Message
    }
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy