question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Websocket fails with a Kleisli Routes monad

See original GitHub issue

Minimal example:

import scala.concurrent.duration._

import cats.implicits._
import cats.data.Kleisli
import cats.effect._
import fs2._
import org.http4s._
import org.http4s.dsl.Http4sDsl
import org.http4s.implicits._
import org.http4s.server.blaze._
import org.http4s.server.websocket.WebSocketBuilder
import org.http4s.websocket.WebSocketFrame

case class WebSocketRoutes[F[_]: Concurrent: Timer]() extends Http4sDsl[F] {

  val routes: HttpRoutes[F] = HttpRoutes.of[F] {
    case GET -> Root =>
      val toClient: Stream[F, WebSocketFrame] =
        Stream.awakeEvery[F](1.seconds).map(d => WebSocketFrame.Text(s"Ping! $d"))
      val fromClient: Pipe[F, WebSocketFrame, Unit] = _.evalMap {
        case WebSocketFrame.Text(t, _) => F.delay(println(t))
        case f                         => F.delay(println(s"Unknown type: $f"))
      }
      WebSocketBuilder[F].build(toClient, fromClient)
  }

}

object Middleware {

  def apply[F[_]: Sync](
      name: String
  )(httpApp: HttpApp[Kleisli[F, String, *]]): HttpApp[F] =
    Kleisli { request =>
      for {
        appRequest <- F.pure(request.mapK(Kleisli.liftK[F, String]))
        appResponse <- httpApp.run(appRequest).run(name)
        response = appResponse.mapK(Kleisli.applyK(name))
      } yield response
    }

}

object Main extends IOApp {

  type AppIO[A] = Kleisli[IO, String, A]

  def run(args: List[String]): IO[ExitCode] = {
    val routes = WebSocketRoutes[AppIO]().routes.orNotFound

    val app = Middleware("test")(routes)

    BlazeServerBuilder[IO](scala.concurrent.ExecutionContext.global)
      .bindHttp(8080, "localhost")
      .withHttpApp(app)
      .serve
      .compile
      .drain
      .as(ExitCode.Success)

  }

}

Full Stack trace:

[info] 23:11:17.950 [blaze-selector-1] INFO org.http4s.blaze.channel.nio1.NIO1SocketServerGroup - Accepted connection from /127.0.0.1:51343
[info] 23:11:17.974 [ioapp-compute-1] DEBUG org.http4s.server.blaze.Http1ServerStage$$anon$1 - Websocket key: Some(WebSocketContext(WebSocket(Stream(..),org.http4s.server.websocket.WebSocketBuilder$Builder$$Lambda$299/1538312351@2487cace,Kleisli(cats.data.KleisliFunctions$$Lambda$298/754950929@33f502e0)),Headers(),Kleisli(cats.data.KleisliFunctions$$Lambda$298/754950929@365d5f01)))
[info] Request headers: Headers(Host: 127.0.0.1:8080, Connection: Upgrade, Upgrade: websocket, Sec-WebSocket-Version: 13, Sec-WebSocket-Key: 4+mLlx5cT/kKSia8zG5MSw==)
[info] 23:11:17.985 [scala-execution-context-global-38] DEBUG org.http4s.server.blaze.Http1ServerStage$$anon$1 - Switching pipeline segments for websocket
[info] 23:11:17.999 [scala-execution-context-global-38] DEBUG org.http4s.server.blaze.Http1ServerStage$$anon$1 - Shutting down HttpPipeline
[info] 23:11:18.000 [scala-execution-context-global-38] DEBUG org.http4s.server.blaze.Http1ServerStage$$anon$1 - Canceled request
[info] 23:11:18.000 [scala-execution-context-global-38] DEBUG org.http4s.server.blaze.Http1ServerStage$$anon$1 - Shutting down.
[info] 23:11:18.000 [scala-execution-context-global-38] DEBUG org.http4s.blazecore.IdleTimeoutStage - Stage IdleTimeoutStage sending inbound command: Connected
[info] 23:11:18.000 [scala-execution-context-global-38] DEBUG org.http4s.server.blaze.WebSocketDecoder - Starting up.
[info] 23:11:18.000 [scala-execution-context-global-38] DEBUG org.http4s.server.blaze.WebSocketDecoder - Stage WebSocketDecoder sending inbound command: Connected
[info] 23:11:18.000 [scala-execution-context-global-38] DEBUG org.http4s.server.blaze.WSFrameAggregator - Starting up.
[info] 23:11:18.000 [scala-execution-context-global-38] DEBUG org.http4s.server.blaze.WSFrameAggregator - Stage WSFrameAggregator sending inbound command: Connected
[info] 23:11:18.000 [scala-execution-context-global-38] DEBUG org.http4s.blazecore.websocket.Http4sWSStage - Starting up.
[info] 23:11:18.006 [scala-execution-context-global-38] ERROR org.http4s.server.blaze.WSFrameAggregator - Exception caught when attempting inbound command
[info] java.lang.ClassCastException: cats.data.Kleisli cannot be cast to cats.effect.IO
[info]  at cats.effect.IOLowPriorityInstances$IOEffect.attempt(IO.scala:863)
[info]  at cats.syntax.ApplicativeErrorOps$.attempt$extension(applicativeError.scala:52)
[info]  at org.http4s.blazecore.websocket.Http4sWSStage.stageStartup(Http4sWSStage.scala:132)
[info]  at org.http4s.blaze.pipeline.Stage.inboundCommand(Stages.scala:75)
[info]  at org.http4s.blaze.pipeline.Stage.inboundCommand$(Stages.scala:73)
[info]  at org.http4s.blazecore.websocket.Http4sWSStage.inboundCommand(Http4sWSStage.scala:26)
[info]  at org.http4s.blaze.pipeline.Head.sendInboundCommand(Stages.scala:258)
[info]  at org.http4s.blaze.pipeline.Head.sendInboundCommand$(Stages.scala:254)
[info]  at org.http4s.server.blaze.WSFrameAggregator.sendInboundCommand(WSFrameAggregator.scala:22)
[info]  at org.http4s.blaze.pipeline.Head.inboundCommand(Stages.scala:276)
[info]  at org.http4s.blaze.pipeline.Head.inboundCommand$(Stages.scala:274)
[info]  at org.http4s.server.blaze.WSFrameAggregator.inboundCommand(WSFrameAggregator.scala:22)
[info]  at org.http4s.blaze.pipeline.Head.sendInboundCommand(Stages.scala:258)
[info]  at org.http4s.blaze.pipeline.Head.sendInboundCommand$(Stages.scala:254)
[info]  at org.http4s.server.blaze.WebSocketDecoder.sendInboundCommand(WebSocketDecoder.scala:14)
[info]  at org.http4s.blaze.pipeline.Head.inboundCommand(Stages.scala:276)
[info]  at org.http4s.blaze.pipeline.Head.inboundCommand$(Stages.scala:274)
[info]  at org.http4s.server.blaze.WebSocketDecoder.inboundCommand(WebSocketDecoder.scala:14)
[info]  at org.http4s.blaze.pipeline.Head.sendInboundCommand(Stages.scala:258)
[info]  at org.http4s.blaze.pipeline.Head.sendInboundCommand$(Stages.scala:254)
[info]  at org.http4s.blazecore.IdleTimeoutStage.sendInboundCommand(IdleTimeoutStage.scala:18)
[info]  at org.http4s.blaze.pipeline.Tail.replaceTail(Stages.scala:183)
[info]  at org.http4s.blaze.pipeline.Tail.replaceTail$(Stages.scala:162)
[info]  at org.http4s.server.blaze.Http1ServerStage.replaceTail(Http1ServerStage.scala:73)
[info]  at org.http4s.server.blaze.WebSocketSupport.$anonfun$renderResponse$7(WebSocketSupport.scala:83)
[info]  at scala.concurrent.impl.Promise$Transformation.run(Promise.scala:447)
[info]  at java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(ForkJoinTask.java:1402)
[info]  at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
[info]  at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
[info]  at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
[info]  at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
[info] 23:11:18.006 [scala-execution-context-global-38] DEBUG org.http4s.server.blaze.WSFrameAggregator - Shutting down.
[info] 23:11:18.006 [scala-execution-context-global-38] DEBUG org.http4s.server.blaze.WebSocketDecoder - Shutting down.
[info] 23:11:18.006 [scala-execution-context-global-38] DEBUG org.http4s.blazecore.IdleTimeoutStage - Shutting down idle timeout stage
[info] 23:11:18.006 [scala-execution-context-global-38] DEBUG org.http4s.blazecore.IdleTimeoutStage - Shutting down.
[info] 23:11:18.006 [scala-execution-context-global-38] DEBUG org.http4s.blaze.channel.nio1.NIO1HeadStage - Shutting down.
[info] 23:11:18.007 [blaze-selector-1] ERROR org.http4s.blaze.channel.nio1.NIO1HeadStage - Abnormal NIO1HeadStage termination
[info] java.lang.ClassCastException: cats.data.Kleisli cannot be cast to cats.effect.IO
[info]  at cats.effect.IOLowPriorityInstances$IOEffect.attempt(IO.scala:863)
[info]  at cats.syntax.ApplicativeErrorOps$.attempt$extension(applicativeError.scala:52)
[info]  at org.http4s.blazecore.websocket.Http4sWSStage.stageStartup(Http4sWSStage.scala:132)
[info]  at org.http4s.blaze.pipeline.Stage.inboundCommand(Stages.scala:75)
[info]  at org.http4s.blaze.pipeline.Stage.inboundCommand$(Stages.scala:73)
[info]  at org.http4s.blazecore.websocket.Http4sWSStage.inboundCommand(Http4sWSStage.scala:26)
[info]  at org.http4s.blaze.pipeline.Head.sendInboundCommand(Stages.scala:258)
[info]  at org.http4s.blaze.pipeline.Head.sendInboundCommand$(Stages.scala:254)
[info]  at org.http4s.server.blaze.WSFrameAggregator.sendInboundCommand(WSFrameAggregator.scala:22)
[info]  at org.http4s.blaze.pipeline.Head.inboundCommand(Stages.scala:276)
[info]  at org.http4s.blaze.pipeline.Head.inboundCommand$(Stages.scala:274)
[info]  at org.http4s.server.blaze.WSFrameAggregator.inboundCommand(WSFrameAggregator.scala:22)
[info]  at org.http4s.blaze.pipeline.Head.sendInboundCommand(Stages.scala:258)
[info]  at org.http4s.blaze.pipeline.Head.sendInboundCommand$(Stages.scala:254)
[info]  at org.http4s.server.blaze.WebSocketDecoder.sendInboundCommand(WebSocketDecoder.scala:14)
[info]  at org.http4s.blaze.pipeline.Head.inboundCommand(Stages.scala:276)
[info]  at org.http4s.blaze.pipeline.Head.inboundCommand$(Stages.scala:274)
[info]  at org.http4s.server.blaze.WebSocketDecoder.inboundCommand(WebSocketDecoder.scala:14)
[info]  at org.http4s.blaze.pipeline.Head.sendInboundCommand(Stages.scala:258)
[info]  at org.http4s.blaze.pipeline.Head.sendInboundCommand$(Stages.scala:254)
[info]  at org.http4s.blazecore.IdleTimeoutStage.sendInboundCommand(IdleTimeoutStage.scala:18)
[info]  at org.http4s.blaze.pipeline.Tail.replaceTail(Stages.scala:183)
[info]  at org.http4s.blaze.pipeline.Tail.replaceTail$(Stages.scala:162)
[info]  at org.http4s.server.blaze.Http1ServerStage.replaceTail(Http1ServerStage.scala:73)
[info]  at org.http4s.server.blaze.WebSocketSupport.$anonfun$renderResponse$7(WebSocketSupport.scala:83)
[info]  at scala.concurrent.impl.Promise$Transformation.run(Promise.scala:447)
[info]  at java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(ForkJoinTask.java:1402)
[info]  at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
[info]  at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
[info]  at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
[info]  at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
[info] 23:11:18.007 [blaze-selector-1] DEBUG org.http4s.blaze.channel.nio1.NIO1HeadStage - Stage NIO1HeadStage sending inbound command: Disconnected
[info] 23:11:18.007 [blaze-selector-1] DEBUG org.http4s.blazecore.IdleTimeoutStage - Shutting down idle timeout stage
[info] 23:11:18.007 [blaze-selector-1] DEBUG org.http4s.blazecore.IdleTimeoutStage - Shutting down.
[info] 23:11:18.007 [blaze-selector-1] DEBUG org.http4s.blazecore.IdleTimeoutStage - Stage IdleTimeoutStage sending inbound command: Disconnected
[info] 23:11:18.007 [blaze-selector-1] DEBUG org.http4s.server.blaze.WebSocketDecoder - Shutting down.
[info] 23:11:18.007 [blaze-selector-1] DEBUG org.http4s.server.blaze.WebSocketDecoder - Stage WebSocketDecoder sending inbound command: Disconnected
[info] 23:11:18.007 [blaze-selector-1] DEBUG org.http4s.server.blaze.WSFrameAggregator - Shutting down.
[info] 23:11:18.008 [blaze-selector-1] DEBUG org.http4s.server.blaze.WSFrameAggregator - Stage WSFrameAggregator sending inbound command: Disconnected
[info] 23:11:18.010 [blaze-selector-1] DEBUG org.http4s.blazecore.websocket.Http4sWSStage - Shutting down.

I tried debugging it for a the past 2 hours, but I’m not deep enough in the http4s codebase to resolve this within a reasonable amount of time. Hopeful the solution for this will be obvious to someone around here.

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:2
  • Comments:16 (13 by maintainers)

github_iconTop GitHub Comments

1reaction
pjancommented, Sep 20, 2020

Given that we need both an F ~> G as well as a G ~>F for the transformation of the Pipe (inside the Websocket > WebsocketContext > Response) for this to work, it would still come with API breakage.

0reactions
armanbilgecommented, May 2, 2022
Read more comments on GitHub >

github_iconTop Results From Across the Web

http4s/http4s - Gitter
ResponseF.withEntity("This is a WebSocket route.").pure[F], It turns out that the builder itself immediately gives an error 501 to client.
Read more >
Natchez-Http4s - tpolecat
A mechanism to discharge a Trace[F] constraint on HttpRoutes[F] , which constructs ... Here is the basic pattern for HTTP applications without WebSockets....
Read more >
BracketRequestResponse - javadoc.io
This can happen due to a bug in a server implementation, where it fails to drain ... just the normal context route function...
Read more >
Play Framework 2.5, routes to the web socket fails to compile
I can fix the problem by removing the parenthesis in routes. That is, GET /sample/monitor controllers.sample.Connection.monitor. instead of
Read more >
cats.data.OptionT Scala Example - ProgramCreek.com
Monad object implicits { // Given an entry point and HTTP Routes in Kleisli[F, Span[F], ?] return routes in F. A new span...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found