Skip to content

Commit

Permalink
Merge pull request #1763 from softwaremill/default-backend
Browse files Browse the repository at this point in the history
Introduce default backends
  • Loading branch information
adamw committed Mar 14, 2023
2 parents c158735 + 41b0de8 commit af766cc
Show file tree
Hide file tree
Showing 26 changed files with 180 additions and 100 deletions.
29 changes: 20 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,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.
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 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.
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:

Expand All @@ -29,7 +29,7 @@ val query = "http language:scala"
// `sort` is removed, as the value is not defined
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]
Expand All @@ -51,18 +51,29 @@ sttp (v1) documentation is available at [sttp.softwaremill.com/en/v1](https://st

scaladoc is available at [https://www.javadoc.io](https://www.javadoc.io/doc/com.softwaremill.sttp.client4/core_2.12/3.8.13)

## Quickstart with scala-cli

Add the following directive to the top of your scala file to add the core sttp dependency:
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"
import sttp.client4.quick._
quickRequest.get(uri"http://httpbin.org/ip").send()
```

The `quick` package import brings in the sttp API and a pre-configured, global synchronous backend instance.

## Quickstart with Ammonite

If you are an [Ammonite](http://ammonite.io) user, you can quickly start experimenting with sttp by copy-pasting the following:
Similarly, using [Ammonite](http://ammonite.io):

```scala
import $ivy.`com.softwaremill.sttp.client4::core:3.8.13`
import sttp.client4.quick._
quickRequest.get(uri"http://httpbin.org/ip").send(backend)
quickRequest.get(uri"http://httpbin.org/ip").send()
```

This brings in the sttp API and a synchronous backend instance.

## Quickstart with sbt

Add the following dependency:
Expand Down Expand Up @@ -102,7 +113,7 @@ If you'd like to run the tests using *only* the JVM backend, execute: `sbt rootJ

### Importing into IntelliJ

By default, when importing to IntelliJ, only the Scala 2.13/JVM subprojects will be imported. This is controlled by the `ideSkipProject` setting in `build.sbt` (inside `commonSettings`).
By default, when importing to IntelliJ or Metals, only the Scala 2.13/JVM subprojects will be imported. This is controlled by the `ideSkipProject` setting in `build.sbt` (inside `commonSettings`).

If you'd like to work on a different platform or Scala version, simply change this setting temporarily so that the correct subprojects are imported. For example:

Expand Down Expand Up @@ -150,4 +161,4 @@ We offer commercial support for sttp and related technologies, as well as develo

## Copyright

Copyright (C) 2017-2022 SoftwareMill [https://softwaremill.com](https://softwaremill.com).
Copyright (C) 2017-2023 SoftwareMill [https://softwaremill.com](https://softwaremill.com).
21 changes: 21 additions & 0 deletions core/src/main/scalajs/sttp/client4/DefaultFutureBackend.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package sttp.client4

import sttp.client4.fetch.FetchBackend
import sttp.client4.testing.WebSocketBackendStub

import scala.concurrent.{ExecutionContext, Future}

object DefaultFutureBackend {

/** Creates a default websocket-capable backend which uses [[Future]] to represent side effects, with the given
* `options`. Currently based on [[FetchBackend]].
*/
def apply()(implicit ec: ExecutionContext = ExecutionContext.global): WebSocketBackend[Future] = FetchBackend()

/** Create a stub backend for testing, which uses [[Future]] to represent side effects, and doesn't support streaming.
*
* See [[WebSocketBackendStub]] for details on how to configure stub responses.
*/
def stub(implicit ec: ExecutionContext = ExecutionContext.global): WebSocketBackendStub[Future] =
WebSocketBackendStub.asynchronousFuture
}
4 changes: 1 addition & 3 deletions core/src/main/scalajs/sttp/client4/quick.scala
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package sttp.client4

import sttp.client4.fetch.FetchBackend

import scala.concurrent.Future

object quick extends SttpApi {
lazy val backend: Backend[Future] = FetchBackend()
lazy val backend: Backend[Future] = DefaultFutureBackend()

implicit class RichRequest[T](val request: Request[T]) {
def send(): Future[Response[T]] = backend.send(request)
Expand Down
25 changes: 25 additions & 0 deletions core/src/main/scalajvm/sttp/client4/DefaultFutureBackend.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package sttp.client4

import sttp.client4.httpclient.HttpClientFutureBackend
import sttp.client4.testing.WebSocketBackendStub

import scala.concurrent.{ExecutionContext, Future}

object DefaultFutureBackend {

/** Creates a default websocket-capable backend which uses [[Future]] to represent side effects, with the given
* `options`. Currently based on [[HttpClientFutureBackend]].
*/
def apply(
options: BackendOptions = BackendOptions.Default
)(implicit ec: ExecutionContext = ExecutionContext.global): WebSocketBackend[Future] = {
HttpClientFutureBackend(options, identity, PartialFunction.empty)
}

/** Create a stub backend for testing, which uses [[Future]] to represent side effects, and doesn't support streaming.
*
* See [[WebSocketBackendStub]] for details on how to configure stub responses.
*/
def stub(implicit ec: ExecutionContext = ExecutionContext.global): WebSocketBackendStub[Future] =
WebSocketBackendStub.asynchronousFuture
}
16 changes: 16 additions & 0 deletions core/src/main/scalajvm/sttp/client4/DefaultSyncBackend.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package sttp.client4

import sttp.client4.httpclient.HttpClientSyncBackend
import sttp.client4.testing.SyncBackendStub

object DefaultSyncBackend {

/** Creates a default synchronous backend with the given `options`, which is currently based on
* [[HttpClientSyncBackend]].
*/
def apply(options: BackendOptions = BackendOptions.Default): SyncBackend =
HttpClientSyncBackend(options, identity, PartialFunction.empty)

/** Create a stub backend for testing. See [[SyncBackendStub]] for details on how to configure stub responses. */
def stub: SyncBackendStub = SyncBackendStub
}
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,6 @@ object HttpClientBackend {
}
}

// Left here for bincompat
private[client4] def defaultClient(options: BackendOptions): HttpClient =
defaultClient(options, None)

private[client4] def defaultClient(options: BackendOptions, executor: Option[Executor]): HttpClient = {
var clientBuilder = HttpClient
.newBuilder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,14 @@ object HttpClientFutureBackend {
customizeRequest: HttpRequest => HttpRequest,
customEncodingHandler: InputStreamEncodingHandler
)(implicit ec: ExecutionContext): WebSocketBackend[Future] =
wrappers.FollowRedirectsBackend(new HttpClientFutureBackend(client, closeClient, customizeRequest, customEncodingHandler))
wrappers.FollowRedirectsBackend(
new HttpClientFutureBackend(client, closeClient, customizeRequest, customEncodingHandler)
)

def apply(
options: BackendOptions = BackendOptions.Default,
customizeRequest: HttpRequest => HttpRequest = identity,
customEncodingHandler: InputStreamEncodingHandler = PartialFunction.empty
options: BackendOptions = BackendOptions.Default,
customizeRequest: HttpRequest => HttpRequest = identity,
customEncodingHandler: InputStreamEncodingHandler = PartialFunction.empty
)(implicit ec: ExecutionContext = ExecutionContext.global): WebSocketBackend[Future] = {
val executor = Some(ec).collect { case executor: Executor => executor }
HttpClientFutureBackend(
Expand All @@ -107,9 +109,9 @@ object HttpClientFutureBackend {
customEncodingHandler
)

/** Create a stub backend for testing, which uses the [[Future]] response wrapper, and doesn't support streaming.
/** Create a stub backend for testing, which uses [[Future]] to represent side effects, and doesn't support streaming.
*
* See [[SttpBackendStub]] for details on how to configure stub responses.
* See [[WebSocketBackendStub]] for details on how to configure stub responses.
*/
def stub(implicit ec: ExecutionContext = ExecutionContext.global): WebSocketBackendStub[Future] =
WebSocketBackendStub.asynchronousFuture
Expand Down
4 changes: 1 addition & 3 deletions core/src/main/scalajvm/sttp/client4/quick.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package sttp.client4

import sttp.client4.httpclient.HttpClientSyncBackend

object quick extends SttpApi {
lazy val backend: SyncBackend = HttpClientSyncBackend()
lazy val backend: SyncBackend = DefaultSyncBackend()

implicit class RichRequest[T](val request: Request[T]) {
def send(): Response[T] = backend.send(request)
Expand Down
13 changes: 13 additions & 0 deletions core/src/main/scalanative/sttp/client4/DefaultSyncBackend.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package sttp.client4

import sttp.client4.curl.CurlBackend
import sttp.client4.testing.SyncBackendStub

object DefaultSyncBackend {

/** Creates a default synchronous backend, which is currently based on [[CurlBackend]]. */
def apply(): SyncBackend = CurlBackend()

/** Create a stub backend for testing. See [[SyncBackendStub]] for details on how to configure stub responses. */
def stub: SyncBackendStub = SyncBackendStub
}
4 changes: 1 addition & 3 deletions core/src/main/scalanative/sttp/client4/quick.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package sttp.client4

import sttp.client4.curl.CurlBackend

object quick extends SttpApi {
lazy val backend: SyncBackend = CurlBackend()
lazy val backend: SyncBackend = DefaultSyncBackend()

implicit class RichRequest[T](val request: Request[T]) {
def send(): Response[T] = backend.send(request)
Expand Down
47 changes: 37 additions & 10 deletions docs/backends/summary.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,34 @@ 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:

```eval_rst
==================================== ================================ ================================================= ========================== ===================
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
Expand Down Expand Up @@ -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.

Expand All @@ -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).
Expand All @@ -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)
Expand All @@ -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
================================ ============================ ========================================= ===================
```
Expand All @@ -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
* [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).
3 changes: 1 addition & 2 deletions docs/backends/wrappers/logging.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,9 @@ Example usage:

```scala mdoc:compile-only
import sttp.client4._
import sttp.client4.httpclient.HttpClientSyncBackend
import sttp.client4.logging.slf4j.Slf4jLoggingBackend

val backend = Slf4jLoggingBackend(HttpClientSyncBackend())
val backend = Slf4jLoggingBackend(DefaultSyncBackend())
basicRequest.get(uri"https://httpbin.org/get").send(backend)

// Logs:
Expand Down
Loading

0 comments on commit af766cc

Please sign in to comment.