Skip to content

Commit

Permalink
Update ws docs
Browse files Browse the repository at this point in the history
  • Loading branch information
adamw committed Aug 18, 2023
1 parent 1a6d13e commit b5e99eb
Showing 1 changed file with 37 additions and 25 deletions.
62 changes: 37 additions & 25 deletions docs/websockets.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,50 +2,60 @@

One of the optional capabilities (represented as `WebSockets`) that a backend can support are websockets (see [backends summary](backends/summary.md)). Websocket requests are described exactly the same as regular requests, starting with `basicRequest`, adding headers, specifying the request method and uri.

A websocket request will be sent instead of a regular one if the response specification includes handling the response as a websocket. A number of `asWebSocket(...)` methods are available, giving a couple of variants of working with websockets.
A websocket request will be sent instead of a regular one if the response specification includes handling the response as a websocket. Depending on the backend you are using, there are three variants of websocket response specifications: synchronous, asynchronous and streaming. To use them, add one of the following imports:

* `import sttp.client4.ws.sync._` if you are using a synchronous backend (such as `DefaultSyncBackend`), without any effect wrappers
* `import sttp.client4.ws.async._` if you are using an asynchronous backend (e.g. based on `Future`s or `IO`s)
* `import sttp.client4.ws.stream._` if you want to handle web socket messages using a non-blocking stream (e.g. `fs2.Stream` or `akka.stream.scaladsl.Source`)

The above imports will bring into scope a number of `asWebSocket(...)` methods, giving a couple of variants of working with websockets.

## Using `WebSocket`

The first possibility is using `sttp.client4.ws.WebSocket[F]`, where `F` is the backend-specific effects wrapper, such as `Future` or `IO`. It contains two basic methods, both of which use the `F` effect to return results:
The first variant of interacting with web sockets is using `sttp.client4.ws.SyncWebSocket` (sync variant), or `sttp.ws.WebSocket[F]` (async variant), where `F` is the backend-specific effects wrapper, such as `Future` or `IO`. These classes contain two basic methods:

* `def receive: F[WebSocketFrame]` which will complete once a message is available, and return the next incoming frame (which can be a data, ping, pong or close)
* `def send(f: WebSocketFrame, isContinuation: Boolean = false): F[Unit]`, which sends a message to the websocket. The `WebSocketFrame` companion object contains methods for creating binary/text messages. When using fragmentation, the first message should be sent using `finalFragment = false`, and subsequent messages using `isContinuation = true`.
* `def receive: WebSocketFrame` (optionally wrapped with `F[_]` in the async variant) which will complete once a message is available, and return the next incoming frame (which can be a data, ping, pong or close)
* `def send(f: WebSocketFrame, isContinuation: Boolean = false): Unit` (again optionally wrapped with `F[_]`), which sends a message to the websocket. The `WebSocketFrame` companion object contains methods for creating binary/text messages. When using fragmentation, the first message should be sent using `finalFragment = false`, and subsequent messages using `isContinuation = true`.

The `WebSocket` trait also contains other methods for receiving only text/binary messages, as well as automatically sending `Pong` responses when a `Ping` is received.
The `SyncWebSocket` / `WebSocket` classes also contain other methods for receiving only text/binary messages, as well as automatically sending `Pong` responses when a `Ping` is received.

The following response specifications which use `WebSocket[F]` are available (the first type parameter of `ResponseAs` specifies the type returned as the response body, the second - the capabilities that the backend is required to support to send the request):
The following response specifications which use `SyncWebSocket` are available in the `sttp.client4.ws.sync` object (the second type parameter of `WebSocketResponseAs` specifies the type returned as the response body):

```scala mdoc:compile-only
import sttp.client4._
import sttp.model.ResponseMetadata
import sttp.ws.WebSocket
import sttp.client4.ws.SyncWebSocket

// when using import sttp.client4.ws.sync._

def asWebSocket[F[_], T](f: WebSocket[F] => F[T]):
WebSocketResponseAs[F, Either[String, T]] = ???
def asWebSocket[T](f: SyncWebSocket => T):
WebSocketResponseAs[Identity, Either[String, T]] = ???

def asWebSocketWithMetadata[F[_], T](
f: (WebSocket[F], ResponseMetadata) => F[T]
): WebSocketResponseAs[F, Either[String, T]] = ???
def asWebSocketWithMetadata[T](
f: (SyncWebSocket, ResponseMetadata) => T
): WebSocketResponseAs[Identity, Either[String, T]] = ???

def asWebSocketAlways[F[_], T](f: WebSocket[F] => F[T]):
WebSocketResponseAs[F, T] = ???
def asWebSocketAlways[T](f: SyncWebSocket => T):
WebSocketResponseAs[Identity, T] = ???

def asWebSocketAlwaysWithMetadata[F[_], T](
f: (WebSocket[F], ResponseMetadata) => F[T]
): WebSocketResponseAs[F, T] = ???
def asWebSocketAlwaysWithMetadata[T](
f: (SyncWebSocket, ResponseMetadata) => T
): WebSocketResponseAs[Identity, T] = ???

def asWebSocketUnsafe[F[_]]:
WebSocketResponseAs[F, Either[String, WebSocket[F]]] = ???
def asWebSocketUnsafe:
WebSocketResponseAs[Identity, Either[String, SyncWebSocket]] = ???

def asWebSocketUnsafeAlways[F[_]]:
WebSocketResponseAs[F, WebSocket[F]] = ???
def asWebSocketAlwaysUnsafe:
WebSocketResponseAs[Identity, SyncWebSocket] = ???
```

The first variant, `asWebSocket`, passes an open `WebSocket` to the user-provided function. This function should return an effect which completes, once interaction with the websocket is finished. The backend can then safely close the websocket. The value that's returned as the response body is either an error (represented as a `String`), in case the websocket upgrade didn't complete successfully, or the value returned by the websocket-interacting method.
The first variant, `asWebSocket`, passes an open `SyncWebSocket` to the user-provided function. This function should only return once interaction with the websocket is finished. The backend can then safely close the websocket. The value that's returned as the response body is either an error (represented as a `String`), in case the websocket upgrade didn't complete successfully, or the value returned by the websocket-interacting method.

The second variant (`asWebSocketAlways`) is similar, but any errors due to failed websocket protocol upgrades are represented as failed effects (exceptions).
The second variant (`asWebSocketAlways`) is similar, but any errors due to failed websocket protocol upgrades are represented as exceptions.

The remaining two variants return the open `WebSocket` directly, as the response body. It is then the responsibility of the client code to close the websocket, once it's no longer needed.
The remaining two variants return the open `SyncWebSocket` directly, as the response body. It is then the responsibility of the client code to close the websocket, once it's no longer needed.

Similar response specifications, but using an effect wrapper and `WebSocket[F]`, are available in the `sttp.client4.ws.async` objet.

See also the [examples](examples.md), which include examples involving websockets.

Expand All @@ -60,6 +70,8 @@ import sttp.client4._
import sttp.capabilities.{Streams, WebSockets}
import sttp.ws.WebSocketFrame

// when using import sttp.client4.ws.stream._

def asWebSocketStream[S](s: Streams[S])(p: s.Pipe[WebSocketFrame.Data[_], WebSocketFrame]):
WebSocketStreamResponseAs[Either[String, Unit], S] = ???

Expand All @@ -69,7 +81,7 @@ def asWebSocketStreamAlways[S](s: Streams[S])(p: s.Pipe[WebSocketFrame.Data[_],

Using streaming websockets requires the backend to support the given streaming capability (see also [streaming](requests/streaming.md)). Streaming capabilities are described as implementations of `Streams[S]`, and are provided by backend implementations, e.g. `AkkaStreams` or `Fs2Streams[F]`.

When working with streams of websocket frames keep in mind that a text payload maybe fragmented into multiple frames.
When working with streams of websocket frames keep in mind that a text payload may be fragmented into multiple frames.
sttp provides two useful methods (`fromTextPipe`, `fromTextPipeF`) for each backend to aggregate these fragments back into complete messages.
These methods can be found in corresponding WebSockets classes for given effect type:

Expand Down

0 comments on commit b5e99eb

Please sign in to comment.