fs2 backend
The fs2 backends are asynchronous. They can be created for any type implementing the cats.effect.Async typeclass, such as cats.effect.IO. Sending a request is a non-blocking, lazily-evaluated operation and results in a wrapped response. There’s a transitive dependency on cats-effect.
Using HttpClient
To use, add the following dependency to your project:
"com.softwaremill.sttp.client3" %% "fs2" % "3.6.1" // for cats-effect 3.x & fs2 3.x
// or
"com.softwaremill.sttp.client3" %% "fs2-ce2" % "3.6.1" // 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:
import cats.effect.IO
import sttp.client3.httpclient.fs2.HttpClientFs2Backend
HttpClientFs2Backend.resource[IO]().use { backend => ??? }
or, by providing a custom Dispatcher:
import cats.effect.IO
import cats.effect.std.Dispatcher
import sttp.client3.httpclient.fs2.HttpClientFs2Backend
val dispatcher: Dispatcher[IO] = ???
HttpClientFs2Backend[IO](dispatcher).flatMap { backend => ??? }
or, if you’d like to instantiate the HttpClient yourself:
import cats.effect.IO
import cats.effect.std.Dispatcher
import java.net.http.HttpClient
import sttp.client3.httpclient.fs2.HttpClientFs2Backend
val httpClient: HttpClient = ???
val dispatcher: Dispatcher[IO] = ???
val backend = HttpClientFs2Backend.usingClient[IO](httpClient, dispatcher)
This backend is based on the built-in java.net.http.HttpClient available from Java 11 onwards.
Host header override is supported in environments running Java 12 onwards, but it has to be enabled by system property:
-Djdk.httpclient.allowRestrictedHeaders=host
Using async-http-client
To use, add the following dependency to your project:
"com.softwaremill.sttp.client3" %% "async-http-client-backend-fs2" % "3.6.1" // for cats-effect 3.x & fs2 3.x
// or
"com.softwaremill.sttp.client3" %% "async-http-client-backend-fs2-ce2" % "3.6.1" // for cats-effect 2.x & fs2 2.x
This backend depends on async-http-client and uses Netty behind the scenes.
Next you’ll need to define a backend instance as an implicit value. This can be done in two basic ways:
by creating a
Resource, which will instantiate the backend (along with aDispatcher) and close it after it has been usedby creating an effect, which describes how a backend is created, or instantiating the backend directly. In this case, you’ll need to close the backend manually, as well as provide a
Dispatcherinstance
Below you can find a non-comprehensive summary of how the backend can be created. The easiest form is to use a cats-effect Resource:
import cats.effect.IO
import sttp.client3.asynchttpclient.fs2.AsyncHttpClientFs2Backend
AsyncHttpClientFs2Backend.resource[IO]().use { backend => ??? }
or, by providing a custom dispatcher:
import cats.effect.IO
import cats.effect.std.Dispatcher
import sttp.client3.asynchttpclient.fs2.AsyncHttpClientFs2Backend
val dispatcher: Dispatcher[IO] = ???
AsyncHttpClientFs2Backend[IO](dispatcher).flatMap { backend => ??? }
or, if you’d like to use a custom configuration:
import cats.effect.IO
import cats.effect.std.Dispatcher
import org.asynchttpclient.AsyncHttpClientConfig
import sttp.client3.asynchttpclient.fs2.AsyncHttpClientFs2Backend
val dispatcher: Dispatcher[IO] = ???
val config: AsyncHttpClientConfig = ???
AsyncHttpClientFs2Backend.usingConfig[IO](config, dispatcher).flatMap { backend => ??? }
or, if you’d like to use adjust the configuration sttp creates:
import cats.effect.IO
import cats.effect.std.Dispatcher
import org.asynchttpclient.DefaultAsyncHttpClientConfig
import sttp.client3.SttpBackendOptions
import sttp.client3.asynchttpclient.fs2.AsyncHttpClientFs2Backend
val sttpOptions: SttpBackendOptions = SttpBackendOptions.Default
val adjustFunction: DefaultAsyncHttpClientConfig.Builder => DefaultAsyncHttpClientConfig.Builder = ???
val dispatcher: Dispatcher[IO] = ???
AsyncHttpClientFs2Backend.usingConfigBuilder[IO](dispatcher, adjustFunction, sttpOptions).flatMap { backend => ??? }
or, if you’d like to instantiate the AsyncHttpClient yourself:
import cats.effect.IO
import cats.effect.std.Dispatcher
import org.asynchttpclient.AsyncHttpClient
import sttp.client3.asynchttpclient.fs2.AsyncHttpClientFs2Backend
val dispatcher: Dispatcher[IO] = ???
val asyncHttpClient: AsyncHttpClient = ???
val backend = AsyncHttpClientFs2Backend.usingClient[IO](asyncHttpClient, dispatcher)
Using Armeria
To use, add the following dependency to your project:
"com.softwaremill.sttp.client3" %% "armeria-backend-fs2" % "3.6.1" // for cats-effect 3.x & fs2 3.x
// or
"com.softwaremill.sttp.client3" %% "armeria-backend-fs2" % "3.6.1" // for cats-effect 2.x & fs2 2.x
create client:
import cats.effect.IO
import cats.effect.std.Dispatcher
import sttp.client3.armeria.fs2.ArmeriaFs2Backend
ArmeriaFs2Backend.resource[IO]().use { backend => ??? }
val dispatcher: Dispatcher[IO] = ???
// You can use the default client which reuses the connection pool of ClientFactory.ofDefault()
ArmeriaFs2Backend.usingDefaultClient[IO](dispatcher)
or, if you’d like to instantiate the WebClient yourself:
import cats.effect.IO
import cats.effect.std.Dispatcher
import com.linecorp.armeria.client.WebClient
import com.linecorp.armeria.client.circuitbreaker._
import sttp.client3.armeria.fs2.ArmeriaFs2Backend
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()
val backend = ArmeriaFs2Backend.usingClient[IO](client, dispatcher)
Note
A WebClient could fail to follow redirects if the WebClient is created with a base URI and a redirect location is a different URI.
This backend is built on top of Armeria. Armeria’s ClientFactory manages connections and protocol-specific properties. Please visit the official documentation to learn how to configure it.
Streaming
The fs2 backends support streaming for any instance of the cats.effect.Effect typeclass, such as cats.effect.IO. If IO is used then the type of supported streams is fs2.Stream[IO, Byte]. The streams capability is represented as sttp.client3.fs2.Fs2Streams.
Requests can be sent with a streaming body like this:
import cats.effect.IO
import fs2.Stream
import sttp.capabilities.fs2.Fs2Streams
import sttp.client3._
import sttp.client3.armeria.fs2.ArmeriaFs2Backend
import sttp.client3.asynchttpclient.fs2.AsyncHttpClientFs2Backend
val effect = AsyncHttpClientFs2Backend.resource[IO]().use { backend =>
val stream: Stream[IO, Byte] = ???
basicRequest
.streamBody(Fs2Streams[IO])(stream)
.post(uri"...")
.send(backend)
}
Responses can also be streamed:
import cats.effect.IO
import fs2.Stream
import sttp.capabilities.fs2.Fs2Streams
import sttp.client3._
import sttp.client3.asynchttpclient.fs2.AsyncHttpClientFs2Backend
import scala.concurrent.duration.Duration
val effect = AsyncHttpClientFs2Backend.resource[IO]().use { backend =>
val response: IO[Response[Either[String, Stream[IO, Byte]]]] =
basicRequest
.post(uri"...")
.response(asStreamUnsafe(Fs2Streams[IO]))
.readTimeout(Duration.Inf)
.send(backend)
response
}
Websockets
The fs2 backends support both regular and streaming websockets.
Server-sent events
Received data streams can be parsed to a stream of server-sent events (SSE):
import cats.effect._
import fs2.Stream
import sttp.client3._
import sttp.capabilities.fs2.Fs2Streams
import sttp.client3.impl.fs2.Fs2ServerSentEvents
import sttp.model.sse.ServerSentEvent
def processEvents(source: Stream[IO, ServerSentEvent]): IO[Unit] = ???
basicRequest.response(asStream(Fs2Streams[IO])(stream =>
processEvents(stream.through(Fs2ServerSentEvents.parse))))