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

io.reactors.protocol.server-protocols.scala Maven / Gradle / Ivy

The newest version!
package io.reactors
package protocol






/** Communication patterns based on request-reply.
 */
trait ServerProtocols {
  self: Patterns =>

  /** A server channel accepts tuples with the request event and channel to reply on.
   */
  type Server[T, S] = Channel[Server.Req[T, S]]

  /** Contains various options for tuning the server protocol.
   */
  object Server {
    type Req[T, S] = (T, Channel[S])
  }

  implicit class ServerChannelBuilderOps(val builder: ChannelBuilder) {
    /** Open a new server channel.
     *
     *  The returned connector only has the server type, but will not serve incoming
     *  requests - for this, `serve` must be called on the connector.
     */
    def server[T, S]: Connector[Server.Req[T, S]] = builder.open[Server.Req[T, S]]
  }

  implicit class ServerConnectorOps[T, S](val conn: Connector[Server.Req[T, S]]) {
    /** Installs a serving function to the specified connector.
     *
     *  Takes an optional stopping predicate that must return `true` for requests that
     *  seal the connector.
     */
    def serve(f: T => S)(
      implicit stop: T => Boolean = (req: T) => false
    ): Connector[Server.Req[T, S]] = {
      conn.events onMatch {
        case (x, ch) =>
          if (stop(x)) {
            conn.seal()
          } else {
            ch ! f(x)
          }
      }
      conn
    }
  }

  implicit class ServerSystemOps(val system: ReactorSystem) {
    /** Creates a server reactor.
     *
     *  This reactor always responds by mapping the request of type `T` into a response
     *  of type `S`, with the specified function `f`.
     *
     *  If the optional `stop` predicate returns `true` for some request, the reactor's
     *  main channel gets sealed.
     */
    def server[T, S](f: T => S)(
      implicit stop: T => Boolean = (req: T) => false
    ): Server[T, S] = {
      system.spawn(Reactor[Server.Req[T, S]](_.main.serve(f)))
    }

    /** Creates a server reactor that optionally responds to requests.
     *
     *  This reactor uses the function `f` to create a response of type `S` from the
     *  request type `T`. If the value obtained this way is not `nil`, the server
     *  responds.
     *
     *  If the optional `stop` predicate returns `true` for some request, the reactor's
     *  main channel gets sealed.
     */
    def maybeServer[T, S](f: T => S, nil: S)(
      implicit stop: T => Boolean = (req: T) => false
    ): Server[T, S] = {
      system.spawn(Reactor[Server.Req[T, S]] { self =>
        self.main.events onMatch {
          case (x, ch) =>
            if (stop(x)) {
              self.main.seal()
            } else {
              val resp = f(x)
              if (resp != nil) ch ! resp
            }
        }
      })
    }
  }

  implicit class ServerOps[T, @specialized(Int, Long, Double) S: Arrayable](
    val server: Server[T, S]
  ) {
    /** Request a single reply from the server channel.
     *
     *  Returns an `IVar` with the server response.
     *  If the server responds with multiple events, only the first event is returned.
     *
     *  @param x     request event
     *  @return      the single assignment variable with the reply
     */
    def ?(x: T): IVar[S] = {
      val connector = Reactor.self.system.channels.open[S]
      val result = connector.events.toIVar
      result.onDone(connector.seal())
      server ! (x, connector.channel)
      result
    }

    /** Send a request to the server, but ignore the response.
     *
     *  @param x     request event
     */
    def ?!(x: T): Unit = server ! (x, new Channel.Zero[S])
  }

  implicit class ServerStreamOps[T, @specialized(Int, Long, Double) S: Arrayable](
    val server: Server[(T, S), S]
  ) {
    /** Request a stream of replies from the server channel.
     *
     *  The stream is interrupted when the server sends a `term` event.
     *
     *  Server can reply with multiple events.
     *  There is no backpressure in this algorithm, so clients are responsible for
     *  ensuring that the server does not flood the client.
     *  Furthermote, the client can at any point unsubscribe from the event
     *  stream without notifying the server, so ensuring that the server sends a finite
     *  stream is strongly recommended.
     *
     *  @param x     request event
     *  @param term  termination event, server must use it to indicate the end of stream
     *  @return      a signal emitting the server replies
     */
    def streaming(x: T, term: S): Signal[S] = {
      val connector = Reactor.self.system.channels.open[S]
      val result = connector.events.takeWhile(_ != term).toEmpty
      result.onDone(connector.seal())
      server ! ((x, term), connector.channel)
      result.withSubscription(Subscription { connector.seal() })
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy