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

io.fmq.proxy.Proxy.scala Maven / Gradle / Ivy

The newest version!
package io.fmq
package proxy

import cats.data.{Kleisli, NonEmptyList}
import cats.effect.kernel.{Async, Outcome, Resource}
import cats.syntax.apply._
import cats.syntax.flatMap._
import cats.syntax.functor._
import io.fmq.poll.{ConsumerHandler, PollEntry, PollTimeout, Poller}
import io.fmq.socket.{BidirectionalSocket, ConsumerSocket, ProducerSocket}

import scala.concurrent.ExecutionContext

@SuppressWarnings(Array("org.wartremover.warts.Overloading"))
final class Proxy[F[_]: Async](ctx: Context[F]) {

  def unidirectional(frontend: ConsumerSocket[F], backend: ProducerSocket[F]): Resource[F, Proxy.Configured[F]] =
    unidirectional(frontend, backend, None)

  def unidirectional(
      frontend: ConsumerSocket[F],
      backend: ProducerSocket[F],
      control: Option[Control[F]]
  ): Resource[F, Proxy.Configured[F]] =
    for {
      poller <- ctx.createPoller
    } yield {
      val items = NonEmptyList.one(
        PollEntry.Read(frontend, forward(backend, control))
      )

      new Proxy.Configured[F](poller, items)
    }

  def bidirectional(frontend: BidirectionalSocket[F], backend: BidirectionalSocket[F]): Resource[F, Proxy.Configured[F]] =
    bidirectional(frontend, backend, None, None)

  def bidirectional(
      frontend: BidirectionalSocket[F],
      backend: BidirectionalSocket[F],
      controlIn: Option[Control[F]],
      controlOut: Option[Control[F]]
  ): Resource[F, Proxy.Configured[F]] =
    for {
      poller <- ctx.createPoller
    } yield {
      val items = NonEmptyList.of(
        PollEntry.Read(frontend, forward(backend, controlIn)),
        PollEntry.Read(backend, forward(frontend, controlOut))
      )

      new Proxy.Configured[F](poller, items)
    }

  private def forward(target: ProducerSocket[F], capture: Option[Control[F]]): ConsumerHandler[F] = {

    val withCapture: (ProducerSocket[F] => F[Unit]) => F[Unit] =
      capture match {
        case Some(c) => f => f(c.socket)
        case None    => _ => Async[F].unit
      }

    def send(message: Array[Byte], socket: ConsumerSocket[F]): F[Unit] =
      socket.hasReceiveMore.ifM(
        target.sendMore(message) *> withCapture(_.sendMore(message)) *> loop(socket),
        target.send(message) >> withCapture(_.send(message))
      )

    def loop(socket: ConsumerSocket[F]): F[Unit] =
      for {
        message <- socket.receive[Array[Byte]]
        _       <- send(message, socket)
      } yield ()

    Kleisli(from => loop(from))
  }

}

object Proxy {

  final class Configured[F[_]: Async] private[Proxy] (
      poller: Poller[F],
      items: NonEmptyList[PollEntry[F]]
  ) {

    def start(ec: ExecutionContext): Resource[F, F[Outcome[F, Throwable, Unit]]] =
      Async[F].backgroundOn(poller.poll(items, PollTimeout.Infinity).foreverM[Unit], ec)

  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy