Skip to content

Commit

Permalink
Docs
Browse files Browse the repository at this point in the history
  • Loading branch information
adamw committed Jan 8, 2025
1 parent b7750b4 commit 260127d
Show file tree
Hide file tree
Showing 12 changed files with 39 additions and 25 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import sttp.client4.quick.*
println(quickRequest.get(uri"http://httpbin.org/ip").send())
```

sttp client addresses common HTTP client use cases, such as interacting with JSON APIs (with automatic serialization of request bodies and deserialization of response bodies), uploading and downloading files, submitting form data, handling multi-part requests, and working with WebSockets.
sttp client addresses common HTTP client use cases, such as interacting with JSON APIs (with automatic serialization of request bodies and deserialization of response bodies), uploading and downloading files, submitting form data, handling multipart requests, and working with WebSockets.

The driving principle of sttp client's design is to provide a clean, programmer-friendly API to describe HTTP requests, along with response handling. This ensures that resources, such as HTTP connections, are used safely, also in the presence of errors.

Expand Down
1 change: 0 additions & 1 deletion core/src/main/scala/sttp/client4/testing/BackendStub.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import scala.concurrent.ExecutionContext
* Predicates can match requests basing on the URI or headers. A [[ClassCastException]] might occur if for a given
* request, a response is specified with the incorrect or inconvertible body type.
*/

class BackendStub[F[_]](
monad: MonadError[F],
matchers: PartialFunction[GenericRequest[_, _], F[Response[_]]],
Expand Down
2 changes: 1 addition & 1 deletion docs/backends/zio.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ The `HttpClient` ZIO backend supports both regular and streaming [websockets](..
A stub backend can be created through the `.stub` method on the companion object, and configured as described in the
[testing](../testing/stub.md) section.

A layer with the stub `SttpBackend` can be then created by simply calling `ZLayer.succeed(sttpBackendStub)`.
A layer with the stub `Backend` can be then created by simply calling `ZLayer.succeed(backendStub)`.

## Server-sent events

Expand Down
3 changes: 2 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import sttp.client4.quick.*
println(quickRequest.get(uri"http://httpbin.org/ip").send())
```

sttp client addresses common HTTP client use cases, such as interacting with JSON APIs (with automatic serialization of request bodies and deserialization of response bodies), uploading and downloading files, submitting form data, handling multi-part requests, and working with WebSockets.
sttp client addresses common HTTP client use cases, such as interacting with JSON APIs (with automatic serialization of request bodies and deserialization of response bodies), uploading and downloading files, submitting form data, handling multipart requests, and working with WebSockets.

The driving principle of sttp client's design is to provide a clean, programmer-friendly API to describe HTTP requests, along with response handling. This ensures that resources, such as HTTP connections, are used safely, also in the presence of errors.

Expand Down Expand Up @@ -135,6 +135,7 @@ Third party projects:
other/xml
other/resilience
other/openapi
other/sse
.. toctree::
:maxdepth: 2
Expand Down
5 changes: 5 additions & 0 deletions docs/other/sse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Server-sent events

All backends that support [streaming](../requests/streaming.md) also support [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events). Moreover, synchronous backends can also support SSE, when combined with [Ox](https://ox.softwaremill.com).

Refer to the documentation of individual backends, for more information on how to use SSE with a given backend, as well as usage examples.
12 changes: 7 additions & 5 deletions docs/other/websockets.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# WebSockets

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.
One of the optional capabilities 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. 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:

Expand All @@ -10,9 +10,11 @@ A websocket request will be sent instead of a regular one if the response specif

The above imports will bring into scope a number of `asWebSocket(...)` methods, giving a couple of variants of working with websockets. Alternatively, you can extend the `SttpWebSocketSyncApi`, `SttpWebSocketAsyncApi` or `SttpWebSocketStreamApi` traits, to group all used sttp client features within a single object.

Refer to the documentation of individual backends for additional notes, or restrictions, when using WebSockets.

## Using `WebSocket`

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:
The first possibility 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: 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`.
Expand Down Expand Up @@ -92,7 +94,7 @@ effect type class name
================ ==========================================
```

## Using blocking, sycnhronous Ox streams
## Using blocking, synchronous Ox streams

[Ox](https://ox.softwaremill.com) is a Scala 3 toolkit that allows you to handle concurrency and resiliency in direct-style, leveraging Java 21 virtual threads.
If you're using Ox with `sttp`, you can use the `DefaultSyncBackend` from `sttp-core` for HTTP communication. An additional `ox` module allows handling WebSockets
Expand Down Expand Up @@ -128,14 +130,14 @@ basicRequest

See the [full example here](https://github.com/softwaremill/sttp/blob/master/examples/src/main/scala/sttp/client4/examples/ws/wsOxExample.scala).

Make sure that the `Source` is contiunually read. This will guarantee that server-side `Close` signal is received and handled.
Make sure that the `Source` is continually read. This will guarantee that server-side `Close` signal is received and handled.
If you don't want to process frames from the server, you can at least handle it with a `fork { source.drain() }`.

You don't need to manually call `ws.close()` when using this approach, this will be handled automatically underneath,
according to following rules:
- If the request `Sink` is closed due to an upstream error, a `Close` frame is sent, and the `Source` with incoming responses gets completed as `Done`.
- If the request `Sink` completes as `Done`, a `Close` frame is sent, and the response `Sink` keeps receiving responses until the server closes communication.
- If the response `Source` is closed by a `Close` frome from the server or due to an error, the request Sink is closed as `Done`, which will still send all outstanding buffered frames, and then finish.
- If the response `Source` is closed by a `Close` frame from the server or due to an error, the request Sink is closed as `Done`, which will still send all outstanding buffered frames, and then finish.

Read more about Ox, structured concurrency, Sources and Sinks on the [project website](https://ox.softwaremill.com).

Expand Down
29 changes: 18 additions & 11 deletions docs/testing/stub.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
# The stub backend

If you need a stub backend for use in tests instead of a "real" backend (you probably don't want to make HTTP calls during unit tests), you can use the `SttpBackendStub` class. It allows specifying how the backend should respond to requests matching given predicates.
If you need a stub backend for use in tests instead of a "real" backend (you probably don't want to make HTTP calls during unit tests), you can use the `BackendStub` class. It allows specifying how the backend should respond to requests matching given predicates.

You can also create a stub backend using [akka-http routes](../backends/akka.md).
The [pekko-http](../backends/pekko.md) or [akka-http](../backends/akka.md) backends also provide an alternative way to create a stub, from a request-transforming function.

## Creating a stub backend

An empty backend stub can be created using the following ways:

* by calling `.stub` on the "real" base backend's companion object, e.g. `HttpClientZioBackend.stub` or `HttpClientMonixBackend.stub`
* by using one of the factory methods `SttpBackendStub.synchronous` or `SttpBackendStub.asynchronousFuture`, which return stubs which use the `Identity` or standard Scala's `Future` effects without streaming support
* by using one of the factory methods `BackendStub.synchronous` or `BackendStub.asynchronousFuture`, which return stubs which use the `Identity` or standard Scala's `Future` effects without streaming support
* by explicitly specifying the effect and supported capabilities:
* for Monix `SttpBackendStub[Task, MonixStreams with WebSockets](TaskMonad)`
* for cats `SttpBackendStub[IO, WebSockets](implicitly[MonadAsyncError[IO]])`
* for zio `SttpBackendStub[Task, WebSockets](new RIOMonadAsyncError[Any])`
* for cats-effect `BackendStub[IO](implicitly[MonadAsyncError[IO]])`
* for ZIO `BackendStub[Task](new RIOMonadAsyncError[Any])`
* for Monix `BackendStub[Task](TaskMonad)`
* by instantiating backend stubs which support streaming or WebSockets, mirroring the hierarchy of the base backends:
* `StreamBackendStub`, e.g. `StreamBackendStub[IO, Fs2Streams[IO]](implicitly[MonadAsyncError[IO]])` (for cats-effect with fs2)
* `WebSocketBackendStub`, e.g. `WebSocketBackendStub[Task](new RIOMonadAsyncError[Any])` (for ZIO)
* `WebSocketStreamBackendStub`
* `WebSocketSyncBackendStub`
* by specifying a fallback/delegate backend, see below

Responses used in the stubbing can be created either by directly instantiating the `Response` class, or by using factory methods from `ResponseStub`.

Some code which will be reused among following examples:

```scala mdoc
Expand Down Expand Up @@ -72,7 +79,7 @@ val response2 = basicRequest.post(uri"http://example.org/partialAda").send(testi
This approach to testing has one caveat: the responses are not type-safe. That is, the stub backend cannot match on or verify that the type of the response body matches the response body type, as it was requested. However, when a "raw" response is provided (a `String`, `Array[Byte]`, `InputStream`, or a non-blocking stream wrapped in `RawStream`), it will be handled as specified by the response specification - see below for details.
```

Another way to specify the behaviour is passing response wrapped in the effect to the stub. It is useful if you need to test a scenario with a slow server, when the response should be not returned immediately, but after some time. Example with Futures:
Another way to specify the behavior is passing response wrapped in the effect to the stub. It is useful if you need to test a scenario with a slow server, when the response should be not returned immediately, but after some time. Example with Futures:

```scala mdoc:compile-only
val testingBackend = BackendStub.asynchronousFuture
Expand Down Expand Up @@ -112,7 +119,7 @@ basicRequest.get(uri"http://example.org").send(testingBackend) // Right("O
basicRequest.get(uri"http://example.org").send(testingBackend) // Right("OK, first")
```

Or multiple `Response` instances:
Or multiple `Response` instances, created using `ResponseStub`:

```scala mdoc:compile-only
val testingBackend: SyncBackendStub = SyncBackendStub
Expand Down Expand Up @@ -163,7 +170,7 @@ The following conversions are supported:
* `InputStream` and `String` to `Array[Byte]`
* `WebSocketStub` to `WebSocket`
* `WebSocketStub` and `WebSocket` are supplied to the websocket-consuming functions, if the response specification describes such interactions
* `SttpBackendStub.RawStream` is always treated as a raw stream value, and returned when the response should be returned as a stream or consumed using the provided function
* `BackendStub.RawStream` is always treated as a raw stream value, and returned when the response should be returned as a stream or consumed using the provided function
* any of the above to custom types through mapped response specifications

## Example: returning JSON
Expand All @@ -182,7 +189,7 @@ val response = basicRequest.get(uri"http://example.com")
.send(testingBackend)
```

In the example above, the stub's rules specify that a response with a `String`-body should be returned for any request; the request, on the other hand, specifies that response body should be parsed from a byte array to a custom `User` type. These type don't match, so the `SttpBackendStub` will in this case convert the body to the desired type.
In the example above, the stub's rules specify that a response with a `String`-body should be returned for any request; the request, on the other hand, specifies that response body should be parsed from a byte array to a custom `User` type. These type don't match, so the `BackendStub` will in this case convert the body to the desired type.

## Example: returning a file

Expand Down Expand Up @@ -240,7 +247,7 @@ val response2 = basicRequest.post(uri"http://api.internal/b").send(testingBacken

## Testing streams

Streaming responses can be stubbed the same as ordinary values, with one difference. If the stubbed response contains the raw stream, which should be then transformed as described by the response specification, the stub must know that it handles a raw stream. This can be achieved by wrapping the stubbed stream using `SttpBackendStub.RawStream`.
Streaming responses can be stubbed the same as ordinary values, with one difference. If the stubbed response contains the raw stream, which should be then transformed as described by the response specification, the stub must know that it handles a raw stream. This can be achieved by wrapping the stubbed stream using `BackendStub.RawStream`.

If the response specification is a resource-safe consumer of the stream, the function will only be invoked if the body is a `RawStream` (with the contained value).

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ object HttpClientCatsBackend {

/** Create a stub backend for testing, which uses the [[F]] response wrapper.
*
* See [[SttpBackendStub]] for details on how to configure stub responses.
* See [[sttp.client4.testing.BackendStub]] for details on how to configure stub responses.
*/
def stub[F[_]: Async]: WebSocketBackendStub[F] = WebSocketBackendStub(new CatsMonadAsyncError[F])
}
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ object HttpClientFs2Backend {
/** Create a stub backend for testing, which uses the [[F]] response wrapper, and supports `Stream[F, Byte]`
* streaming.
*
* See [[SttpBackendStub]] for details on how to configure stub responses.
* See [[sttp.client4.testing.BackendStub]] for details on how to configure stub responses.
*/
def stub[F[_]: Concurrent]: WebSocketStreamBackendStub[F, Fs2Streams[F]] = WebSocketStreamBackendStub(implicitly)
}
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ object HttpClientFs2Backend {
/** Create a stub backend for testing, which uses the [[F]] response wrapper, and supports `Stream[F, Byte]`
* streaming.
*
* See [[SttpBackendStub]] for details on how to configure stub responses.
* See [[sttp.client4.testing.BackendStub]] for details on how to configure stub responses.
*/
def stub[F[_]: Async]: WebSocketStreamBackendStub[F, Fs2Streams[F]] = WebSocketStreamBackendStub(implicitly)
}
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ object HttpClientMonixBackend {
/** Create a stub backend for testing, which uses the [[Task]] response wrapper, and supports `Observable[ByteBuffer]`
* streaming.
*
* See [[SttpBackendStub]] for details on how to configure stub responses.
* See [[sttp.client4.testing.BackendStub]] for details on how to configure stub responses.
*/
def stub: WebSocketStreamBackendStub[Task, MonixStreams] = WebSocketStreamBackendStub(TaskMonadAsyncError)
}
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ object HttpClientZioBackend {
/** Create a stub backend for testing, which uses the [[Task]] response wrapper, and supports `Stream[Throwable,
* ByteBuffer]` streaming.
*
* See [[SttpBackendStub]] for details on how to configure stub responses.
* See [[sttp.client4.testing.BackendStub]] for details on how to configure stub responses.
*/
def stub: WebSocketStreamBackendStub[Task, ZioStreams] = WebSocketStreamBackendStub(new RIOMonadAsyncError[Any])

Expand Down

0 comments on commit 260127d

Please sign in to comment.