How to open a websocket connection using a custom POST request with body as the handshake in ZIO?

175 Views Asked by At

I'm working on a Scala client-server application using ZIO. My server exposes a RESTful API and must be able to handle WebSocket connections. I want to implement something similar to the way kubectl and the Kubernetes API server work, where a RESTful request with a payload is sent to the server, including an upgrade header in the initial request to establish a WebSocket connection.

I can't find any ZIO documentation for this specific case or any way to handle websocket connection manually like a WebSocketBuilder. Currently, I'm using the standard way of opening a WebSocket channel with ZIO:

  val httpSocket: Http[Any, Throwable, WebSocketChannelEvent, Unit] =
    Http

      // Listen for all websocket channel events
      .collectZIO[WebSocketChannelEvent] {
        ...
      }

  val app: ZIO[Any with Client with Scope, Throwable, Response] =
    httpSocket.toSocketApp.connect(url) *> ZIO.never

  val run = app.provide(zio.http.Client.default, Scope.default)

I looked at the ZClient.scala code that is responsible for handling the connection in the above code, and it seems like if I can override the socketInternal method, I could use a custom Request to open a connection, and pass the new object in the app environment.

    protected override def socketInternal[R](
      app: SocketApp[R],
      headers: Headers,
      hostOption: Option[String],
      path: Path,
      portOption: Option[Int],
      queries: QueryParams,
      schemeOption: Option[Scheme],
      version: Version,
    )(implicit trace: Trace): ZIO[R with Scope, Throwable, Response] =
      for {
        env      <- ZIO.environment[R]
        location <- ZIO.fromOption {
          for {
            host   <- hostOption
            port   <- portOption
            scheme <- schemeOption
          } yield URL.Location.Absolute(scheme, host, port)
        }.orElseSucceed(URL.Location.Relative)
        res      <- requestAsync(
          Request
            .get(URL(path, location))
            .copy(
              version = version,
              headers = headers,
            ),
          clientConfig = config.copy(socketApp = Some(app.provideEnvironment(env))),
        ).withFinalizer {
          case resp: Response.CloseableResponse => resp.close.orDie
          case _                                => ZIO.unit
        }
      } yield res

However class ClientLive is final, and uses some package private classes and traits like CloseableResponse, so I can't inherit from it.

Any suggestion on how to approach this?

0

There are 0 best solutions below