Websocket fails with a Kleisli Routes monad
See original GitHub issueMinimal 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:
- Created 3 years ago
- Reactions:2
- Comments:16 (13 by maintainers)
Top 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 >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
Given that we need both an
F ~> G
as well as aG ~>F
for the transformation of thePipe
(inside the Websocket > WebsocketContext > Response) for this to work, it would still come with API breakage.Fixed in https://github.com/http4s/http4s/pull/5152 I believe.