Custom backends

It is also entirely possible to write custom backends (if doing so, please consider contributing!) or wrap an existing one. One can even write completely generic wrappers for any delegate backend, as each backend comes equipped with a monad for the used effect type. This brings the possibility to map and flatMap over responses.

Possible use-cases for wrapper-backend include:

  • logging

  • capturing metrics

  • request signing (transforming the request before sending it to the delegate)

See also the section on resilience which covers topics such as retries, circuit breaking and rate limiting.

Request attributes

Each request contains a attributes: AttributeMap type-safe map. This map can be used to tag the request with any backend-specific information, and isn’t used in any way by sttp itself.

Attributes can be added to a request using the def attribute[T](k: AttributeKey[T], v: T) method, and read using the def attribute[T](k: Attribute[T]): Option[T] method.

Backends, or backend wrappers can use attributes e.g. for logging, passing a metric name, using different connection pools, or even different delegate backends.

Listener backend

The sttp.client4.listener.ListenerBackend can make it easier to create backend wrappers which need to be notified about request lifecycle events: when a request is started, and when it completes either successfully or with an exception. This is possible by implementing a sttp.client4.listener.RequestListener. This is how e.g. the slf4j backend is implemented.

A request listener can associate a value with a request, which will then be passed to the request completion notification methods.

A side-effecting request listener, of type RequestListener[Identity, L], can be lifted to a request listener RequestListener[F, L] given a MonadError[F], using the RequestListener.lift method.

Backend wrappers and redirects

See the appropriate section in docs on redirects.

Logging backend wrapper

A good example on how to implement a logging backend wrapper is the logging backend wrapper implementation. It uses the ListenerBackend to get notified about request lifecycle events.

To adjust the logs to your needs, or to integrate with your logging framework, simply copy the code and modify as needed.

Examples backend wrappers

A number of example backend wrappers can be found in examples.

Example new backend

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" % "4.0.0-M24" % Test classifier "tests"

Implement your backend and extend the HttpTest class:

import sttp.client4.*
import sttp.client4.testing.{ConvertToFuture, HttpTest}
import scala.concurrent.Future

class MyCustomBackendHttpTest extends HttpTest[Future]:
  override implicit val convertToFuture: ConvertToFuture[Future] = ConvertToFuture.future
  override val backend: Backend[Future] = ??? //new MyCustomBackend()
  override def timeoutToNone[T](t: Future[T], timeoutMillis: Int): Future[Option[T]] = ???

Custom backend wrapper using cats

When implementing a backend wrapper using cats, it might be useful to import:

import sttp.client4.impl.cats.implicits.*

from the cats integration module. The module should be available on the classpath after adding following dependency:

"com.softwaremill.sttp.client4" %% "cats" % "4.0.0-M24" // for cats-effect 3.x
// or
"com.softwaremill.sttp.client4" %% "catsce2" % "4.0.0-M24" // for cats-effect 2.x

The object contains implicits to convert a cats MonadError into the sttp MonadError, as well as a way to map the effects wrapper used with the .mapK extension method for the backend.