I'm just learning FP, so maybe i'm doing this completely wrong.
Using cats, cats-effects, fs2, fs2-io.
Following code takes takes port as an argument then creates Server Socket and Client Socket connected to 127.0.0.1 on given port.
What I need this function to return is allocated Server Socket port.
Question: how can I do Int => F[Int]
while running fs2.Stream under the method's hood?
I'm using cats-effects IOApp to run the program.
My code:
trait TCP[F[_]] {
def createTunnel(localPort: Int): F[TCP.CreatedTunnel]
}
object TCP {
implicit def apply[F[_]](implicit ev: TCP[F]): TCP[F] = ev
final case class CreatedTunnel(remotePort: Int) extends AnyVal
def impl[F[_]: Applicative : ContextShift : ConcurrentEffect](socketGroup: SocketGroup): TCP[F] = new TCP[F] {
override def createTunnel(localPort: Int): F[CreatedTunnel] = {
val localBindAddress = Deferred[F, InetSocketAddress]
val stream = Stream
.eval(socketGroup.serverWithLocalAddress[F](new InetSocketAddress(InetAddress.getByName("0.0.0.0"), 0)))
.flatMap {
case Left(localAddress) =>
Stream.eval_(localBindAddress.flatMap(_.complete(localAddress)))
case Right(serverSocket) =>
Stream.resource(serverSocket.map { socket =>
Stream
.eval(socketGroup.client[F](new InetSocketAddress("127.0.0.1", localPort)))
.map { clientSocket =>
clientSocket.reads(1024).through(socket.writes())
socket.reads(1024).through(clientSocket.writes())
}
})
}
// -------------------------------------------
// (!!!) I need to return this,
// but i need to run stream above first
// -------------------------------------------
localBindAddress.map(_.get.map(addr => TCP.CreatedTunnel(addr.getPort)))
}
}
}