diff --git a/README.md b/README.md index b431c14355..c83644e7e3 100755 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ sttp (v2) documentation is available at [sttp.softwaremill.com/en/v2](https://st sttp (v1) documentation is available at [sttp.softwaremill.com/en/v1](https://sttp.softwaremill.com/en/v1). -scaladoc is available at [https://www.javadoc.io](https://www.javadoc.io/doc/com.softwaremill.sttp.client4/core_2.12/3.8.13) +scaladoc is available at [https://www.javadoc.io](https://www.javadoc.io/doc/com.softwaremill.sttp.client4/core_2.12/4.0.0-M1) ## Quickstart with scala-cli @@ -57,7 +57,7 @@ Add the following directive to the top of your scala file to add the core sttp d If you are using [scala-cli](https://scala-cli.virtuslab.org), you can quickly start experimenting with sttp by copy-pasting the following: ``` -//> using lib "com.softwaremill.sttp.client4:core:3.8.13" +//> using lib "com.softwaremill.sttp.client4:core:4.0.0-M1" import sttp.client4.quick._ quickRequest.get(uri"http://httpbin.org/ip").send() ``` @@ -69,7 +69,7 @@ The `quick` package import brings in the sttp API and a pre-configured, global s Similarly, using [Ammonite](http://ammonite.io): ```scala -import $ivy.`com.softwaremill.sttp.client4::core:3.8.13` +import $ivy.`com.softwaremill.sttp.client4::core:4.0.0-M1` import sttp.client4.quick._ quickRequest.get(uri"http://httpbin.org/ip").send() ``` @@ -79,7 +79,7 @@ quickRequest.get(uri"http://httpbin.org/ip").send() Add the following dependency: ```scala -"com.softwaremill.sttp.client4" %% "core" % "3.8.13" +"com.softwaremill.sttp.client4" %% "core" % "4.0.0-M1" ``` Then, import: @@ -134,7 +134,7 @@ The documentation is typechecked using [mdoc](https://scalameta.org/mdoc/). The When generating documentation, it's best to set the version to the current one, so that the generated doc files don't include modifications with the current snapshot version. -That is, in sbt run: `set version := "3.8.13"`, before running `mdoc` in `docs`. +That is, in sbt run: `set version := "4.0.0-M1"`, before running `mdoc` in `docs`. ### Testing the Scala.JS backend diff --git a/generated-docs/out/adr/0003-separate-backend-types.md b/generated-docs/out/adr/0003-separate-backend-types.md new file mode 100644 index 0000000000..ac681ce9ab --- /dev/null +++ b/generated-docs/out/adr/0003-separate-backend-types.md @@ -0,0 +1,86 @@ +# 3. Separate backend types + +Date: 2023-02-22 + +## Context + +Sttp base types are [being refactored](https://github.com/softwaremill/sttp/pull/1703) to decrease the learning curve, +and generally improve the developer experience. + +## Decision + +The `SttpBackend` type is split into a type hierarchy, with new subtypes representing backends with different +effects and capabilities. + +The root of the hierarchy is `GenericBackend`, which corresponds to the old backend: it is parametrised with the +type constructor used to represent effects, and a set of capabilities that the backend supports. Additionally, there's +a number of subtypes: `Backend`, `SyncBackend`, `StreamBackend`, `WebSocketBackend` and `WebSocketStreamBackend` which +fix the capabilities, and in case of `SyncBackend` also the effect type. + +### What do we gain + +Thanks to the different kinds of backends being represented by top-level types, the `send` methods on request +descriptions can now have more developer-friendly and precise signatures. For example: + +```scala +class Request[T] { + def send[F[_]](backend: Backend[F]): F[Response[T]] = backend.send(this) + def send(backend: SyncBackend): Response[T] = backend.send(this) +} +``` + +Specifically, for a request sent using a synchronous request, the result type is a `Response` directly, without +the `Identity` wrapper. This improves ergonomics (e.g. when using autocomplete) and type inference. + +Moreover: +* users are exposed to simpler types, such as `SyncBackend` instead of `SttpBackend[Identity, Any]` +* error messages are more precise, pointing to the specific backend type that is required to send a request + +### What do we loose + +Backend wrappers, which are designed to work with any delegate backend, now need to have 5 alternate constructors, +for each specific backend subtype. For example: + +```scala +object FollowRedirectsBackend { + def apply(delegate: SyncBackend): SyncBackend = new FollowRedirectsBackend(delegate) with SyncBackend {} + def apply[F[_]](delegate: Backend[F]): Backend[F] = new FollowRedirectsBackend(delegate) with Backend[F] {} + def apply[F[_]](delegate: WebSocketBackend[F]): WebSocketBackend[F] = new FollowRedirectsBackend(delegate) with WebSocketBackend[F] {} + def apply[F[_], S](delegate: StreamBackend[F, S]): StreamBackend[F, S] = new FollowRedirectsBackend(delegate) with StreamBackend[F, S] {} + def apply[F[_], S](delegate: WebSocketStreamBackend[F, S]): WebSocketStreamBackend[F, S] = new FollowRedirectsBackend(delegate) with WebSocketStreamBackend[F, S] {} +} +``` + +Additionally, adding more capabilities will require enriching the type hierarchy with more subtypes, as well as adding +new variants to any backend wrappers. However, in the course of sttp's history, no new capabilities have been added, and +we do not foresee having to add more in the future. + +### Alternate designs + +We [have explored](https://github.com/adpi2/sttp/pull/5) an alternate design, where the `GenericBackend` was a +stand-alone type, to which types representing to specific capabilities (`Backend`, `StreamBackend` etc.) delegate. +This partially replaced the inheritance with composition. + +The main goal of the change was to allow implementing generic backend wrappers in a more straightforward way. The +delegating types included a `SelfType` type parameter, which was leveraged by generic backend wrappers, e.g.: + +```scala +trait SyncBackend extends Backend[Identity] { self => + override type Capabilities <: Any + override type SelfType <: SyncBackend +} + +object FollowRedirectsBackend { + def apply[F[_]](delegate: Backend[F]): delegate.SelfType = + delegate.wrap(new FollowRedirectsBackend(_, FollowRedirectsConfig.Default)) +} +``` + +However, this had two major drawbacks: + +* the type inference on the wrapped backends was worse, as it returned e.g. `SyncBackend.SelfType` instead of + `SyncBackend` +* alternatively, we could fix the self type to become type aliases instead of type bounds, but then the subtyping + relation between the backend types has been lost +* additionally, using two wrappers in a generic way resulted in type such as `delegate.SelfType#SelfType`, which + are not only not readable, but also rejected by the Scala 3 compiler. \ No newline at end of file diff --git a/generated-docs/out/adr/0004-separate-request-types.md b/generated-docs/out/adr/0004-separate-request-types.md new file mode 100644 index 0000000000..ce646b45bb --- /dev/null +++ b/generated-docs/out/adr/0004-separate-request-types.md @@ -0,0 +1,47 @@ +# 4. Separate request types + +Date: 2023-02-23 + +## Context + +Sttp base types are [being refactored](https://github.com/softwaremill/sttp/pull/1703) to decrease the learning curve, +and generally improve the developer experience. + +## Decision + +The `RequestT` type is being split into a type hierarchy, with new subtypes representing requests requiring different +capabilities. + +The requests are split into request builders: top-level `PartialRequest` and traits with common methods: +`PartialRequestBuilder` and `RequestBuilder`. + +Additionally, we've got a base trait for a generic request (that is a request description, which includes the uri +and method), `GenericRequest`. This is then implemented by a number of top-level classes: `Request`, `StreamRequest`, +`WebSocketRequest` and `WebSocketStreamRequest`. The capabilities supported by these requests are fixed. Setting +a response description or an input stream body promotes a `Request` or `PartialRequest` to the appropriate type. + +### What do we gain + +The main gain is that the types are simpler. Before, the `RequestT[U, T, R]` type had three type parameters: +a marker type constructor, specifying if the uri/method are specified; the target type, to which the response is +deserialized; and the set of required capabilities. + +With the new design, the base `Request[T]` type has one type parameter only (response target type). This is expanded +to two type parameters in the other request types, to express the additional requirements. + +Moreover, the specific request types clearly specify what type of `Backend` is required to send the request. + +### What do we loose + +It is harder to write generic methods which manipulate *any* request description (including partial request). This +is still possible, by properly parametrizing the method with the subtype of `PartialRequestBuilder` used, but might +not be as straightforward as before. + +Moreover, integration with Tapir might be more challenging, as we cannot simply accept an endpoint with a set of +capabilities, and produce a request with a mirror capability set. Special-casing on streaming/websocket capabilities +will be required. + +Finally, some flexibility is lost, as there are no partial streaming/websocket requests. That is, the method & uri +need to be specified on the request *before* specifying that the body should be streamed or that the response +should be a web socket one. This could be amended by introducing additional `PartialRequest` subtypes, however it is +not clear right now that it is necessary. \ No newline at end of file diff --git a/generated-docs/out/backends/akka.md b/generated-docs/out/backends/akka.md index ec5b81dd32..1264504c94 100644 --- a/generated-docs/out/backends/akka.md +++ b/generated-docs/out/backends/akka.md @@ -3,7 +3,7 @@ This backend is based on [akka-http](http://doc.akka.io/docs/akka-http/current/scala/http/). To use, add the following dependency to your project: ``` -"com.softwaremill.sttp.client4" %% "akka-http-backend" % "3.8.13" +"com.softwaremill.sttp.client4" %% "akka-http-backend" % "4.0.0-M1" ``` A fully **asynchronous** backend. Uses the `Future` effect to return responses. There are also [other `Future`-based backends](future.md), which don't depend on Akka. @@ -18,7 +18,6 @@ Next you'll need to add create the backend instance: ```scala import sttp.client4.akkahttp._ - val backend = AkkaHttpBackend() ``` @@ -46,8 +45,8 @@ import akka.util.ByteString val source: Source[ByteString, Any] = ??? basicRequest - .streamBody(AkkaStreams)(source) .post(uri"...") + .streamBody(AkkaStreams)(source) ``` To receive the response body as a stream: @@ -114,6 +113,8 @@ import sttp.client4._ def processEvents(source: Source[ServerSentEvent, Any]): Future[Unit] = ??? -basicRequest.response(asStream(AkkaStreams)(stream => - processEvents(stream.via(AkkaHttpServerSentEvents.parse)))) +basicRequest + .get(uri"...") + .response(asStream(AkkaStreams)(stream => + processEvents(stream.via(AkkaHttpServerSentEvents.parse)))) ``` diff --git a/generated-docs/out/backends/catseffect.md b/generated-docs/out/backends/catseffect.md index c8ef318cc8..1ced936163 100644 --- a/generated-docs/out/backends/catseffect.md +++ b/generated-docs/out/backends/catseffect.md @@ -76,9 +76,9 @@ Creation of the backend can be done in two basic ways: Firstly, add the following dependency to your project: ```scala -"com.softwaremill.sttp.client4" %% "armeria-backend-cats" % "3.8.13" // for cats-effect 3.x +"com.softwaremill.sttp.client4" %% "armeria-backend-cats" % "4.0.0-M1" // for cats-effect 3.x // or -"com.softwaremill.sttp.client4" %% "armeria-backend-cats-ce2" % "3.8.13" // for cats-effect 2.x +"com.softwaremill.sttp.client4" %% "armeria-backend-cats-ce2" % "4.0.0-M1" // for cats-effect 2.x ``` create client: @@ -112,10 +112,10 @@ import sttp.client4.armeria.cats.ArmeriaCatsBackend // Fluently build Armeria WebClient with built-in decorators val client = WebClient.builder("https://my-service.com") - // Open circuit on 5xx server error status - .decorator(CircuitBreakerClient.newDecorator(CircuitBreaker.ofDefaultName(), - CircuitBreakerRule.onServerErrorStatus())) - .build() + // Open circuit on 5xx server error status + .decorator(CircuitBreakerClient.newDecorator(CircuitBreaker.ofDefaultName(), + CircuitBreakerRule.onServerErrorStatus())) + .build() val backend = ArmeriaCatsBackend.usingClient[IO](client) ``` diff --git a/generated-docs/out/backends/finagle.md b/generated-docs/out/backends/finagle.md index 453e6e5025..9d907a2732 100644 --- a/generated-docs/out/backends/finagle.md +++ b/generated-docs/out/backends/finagle.md @@ -3,14 +3,13 @@ To use, add the following dependency to your project: ``` -"com.softwaremill.sttp.client4" %% "finagle-backend" % "3.8.13" +"com.softwaremill.sttp.client4" %% "finagle-backend" % "4.0.0-M1" ``` Next you'll need to add an implicit value: ```scala import sttp.client4.finagle.FinagleBackend - val backend = FinagleBackend() ``` diff --git a/generated-docs/out/backends/fs2.md b/generated-docs/out/backends/fs2.md index 03a6cc884e..eb79a4bc4e 100644 --- a/generated-docs/out/backends/fs2.md +++ b/generated-docs/out/backends/fs2.md @@ -12,9 +12,9 @@ Creation of the backend can be done in two basic ways: Firstly, add the following dependency to your project: ```scala -"com.softwaremill.sttp.client4" %% "fs2" % "3.8.13" // for cats-effect 3.x & fs2 3.x +"com.softwaremill.sttp.client4" %% "fs2" % "4.0.0-M1" // for cats-effect 3.x & fs2 3.x // or -"com.softwaremill.sttp.client4" %% "fs2-ce2" % "3.8.13" // for cats-effect 2.x & fs2 2.x +"com.softwaremill.sttp.client4" %% "fs2-ce2" % "4.0.0-M1" // for cats-effect 2.x & fs2 2.x ``` Obtain a cats-effect `Resource` which creates the backend, and closes the thread pool after the resource is no longer used: @@ -78,9 +78,9 @@ Host header override is supported in environments running Java 12 onwards, but i To use, add the following dependency to your project: ```scala -"com.softwaremill.sttp.client4" %% "armeria-backend-fs2" % "3.8.13" // for cats-effect 3.x & fs2 3.x +"com.softwaremill.sttp.client4" %% "armeria-backend-fs2" % "4.0.0-M1" // for cats-effect 3.x & fs2 3.x // or -"com.softwaremill.sttp.client4" %% "armeria-backend-fs2" % "3.8.13" // for cats-effect 2.x & fs2 2.x +"com.softwaremill.sttp.client4" %% "armeria-backend-fs2" % "4.0.0-M1" // for cats-effect 2.x & fs2 2.x ``` create client: @@ -110,11 +110,11 @@ val dispatcher: Dispatcher[IO] = ??? // Fluently build Armeria WebClient with built-in decorators val client = WebClient.builder("https://my-service.com") - // Open circuit on 5xx server error status - .decorator(CircuitBreakerClient.newDecorator(CircuitBreaker.ofDefaultName(), - CircuitBreakerRule.onServerErrorStatus())) - .build() - + // Open circuit on 5xx server error status + .decorator(CircuitBreakerClient.newDecorator(CircuitBreaker.ofDefaultName(), + CircuitBreakerRule.onServerErrorStatus())) + .build() + val backend = ArmeriaFs2Backend.usingClient[IO](client, dispatcher) ``` @@ -143,8 +143,8 @@ val effect = HttpClientFs2Backend.resource[IO]().use { backend => val stream: Stream[IO, Byte] = ??? basicRequest - .streamBody(Fs2Streams[IO])(stream) .post(uri"...") + .streamBody(Fs2Streams[IO])(stream) .send(backend) } ``` @@ -189,6 +189,8 @@ import sttp.model.sse.ServerSentEvent def processEvents(source: Stream[IO, ServerSentEvent]): IO[Unit] = ??? -basicRequest.response(asStream(Fs2Streams[IO])(stream => - processEvents(stream.through(Fs2ServerSentEvents.parse)))) +basicRequest + .get(uri"") + .response(asStream(Fs2Streams[IO])(stream => + processEvents(stream.through(Fs2ServerSentEvents.parse)))) ``` diff --git a/generated-docs/out/backends/future.md b/generated-docs/out/backends/future.md index b6cc015f1e..fab08c09cf 100644 --- a/generated-docs/out/backends/future.md +++ b/generated-docs/out/backends/future.md @@ -20,7 +20,7 @@ Class Supported stream type To use, you don't need any extra dependencies, `core` is enough: ``` -"com.softwaremill.sttp.client4" %% "core" % "3.8.13" +"com.softwaremill.sttp.client4" %% "core" % "4.0.0-M1" ``` You'll need the following imports: @@ -59,7 +59,7 @@ Host header override is supported in environments running Java 12 onwards, but i To use, add the following dependency to your project: ```scala -"com.softwaremill.sttp.client4" %% "okhttp-backend" % "3.8.13" +"com.softwaremill.sttp.client4" %% "okhttp-backend" % "4.0.0-M1" ``` and some imports: @@ -91,7 +91,7 @@ This backend depends on [OkHttp](http://square.github.io/okhttp/) and fully supp To use, add the following dependency to your project: ``` -"com.softwaremill.sttp.client4" %% "armeria-backend" % "3.8.13" +"com.softwaremill.sttp.client4" %% "armeria-backend" % "4.0.0-M1" ``` add imports: diff --git a/generated-docs/out/backends/http4s.md b/generated-docs/out/backends/http4s.md index b2b575f4a9..992a74eae4 100644 --- a/generated-docs/out/backends/http4s.md +++ b/generated-docs/out/backends/http4s.md @@ -3,9 +3,9 @@ This backend is based on [http4s](https://http4s.org) (client) and is **asynchronous**. To use, add the following dependency to your project: ```scala -"com.softwaremill.sttp.client4" %% "http4s-backend" % "3.8.13" // for cats-effect 3.x & http4s 1.0.0-Mx +"com.softwaremill.sttp.client4" %% "http4s-backend" % "4.0.0-M1" // for cats-effect 3.x & http4s 1.0.0-Mx // or -"com.softwaremill.sttp.client4" %% "http4s-ce2-backend" % "3.8.13" // for cats-effect 2.x & http4s 0.21.x +"com.softwaremill.sttp.client4" %% "http4s-ce2-backend" % "4.0.0-M1" // for cats-effect 2.x & http4s 0.21.x ``` The backend can be created in a couple of ways, e.g.: @@ -17,10 +17,10 @@ import sttp.client4._ import sttp.client4.http4s._ // the "org.http4s" %% "http4s-ember-client" % http4sVersion dependency needs to be explicitly added -Http4sBackend.usingDefaultEmberClientBuilder[IO](): Resource[IO, SttpBackend[IO, Fs2Streams[IO]]] +Http4sBackend.usingDefaultEmberClientBuilder[IO](): Resource[IO, StreamBackend[IO, Fs2Streams[IO]]] // the "org.http4s" %% "http4s-blaze-client" % http4sVersion dependency needs to be explicitly added -Http4sBackend.usingDefaultBlazeClientBuilder[IO](): Resource[IO, SttpBackend[IO, Fs2Streams[IO]]] +Http4sBackend.usingDefaultBlazeClientBuilder[IO](): Resource[IO, StreamBackend[IO, Fs2Streams[IO]]] ``` Sending a request is a non-blocking, lazily-evaluated operation and results in a wrapped response. There's a transitive dependency on `http4s`. diff --git a/generated-docs/out/backends/javascript/fetch.md b/generated-docs/out/backends/javascript/fetch.md index 8615308e67..95d0a9ed84 100644 --- a/generated-docs/out/backends/javascript/fetch.md +++ b/generated-docs/out/backends/javascript/fetch.md @@ -7,7 +7,7 @@ A JavaScript backend with web socket support. Implemented using the [Fetch API]( This is the default backend, available in the main jar for JS. To use, add the following dependency to your project: ``` -"com.softwaremill.sttp.client4" %%% "core" % "3.8.13" +"com.softwaremill.sttp.client4" %%% "core" % "4.0.0-M1" ``` And create the backend instance: @@ -26,7 +26,7 @@ Note that `Fetch` does not pass cookies by default. If your request needs cookie To use, add the following dependency to your project: ``` -"com.softwaremill.sttp.client4" %%% "monix" % "3.8.13" +"com.softwaremill.sttp.client4" %%% "monix" % "4.0.0-M1" ``` And create the backend instance: @@ -40,7 +40,7 @@ val backend = FetchMonixBackend() To use, add the following dependency to your project: ``` -"com.softwaremill.sttp.client4" %%% "zio" % "3.8.13" +"com.softwaremill.sttp.client4" %%% "zio" % "4.0.0-M1" ``` And create the backend instance: @@ -55,13 +55,13 @@ Any effect implementing the cats-effect `Concurrent` typeclass can be used. To u your project: ``` -"com.softwaremill.sttp.client4" %%% "cats" % "3.8.13" +"com.softwaremill.sttp.client4" %%% "cats" % "4.0.0-M1" ``` If you are on Cats Effect 2 (CE2) you will need to add the CE2 specific dependency instead: ``` -"com.softwaremill.sttp.client4" %%% "catsce2 % "3.8.13" +"com.softwaremill.sttp.client4" %%% "catsce2 % "4.0.0-M1" ``` And create the backend instance: @@ -129,7 +129,7 @@ Streaming support is provided via `FetchMonixBackend`. Note that streaming suppo To use, add the following dependency to your project: ``` -"com.softwaremill.sttp.client4" %%% "monix" % "3.8.13" +"com.softwaremill.sttp.client4" %%% "monix" % "4.0.0-M1" ``` An example of streaming a response: @@ -174,6 +174,6 @@ import sttp.client4._ def processEvents(source: Observable[ServerSentEvent]): Task[Unit] = ??? -basicRequest.response(asStream(MonixStreams)(stream => +basicRequest.response(asStream(MonixStreams)(stream => processEvents(stream.transform(MonixServerSentEvents.parse)))) ``` diff --git a/generated-docs/out/backends/monix.md b/generated-docs/out/backends/monix.md index 79772b86dc..5725b64e98 100644 --- a/generated-docs/out/backends/monix.md +++ b/generated-docs/out/backends/monix.md @@ -12,7 +12,7 @@ Creation of the backend can be done in two basic ways: Firstly, add the following dependency to your project: ``` -"com.softwaremill.sttp.client4" %% "monix" % "3.8.13" +"com.softwaremill.sttp.client4" %% "monix" % "4.0.0-M1" ``` and create the backend using: @@ -26,16 +26,12 @@ HttpClientMonixBackend().flatMap { backend => ??? } HttpClientMonixBackend.resource().use { backend => ??? } // or, if you'd like to instantiate the HttpClient yourself: - import java.net.http.HttpClient - val httpClient: HttpClient = ??? val backend = HttpClientMonixBackend.usingClient(httpClient) // or, obtain a cats-effect Resource with a custom instance of the HttpClient: - import java.net.http.HttpClient - val httpClient: HttpClient = ??? HttpClientMonixBackend.resourceUsingClient(httpClient).use { backend => ??? } ``` @@ -54,7 +50,7 @@ Host header override is supported in environments running Java 12 onwards, but i To use, add the following dependency to your project: ```scala -"com.softwaremill.sttp.client4" %% "okhttp-backend-monix" % "3.8.13" +"com.softwaremill.sttp.client4" %% "okhttp-backend-monix" % "4.0.0-M1" ``` Create the backend using: @@ -68,9 +64,7 @@ OkHttpMonixBackend().flatMap { backend => ??? } OkHttpMonixBackend.resource().use { backend => ??? } // or, if you'd like to instantiate the OkHttpClient yourself: - import okhttp3._ - val okHttpClient: OkHttpClient = ??? val backend = OkHttpMonixBackend.usingClient(okHttpClient) ``` @@ -82,7 +76,7 @@ This backend depends on [OkHttp](http://square.github.io/okhttp/) and fully supp To use, add the following dependency to your project: ``` -"com.softwaremill.sttp.client4" %% "armeria-backend-monix" % "3.8.13" +"com.softwaremill.sttp.client4" %% "armeria-backend-monix" % "4.0.0-M1" ``` add imports: @@ -137,7 +131,7 @@ import sttp.client4.httpclient.monix.HttpClientMonixBackend import monix.reactive.Observable HttpClientMonixBackend().flatMap { backend => - val obs: Observable[Array[Byte]] = ??? + val obs: Observable[Array[Byte]] = ??? basicRequest .streamBody(MonixStreams)(obs) @@ -164,7 +158,7 @@ HttpClientMonixBackend().flatMap { backend => .response(asStreamUnsafe(MonixStreams)) .readTimeout(Duration.Inf) .send(backend) - response + response } ``` @@ -187,6 +181,6 @@ import sttp.client4._ def processEvents(source: Observable[ServerSentEvent]): Task[Unit] = ??? -basicRequest.response(asStream(MonixStreams)(stream => +basicRequest.response(asStream(MonixStreams)(stream => processEvents(stream.transform(MonixServerSentEvents.parse)))) ``` diff --git a/generated-docs/out/backends/native/curl.md b/generated-docs/out/backends/native/curl.md index e7a96d387f..eec2cca161 100644 --- a/generated-docs/out/backends/native/curl.md +++ b/generated-docs/out/backends/native/curl.md @@ -5,7 +5,7 @@ A Scala Native backend implemented using [Curl](https://github.com/curl/curl/blo To use, add the following dependency to your project: ``` -"com.softwaremill.sttp.client4" %%% "core" % "3.8.13" +"com.softwaremill.sttp.client4" %%% "core" % "4.0.0-M1" ``` and initialize one of the backends: diff --git a/generated-docs/out/backends/scalaz.md b/generated-docs/out/backends/scalaz.md index c43ba694f4..7bf26f8300 100644 --- a/generated-docs/out/backends/scalaz.md +++ b/generated-docs/out/backends/scalaz.md @@ -8,7 +8,7 @@ The [Scalaz](https://github.com/scalaz/scalaz) backend is **asynchronous**. Send To use, add the following dependency to your project: ``` -"com.softwaremill.sttp.client4" %% "armeria-backend-scalaz" % "3.8.13" +"com.softwaremill.sttp.client4" %% "armeria-backend-scalaz" % "4.0.0-M1" ``` add imports: diff --git a/generated-docs/out/backends/summary.md b/generated-docs/out/backends/summary.md index 476b98c2c4..af24513f92 100644 --- a/generated-docs/out/backends/summary.md +++ b/generated-docs/out/backends/summary.md @@ -4,17 +4,24 @@ sttp supports a number of synchronous and asynchronous backends. It's the backen Choosing the right backend depends on a number of factors: whether you are using sttp to explore some data, or is it a production system; are you using a synchronous, blocking architecture, or an asynchronous one; do you work mostly with Scala's `Future`, or maybe you use some form of a `Task` abstraction; finally, if you want to stream requests/responses, or not. -Which one to choose? +## Default backends -* for simple exploratory requests, use the [synchronous](synchronous.md) `HttpClientSyncBackend`. -* if you have Akka in your stack, use the [Akka backend](akka.md) -* if you are using `Future` without Akka, use the `HttpClientFutureBackend` -* finally, if you are using a functional effect wrapper, use one of the "functional" backends, for [ZIO](zio.md), [Monix](monix.md), [Scalaz](scalaz.md), [cats-effect](catseffect.md) or [fs2](fs2.md). +As a starting point, the default backends are good choice. Depending on the platform, the following are available: + +* on the JVM, `DefaultSyncBackend` and `DefaultFutureBackend`: both based on Java's HTTP client +* on JS, `DefaultFutureBackend`, based on Fetch +* on Native, `DefaultSyncBackend`, based on curl -Each backend has two type parameters: +These default backends provide limited customisation options, hence for any more advanced use-cases, simply substitute them with a specific implementation. E.g. the `HttpClientSyncBackend` backend, which is the underlying implementation of `DefaultSyncBackend`, offers customisation options not available in the default one. + +## Backends overview + +Which one to choose? -* `F[_]`, the effects wrapper for responses. That is, when you invoke `send(backend)` on a request description, do you get a `Response[_]` directly, or is it wrapped in a `Future` or a `Task`? -* `P`, the capabilities supported by the backend, in addition to `Effect[F]`. If `Any`, no additional capabilities are provided. Might include `Streams` (the ability to send and receive streaming bodies) and `WebSockets` (the ability to handle websocket requests). +* for simple exploratory requests, use the [synchronous](synchronous.md) `DefaultSyncBackend` / `HttpClientSyncBackend`. +* if you have Akka in your stack, use the [Akka backend](akka.md) +* if you are using `Future` without Akka, use the `DefaultFutureBackend` / `HttpClientFutureBackend` +* finally, if you are using a functional effect wrapper, use one of the "functional" backends, for [ZIO](zio.md), [Monix](monix.md), [Scalaz](scalaz.md), [cats-effect](catseffect.md) or [fs2](fs2.md). Below is a summary of all the JVM backends; see the sections on individual backend implementations for more information: @@ -22,7 +29,9 @@ Below is a summary of all the JVM backends; see the sections on individual backe ==================================== ================================ ================================================= ========================== =================== Class Effect type Supported stream type Supports websockets Fully non-blocking ==================================== ================================ ================================================= ========================== =================== +``DefaultSyncBackend`` None (``Identity``) n/a no no ``HttpClientSyncBackend`` None (``Identity``) n/a no no +``DefaultFutureBackend`` ``scala.concurrent.Future`` n/a yes (regular) no ``HttpClientFutureBackend`` ``scala.concurrent.Future`` n/a yes (regular) no ``HttpClientMonixBackend`` ``monix.eval.Task`` ``monix.reactive.Observable[ByteBuffer]`` yes (regular & streaming) yes ``HttpClientFs2Backend`` ``F[_]: cats.effect.Concurrent`` ``fs2.Stream[F, Byte]`` yes (regular & streaming) yes @@ -50,7 +59,7 @@ Class Effect type Supported ==================================== ================================ ================================================= ========================== =================== ``` -The backends work with Scala 2.11, 2.12, 2.13 and 3 (with some exceptions for 2.11 and 3). +The backends work with Scala 2.12, 2.13 and 3. Backends supporting cats-effect are available in versions for cats-effect 2.x (dependency artifacts have the `-ce2` suffix) and 3.x. @@ -59,6 +68,7 @@ All backends that support asynchronous/non-blocking streams, also support server There are also backends which wrap other backends to provide additional functionality. These include: * `TryBackend`, which safely wraps any exceptions thrown by a synchronous backend in `scala.util.Try` +* `EitherBackend`, which represents exceptions as the left side of an `Either` * `OpenTelemetryTracingBackend`, for OpenTelemetry-compatible distributed tracing. See the [dedicated section](wrappers/opentelemetry.md). * `OpenTelemetryMetricsBackend`, for OpenTelemetry-compatible metrics. See the [dedicated section](wrappers/opentelemetry.md). * `PrometheusBackend`, for gathering Prometheus-format metrics. See the [dedicated section](wrappers/prometheus.md). @@ -73,6 +83,7 @@ In addition, there are also backends for Scala.JS: ================================ ================================ ========================================= =================== Class Effect type Supported stream type Supports websockets ================================ ================================ ========================================= =================== +``DefaultFutureBackend`` ``scala.concurrent.Future`` n/a yes (regular) ``FetchBackend`` ``scala.concurrent.Future`` n/a yes (regular) ``FetchMonixBackend`` ``monix.eval.Task`` ``monix.reactive.Observable[ByteBuffer]`` yes (regular & streaming) ``FetchZioBackend`` ``zio.Task`` ``zio.stream.Stream[Throwable, Byte]`` yes (regular & streaming) @@ -86,6 +97,7 @@ And a backend for scala-native: ================================ ============================ ========================================= =================== Class Effect type Supported stream type Supports websockets ================================ ============================ ========================================= =================== +``DefaultSyncBackend`` None (``Identity``) n/a no ``CurlBackend`` None (``Identity``) n/a no ================================ ============================ ========================================= =================== ``` @@ -94,4 +106,19 @@ Finally, there are third-party backends: * [sttp-play-ws](https://github.com/scalamania/sttp-play-ws) for "standard" play-ws (not standalone). * [akkaMonixSttpBackend](https://github.com/fullfacing/akkaMonixSttpBackend), an Akka-based backend, but using Monix's `Task` & `Observable`. -* [be-kind-rewind](https://github.com/reibitto/be-kind-rewind), a VCR testing library for Scala \ No newline at end of file +* [be-kind-rewind](https://github.com/reibitto/be-kind-rewind), a VCR testing library for Scala + +## Backend types + +Depending on the capabilities that a backend supports, the exact backend type differs: + +* `SyncBackend` are backends which are synchronous, blocking, and don't support streaming. +* `Backend[F]` are backends which don't support streaming or web sockets, and use `F` to represent side effects (e.g. obtaining a response for a request) +* `StreamBackend[F, S]` are backends which support streaming, use `F` to represent side effects, and `S` to represent streams of bytes. +* `WebSocketBackend[F]` are backends which support web sockets, use `F` to represent side effects +* `WebSocketStreamBackend[F, S]` are backends which support web sockets and streaming, use `F` to represent side effects, and `S` to represent streams of bytes. + +Each backend type extends `GenericBackend` has two type parameters: + +* `F[_]`, the type constructor used to represent side effects. That is, when you invoke `send(backend)` on a request description, do you get a `Response[_]` directly, or is it wrapped in a `Future` or a `Task`? +* `P`, the capabilities supported by the backend, in addition to `Effect[F]`. If `Any`, no additional capabilities are provided. Might include `Streams` (the ability to send and receive streaming bodies) and `WebSockets` (the ability to handle websocket requests). \ No newline at end of file diff --git a/generated-docs/out/backends/synchronous.md b/generated-docs/out/backends/synchronous.md index dcb2732db3..ddcfdd8cb2 100644 --- a/generated-docs/out/backends/synchronous.md +++ b/generated-docs/out/backends/synchronous.md @@ -7,7 +7,7 @@ There are several synchronous backend implementations. Sending a request using t The default **synchronous** backend. To use, you don't need any extra dependencies, `core` is enough: ``` -"com.softwaremill.sttp.client4" %% "core" % "3.8.13" +"com.softwaremill.sttp.client4" %% "core" % "4.0.0-M1" ``` Create the backend using: @@ -23,7 +23,6 @@ or, if you'd like to instantiate the HttpClient yourself: ```scala import sttp.client4.httpclient.HttpClientSyncBackend import java.net.http.HttpClient - val httpClient: HttpClient = ??? val backend = HttpClientSyncBackend.usingClient(httpClient) ``` @@ -41,7 +40,7 @@ Host header override is supported in environments running Java 12 onwards, but i To use, you don't need any extra dependencies, `core` is enough: ``` -"com.softwaremill.sttp.client4" %% "core" % "3.8.13" +"com.softwaremill.sttp.client4" %% "core" % "4.0.0-M1" ``` Create the backend using: @@ -63,7 +62,7 @@ This backend supports host header override, but it has to be enabled by system p To use, add the following dependency to your project: ``` -"com.softwaremill.sttp.client4" %% "okhttp-backend" % "3.8.13" +"com.softwaremill.sttp.client4" %% "okhttp-backend" % "4.0.0-M1" ``` Create the backend using: diff --git a/generated-docs/out/backends/wrappers/custom.md b/generated-docs/out/backends/wrappers/custom.md index cd96660d79..44a5a206cf 100644 --- a/generated-docs/out/backends/wrappers/custom.md +++ b/generated-docs/out/backends/wrappers/custom.md @@ -45,10 +45,10 @@ metrics for completed requests and wraps any `Future`-based backend: import sttp.capabilities.Effect import sttp.client4._ import sttp.client4.akkahttp._ +import sttp.client4.wrappers.DelegateBackend import scala.concurrent.Future import scala.concurrent.ExecutionContext.Implicits.global import scala.util._ - // the metrics infrastructure trait MetricsServer { def reportDuration(name: String, duration: Long): Unit @@ -59,11 +59,11 @@ class CloudMetricsServer extends MetricsServer { } // the backend wrapper -class MetricWrapper[P](delegate: SttpBackend[Future, P], +abstract class MetricWrapper[P](delegate: GenericBackend[Future, P], metrics: MetricsServer) - extends DelegateBackend[Future, P](delegate) { + extends DelegateBackend(delegate) { - override def send[T, R >: P with Effect[Future]](request: Request[T, R]): Future[Response[T]] = { + override def send[T](request: GenericRequest[T, P with Effect[Future]]): Future[Response[T]] = { val start = System.currentTimeMillis() def report(metricSuffix: String): Unit = { @@ -74,17 +74,22 @@ class MetricWrapper[P](delegate: SttpBackend[Future, P], delegate.send(request).andThen { case Success(response) if response.is200 => report("ok") - case Success(response) => report("notok") - case Failure(t) => report("exception") + case Success(response) => report("notok") + case Failure(t) => report("exception") } } } +object MetricWrapper { + def apply[S]( + backend: WebSocketStreamBackend[Future, S], + metrics: MetricsServer + ): WebSocketStreamBackend[Future, S] = + new MetricWrapper(backend, metrics) with WebSocketStreamBackend[Future, S] {} +} + // example usage -val backend = new MetricWrapper( - AkkaHttpBackend(), - new CloudMetricsServer() -) +val backend = MetricWrapper(AkkaHttpBackend(), new CloudMetricsServer()) basicRequest .get(uri"http://company.com/api/service1") @@ -102,24 +107,25 @@ Handling retries is a complex problem when it comes to HTTP requests. When is a * only idempotent HTTP methods (such as `GET`) could potentially be retried * some HTTP status codes might also be retryable (e.g. `500 Internal Server Error` or `503 Service Unavailable`) -In some cases it's possible to implement a generic retry mechanism; such a mechanism should take into account logging, metrics, limiting the number of retries and a backoff mechanism. These mechanisms could be quite simple, or involve e.g. retry budgets (see [Finagle's](https://twitter.github.io/finagle/guide/Clients.md#retries) documentation on retries). In sttp, it's possible to recover from errors using the `responseMonad`. A starting point for a retrying backend could be: +In some cases it's possible to implement a generic retry mechanism; such a mechanism should take into account logging, metrics, limiting the number of retries and a backoff mechanism. These mechanisms could be quite simple, or involve e.g. retry budgets (see [Finagle's](https://twitter.github.io/finagle/guide/Clients.md#retries) documentation on retries). In sttp, it's possible to recover from errors using the `monad`. A starting point for a retrying backend could be: ```scala import sttp.capabilities.Effect import sttp.client4._ +import sttp.client4.wrappers.DelegateBackend class RetryingBackend[F[_], P]( - delegate: SttpBackend[F, P], - shouldRetry: RetryWhen, - maxRetries: Int) - extends DelegateBackend[F, P](delegate) { + delegate: GenericBackend[F, P], + shouldRetry: RetryWhen, + maxRetries: Int) + extends DelegateBackend(delegate) { - override def send[T, R >: P with Effect[F]](request: Request[T, R]): F[Response[T]] = { + override def send[T](request: GenericRequest[T, P with Effect[F]]): F[Response[T]] = { sendWithRetryCounter(request, 0) } - private def sendWithRetryCounter[T, R >: P with Effect[F]]( - request: Request[T, R], retries: Int): F[Response[T]] = { + private def sendWithRetryCounter[T]( + request: GenericRequest[T, P with Effect[F]], retries: Int): F[Response[T]] = { val r = monad.handleError(delegate.send(request)) { case t if shouldRetry(request, Left(t)) && retries < maxRetries => @@ -135,6 +141,11 @@ class RetryingBackend[F[_], P]( } } } + +object RetryingBackend { + def apply[F[_]](backend: WebSocketBackend[F], shouldRetry: RetryWhen, maxRetries: Int): WebSocketBackend[F] = + new RetryingBackend(backend, shouldRetry, maxRetries) with WebSocketBackend[F] {} +} ``` ## Example backend with circuit breaker @@ -148,29 +159,33 @@ Below is an example on how to implement a backend wrapper, which integrates with ```scala import io.github.resilience4j.circuitbreaker.{CallNotPermittedException, CircuitBreaker} import sttp.capabilities.Effect -import sttp.client4.{Request, Response, SttpBackend, DelegateBackend} +import sttp.client4.{GenericBackend, GenericRequest, Backend, Response} +import sttp.client4.wrappers.DelegateBackend import sttp.monad.MonadError import java.util.concurrent.TimeUnit class CircuitSttpBackend[F[_], P]( - circuitBreaker: CircuitBreaker, - delegate: SttpBackend[F, P]) extends DelegateBackend[F, P](delegate) { + circuitBreaker: CircuitBreaker, + delegate: GenericBackend[F, P]) extends DelegateBackend(delegate) { - override def send[T, R >: P with Effect[F]](request: Request[T, R]): F[Response[T]] = { + override def send[T](request: GenericRequest[T, P with Effect[F]]): F[Response[T]] = { CircuitSttpBackend.decorateF(circuitBreaker, delegate.send(request)) } } object CircuitSttpBackend { + def apply[F[_]](circuitBreaker: CircuitBreaker, backend: Backend[F]): Backend[F] = + new CircuitSttpBackend(circuitBreaker, backend) with Backend[F] {} + def decorateF[F[_], T]( - circuitBreaker: CircuitBreaker, - service: => F[T] - )(implicit monadError: MonadError[F]): F[T] = { + circuitBreaker: CircuitBreaker, + service: => F[T] + )(implicit monadError: MonadError[F]): F[T] = { monadError.suspend { if (!circuitBreaker.tryAcquirePermission()) { monadError.error(CallNotPermittedException - .createCallNotPermittedException(circuitBreaker)) + .createCallNotPermittedException(circuitBreaker)) } else { val start = System.nanoTime() try { @@ -203,25 +218,31 @@ Below is an example on how to implement a backend wrapper, which integrates with import io.github.resilience4j.ratelimiter.RateLimiter import sttp.capabilities.Effect import sttp.monad.MonadError -import sttp.client4.{Request, Response, SttpBackend, DelegateBackend} +import sttp.client4.{GenericBackend, GenericRequest, Response, StreamBackend} +import sttp.client4.wrappers.DelegateBackend class RateLimitingSttpBackend[F[_], P]( - rateLimiter: RateLimiter, - delegate: SttpBackend[F, P] - )(implicit monadError: MonadError[F]) extends DelegateBackend[F, P](delegate) { + rateLimiter: RateLimiter, + delegate: GenericBackend[F, P] + )(implicit monadError: MonadError[F]) extends DelegateBackend(delegate) { - override def send[T, R >: P with Effect[F]](request: Request[T, R]): F[Response[T]] = { + override def send[T](request: GenericRequest[T, P with Effect[F]]): F[Response[T]] = { RateLimitingSttpBackend.decorateF(rateLimiter, delegate.send(request)) } } object RateLimitingSttpBackend { + def apply[F[_], S]( + rateLimiter: RateLimiter, + backend: StreamBackend[F, S] + )(implicit monadError: MonadError[F]): StreamBackend[F, S] = + new RateLimitingSttpBackend(rateLimiter, backend) with StreamBackend[F, S] {} def decorateF[F[_], T]( - rateLimiter: RateLimiter, - service: => F[T] - )(implicit monadError: MonadError[F]): F[T] = { - monadError.suspend { + rateLimiter: RateLimiter, + service: => F[T] + )(implicit monadError: MonadError[F]): F[T] = { + monadError.suspend { try { RateLimiter.waitForPermission(rateLimiter) service @@ -239,7 +260,7 @@ object RateLimitingSttpBackend { Implementing a new backend is made easy as the tests are published in the `core` jar file under the `tests` classifier. Simply add the follow dependencies to your `build.sbt`: ``` -"com.softwaremill.sttp.client4" %% "core" % "3.8.13" % Test classifier "tests" +"com.softwaremill.sttp.client4" %% "core" % "4.0.0-M1" % Test classifier "tests" ``` Implement your backend and extend the `HttpTest` class: @@ -251,8 +272,7 @@ import scala.concurrent.Future class MyCustomBackendHttpTest extends HttpTest[Future] { override implicit val convertToFuture: ConvertToFuture[Future] = ConvertToFuture.future - override lazy val backend: SttpBackend[Future, Any] = ??? //new MyCustomBackend() - + override lazy val backend: Backend[Future] = ??? //new MyCustomBackend() override def timeoutToNone[T](t: Future[T], timeoutMillis: Int): Future[Option[T]] = ??? } ``` @@ -270,9 +290,9 @@ import sttp.client4.impl.cats.implicits._ from the cats integration module. The module should be available on the classpath after adding following dependency: ```scala -"com.softwaremill.sttp.client4" %% "cats" % "3.8.13" // for cats-effect 3.x +"com.softwaremill.sttp.client4" %% "cats" % "4.0.0-M1" // for cats-effect 3.x // or -"com.softwaremill.sttp.client4" %% "catsce2" % "3.8.13" // for cats-effect 2.x +"com.softwaremill.sttp.client4" %% "catsce2" % "4.0.0-M1" // for cats-effect 2.x ``` The object contains implicits to convert a cats `MonadError` into the sttp `MonadError`, diff --git a/generated-docs/out/backends/wrappers/logging.md b/generated-docs/out/backends/wrappers/logging.md index 72faec74cd..42caba7967 100644 --- a/generated-docs/out/backends/wrappers/logging.md +++ b/generated-docs/out/backends/wrappers/logging.md @@ -28,7 +28,7 @@ Log levels can be configured when creating the `LoggingBackend`, or specified in To use the [slf4j](http://www.slf4j.org) logging backend wrapper, add the following dependency to your project: ``` -"com.softwaremill.sttp.client4" %% "slf4j-backend" % "3.8.13" +"com.softwaremill.sttp.client4" %% "slf4j-backend" % "4.0.0-M1" ``` There are three backend wrappers available, which log request & response information using a slf4j `Logger`. To see the logs, you'll need to use an slf4j-compatible logger implementation, e.g. [logback](http://logback.qos.ch), or use a binding, e.g. [log4j-slf4j](https://logging.apache.org/log4j/2.0/log4j-slf4j-impl/index.html). @@ -39,7 +39,7 @@ Example usage: import sttp.client4._ import sttp.client4.logging.slf4j.Slf4jLoggingBackend -val backend = Slf4jLoggingBackend(HttpClientSyncBackend()) +val backend = Slf4jLoggingBackend(DefaultSyncBackend()) basicRequest.get(uri"https://httpbin.org/get").send(backend) ``` @@ -50,5 +50,5 @@ To create a customised logging backend, see the section on [custom backends](cus To use the [scribe](https://github.com/outr/scribe) logging backend wrapper, add the following dependency to your project: ``` -"com.softwaremill.sttp.client4" %% "scribe-backend" % "3.8.13" +"com.softwaremill.sttp.client4" %% "scribe-backend" % "4.0.0-M1" ``` \ No newline at end of file diff --git a/generated-docs/out/backends/wrappers/opentelemetry.md b/generated-docs/out/backends/wrappers/opentelemetry.md index 506748ce1c..41fe03a302 100644 --- a/generated-docs/out/backends/wrappers/opentelemetry.md +++ b/generated-docs/out/backends/wrappers/opentelemetry.md @@ -12,7 +12,7 @@ The backend depends only on [opentelemetry-api](https://github.com/open-telemetr following dependency to your project: ``` -"com.softwaremill.sttp.client4" %% "opentelemetry-metrics-backend" % "3.8.13" +"com.softwaremill.sttp.client4" %% "opentelemetry-metrics-backend" % "4.0.0-M1" ``` Then an instance can be obtained as follows: @@ -24,7 +24,7 @@ import sttp.client4.opentelemetry._ import io.opentelemetry.api.OpenTelemetry // any effect and capabilities are supported -val sttpBackend: SttpBackend[Future, Any] = ??? +val sttpBackend: Backend[Future] = ??? val openTelemetry: OpenTelemetry = ??? OpenTelemetryMetricsBackend(sttpBackend, openTelemetry) @@ -38,13 +38,15 @@ import sttp.client4._ import sttp.client4.opentelemetry._ import io.opentelemetry.api.OpenTelemetry -val sttpBackend: SttpBackend[Future, Any] = ??? +val sttpBackend: Backend[Future] = ??? val openTelemetry: OpenTelemetry = ??? OpenTelemetryMetricsBackend( sttpBackend, - openTelemetry, - responseToSuccessCounterMapper = _ => Some(CollectorConfig("my_custom_counter_name")) + OpenTelemetryMetricsConfig( + openTelemetry, + responseToSuccessCounterMapper = _ => Some(CollectorConfig("my_custom_counter_name")) + ) ) ``` @@ -53,8 +55,8 @@ OpenTelemetryMetricsBackend( To use, add the following dependency to your project (the `zio-*` modules depend on ZIO 2.x; for ZIO 1.x support, use `zio1-*`): ``` -"com.softwaremill.sttp.client4" %% "opentelemetry-tracing-zio-backend" % "3.8.13" // for ZIO 2.x -"com.softwaremill.sttp.client4" %% "opentelemetry-tracing-zio1-backend" % "3.8.13" // for ZIO 1.x +"com.softwaremill.sttp.client4" %% "opentelemetry-tracing-zio-backend" % "4.0.0-M1" // for ZIO 2.x +"com.softwaremill.sttp.client4" %% "opentelemetry-tracing-zio1-backend" % "4.0.0-M1" // for ZIO 1.x ``` This backend depends on [zio-opentelemetry](https://github.com/zio/zio-telemetry). @@ -70,7 +72,7 @@ import zio._ import zio.telemetry.opentelemetry._ import sttp.client4.opentelemetry.zio._ -val zioBackend: SttpBackend[Task, Any] = ??? +val zioBackend: Backend[Task] = ??? val tracing: Tracing = ??? OpenTelemetryTracingZioBackend(zioBackend, tracing) diff --git a/generated-docs/out/backends/wrappers/prometheus.md b/generated-docs/out/backends/wrappers/prometheus.md index 65ecfdbc53..c35ad70086 100644 --- a/generated-docs/out/backends/wrappers/prometheus.md +++ b/generated-docs/out/backends/wrappers/prometheus.md @@ -3,7 +3,7 @@ To use, add the following dependency to your project: ``` -"com.softwaremill.sttp.client4" %% "prometheus-backend" % "3.8.13" +"com.softwaremill.sttp.client4" %% "prometheus-backend" % "4.0.0-M1" ``` and some imports: @@ -18,7 +18,6 @@ The Prometheus backend wraps any other backend, for example: ```scala import sttp.client4.akkahttp._ - val backend = PrometheusBackend(AkkaHttpBackend()) ``` @@ -26,10 +25,11 @@ It gathers request execution times in `Histogram`. It uses by default `sttp_requ ```scala import sttp.client4.akkahttp._ - val backend = PrometheusBackend( AkkaHttpBackend(), - requestToHistogramNameMapper = request => Some(HistogramCollectorConfig(request.uri.host.getOrElse("example.com"))) + PrometheusConfig( + requestToHistogramNameMapper = request => Some(HistogramCollectorConfig(request.uri.host.getOrElse("example.com"))) + ) ) ``` @@ -37,18 +37,18 @@ You can disable request histograms by passing `None` returning function: ```scala import sttp.client4.akkahttp._ - -val backend = PrometheusBackend(AkkaHttpBackend(), requestToHistogramNameMapper = _ => None) +val backend = PrometheusBackend(AkkaHttpBackend(), PrometheusConfig(requestToHistogramNameMapper = _ => None)) ``` This backend also offers `Gauge` with currently in-progress requests number. It uses by default `sttp_requests_in_progress` name, defined in `PrometheusBackend.DefaultRequestsInProgressGaugeName`. It is possible to define custom gauge name by passing function mapping request to gauge name: ```scala import sttp.client4.akkahttp._ - val backend = PrometheusBackend( AkkaHttpBackend(), - requestToInProgressGaugeNameMapper = request => Some(CollectorConfig(request.uri.host.getOrElse("example.com"))) + PrometheusConfig( + requestToInProgressGaugeNameMapper = request => Some(CollectorConfig(request.uri.host.getOrElse("example.com"))) + ) ) ``` @@ -56,6 +56,5 @@ You can disable request in-progress gauges by passing `None` returning function: ```scala import sttp.client4.akkahttp._ - -val backend = PrometheusBackend(AkkaHttpBackend(), requestToInProgressGaugeNameMapper = _ => None) +val backend = PrometheusBackend(AkkaHttpBackend(), PrometheusConfig(requestToInProgressGaugeNameMapper = _ => None)) ``` diff --git a/generated-docs/out/backends/zio.md b/generated-docs/out/backends/zio.md index 92239d0ec9..2d80f9797a 100644 --- a/generated-docs/out/backends/zio.md +++ b/generated-docs/out/backends/zio.md @@ -9,8 +9,8 @@ The `*-zio` modules depend on ZIO 2.x. For ZIO 1.x support, use modules with the To use, add the following dependency to your project: ``` -"com.softwaremill.sttp.client4" %% "zio" % "3.8.13" // for ZIO 2.x -"com.softwaremill.sttp.client4" %% "zio1" % "3.8.13" // for ZIO 1.x +"com.softwaremill.sttp.client4" %% "zio" % "4.0.0-M1" // for ZIO 2.x +"com.softwaremill.sttp.client4" %% "zio1" % "4.0.0-M1" // for ZIO 1.x ``` Create the backend using: @@ -24,9 +24,7 @@ HttpClientZioBackend().flatMap { backend => ??? } HttpClientZioBackend.scoped().flatMap { backend => ??? } // or, if you'd like to instantiate the HttpClient yourself: - import java.net.http.HttpClient - val httpClient: HttpClient = ??? val backend = HttpClientZioBackend.usingClient(httpClient) @@ -47,8 +45,8 @@ Host header override is supported in environments running Java 12 onwards, but i To use, add the following dependency to your project: ``` -"com.softwaremill.sttp.client4" %% "armeria-backend-zio" % "3.8.13" // for ZIO 2.x -"com.softwaremill.sttp.client4" %% "armeria-backend-zio1" % "3.8.13" // for ZIO 1.x +"com.softwaremill.sttp.client4" %% "armeria-backend-zio" % "4.0.0-M1" // for ZIO 2.x +"com.softwaremill.sttp.client4" %% "armeria-backend-zio1" % "4.0.0-M1" // for ZIO 1.x ``` add imports: @@ -108,15 +106,15 @@ import sttp.client4._ import sttp.client4.httpclient.zio._ import zio._ -class MyService(sttpBackend: SttpBackend[Task, Any]) { +class MyService(sttpBackend: Backend[Task]) { def runLogic(): Task[Response[String]] = { val request = basicRequest.response(asStringAlways).get(uri"https://httpbin.org/get") - sttpBackend.send(request) + request.send(sttpBackend) } } object MyService { - val live: ZLayer[SttpBackend[Task, Any], Any, MyService] = ZLayer.fromFunction(new MyService(_)) + val live: ZLayer[Backend[Task], Any, MyService] = ZLayer.fromFunction(new MyService(_)) } ZLayer.make[MyService](MyService.live, HttpClientZioBackend.layer()) @@ -128,13 +126,11 @@ As yet another alternative to effectfully or resourcefully creating backend inst ```scala package sttp.client4.httpclient.zio + type SttpClient = SttpBackend[Task, ZioStreams with WebSockets] -type SttpClient = SttpBackend[Task, ZioStreams with WebSockets] - -// or, when using Armeria -package sttp.client4.armeria.zio - -type SttpClient = SttpBackend[Task, ZioStreams] + // or, when using Armeria + package sttp.client4.armeria.zio + type SttpClient = SttpBackend[Task, ZioStreams] ``` The lifecycle of the `SttpClient` service is described by `ZLayer`s, which can be created using the `.layer`/`.layerUsingConfig`/... methods on `HttpClientZioBackend` / `ArmeriaZioBackend`. @@ -143,12 +139,12 @@ The `SttpClient` companion object contains effect descriptions which use the `St ```scala mdoc:compile-only import sttp.client4._ -import sttp.client4.httpclient.zio._ -import zio._ + import sttp.client4.httpclient.zio._ + import zio._ -val request = basicRequest.get(uri"https://httpbin.org/get") -val sent: ZIO[SttpClient, Throwable, Response[Either[String, String]]] = - send(request) + val request = basicRequest.get(uri"https://httpbin.org/get") + val sent: ZIO[SttpClient, Throwable, Response[Either[String, String]]] = + send(request) ``` ## Streaming @@ -165,14 +161,14 @@ import sttp.client4._ import zio.stream._ import zio.Task -val sttpBackend: SttpBackend[Task, ZioStreams] = ??? -val s: Stream[Throwable, Byte] = ??? +val sttpBackend: StreamBackend[Task, ZioStreams] = ??? +val s: Stream[Throwable, Byte] = ??? val request = basicRequest - .streamBody(ZioStreams)(s) .post(uri"...") + .streamBody(ZioStreams)(s) -sttpBackend.send(request) +request.send(sttpBackend) ``` And receive response bodies as a stream: @@ -186,7 +182,7 @@ import zio.stream._ import scala.concurrent.duration.Duration -val sttpBackend: SttpBackend[Task, ZioStreams] = ??? +val sttpBackend: StreamBackend[Task, ZioStreams] = ??? val request = basicRequest @@ -194,7 +190,7 @@ val request = .response(asStreamUnsafe(ZioStreams)) .readTimeout(Duration.Inf) -val response: ZIO[Any, Throwable, Response[Either[String, Stream[Throwable, Byte]]]] = sttpBackend.send(request) +val response: ZIO[Any, Throwable, Response[Either[String, Stream[Throwable, Byte]]]] = request.send(sttpBackend) ``` ## Websockets @@ -223,6 +219,7 @@ import sttp.client4._ def processEvents(source: Stream[Throwable, ServerSentEvent]): Task[Unit] = ??? -basicRequest.response(asStream(ZioStreams)(stream => - processEvents(stream.viaFunction(ZioServerSentEvents.parse)))) +basicRequest + .get(uri"...") + .response(asStream(ZioStreams)(stream => processEvents(stream.viaFunction(ZioServerSentEvents.parse)))) ``` diff --git a/generated-docs/out/conf/proxy.md b/generated-docs/out/conf/proxy.md index 95cee22708..2f900b9fd9 100644 --- a/generated-docs/out/conf/proxy.md +++ b/generated-docs/out/conf/proxy.md @@ -15,7 +15,7 @@ Otherwise, proxy values can be specified manually when creating a backend: ```scala import sttp.client4._ -val backend = HttpClientSyncBackend( +val backend = DefaultSyncBackend( options = BackendOptions.httpProxy("some.host", 8080)) basicRequest @@ -33,7 +33,7 @@ BackendOptions.httpProxy("some.host", 8080, "username", "password") ## Ignoring and allowing specific hosts -There are two additional settings that can be provided to via `SttpBackendOptions`: +There are two additional settings that can be provided to via `BackendOptions`: * `nonProxyHosts`: used to define hosts for which request SHOULD NOT be proxied * `onlyProxyHosts`: used to define hosts for which request SHOULD be proxied diff --git a/generated-docs/out/conf/redirects.md b/generated-docs/out/conf/redirects.md index 554c8b4a7f..334ea01c08 100644 --- a/generated-docs/out/conf/redirects.md +++ b/generated-docs/out/conf/redirects.md @@ -34,11 +34,14 @@ You can disable the stripping of all sensitive headers using the following code: ```scala import sttp.client4._ - -val myBackend: SttpBackend[Identity, Any] = HttpClientSyncBackend() -val backend: SttpBackend[Identity, Any] = new FollowRedirectsBackend( - delegate = myBackend, - sensitiveHeaders = Set.empty +import sttp.client4.wrappers.{FollowRedirectsBackend, FollowRedirectsConfig} + +val myBackend: SyncBackend = DefaultSyncBackend() +val backend: SyncBackend = FollowRedirectsBackend( + delegate = myBackend, + FollowRedirectsConfig( + sensitiveHeaders = Set.empty + ) ) ``` @@ -47,11 +50,14 @@ If you just want to disable stripping of the `Authorization` header, you can do ```scala import sttp.client4._ import sttp.model._ +import sttp.client4.wrappers.{FollowRedirectsBackend, FollowRedirectsConfig} -val myBackend: SttpBackend[Identity, Any] = HttpClientSyncBackend() -val backend: SttpBackend[Identity, Any] = new FollowRedirectsBackend( +val myBackend: SyncBackend = DefaultSyncBackend() +val backend: SyncBackend = FollowRedirectsBackend( delegate = myBackend, - sensitiveHeaders = HeaderNames.SensitiveHeaders.filterNot(_ == HeaderNames.Authorization.toLowerCase) + FollowRedirectsConfig( + sensitiveHeaders = HeaderNames.SensitiveHeaders.filterNot(_ == HeaderNames.Authorization.toLowerCase) + ) ) ``` @@ -66,23 +72,23 @@ For example: ```scala import sttp.capabilities.Effect import sttp.client4._ +import sttp.client4.wrappers.FollowRedirectsBackend import sttp.monad.MonadError -class MyWrapper[F[_], P] private(delegate: SttpBackend[F, P]) - extends SttpBackend[F, P] { +abstract class MyWrapper[F[_], P] private (delegate: GenericBackend[F, P]) + extends GenericBackend[F, P] { - def send[T, R >: P with Effect[F]](request: Request[T, R]): F[Response[T]] = ??? + def send[T](request: GenericRequest[T, P with Effect[F]]): F[Response[T]] = ??? def close(): F[Unit] = ??? - def responseMonad: MonadError[F] = ??? + def monad: MonadError[F] = ??? } object MyWrapper { - def apply[F[_], P]( - delegate: SttpBackend[F, P]): SttpBackend[F, P] = { + def apply[F[_]](delegate: Backend[F]): Backend[F] = { // disables any other FollowRedirectsBackend-s further down the delegate chain - new FollowRedirectsBackend(new MyWrapper(delegate)) + FollowRedirectsBackend(new MyWrapper(delegate) with Backend[F] {}) } } ``` @@ -95,12 +101,15 @@ For example: ```scala import sttp.client4._ +import sttp.client4.wrappers.{FollowRedirectsBackend, FollowRedirectsConfig} import sttp.model.Uri.QuerySegmentEncoding -val myBackend: SttpBackend[Identity, Any] = HttpClientSyncBackend() -val backend: SttpBackend[Identity, Any] = new FollowRedirectsBackend( +val myBackend: SyncBackend = DefaultSyncBackend() +val backend: SyncBackend = FollowRedirectsBackend( delegate = myBackend, - // encodes all special characters in the query segment, including the allowed ones - transformUri = _.querySegmentsEncoding(QuerySegmentEncoding.All) + FollowRedirectsConfig( + // encodes all special characters in the query segment, including the allowed ones + transformUri = _.querySegmentsEncoding(QuerySegmentEncoding.All) + ) ) ``` \ No newline at end of file diff --git a/generated-docs/out/conf/ssl.md b/generated-docs/out/conf/ssl.md index a7c0607204..fd44e7f23c 100644 --- a/generated-docs/out/conf/ssl.md +++ b/generated-docs/out/conf/ssl.md @@ -63,6 +63,7 @@ Using `SSLContext` from [first section](#ssl-context) define a function to custo ```scala import sttp.client4._ +import sttp.client4.httpurlconnection.HttpURLConnectionBackend import java.net.HttpURLConnection import javax.net.ssl.HttpsURLConnection @@ -129,13 +130,12 @@ import cats.effect.IO import cats.effect.kernel.Resource import cats.effect.std.Dispatcher import java.net.http.HttpClient -import sttp.capabilities.WebSockets import sttp.capabilities.fs2.Fs2Streams -import sttp.client4.SttpBackend +import sttp.client4.WebSocketStreamBackend import sttp.client4.httpclient.fs2.HttpClientFs2Backend val httpClient: HttpClient = HttpClient.newBuilder().sslContext(ssl).build() -val backend: Resource[IO, SttpBackend[IO, Fs2Streams[IO] with WebSockets]] = HttpClientFs2Backend.resourceUsingClient[IO](httpClient) +val backend: Resource[IO, WebSocketStreamBackend[IO, Fs2Streams[IO]]] = HttpClientFs2Backend.resourceUsingClient[IO](httpClient) ``` ## Using Async-http-client (deprecated) @@ -148,6 +148,7 @@ Using `kmf: KeyManagerFactory` and `tmf: TrustManagerFactory` from [first sectio Backends using `AsyncHttpClient` provides factory methods accepting custom config. + ```scala import io.netty.handler.ssl.SslContextBuilder import org.asynchttpclient.{AsyncHttpClientConfig, DefaultAsyncHttpClientConfig} diff --git a/generated-docs/out/conf/timeouts.md b/generated-docs/out/conf/timeouts.md index 7fe39217fd..a654ded994 100644 --- a/generated-docs/out/conf/timeouts.md +++ b/generated-docs/out/conf/timeouts.md @@ -12,7 +12,7 @@ import sttp.client4._ import scala.concurrent.duration._ // all backends provide a constructor that allows to specify backend options -val backend = HttpURLConnectionBackend( +val backend = DefaultSyncBackend( options = BackendOptions.connectionTimeout(1.minute)) basicRequest diff --git a/generated-docs/out/examples.md b/generated-docs/out/examples.md index bb59e80464..9d35361944 100644 --- a/generated-docs/out/examples.md +++ b/generated-docs/out/examples.md @@ -7,7 +7,7 @@ All of the examples are available [in the sources](https://github.com/softwaremi Required dependencies: ```scala -libraryDependencies ++= List("com.softwaremill.sttp.client4" %% "core" % "3.8.13") +libraryDependencies ++= List("com.softwaremill.sttp.client4" %% "core" % "4.0.0-M1") ``` Example code: @@ -22,7 +22,7 @@ Example code: Required dependencies: ```scala -libraryDependencies ++= List("com.softwaremill.sttp.client4" %% "core" % "3.8.13") +libraryDependencies ++= List("com.softwaremill.sttp.client4" %% "core" % "4.0.0-M1") ``` Example code: @@ -38,8 +38,8 @@ Required dependencies: ```scala libraryDependencies ++= List( - "com.softwaremill.sttp.client4" %% "akka-http-backend" % "3.8.13", - "com.softwaremill.sttp.client4" %% "json4s" % "3.8.13", + "com.softwaremill.sttp.client4" %% "akka-http-backend" % "4.0.0-M1", + "com.softwaremill.sttp.client4" %% "json4s" % "4.0.0-M1", "org.json4s" %% "json4s-native" % "3.6.0" ) ``` @@ -57,9 +57,9 @@ Required dependencies: ```scala libraryDependencies ++= List( - "com.softwaremill.sttp.client4" %% "zio" % "3.8.13", - "com.softwaremill.sttp.client4" %% "circe" % "3.8.13", - "io.circe" %% "circe-generic" % "0.14.1" + "com.softwaremill.sttp.client4" %% "zio" % "4.0.0-M1", + "com.softwaremill.sttp.client4" %% "circe" % "4.0.0-M1", + "io.circe" %% "circe-generic" % "0.14.5" ) ``` @@ -76,9 +76,9 @@ Required dependencies: ```scala libraryDependencies ++= List( - "com.softwaremill.sttp.client4" %% "monix" % "3.8.13", - "com.softwaremill.sttp.client4" %% "circe" % "3.8.13", - "io.circe" %% "circe-generic" % "0.14.1" + "com.softwaremill.sttp.client4" %% "monix" % "4.0.0-M1", + "com.softwaremill.sttp.client4" %% "circe" % "4.0.0-M1", + "io.circe" %% "circe-generic" % "0.14.5" ) ``` @@ -95,9 +95,9 @@ Required dependencies: ```scala libraryDependencies ++= List( - "com.softwaremill.sttp.client4" %% "slf4j-backend" % "3.8.13", - "com.softwaremill.sttp.client4" %% "circe" % "3.8.13", - "io.circe" %% "circe-generic" % "0.14.1" + "com.softwaremill.sttp.client4" %% "slf4j-backend" % "4.0.0-M1", + "com.softwaremill.sttp.client4" %% "circe" % "4.0.0-M1", + "io.circe" %% "circe-generic" % "0.14.5" ) ``` @@ -114,9 +114,9 @@ Required dependencies: ```scala libraryDependencies ++= List( - "com.softwaremill.sttp.client4" %% "monix" % "3.8.13", - "com.softwaremill.sttp.client4" %% "circe" % "3.8.13", - "io.circe" %% "circe-generic" % "0.14.1" + "com.softwaremill.sttp.client4" %% "monix" % "4.0.0-M1", + "com.softwaremill.sttp.client4" %% "circe" % "4.0.0-M1", + "io.circe" %% "circe-generic" % "0.14.5" ) ``` @@ -132,7 +132,7 @@ Example code: Required dependencies: ```scala -libraryDependencies ++= List("com.softwaremill.sttp.client4" %% "core" % "3.8.13") +libraryDependencies ++= List("com.softwaremill.sttp.client4" %% "core" % "4.0.0-M1") ``` Example code: @@ -146,7 +146,7 @@ Example code: Required dependencies: ```scala -libraryDependencies ++= List("com.softwaremill.sttp.client4" %% "zio" % "3.8.13") +libraryDependencies ++= List("com.softwaremill.sttp.client4" %% "zio" % "4.0.0-M1") ``` Example code: @@ -161,7 +161,7 @@ Example code: Required dependencies: ```scala -libraryDependencies ++= List("com.softwaremill.sttp.client4" %% "fs2" % "3.8.13") +libraryDependencies ++= List("com.softwaremill.sttp.client4" %% "fs2" % "4.0.0-M1") ``` Example code: @@ -176,7 +176,7 @@ Example code: Required dependencies: ```scala -libraryDependencies ++= List("com.softwaremill.sttp.client4" %% "monix" % "3.8.13") +libraryDependencies ++= List("com.softwaremill.sttp.client4" %% "monix" % "4.0.0-M1") ``` Example code: @@ -191,7 +191,7 @@ Example code: Required dependencies: ```scala -libraryDependencies ++= List("com.softwaremill.sttp.client4" %% "akka-http-backend" % "3.8.13") +libraryDependencies ++= List("com.softwaremill.sttp.client4" %% "akka-http-backend" % "4.0.0-M1") ``` Example code: @@ -206,7 +206,7 @@ Example code: Required dependencies: ```scala -libraryDependencies ++= List("com.softwaremill.sttp.client4" %% "monix" % "3.8.13") +libraryDependencies ++= List("com.softwaremill.sttp.client4" %% "monix" % "4.0.0-M1") ``` Example code: @@ -221,7 +221,7 @@ Example code: Required dependencies: ```scala -libraryDependencies ++= List("com.softwaremill.sttp.client4" %% "fs2" % "3.8.13") +libraryDependencies ++= List("com.softwaremill.sttp.client4" %% "fs2" % "4.0.0-M1") ``` Example code: @@ -236,7 +236,7 @@ Example code: Required dependencies: ```scala -libraryDependencies ++= List("com.softwaremill.sttp.client4" %% "zio" % "3.8.13") +libraryDependencies ++= List("com.softwaremill.sttp.client4" %% "zio" % "4.0.0-M1") ``` Example code: @@ -251,7 +251,7 @@ Example code: Required dependencies: ```scala -libraryDependencies ++= List("com.softwaremill.sttp.client4" %% "zio" % "3.8.13") +libraryDependencies ++= List("com.softwaremill.sttp.client4" %% "zio" % "4.0.0-M1") ``` Example code: @@ -266,7 +266,7 @@ Example code: Required dependencies: ```scala -libraryDependencies ++= List("com.softwaremill.sttp.client4" %% "core" % "3.8.13") +libraryDependencies ++= List("com.softwaremill.sttp.client4" %% "core" % "4.0.0-M1") ``` Example code: diff --git a/generated-docs/out/how.md b/generated-docs/out/how.md index 6c4fb55e28..1814672bfc 100644 --- a/generated-docs/out/how.md +++ b/generated-docs/out/how.md @@ -12,8 +12,7 @@ To start describing a request, import the sttp client package and customise `bas ```scala import sttp.client4._ - -val myRequest: Request[_, _] = ??? // basicRequest.(...) +val myRequest: Request[_] = ??? // basicRequest.(...) ``` An alternative to importing the `sttp.client4._` package, is to extend the `sttp.client4.SttpApi` trait. That way, multiple integrations can be grouped in one object, thus reducing the number of necessary imports. @@ -32,9 +31,8 @@ For example, the following sends a synchronous request, using the default JVM ba ```scala import sttp.client4._ - -val myRequest: Request[String, Any] = ??? -val backend = HttpClientSyncBackend() +val myRequest: Request[String] = ??? +val backend = DefaultSyncBackend() val response = myRequest.send(backend) ``` diff --git a/generated-docs/out/index.md b/generated-docs/out/index.md index bd4b905f5e..a81d849f06 100644 --- a/generated-docs/out/index.md +++ b/generated-docs/out/index.md @@ -3,9 +3,9 @@ Welcome! [sttp client](https://github.com/softwaremill/sttp) is an open-source library which provides a clean, programmer-friendly API to describe HTTP -requests and how to handle responses. Requests are sent using one of the backends, which wrap other Scala or Java HTTP client implementations. The backends can integrate with a variety of Scala stacks, providing both synchronous and asynchronous, procedural and functional interfaces. - -Backend implementations include ones based on [akka-http](https://doc.akka.io/docs/akka-http/current/scala/http/), [http4s](https://http4s.org), [OkHttp](http://square.github.io/okhttp/), and HTTP clients which ship with Java. They integrate with [Akka](https://akka.io), [Monix](https://monix.io), [fs2](https://github.com/functional-streams-for-scala/fs2), [cats-effect](https://github.com/typelevel/cats-effect), [scalaz](https://github.com/scalaz/scalaz) and [ZIO](https://github.com/zio/zio). Supported Scala versions include 2.11, 2.12, 2.13 and 3, Scala.JS and Scala Native. +requests and how to handle responses. Requests are sent using one of the backends, which wrap lower-level Scala or Java HTTP client implementations. The backends can integrate with a variety of Scala stacks, providing both synchronous and asynchronous, procedural and functional interfaces. + +Backend implementations include the HTTP client that is shipped with Java, as well as ones based on [akka-http](https://doc.akka.io/docs/akka-http/current/scala/http/), [http4s](https://http4s.org), [OkHttp](http://square.github.io/okhttp/). They integrate with [Akka](https://akka.io), [Monix](https://monix.io), [fs2](https://github.com/functional-streams-for-scala/fs2), [cats-effect](https://github.com/typelevel/cats-effect), [scalaz](https://github.com/scalaz/scalaz) and [ZIO](https://github.com/zio/zio). Supported Scala versions include 2.12, 2.13 and 3, Scala.JS and Scala Native; supported Java versions include 11+. Here's a quick example of sttp client in action: @@ -20,7 +20,7 @@ val sort: Option[String] = None val request = basicRequest.get( uri"https://api.github.com/search/repositories?q=$query&sort=$sort") -val backend = HttpClientSyncBackend() +val backend = DefaultSyncBackend() val response = request.send(backend) // response.header(...): Option[String] @@ -70,7 +70,6 @@ We offer commercial support for sttp and related technologies, as well as develo :caption: Getting started quickstart - simple_sync how goals community diff --git a/generated-docs/out/json.md b/generated-docs/out/json.md index f74b03ad9a..38a445eff9 100644 --- a/generated-docs/out/json.md +++ b/generated-docs/out/json.md @@ -15,9 +15,9 @@ The type signatures vary depending on the underlying library (required implicits ```scala import sttp.client4._ -def asJson[B]: ResponseAs[Either[ResponseException[String, Exception], B], Any] = ??? -def asJsonAlways[B]: ResponseAs[Either[DeserializationException[Exception], B], Any] = ??? -def asJsonEither[E, B]: ResponseAs[Either[ResponseException[E, Exception], B], Any] = ??? +def asJson[B]: ResponseAs[Either[ResponseException[String, Exception], B]] = ??? +def asJsonAlways[B]: ResponseAs[Either[DeserializationException[Exception], B]] = ??? +def asJsonEither[E, B]: ResponseAs[Either[ResponseException[E, Exception], B]] = ??? ``` The response specifications can be further refined using `.getRight` and `.getEither`, see [response body specifications](responses/body.md). @@ -34,7 +34,7 @@ case class ResponsePayload(data: String) JSON encoding of bodies and decoding of responses can be handled using [Circe](https://circe.github.io/circe/) by the `circe` module. To use add the following dependency to your project: ```scala -"com.softwaremill.sttp.client4" %% "circe" % "3.8.13" +"com.softwaremill.sttp.client4" %% "circe" % "4.0.0-M1" ``` This module adds a body serialized, so that json payloads can be sent as request bodies. To send a payload of type `T` as json, a `io.circe.Encoder[T]` implicit value must be available in scope. @@ -46,10 +46,9 @@ Response can be parsed into json using `asJson[T]`, provided there's an implicit import sttp.client4._ import sttp.client4.circe._ -val backend: SttpBackend[Identity, Any] = HttpClientSyncBackend() +val backend: SyncBackend = DefaultSyncBackend() import io.circe.generic.auto._ - val requestPayload = RequestPayload("some data") val response: Identity[Response[Either[ResponseException[String, io.circe.Error], ResponsePayload]]] = @@ -67,7 +66,7 @@ Arbitrary JSON structures can be traversed by parsing the result as `io.circe.Js To encode and decode json using json4s, add the following dependency to your project: ``` -"com.softwaremill.sttp.client4" %% "json4s" % "3.8.13" +"com.softwaremill.sttp.client4" %% "json4s" % "4.0.0-M1" "org.json4s" %% "json4s-native" % "3.6.0" ``` @@ -81,7 +80,7 @@ Usage example: import sttp.client4._ import sttp.client4.json4s._ -val backend: SttpBackend[Identity, Any] = HttpClientSyncBackend() +val backend: SyncBackend = DefaultSyncBackend() val requestPayload = RequestPayload("some data") @@ -101,7 +100,7 @@ val response: Identity[Response[Either[ResponseException[String, Exception], Res To encode and decode JSON using [spray-json](https://github.com/spray/spray-json), add the following dependency to your project: ``` -"com.softwaremill.sttp.client4" %% "spray-json" % "3.8.13" +"com.softwaremill.sttp.client4" %% "spray-json" % "4.0.0-M1" ``` Using this module it is possible to set request bodies and read response bodies as your custom types, using the implicitly available instances of `spray.json.JsonWriter` / `spray.json.JsonReader` or `spray.json.JsonFormat`. @@ -113,7 +112,7 @@ import sttp.client4._ import sttp.client4.sprayJson._ import spray.json._ -val backend: SttpBackend[Identity, Any] = HttpClientSyncBackend() +val backend: SyncBackend = DefaultSyncBackend() implicit val payloadJsonFormat: RootJsonFormat[RequestPayload] = ??? implicit val myResponseJsonFormat: RootJsonFormat[ResponsePayload] = ??? @@ -133,7 +132,7 @@ val response: Identity[Response[Either[ResponseException[String, Exception], Res To encode and decode JSON using [play-json](https://www.playframework.com), add the following dependency to your project: ```scala -"com.softwaremill.sttp.client4" %% "play-json" % "3.8.13" +"com.softwaremill.sttp.client4" %% "play-json" % "4.0.0-M1" ``` To use, add an import: `import sttp.client4.playJson._`. @@ -145,13 +144,13 @@ To encode and decode JSON using the high-performance [zio-json](https://zio.gith The `zio-json` module depends on ZIO 2.x. For ZIO 1.x support, use `zio1-json`. ```scala -"com.softwaremill.sttp.client4" %% "zio-json" % "3.8.13" // for ZIO 2.x -"com.softwaremill.sttp.client4" %% "zio1-json" % "3.8.13" // for ZIO 1.x +"com.softwaremill.sttp.client4" %% "zio-json" % "4.0.0-M1" // for ZIO 2.x +"com.softwaremill.sttp.client4" %% "zio1-json" % "4.0.0-M1" // for ZIO 1.x ``` or for ScalaJS (cross build) projects: ```scala -"com.softwaremill.sttp.client4" %%% "zio-json" % "3.8.13" // for ZIO 2.x -"com.softwaremill.sttp.client4" %%% "zio1-json" % "3.8.13" // for ZIO 1.x +"com.softwaremill.sttp.client4" %%% "zio-json" % "4.0.0-M1" // for ZIO 2.x +"com.softwaremill.sttp.client4" %%% "zio1-json" % "4.0.0-M1" // for ZIO 1.x ``` To use, add an import: `import sttp.client4.ziojson._` (or extend `SttpZioJsonApi`), define an implicit `JsonCodec`, or `JsonDecoder`/`JsonEncoder` for your datatype. @@ -163,7 +162,7 @@ import sttp.client4._ import sttp.client4.ziojson._ import zio.json._ -val backend: SttpBackend[Identity, Any] = HttpClientSyncBackend() +val backend: SyncBackend = DefaultSyncBackend() implicit val payloadJsonEncoder: JsonEncoder[RequestPayload] = DeriveJsonEncoder.gen[RequestPayload] implicit val myResponseJsonDecoder: JsonDecoder[ResponsePayload] = DeriveJsonDecoder.gen[ResponsePayload] @@ -171,11 +170,11 @@ implicit val myResponseJsonDecoder: JsonDecoder[ResponsePayload] = DeriveJsonDec val requestPayload = RequestPayload("some data") val response: Identity[Response[Either[ResponseException[String, String], ResponsePayload]]] = - basicRequest - .post(uri"...") - .body(requestPayload) - .response(asJson[ResponsePayload]) - .send(backend) +basicRequest + .post(uri"...") + .body(requestPayload) + .response(asJson[ResponsePayload]) + .send(backend) ``` ## Jsoniter-scala @@ -183,13 +182,13 @@ val response: Identity[Response[Either[ResponseException[String, String], Respon To encode and decode JSON using the [high(est)-performant](https://plokhotnyuk.github.io/jsoniter-scala/) [jsoniter-scala](https://github.com/plokhotnyuk/jsoniter-scala) library, one add the following dependency to your project. ```scala -"com.softwaremill.sttp.client4" %% "jsoniter" % "3.8.13" +"com.softwaremill.sttp.client4" %% "jsoniter" % "4.0.0-M1" ``` or for ScalaJS (cross build) projects: ```scala -"com.softwaremill.sttp.client4" %%% "jsoniter" % "3.8.13" +"com.softwaremill.sttp.client4" %%% "jsoniter" % "4.0.0-M1" ``` To use, add an import: `import sttp.client4.jsoniter._` (or extend `SttpJsonIterJsonApi`), define an implicit `JsonCodec`, or `JsonDecoder`/`JsonEncoder` for your datatype. @@ -203,7 +202,7 @@ import sttp.client4.jsoniter._ import com.github.plokhotnyuk.jsoniter_scala.core._ import com.github.plokhotnyuk.jsoniter_scala.macros._ -val backend: SttpBackend[Identity, Any] = HttpClientSyncBackend() +val backend: SyncBackend = DefaultSyncBackend() implicit val payloadJsonCodec: JsonValueCodec[RequestPayload] = JsonCodecMaker.make //note that the jsoniter doesn't support 'implicit defs' and so either has to be generated seperatly @@ -211,11 +210,11 @@ implicit val jsonEitherDecoder: JsonValueCodec[ResponsePayload] = JsonCodecMaker val requestPayload = RequestPayload("some data") val response: Identity[Response[Either[ResponseException[String, Exception], ResponsePayload]]] = - basicRequest - .post(uri"...") - .body(requestPayload) - .response(asJson[ResponsePayload]) - .send(backend) +basicRequest + .post(uri"...") + .body(requestPayload) + .response(asJson[ResponsePayload]) + .send(backend) ``` ## uPickle @@ -223,13 +222,13 @@ val response: Identity[Response[Either[ResponseException[String, Exception], Res To encode and decode JSON using the [uPickle](https://github.com/com-lihaoyi/upickle) library, add the following dependency to your project: ```scala -"com.softwaremill.sttp.client4" %% "upickle" % "3.8.13" +"com.softwaremill.sttp.client4" %% "upickle" % "4.0.0-M1" ``` or for ScalaJS (cross build) projects: ```scala -"com.softwaremill.sttp.client4" %%% "upickle" % "3.8.13" +"com.softwaremill.sttp.client4" %%% "upickle" % "4.0.0-M1" ``` To use, add an import: `import sttp.client4.upicklejson._` (or extend `SttpUpickleApi`) and define an implicit `ReadWriter` (or separately `Reader` and `Writer`) for your datatype. @@ -240,7 +239,7 @@ import sttp.client4._ import sttp.client4.upicklejson._ import upickle.default._ -val backend: SttpBackend[Identity, Any] = HttpClientSyncBackend() +val backend: SyncBackend = DefaultSyncBackend() implicit val requestPayloadRW: ReadWriter[RequestPayload] = macroRW[RequestPayload] implicit val responsePayloadRW: ReadWriter[ResponsePayload] = macroRW[ResponsePayload] @@ -248,9 +247,9 @@ implicit val responsePayloadRW: ReadWriter[ResponsePayload] = macroRW[ResponsePa val requestPayload = RequestPayload("some data") val response: Identity[Response[Either[ResponseException[String, Exception], ResponsePayload]]] = - basicRequest - .post(uri"...") - .body(requestPayload) - .response(asJson[ResponsePayload]) - .send(backend) +basicRequest + .post(uri"...") + .body(requestPayload) + .response(asJson[ResponsePayload]) + .send(backend) ``` diff --git a/generated-docs/out/model/model.md b/generated-docs/out/model/model.md index bd2279bcb7..a08a973d1a 100644 --- a/generated-docs/out/model/model.md +++ b/generated-docs/out/model/model.md @@ -28,7 +28,7 @@ object Example { val request = basicRequest.header(Header.contentType(MediaType.ApplicationJson)) .get(uri"https://httpbin.org") - val backend = HttpClientSyncBackend() + val backend = DefaultSyncBackend() val response = request.send(backend) if (response.code == StatusCode.Ok) println("Ok!") } @@ -44,7 +44,7 @@ object Example extends HeaderNames with MediaTypes with StatusCodes { val request = basicRequest.header(ContentType, ApplicationJson.toString) .get(uri"https://httpbin.org") - val backend = HttpClientSyncBackend() + val backend = DefaultSyncBackend() val response = request.send(backend) if (response.code == Ok) println("Ok!") } diff --git a/generated-docs/out/model/uri.md b/generated-docs/out/model/uri.md index 8b99a4d98c..ef146a4bbb 100644 --- a/generated-docs/out/model/uri.md +++ b/generated-docs/out/model/uri.md @@ -118,7 +118,6 @@ A fully-featured example: ```scala import sttp.client4._ - val secure = true val scheme = if (secure) "https" else "http" val subdomains = List("sub1", "sub2") diff --git a/generated-docs/out/openapi.md b/generated-docs/out/openapi.md index 860f83016d..5ce1ce5d65 100644 --- a/generated-docs/out/openapi.md +++ b/generated-docs/out/openapi.md @@ -45,8 +45,8 @@ lazy val petstoreApi: Project = project openApiGeneratorName := "scala-sttp", openApiOutputDir := baseDirectory.value.name, libraryDependencies ++= Seq( - "com.softwaremill.sttp.client4" %% "core" % "3.8.13", - "com.softwaremill.sttp.client4" %% "json4s" % "3.8.13", + "com.softwaremill.sttp.client4" %% "core" % "4.0.0-M1", + "com.softwaremill.sttp.client4" %% "json4s" % "4.0.0-M1", "org.json4s" %% "json4s-jackson" % "3.6.8" ) ) @@ -96,8 +96,8 @@ lazy val petstoreApi: Project = project openApiOutputDir := baseDirectory.value.name, openApiIgnoreFileOverride := s"${baseDirectory.in(ThisBuild).value.getPath}/openapi-ignore-file", libraryDependencies ++= Seq( - "com.softwaremill.sttp.client4" %% "core" % "3.8.13", - "com.softwaremill.sttp.client4" %% "json4s" % "3.8.13", + "com.softwaremill.sttp.client4" %% "core" % "4.0.0-M1", + "com.softwaremill.sttp.client4" %% "json4s" % "4.0.0-M1", "org.json4s" %% "json4s-jackson" % "3.6.8" ), (compile in Compile) := ((compile in Compile) dependsOn openApiGenerate).value, diff --git a/generated-docs/out/quickstart.md b/generated-docs/out/quickstart.md index 74cdb77a80..041dc17fce 100644 --- a/generated-docs/out/quickstart.md +++ b/generated-docs/out/quickstart.md @@ -1,10 +1,11 @@ # Quickstart -The core sttp client API comes in a single jar, with a transitive dependency on [sttp model](https://github.com/softwaremill/sttp-model). This also includes a default [synchronous simple client](simple_sync.md) and [synchronous](backends/synchronous.md) and [`Future`-based] backends, based on Java's `HttpClient`. +The core sttp client API comes in a single jar, with a transitive dependency on [sttp model](https://github.com/softwaremill/sttp-model). +This also includes [synchronous](backends/synchronous.md) and [`Future`-based] backends, based on Java's `HttpClient`. -To integrate with other parts of your application and various effect systems, you'll often need to use an alternate backend (but what's important is that the API remains the same!). See the section on [backends](backends/summary.md) for a short guide on which backend to choose, and a list of all implementations. +To integrate with other parts of your application and various effect systems, you'll often need to use an alternate backend, or backend wrappers (but what's important is that the API remains the same!). See the section on [backends](backends/summary.md) for a short guide on which backend to choose, and a list of all implementations. -`sttp client` is available for Scala 2.11, 2.12 and 2.13, as well as for Scala 3 and requires Java 11 or higher. +`sttp client` is available for Scala 2.12 and 2.13, as well as for Scala 3 and requires Java 11 or higher. `sttp client` is also available for Scala.js 1.0 and Scala Native. Note that not all modules are compatible with these platforms, and that each has its own dedicated set of backends. @@ -14,56 +15,125 @@ platforms, and that each has its own dedicated set of backends. The basic dependency which provides the API, together with a synchronous and `Future`-based backends, is: ```scala -"com.softwaremill.sttp.client4" %% "core" % "3.8.13" +"com.softwaremill.sttp.client4" %% "core" % "4.0.0-M1" ``` -## Simple synchronous client +## Using scala-cli -If you'd like to send some requests synchronously, take a look at the [simple synchronous client](simple_sync.md). +Add the following directive to the top of your scala file to add the core sttp dependency: + +``` +//> using lib "com.softwaremill.sttp.client4:core:4.0.0-M1" +``` ## Using Ammonite If you are an [Ammonite](https://ammonite.io) user, you can quickly start experimenting with sttp by copy-pasting the following: ```scala -import $ivy.`com.softwaremill.sttp.client4::core:3.8.13` -import sttp.client4.quick._ +import $ivy.`com.softwaremill.sttp.client4::core:4.0.0-M1` +``` + +## Imports + +Working with sttp is most convenient if you import the `sttp.client4` package entirely: -simpleHttpClient.send(quickRequest.get(uri"http://httpbin.org/ip")) +```scala +import sttp.client4._ ``` -Importing the `quick` object has the same effect as importing `sttp.client4._`, plus defining a synchronous backend (`val backend = HttpClientSyncBackend()`), so that sttp can be used right away. +This brings into scope the starting point for defining requests and some helper methods. All examples in this guide assume that this import is in place. -If the default backend is for some reason insufficient, you can also use one based on OkHttp: +## Synchronous requests + +And that's all you need to start using sttp client! To create and send your first request, import the above, type `basicRequest.` and see where your IDE's auto-complete gets you! Here's a simple request, using the synchronous backend: ```scala -import $ivy.`com.softwaremill.sttp.client4::okhttp-backend:3.8.13` -import sttp.client4.okhttp.quick._ +import sttp.client4._ -quickRequest.get(uri"http://httpbin.org/ip").send(backend) +val backend = DefaultSyncBackend() +val response = basicRequest + .body("Hello, world!") + .post(uri"https://httpbin.org/post?hello=world") + .send(backend) + +println(response.body) ``` -## Imports +Creating a backend allocates resources (such as selector threads / connection pools), so when it's no longer needed, it +should be closed using `.close()`. Typically, you should have one backend instance for your entire application. -Working with sttp is most convenient if you import the `sttp.client4` package entirely: +## Serialising and parsing JSON + +To serialize a custom type to a JSON body, or to deserialize the response body that is in the JSON format, you'll need +to add an integration with a JSON library. See [json](json.md) for a list of available libraries. + +As an example, to integrate with the [uPickle](https://github.com/com-lihaoyi/upickle) library, add the following +dependency: + +```scala +"com.softwaremill.sttp.client4" %% "upickle" % "4.0.0-M1" +``` + +Your code might then look as follows: ```scala import sttp.client4._ +import sttp.client4.upicklejson._ +import upickle.default._ + +val backend = DefaultSyncBackend() + +case class MyRequest(field1: String, field2: Int) +// selected fields from the JSON that is being returned by httpbin +case class HttpBinResponse(origin: String, headers: Map[String, String]) + +implicit val myRequestRW: ReadWriter[MyRequest] = macroRW[MyRequest] +implicit val responseRW: ReadWriter[HttpBinResponse] = macroRW[HttpBinResponse] + +val request = basicRequest + .post(uri"https://httpbin.org/post") + .body(MyRequest("test", 42)) + .response(asJson[HttpBinResponse]) +val response = request.send(backend) + +response.body match { + case Left(e) => println(s"Got response exception:\n$e") + case Right(r) => println(s"Origin's ip: ${r.origin}, header count: ${r.headers.size}") +} ``` -This brings into scope the starting point for defining requests and some helper methods. All examples in this guide assume that this import is in place. +## Adding logging -And that's all you need to start using sttp client! To create and send your first request, import the above, type `basicRequest.` and see where your IDE's auto-complete gets you! Here's a simple request, using the synchronous backend: +Logging can be added using the [logging backend wrapper](backends/wrappers/logging.md). For example, if you'd like to +use slf4j, you'll need the following dependency: + +``` +"com.softwaremill.sttp.client4" %% "slf4j-backend" % "4.0.0-M1" +``` + +Then, you'll need to configure your client: ```scala import sttp.client4._ +import sttp.client4.logging.slf4j.Slf4jLoggingBackend -val backend = HttpClientSyncBackend() -val response = basicRequest - .body("Hello, world!") - .post(uri"https://httpbin.org/post?hello=world").send(backend) +val backend = Slf4jLoggingBackend(DefaultSyncBackend()) +``` -println(response.body) +## Even quicker + +You can skip the step of creating a backend instance, by using `import sttp.client4.quick._` instead of the usual `import sttp.client4._`. +This brings into scope the same sttp API, and additionally a synchronous backend instance, which can be used to send requests. +This backend instance is global (created on first access), can't be customised and shouldn't be closed. + +The `send()` extension method allows sending requests using that `backend` instance: + +```scala +import sttp.client4.quick._ +quickRequest.get(uri"http://httpbin.org/ip").send() ``` +## Next steps + Next, read on [how sttp client works](how.md) or see some [examples](examples.md). diff --git a/generated-docs/out/requests/authentication.md b/generated-docs/out/requests/authentication.md index eae9f73eaa..11a5e88466 100644 --- a/generated-docs/out/requests/authentication.md +++ b/generated-docs/out/requests/authentication.md @@ -30,8 +30,10 @@ This type of authentication works differently. In its assumptions it is based on In order to add digest authentication support just wrap other backend as follows: ```scala -val myBackend: SttpBackend[Identity, Any] = HttpClientSyncBackend() -new DigestAuthenticationBackend(myBackend) +import sttp.client4.wrappers.DigestAuthenticationBackend + +val myBackend: SyncBackend = DefaultSyncBackend() +DigestAuthenticationBackend(myBackend) ``` Then only thing which we need to do is to pass our credentials to the relevant request: @@ -63,7 +65,6 @@ You can use sttp with OAuth2. Looking at the [OAuth2 protocol flow](https://tool 1. (A)/(B) - Your UI needs to enable the user to authenticate. Your application will then receive a callback from the authentication server, which will include an authentication code. 2. (C)/(D) - You need to send a request to the authentication server, passing in the authentication code from step 1. You'll receive an access token in response (and optionally a refresh token). For example, if you were using GitHub as your authentication server, you'd need to take the values of `clientId` and `clientSecret` from the GitHub settings, then take the `authCode` received in step 1 above, and send a request like this: - ```scala import sttp.client4.circe._ import io.circe._ @@ -72,17 +73,15 @@ import io.circe.generic.semiauto._ val authCode = "SplxlOBeZQQYbYS6WxSbIA" val clientId = "myClient123" val clientSecret = "s3cret" - case class MyTokenResponse(access_token: String, scope: String, token_type: String, refresh_token: Option[String]) - implicit val tokenResponseDecoder: Decoder[MyTokenResponse] = deriveDecoder[MyTokenResponse] -val backend = HttpClientSyncBackend() +val backend = DefaultSyncBackend() val tokenRequest = basicRequest - .post(uri"https://github.com/login/oauth/access_token?code=$authCode&grant_type=authorization_code") - .auth - .basic(clientId, clientSecret) - .header("accept", "application/json") + .post(uri"https://github.com/login/oauth/access_token?code=$authCode&grant_type=authorization_code") + .auth + .basic(clientId, clientSecret) + .header("accept","application/json") val authResponse = tokenRequest.response(asJson[MyTokenResponse]).send(backend) val accessToken = authResponse.body.map(_.access_token) ``` diff --git a/generated-docs/out/requests/basics.md b/generated-docs/out/requests/basics.md index 660012f623..9bad37c81e 100644 --- a/generated-docs/out/requests/basics.md +++ b/generated-docs/out/requests/basics.md @@ -36,8 +36,8 @@ A request definition can be created without knowing how it will be sent. But to To invoke the `send(backend)` method on a request description, you'll need an instance of `SttpBackend`: ```scala -val backend = HttpClientSyncBackend() -val response: Identity[Response[Either[String, String]]] = request.send(backend) +val backend = DefaultSyncBackend() +val response: Response[Either[String, String]] = request.send(backend) ``` The default backend uses the `Identity` effect to return responses, which is equivalent to a synchronous call (no effect at all). Other asynchronous backends use other effect types. See the section on [backends](../backends/summary.md) for more details. diff --git a/generated-docs/out/requests/body.md b/generated-docs/out/requests/body.md index 4a01a916bf..7f05de54ef 100644 --- a/generated-docs/out/requests/body.md +++ b/generated-docs/out/requests/body.md @@ -12,7 +12,6 @@ A `String` body can be set on a request as follows: ```scala import sttp.client4._ - basicRequest.body("Hello, world!") ``` @@ -20,7 +19,6 @@ It is also possible to use a different character encoding: ```scala import sttp.client4._ - basicRequest.body("Hello, world!", "utf-8") ``` @@ -35,12 +33,10 @@ val bytes: Array[Byte] = ??? basicRequest.body(bytes) import java.nio.ByteBuffer - val byteBuffer: ByteBuffer = ??? basicRequest.body(byteBuffer) import java.io.ByteArrayInputStream - val inputStream: ByteArrayInputStream = ??? basicRequest.body(inputStream) ``` @@ -61,11 +57,9 @@ To upload a file, simply set the request body as a `File` or `Path`: import sttp.client4._ import java.io.File - basicRequest.body(new File("data.txt")) import java.nio.file.Path - basicRequest.body(Path.of("data.txt")) ``` @@ -83,7 +77,6 @@ By default, the `UTF-8` encoding is used, but can be also specified explicitly: ```scala import sttp.client4._ - basicRequest.body(Map("k1" -> "v1")) basicRequest.body(Map("k1" -> "v1"), "utf-8") basicRequest.body("k1" -> "v1", "k2" -> "v2") @@ -105,7 +98,6 @@ For example, here's how to write a custom serializer for a case class, with seri ```scala import sttp.client4._ import sttp.model.MediaType - case class Person(name: String, surname: String, age: Int) // for this example, assuming names/surnames can't contain commas diff --git a/generated-docs/out/requests/cookies.md b/generated-docs/out/requests/cookies.md index e7b7b07f5c..62e7faea16 100644 --- a/generated-docs/out/requests/cookies.md +++ b/generated-docs/out/requests/cookies.md @@ -24,11 +24,11 @@ It is often necessary to copy cookies from a response, e.g. after a login reques ```scala import sttp.client4._ -val backend = HttpClientSyncBackend() +val backend = DefaultSyncBackend() val loginRequest = basicRequest - .cookie("login", "me") - .body("This is a test") - .post(uri"http://endpoint.com") + .cookie("login", "me") + .body("This is a test") + .post(uri"http://endpoint.com") val response = loginRequest.send(backend) basicRequest.cookies(response) @@ -39,11 +39,11 @@ Or, it's also possible to store only the `sttp.model.CookieWithMeta` objects (a ```scala import sttp.client4._ -val backend = HttpClientSyncBackend() +val backend = DefaultSyncBackend() val loginRequest = basicRequest - .cookie("login", "me") - .body("This is a test") - .post(uri"http://endpoint.com") + .cookie("login", "me") + .body("This is a test") + .post(uri"http://endpoint.com") val response = loginRequest.send(backend) val cookiesFromResponse = response.unsafeCookies diff --git a/generated-docs/out/requests/streaming.md b/generated-docs/out/requests/streaming.md index 784174b007..305720f7f9 100644 --- a/generated-docs/out/requests/streaming.md +++ b/generated-docs/out/requests/streaming.md @@ -23,8 +23,8 @@ val chunks = "Streaming test".getBytes("utf-8").grouped(10).to(Iterable) val source: Source[ByteString, Any] = Source.apply(chunks.toList.map(ByteString(_))) basicRequest - .streamBody(AkkaStreams)(source) .post(uri"...") + .streamBody(AkkaStreams)(source) ``` ```eval_rst diff --git a/generated-docs/out/responses/basics.md b/generated-docs/out/responses/basics.md index 677dd78fb6..a98cc7f171 100644 --- a/generated-docs/out/responses/basics.md +++ b/generated-docs/out/responses/basics.md @@ -22,9 +22,9 @@ Individual headers can be obtained using the methods: import sttp.model._ import sttp.client4._ -val backend = HttpClientSyncBackend() +val backend = DefaultSyncBackend() val request = basicRequest - .get(uri"https://httpbin.org/get") + .get(uri"https://httpbin.org/get") val response = request.send(backend) val singleHeader: Option[String] = response.header(HeaderNames.Server) diff --git a/generated-docs/out/responses/body.md b/generated-docs/out/responses/body.md index 7c6fe121ed..6d7f7014f6 100644 --- a/generated-docs/out/responses/body.md +++ b/generated-docs/out/responses/body.md @@ -28,29 +28,29 @@ import sttp.client4._ import java.io.File import java.nio.file.Path -def ignore: ResponseAs[Unit, Any] = ??? -def asString: ResponseAs[Either[String, String], Any] = ??? -def asStringAlways: ResponseAs[String, Any] = ??? -def asString(encoding: String): ResponseAs[Either[String, String], Any] = ??? -def asStringAlways(encoding: String): ResponseAs[String, Any] = ??? -def asByteArray: ResponseAs[Either[String, Array[Byte]], Any] = ??? -def asByteArrayAlways: ResponseAs[Array[Byte], Any] = ??? -def asParams: ResponseAs[Either[String, Seq[(String, String)]], Any] = ??? -def asParamsAlways: ResponseAs[Seq[(String, String)], Any] = ??? -def asParams(encoding: String): ResponseAs[Either[String, Seq[(String, String)]], Any] = ??? -def asParamsAlways(encoding: String): ResponseAs[Seq[(String, String)], Any] = ??? -def asFile(file: File): ResponseAs[Either[String, File], Any] = ??? -def asFileAlways(file: File): ResponseAs[File, Any] = ??? -def asPath(path: Path): ResponseAs[Either[String, Path], Any] = ??? -def asPathAlways(path: Path): ResponseAs[Path, Any] = ??? - -def asEither[A, B, R](onError: ResponseAs[A, R], - onSuccess: ResponseAs[B, R]): ResponseAs[Either[A, B], R] = ??? -def fromMetadata[T, R](default: ResponseAs[T, R], - conditions: ConditionalResponseAs[T, R]*): ResponseAs[T, R] = ??? - -def asBoth[A, B](l: ResponseAs[A, Any], r: ResponseAs[B, Any]): ResponseAs[(A, B), Any] = ??? -def asBothOption[A, B, R](l: ResponseAs[A, R], r: ResponseAs[B, Any]): ResponseAs[(A, Option[B]), R] = ??? +def ignore: ResponseAs[Unit] = ??? +def asString: ResponseAs[Either[String, String]] = ??? +def asStringAlways: ResponseAs[String] = ??? +def asString(encoding: String): ResponseAs[Either[String, String]] = ??? +def asStringAlways(encoding: String): ResponseAs[String] = ??? +def asByteArray: ResponseAs[Either[String, Array[Byte]]] = ??? +def asByteArrayAlways: ResponseAs[Array[Byte]] = ??? +def asParams: ResponseAs[Either[String, Seq[(String, String)]]] = ??? +def asParamsAlways: ResponseAs[Seq[(String, String)]] = ??? +def asParams(encoding: String): ResponseAs[Either[String, Seq[(String, String)]]] = ??? +def asParamsAlways(encoding: String): ResponseAs[Seq[(String, String)]] = ??? +def asFile(file: File): ResponseAs[Either[String, File]] = ??? +def asFileAlways(file: File): ResponseAs[File] = ??? +def asPath(path: Path): ResponseAs[Either[String, Path]] = ??? +def asPathAlways(path: Path): ResponseAs[Path] = ??? + +def asEither[A, B](onError: ResponseAs[A], + onSuccess: ResponseAs[B]): ResponseAs[Either[A, B]] = ??? +def fromMetadata[T](default: ResponseAs[T], + conditions: ConditionalResponseAs[T]*): ResponseAs[T] = ??? + +def asBoth[A, B](l: ResponseAs[A], r: ResponseAs[B]): ResponseAs[(A, B)] = ??? +def asBothOption[A, B](l: ResponseAs[A], r: ResponseAs[B]): ResponseAs[(A, Option[B])] = ??? ``` Hence, to discard the response body, the request description should include the following: @@ -84,7 +84,7 @@ Sometimes it's convenient to get a failed effect (or an exception thrown) when t ```scala import sttp.client4._ -basicRequest.response(asString.getRight): PartialRequest[String, Any] +basicRequest.response(asString.getRight): PartialRequest[String] ``` The combinator works in all cases where the response body is specified to be deserialized as an `Either`. If the left is already an exception, it will be thrown unchanged. Otherwise, the left-value will be wrapped in an `HttpError`. @@ -110,7 +110,7 @@ As an example, to read the response body as an int, the following response descr ```scala import sttp.client4._ -val asInt: ResponseAs[Either[String, Int], Any] = asString.mapRight(_.toInt) +val asInt: ResponseAs[Either[String, Int]] = asString.mapRight(_.toInt) basicRequest .get(uri"http://example.com") @@ -126,7 +126,7 @@ type JsonError type JsonAST def parseJson(json: String): Either[JsonError, JsonAST] = ??? -val asJson: ResponseAs[Either[JsonError, JsonAST], Any] = asStringAlways.map(parseJson) +val asJson: ResponseAs[Either[JsonError, JsonAST]] = asStringAlways.map(parseJson) basicRequest .response(asJson) @@ -148,17 +148,15 @@ import io.circe._ import io.circe.generic.auto._ sealed trait MyModel - case class SuccessModel(name: String, age: Int) extends MyModel - case class ErrorModel(message: String) extends MyModel -val myRequest: Request[Either[ResponseException[String, io.circe.Error], MyModel], Nothing] = +val myRequest: Request[Either[ResponseException[String, io.circe.Error], MyModel]] = basicRequest .get(uri"https://example.com") .response(fromMetadata( - asJson[ErrorModel], - ConditionalResponseAs(_.code == StatusCode.Ok, asJson[SuccessModel]) + asJson[ErrorModel], + ConditionalResponseAs(_.code == StatusCode.Ok, asJson[SuccessModel]) )) ``` @@ -172,13 +170,9 @@ import io.circe._ import io.circe.generic.auto._ case class MyModel(p1: Int) - sealed trait MyErrorModel - case class Conflict(message: String) extends MyErrorModel - case class BadRequest(message: String) extends MyErrorModel - case class GenericError(message: String) extends MyErrorModel basicRequest @@ -195,25 +189,25 @@ import sttp.capabilities.{Effect, Streams} import sttp.client4._ import sttp.model.ResponseMetadata -def asStream[F[_], T, S](s: Streams[S])(f: s.BinaryStream => F[T]): -ResponseAs[Either[String, T], Effect[F] with S] = ??? +def asStream[F[_], T, S](s: Streams[S])(f: s.BinaryStream => F[T]): + StreamResponseAs[Either[String, T], Effect[F] with S] = ??? def asStreamWithMetadata[F[_], T, S](s: Streams[S])( - f: (s.BinaryStream, ResponseMetadata) => F[T] -): ResponseAs[Either[String, T], Effect[F] with S] = ??? + f: (s.BinaryStream, ResponseMetadata) => F[T] + ): StreamResponseAs[Either[String, T], Effect[F] with S] = ??? -def asStreamAlways[F[_], T, S](s: Streams[S])(f: s.BinaryStream => F[T]): -ResponseAs[T, Effect[F] with S] = ??? +def asStreamAlways[F[_], T, S](s: Streams[S])(f: s.BinaryStream => F[T]): + StreamResponseAs[T, Effect[F] with S] = ??? def asStreamAlwaysWithMetadata[F[_], T, S](s: Streams[S])( - f: (s.BinaryStream, ResponseMetadata) => F[T] -): ResponseAs[T, Effect[F] with S] = ??? + f: (s.BinaryStream, ResponseMetadata) => F[T] + ): StreamResponseAs[T, Effect[F] with S] = ??? -def asStreamUnsafe[S](s: Streams[S]): -ResponseAs[Either[String, s.BinaryStream], S] = ??? +def asStreamUnsafe[S](s: Streams[S]): + StreamResponseAs[Either[String, s.BinaryStream], S] = ??? -def asStreamUnsafeAlways[S](s: Streams[S]): -ResponseAs[s.BinaryStream, S] = ??? +def asStreamUnsafeAlways[S](s: Streams[S]): + StreamResponseAs[s.BinaryStream, S] = ??? ``` All of these specifications require the streaming capability to be passed as a parameter, an implementation of `Streams[S]`. This is used to determine the type of binary streams that are supported, and to require that the backend used to send the request supports the given type of streams. These implementations are provided by the backend implementations, e.g. `AkkaStreams` or `Fs2Streams[F]`. @@ -232,7 +226,7 @@ import sttp.capabilities.akka.AkkaStreams import sttp.client4._ import sttp.client4.akkahttp.AkkaHttpBackend -val backend: SttpBackend[Future, AkkaStreams] = AkkaHttpBackend() +val backend: StreamBackend[Future, AkkaStreams] = AkkaHttpBackend() val response: Future[Response[Either[String, Source[ByteString, Any]]]] = basicRequest diff --git a/generated-docs/out/responses/exceptions.md b/generated-docs/out/responses/exceptions.md index c84e0d2176..dbf07da8fc 100644 --- a/generated-docs/out/responses/exceptions.md +++ b/generated-docs/out/responses/exceptions.md @@ -25,8 +25,7 @@ This means that a typical `asJson` response specification will result in the bod ```scala import sttp.client4._ - -def asJson[T]: ResponseAs[Either[ResponseException[String, Exception], T], Any] = ??? +def asJson[T]: ResponseAs[Either[ResponseException[String, Exception], T]] = ??? ``` There are also the `.getRight` and `.getEither` methods on eligible response specifications, which convert http errors or deserialization exceptions as failed effects. diff --git a/generated-docs/out/simple_sync.md b/generated-docs/out/simple_sync.md deleted file mode 100644 index dd761e6f78..0000000000 --- a/generated-docs/out/simple_sync.md +++ /dev/null @@ -1,88 +0,0 @@ -# Simple synchronous client - -The core module of sttp client includes a simple, synchronous client, which can be used to send requests without -the need to choose or explicitly create a backend. - -A simple request can be sent as follows: - -```scala -import sttp.client4.{SimpleHttpClient, UriContext, basicRequest} - -val client = SimpleHttpClient() -val response = client.send(basicRequest.get(uri"https://httpbin.org/get")) -println(response.body) -``` - -Creating a client allocates resources (such as selector threads / connection pools), so when it's no longer needed, it -should be closed using `.close()`. Typically, you should have one client instance for your entire application. - -## Serialising and parsing JSON - -To serialize a custom type to a JSON body, or to deserialize the response body that is in the JSON format, you'll need -to add an integration with a JSON library. See [json](json.md) for a list of available libraries. - -As an example, to integrate with the [uPickle](https://github.com/com-lihaoyi/upickle) library, add the following -dependency: - -```scala -"com.softwaremill.sttp.client4" %% "upickle" % "3.8.13" -``` - -Your code might then look as follows: - -```scala -import sttp.client4.{SimpleHttpClient, UriContext, basicRequest} -import sttp.client4.upicklejson._ -import upickle.default._ - -val client = SimpleHttpClient() - -case class MyRequest(field1: String, field2: Int) - -// selected fields from the JSON that is being returned by httpbin -case class HttpBinResponse(origin: String, headers: Map[String, String]) - -implicit val myRequestRW: ReadWriter[MyRequest] = macroRW[MyRequest] -implicit val responseRW: ReadWriter[HttpBinResponse] = macroRW[HttpBinResponse] - -val request = basicRequest - .post(uri"https://httpbin.org/post") - .body(MyRequest("test", 42)) - .response(asJson[HttpBinResponse]) -val response = client.send(request) - -response.body match { - case Left(e) => println(s"Got response exception:\n$e") - case Right(r) => println(s"Origin's ip: ${r.origin}, header count: ${r.headers.size}") -} -``` - -## Adding logging - -Logging can be added using the [logging backend wrapper](backends/wrappers/logging.md). For example, if you'd like to -use slf4j, you'll need the following dependency: - -``` -"com.softwaremill.sttp.client4" %% "slf4j-backend" % "3.8.13" -``` - -Then, you'll need to configure your client: - -```scala -import sttp.client4.{SimpleHttpClient, UriContext, basicRequest} -import sttp.client4.logging.slf4j.Slf4jLoggingBackend - -val client = SimpleHttpClient().wrapBackend(Slf4jLoggingBackend(_)) -``` - -## Relationship with backends - -The `SimpleHttpClient` serves as a simple starting point for sending requests in a synchronous way. For more advanced -use-cases, you should use an [sttp backend](backends/summary.md) directly. For example, if you'd like to send requests -asynchronously, getting a `Future` as the result. Or, if you manage side effects using an `IO` or `Task`. - -In fact, an instance of `SimpleHttpClient` is a thin wrapper on top of a backend. - -## Next steps - -Read on [how sttp client works](how.md) or see some more [examples](examples.md). \ No newline at end of file diff --git a/generated-docs/out/testing.md b/generated-docs/out/testing.md index b8e1983294..8dcf3150a7 100644 --- a/generated-docs/out/testing.md +++ b/generated-docs/out/testing.md @@ -24,7 +24,7 @@ import sttp.model._ import sttp.client4.testing._ import java.io.File import scala.concurrent.Future -import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.ExecutionContext.Implicits.global case class User(id: String) ``` @@ -34,7 +34,7 @@ case class User(id: String) Behavior of the stub can be specified using a series of invocations of the `whenRequestMatches` and `thenRespond` methods: ```scala -val testingBackend = SttpBackendStub.synchronous +val testingBackend = SyncBackendStub .whenRequestMatches(_.uri.path.startsWith(List("a", "b"))) .thenRespond("Hello there!") .whenRequestMatches(_.method == Method.POST) @@ -49,7 +49,7 @@ val response2 = basicRequest.post(uri"http://example.org/d/e").send(testingBacke It is also possible to match requests by partial function, returning a response. E.g.: ```scala -val testingBackend = SttpBackendStub.synchronous +val testingBackend = SyncBackendStub .whenRequestMatchesPartial({ case r if r.uri.path.endsWith(List("partial10")) => Response("Not found", StatusCode.NotFound) @@ -75,7 +75,7 @@ val response2 = basicRequest.post(uri"http://example.org/partialAda").send(testi 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: ```scala -val testingBackend = SttpBackendStub.asynchronousFuture +val testingBackend = BackendStub.asynchronousFuture .whenAnyRequest .thenRespondF(Future { Thread.sleep(5000) @@ -88,7 +88,7 @@ val responseFuture = basicRequest.get(uri"http://example.org").send(testingBacke The returned response may also depend on the request: ```scala -val testingBackend = SttpBackendStub.synchronous +val testingBackend = SyncBackendStub .whenAnyRequest .thenRespondF(req => Response.ok(Right(s"OK, got request sent to ${req.uri.host}")) @@ -100,7 +100,7 @@ val response = basicRequest.get(uri"http://example.org").send(testingBackend) You can define consecutive raw responses that will be served: ```scala -val testingBackend: SttpBackendStub[Identity, Any] = SttpBackendStub.synchronous +val testingBackend: SyncBackendStub = SyncBackendStub .whenAnyRequest .thenRespondCyclic("first", "second", "third") @@ -113,7 +113,7 @@ basicRequest.get(uri"http://example.org").send(testingBackend) // Right("O Or multiple `Response` instances: ```scala -val testingBackend: SttpBackendStub[Identity, Any] = SttpBackendStub.synchronous +val testingBackend: SyncBackendStub = SyncBackendStub .whenAnyRequest .thenRespondCyclicResponses( Response.ok[String]("first"), @@ -128,7 +128,7 @@ basicRequest.get(uri"http://example.org").send(testingBackend) // code wil The `sttp.client4.testing` package also contains a utility method to force the body as a string (`forceBodyAsString`) or as a byte array (`forceBodyAsByteArray`), if the body is not a stream or multipart: ```scala -val testingBackend = SttpBackendStub.synchronous +val testingBackend = SyncBackendStub .whenRequestMatches(_.forceBodyAsString.contains("Hello, world!")) .thenRespond("Hello back!") ``` @@ -140,7 +140,7 @@ If the stub is given a request, for which no behavior is stubbed, it will return If you want to simulate an exception being thrown by a backend, e.g. a socket timeout exception, you can do so by throwing the appropriate exception instead of the response, e.g.: ```scala -val testingBackend = SttpBackendStub.synchronous +val testingBackend = SyncBackendStub .whenRequestMatches(_ => true) .thenRespond(throw new SttpClientException.ConnectException( basicRequest.get(uri"http://example.com"), new RuntimeException)) @@ -169,7 +169,7 @@ The following conversions are supported: For example, if you want to return a JSON response, simply use `.withResponse(String)` as below: ```scala -val testingBackend = SttpBackendStub.synchronous +val testingBackend = SyncBackendStub .whenRequestMatches(_ => true) .thenRespond(""" {"username": "john", "age": 65 } """) @@ -195,7 +195,7 @@ With the stub created as follows: ```scala val fileResponseHandle = new File("path/to/file.ext") -SttpBackendStub.synchronous +SyncBackendStub .whenRequestMatches(_ => true) .thenRespond(fileResponseHandle) ``` @@ -212,7 +212,7 @@ import sttp.monad.MonadAsyncError val sourceFile = new File("path/to/file.ext") val destinationFile = new File("path/to/file.ext") -SttpBackendStub(implicitly[MonadAsyncError[IO]]) +BackendStub(implicitly[MonadAsyncError[IO]]) .whenRequestMatches(_ => true) .thenRespondF { _ => FileUtils.copyFile(sourceFile, destinationFile) @@ -226,7 +226,7 @@ It is also possible to create a stub backend which delegates calls to another (p ```scala val testingBackend = - SttpBackendStub.withFallback(HttpClientSyncBackend()) + SyncBackendStub.withFallback(DefaultSyncBackend()) .whenRequestMatches(_.uri.path.startsWith(List("a"))) .thenRespond("I'm a STUB!") @@ -264,7 +264,7 @@ For example: import sttp.ws.testing.WebSocketStub import sttp.ws.WebSocketFrame -val backend = SttpBackendStub.synchronous +val backend = WebSocketBackendStub.synchronous val webSocketStub = WebSocketStub .initialReceive( List(WebSocketFrame.text("Hello from the server!")) @@ -292,8 +292,8 @@ Example usage: ```scala import scala.util.Try -val testingBackend = new RecordingSttpBackend( - SttpBackendStub.synchronous +val testingBackend = RecordingBackend( + SyncBackendStub .whenRequestMatches(_.uri.path.startsWith(List("a", "b"))) .thenRespond("Hello there!") ) @@ -301,5 +301,5 @@ val testingBackend = new RecordingSttpBackend( val response1 = basicRequest.get(uri"http://example.org/a/b/c").send(testingBackend) // response1.body will be Right("Hello there") -testingBackend.allInteractions: List[(Request[_, _], Try[Response[_]])] +testingBackend.allInteractions: List[(GenericRequest[_, _], Try[Response[_]])] ``` \ No newline at end of file diff --git a/generated-docs/out/websockets.md b/generated-docs/out/websockets.md index 692982fffe..24ba11e438 100644 --- a/generated-docs/out/websockets.md +++ b/generated-docs/out/websockets.md @@ -17,29 +17,28 @@ The following response specifications which use `WebSocket[F]` are available (th ```scala import sttp.client4._ -import sttp.capabilities.{Effect, WebSockets} import sttp.model.ResponseMetadata import sttp.ws.WebSocket -def asWebSocket[F[_], T](f: WebSocket[F] => F[T]): -ResponseAs[Either[String, T], Effect[F] with WebSockets] = ??? +def asWebSocket[F[_], T](f: WebSocket[F] => F[T]): + WebSocketResponseAs[F, Either[String, T]] = ??? def asWebSocketWithMetadata[F[_], T]( - f: (WebSocket[F], ResponseMetadata) => F[T] - ): ResponseAs[Either[String, T], Effect[F] with WebSockets] = ??? + f: (WebSocket[F], ResponseMetadata) => F[T] + ): WebSocketResponseAs[F, Either[String, T]] = ??? -def asWebSocketAlways[F[_], T](f: WebSocket[F] => F[T]): -ResponseAs[T, Effect[F] with WebSockets] = ??? +def asWebSocketAlways[F[_], T](f: WebSocket[F] => F[T]): + WebSocketResponseAs[F, T] = ??? def asWebSocketAlwaysWithMetadata[F[_], T]( - f: (WebSocket[F], ResponseMetadata) => F[T] - ): ResponseAs[T, Effect[F] with WebSockets] = ??? + f: (WebSocket[F], ResponseMetadata) => F[T] + ): WebSocketResponseAs[F, T] = ??? -def asWebSocketUnsafe[F[_]]: -ResponseAs[Either[String, WebSocket[F]], Effect[F] with WebSockets] = ??? +def asWebSocketUnsafe[F[_]]: + WebSocketResponseAs[F, Either[String, WebSocket[F]]] = ??? -def asWebSocketUnsafeAlways[F[_]]: -ResponseAs[WebSocket[F], Effect[F] with WebSockets] = ??? +def asWebSocketUnsafeAlways[F[_]]: + WebSocketResponseAs[F, WebSocket[F]] = ??? ``` 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. @@ -54,18 +53,18 @@ See also the [examples](examples.md), which include examples involving websocket Another possibility is to work with websockets by providing a streaming stage, which transforms incoming data frames into outgoing frames. This can be e.g. an [Akka](backends/akka.md) `Flow` or a [fs2](backends/fs2.md) `Pipe`. -The following response specifications are available: +The following response specifications are available: ```scala import sttp.client4._ import sttp.capabilities.{Streams, WebSockets} import sttp.ws.WebSocketFrame -def asWebSocketStream[S](s: Streams[S])(p: s.Pipe[WebSocketFrame.Data[_], WebSocketFrame]): -ResponseAs[Either[String, Unit], S with WebSockets] = ??? +def asWebSocketStream[S](s: Streams[S])(p: s.Pipe[WebSocketFrame.Data[_], WebSocketFrame]): + WebSocketStreamResponseAs[Either[String, Unit], S] = ??? -def asWebSocketStreamAlways[S](s: Streams[S])(p: s.Pipe[WebSocketFrame.Data[_], WebSocketFrame]): -ResponseAs[Unit, S with WebSockets] = ??? +def asWebSocketStreamAlways[S](s: Streams[S])(p: s.Pipe[WebSocketFrame.Data[_], WebSocketFrame]): + WebSocketStreamResponseAs[Unit, S] = ??? ``` 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]`. diff --git a/generated-docs/out/xml.md b/generated-docs/out/xml.md index 77d9f8d1ed..f6d5ee58a5 100644 --- a/generated-docs/out/xml.md +++ b/generated-docs/out/xml.md @@ -51,7 +51,7 @@ From now on, XML serialization/deserialization would work for all classes genera Usage example: ```scala -val backend: SttpBackend[Identity, Any] = HttpClientSyncBackend() +val backend: SyncBackend = DefaultSyncBackend() val requestPayload = Outer(Inner(42, b = true, "horses"), "cats") // `Outer` and `Inner` classes are generated by scalaxb from xsd file import sttpScalaxb._ // imports sttp related serialization / deserialization logic