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?