diff --git a/armeria-backend/cats-ce2/src/main/scala/sttp/client4/armeria/cats/ArmeriaCatsBackend.scala b/armeria-backend/cats-ce2/src/main/scala/sttp/client4/armeria/cats/ArmeriaCatsBackend.scala index fa3b6c7e92..7f21ba0247 100644 --- a/armeria-backend/cats-ce2/src/main/scala/sttp/client4/armeria/cats/ArmeriaCatsBackend.scala +++ b/armeria-backend/cats-ce2/src/main/scala/sttp/client4/armeria/cats/ArmeriaCatsBackend.scala @@ -54,6 +54,7 @@ object ArmeriaCatsBackend { ): Resource[F, Backend[F]] = Resource.make(Sync[F].delay(apply(newClient(options), closeFactory = true)))(_.close()) + /** Creates a backend using the given client. The client's `ClientFactory` is closed when the resource is released. */ def resourceUsingClient[F[_]: Concurrent](client: WebClient): Resource[F, Backend[F]] = Resource.make(Sync[F].delay(apply(client, closeFactory = true)))(_.close()) diff --git a/armeria-backend/cats/src/main/scala/sttp/client4/armeria/cats/ArmeriaCatsBackend.scala b/armeria-backend/cats/src/main/scala/sttp/client4/armeria/cats/ArmeriaCatsBackend.scala index e6926edfd1..86603e6884 100644 --- a/armeria-backend/cats/src/main/scala/sttp/client4/armeria/cats/ArmeriaCatsBackend.scala +++ b/armeria-backend/cats/src/main/scala/sttp/client4/armeria/cats/ArmeriaCatsBackend.scala @@ -53,6 +53,7 @@ object ArmeriaCatsBackend { ): Resource[F, Backend[F]] = Resource.make(Sync[F].delay(apply(newClient(options), closeFactory = true)))(_.close()) + /** Creates a backend using the given client. The client's `ClientFactory` is closed when the resource is released. */ def resourceUsingClient[F[_]: Async](client: WebClient): Resource[F, Backend[F]] = Resource.make(Sync[F].delay(apply(client, closeFactory = true)))(_.close()) diff --git a/armeria-backend/fs2-ce2/src/main/scala/sttp/client4/armeria/fs2/ArmeriaFs2Backend.scala b/armeria-backend/fs2-ce2/src/main/scala/sttp/client4/armeria/fs2/ArmeriaFs2Backend.scala index 2d3b665bec..831bc5bf9d 100644 --- a/armeria-backend/fs2-ce2/src/main/scala/sttp/client4/armeria/fs2/ArmeriaFs2Backend.scala +++ b/armeria-backend/fs2-ce2/src/main/scala/sttp/client4/armeria/fs2/ArmeriaFs2Backend.scala @@ -60,6 +60,7 @@ object ArmeriaFs2Backend { ): Resource[F, StreamBackend[F, Fs2Streams[F]]] = Resource.make(Sync[F].delay(apply(newClient(options), closeFactory = true)))(_.close()) + /** Creates a backend using the given client. The client's `ClientFactory` is closed when the resource is released. */ def resourceUsingClient[F[_]: ConcurrentEffect](client: WebClient): Resource[F, StreamBackend[F, Fs2Streams[F]]] = Resource.make(Sync[F].delay(apply(client, closeFactory = true)))(_.close()) diff --git a/armeria-backend/fs2/src/main/scala/sttp/client4/armeria/fs2/ArmeriaFs2Backend.scala b/armeria-backend/fs2/src/main/scala/sttp/client4/armeria/fs2/ArmeriaFs2Backend.scala index 48525db9fb..3b98fb421c 100644 --- a/armeria-backend/fs2/src/main/scala/sttp/client4/armeria/fs2/ArmeriaFs2Backend.scala +++ b/armeria-backend/fs2/src/main/scala/sttp/client4/armeria/fs2/ArmeriaFs2Backend.scala @@ -75,6 +75,7 @@ object ArmeriaFs2Backend { Resource.make(Sync[F].delay(apply(newClient(options), closeFactory = true, dispatcher)))(_.close()) ) + /** Creates a backend using the given client. The client's `ClientFactory` is closed when the resource is released. */ def resourceUsingClient[F[_]: Async](client: WebClient): Resource[F, StreamBackend[F, Fs2Streams[F]]] = Dispatcher .parallel[F] diff --git a/armeria-backend/monix/src/main/scala/sttp/client4/armeria/monix/ArmeriaMonixBackend.scala b/armeria-backend/monix/src/main/scala/sttp/client4/armeria/monix/ArmeriaMonixBackend.scala index 3a4c90cb7b..aaaede8985 100644 --- a/armeria-backend/monix/src/main/scala/sttp/client4/armeria/monix/ArmeriaMonixBackend.scala +++ b/armeria-backend/monix/src/main/scala/sttp/client4/armeria/monix/ArmeriaMonixBackend.scala @@ -14,7 +14,7 @@ import sttp.client4.impl.monix.TaskMonadAsyncError import sttp.client4.wrappers.FollowRedirectsBackend import sttp.client4.{wrappers, BackendOptions, StreamBackend} import sttp.monad.MonadAsyncError -import cats.effect.ExitCase +import cats.effect.{ExitCase, Resource} private final class ArmeriaMonixBackend(client: WebClient, closeFactory: Boolean)(implicit scheduler: Scheduler) extends AbstractArmeriaBackend[Task, MonixStreams](client, closeFactory, TaskMonadAsyncError) { @@ -54,6 +54,21 @@ object ArmeriaMonixBackend { ): StreamBackend[Task, MonixStreams] = apply(newClient(options), closeFactory = true) + /** @param scheduler The scheduler used for streaming request bodies. Defaults to the global scheduler. */ + def resource(options: BackendOptions = BackendOptions.Default)(implicit + scheduler: Scheduler = Scheduler.global + ): Resource[Task, StreamBackend[Task, MonixStreams]] = + Resource.make(Task.eval(apply(options)))(_.close()) + + /** Creates a backend using the given client. The client's `ClientFactory` is closed when the resource is released. + * @param scheduler + * The scheduler used for streaming request bodies. Defaults to the global scheduler. + */ + def resourceUsingClient(client: WebClient)(implicit + scheduler: Scheduler = Scheduler.global + ): Resource[Task, StreamBackend[Task, MonixStreams]] = + Resource.make(Task.eval(apply(client, closeFactory = true)))(_.close()) + /** @param scheduler The scheduler used for streaming request bodies. Defaults to the global scheduler. */ def usingClient(client: WebClient)(implicit scheduler: Scheduler = Scheduler.global diff --git a/armeria-backend/zio/src/main/scala/sttp/client4/armeria/zio/ArmeriaZioBackend.scala b/armeria-backend/zio/src/main/scala/sttp/client4/armeria/zio/ArmeriaZioBackend.scala index 5d7e124cab..e30c86f4a6 100644 --- a/armeria-backend/zio/src/main/scala/sttp/client4/armeria/zio/ArmeriaZioBackend.scala +++ b/armeria-backend/zio/src/main/scala/sttp/client4/armeria/zio/ArmeriaZioBackend.scala @@ -67,6 +67,7 @@ object ArmeriaZioBackend { ): ZIO[Scope, Throwable, StreamBackend[Task, ZioStreams]] = ZIO.acquireRelease(apply(options))(_.close().ignore) + /** Creates a backend using the given client. The client's `ClientFactory` is closed when the scope is closed. */ def scopedUsingClient(client: WebClient): ZIO[Scope, Throwable, StreamBackend[Task, ZioStreams]] = ZIO.acquireRelease( ZIO diff --git a/armeria-backend/zio1/src/main/scala/sttp/client4/armeria/zio/ArmeriaZioBackend.scala b/armeria-backend/zio1/src/main/scala/sttp/client4/armeria/zio/ArmeriaZioBackend.scala index 93d92a2694..1861831ef1 100644 --- a/armeria-backend/zio1/src/main/scala/sttp/client4/armeria/zio/ArmeriaZioBackend.scala +++ b/armeria-backend/zio1/src/main/scala/sttp/client4/armeria/zio/ArmeriaZioBackend.scala @@ -58,6 +58,16 @@ object ArmeriaZioBackend { def managed(options: BackendOptions = BackendOptions.Default): TaskManaged[StreamBackend[Task, ZioStreams]] = ZManaged.make(apply(options))(_.close().ignore) + /** Creates a backend using the given client. The client's `ClientFactory` is closed when the managed resource is + * released. + */ + def managedUsingClient(client: WebClient): TaskManaged[StreamBackend[Task, ZioStreams]] = + ZManaged.make( + ZIO + .runtime[Any] + .map(runtime => apply(runtime, client, closeFactory = true)) + )(_.close().ignore) + def layer(options: BackendOptions = BackendOptions.Default): Layer[Throwable, SttpClient] = ZLayer.fromManaged(managed(options)) diff --git a/docs/backends/monix.md b/docs/backends/monix.md index e5b9cd226b..ae4cac4d31 100644 --- a/docs/backends/monix.md +++ b/docs/backends/monix.md @@ -66,6 +66,9 @@ OkHttpMonixBackend.resource().use { backend => ??? } import okhttp3._ val okHttpClient: OkHttpClient = ??? val backend = OkHttpMonixBackend.usingClient(okHttpClient) + +// or, obtain a cats-effect Resource with a custom instance of the OkHttpClient: +OkHttpMonixBackend.resourceUsingClient(okHttpClient).use { backend => ??? } ``` This backend depends on [OkHttp](http://square.github.io/okhttp/) and fully supports HTTP/2. @@ -90,6 +93,9 @@ create client: import monix.execution.Scheduler.Implicits.global val backend = ArmeriaMonixBackend() +// or, if you'd like the backend to be wrapped in cats-effect Resource: +ArmeriaMonixBackend.resource().use { backend => ??? } + // You can use the default client which reuses the connection pool of ClientFactory.ofDefault() ArmeriaMonixBackend.usingDefaultClient() ``` @@ -108,6 +114,9 @@ val client = WebClient.builder("https://my-service.com") .build() val backend = ArmeriaMonixBackend.usingClient(client) + +// or, obtain a cats-effect Resource with a custom instance of the WebClient: +ArmeriaMonixBackend.resourceUsingClient(client).use { backend => ??? } ``` ```{note} diff --git a/docs/backends/zio.md b/docs/backends/zio.md index e64a7892d8..d67292c8c2 100644 --- a/docs/backends/zio.md +++ b/docs/backends/zio.md @@ -109,6 +109,8 @@ The `CurlZioBackend` companion object contains methods to create the backend dir When using constructors to express service dependencies, ZIO layers can be used to provide the `SttpBackend` instance, instead of creating one by hand. In this scenario, the lifecycle of a `SttpBackend` service is described by `ZLayer`s, which can be created using the `.layer`/`.layerUsingConfig`/... methods on `HttpClientZioBackend` / `ArmeriaZioBackend`. +A layer created with `.layerUsingClient` closes the given client when the layer is released. + The layers can be used to provide an implementation of the `SttpBackend` dependency when creating services. For example: ```scala mdoc:compile-only diff --git a/effects/cats/src/main/scalajvm/sttp/client4/httpclient/cats/HttpClientCatsBackend.scala b/effects/cats/src/main/scalajvm/sttp/client4/httpclient/cats/HttpClientCatsBackend.scala index 5698b94249..f6453f026a 100644 --- a/effects/cats/src/main/scalajvm/sttp/client4/httpclient/cats/HttpClientCatsBackend.scala +++ b/effects/cats/src/main/scalajvm/sttp/client4/httpclient/cats/HttpClientCatsBackend.scala @@ -143,6 +143,7 @@ object HttpClientCatsBackend { Resource.make(apply(dispatcher, options, customizeRequest, compressionHandlers))(_.close()) ) + /** Creates a backend using the given client. The client is closed when the resource is released. */ def resourceUsingClient[F[_]: Async]( client: HttpClient, customizeRequest: HttpRequest => HttpRequest = identity, diff --git a/effects/fs2-ce2/src/main/scalajvm/sttp/client4/httpclient/fs2/HttpClientFs2Backend.scala b/effects/fs2-ce2/src/main/scalajvm/sttp/client4/httpclient/fs2/HttpClientFs2Backend.scala index 088443cb42..0fa96c7615 100644 --- a/effects/fs2-ce2/src/main/scalajvm/sttp/client4/httpclient/fs2/HttpClientFs2Backend.scala +++ b/effects/fs2-ce2/src/main/scalajvm/sttp/client4/httpclient/fs2/HttpClientFs2Backend.scala @@ -187,6 +187,7 @@ object HttpClientFs2Backend { ): Resource[F, WebSocketStreamBackend[F, Fs2Streams[F]]] = Resource.make(apply(blocker, options, customizeRequest, compressionHandlers))(_.close()) + /** Creates a backend using the given client. The client is closed when the resource is released. */ def resourceUsingClient[F[_]: ConcurrentEffect: ContextShift]( client: HttpClient, blocker: Blocker, diff --git a/effects/fs2/src/main/scalajvm/sttp/client4/httpclient/fs2/HttpClientFs2Backend.scala b/effects/fs2/src/main/scalajvm/sttp/client4/httpclient/fs2/HttpClientFs2Backend.scala index 63b05b1072..66117c1dd2 100644 --- a/effects/fs2/src/main/scalajvm/sttp/client4/httpclient/fs2/HttpClientFs2Backend.scala +++ b/effects/fs2/src/main/scalajvm/sttp/client4/httpclient/fs2/HttpClientFs2Backend.scala @@ -182,6 +182,7 @@ object HttpClientFs2Backend { Resource.make(apply(dispatcher, options, customizeRequest, compressionHandlers))(_.close()) ) + /** Creates a backend using the given client. The client is closed when the resource is released. */ def resourceUsingClient[F[_]: Async]( client: HttpClient, customizeRequest: HttpRequest => HttpRequest = identity, diff --git a/effects/monix/src/main/scalajvm/sttp/client4/httpclient/monix/HttpClientMonixBackend.scala b/effects/monix/src/main/scalajvm/sttp/client4/httpclient/monix/HttpClientMonixBackend.scala index af7c0a14b9..d775fa7214 100644 --- a/effects/monix/src/main/scalajvm/sttp/client4/httpclient/monix/HttpClientMonixBackend.scala +++ b/effects/monix/src/main/scalajvm/sttp/client4/httpclient/monix/HttpClientMonixBackend.scala @@ -160,6 +160,7 @@ object HttpClientMonixBackend { ): Resource[Task, WebSocketStreamBackend[Task, MonixStreams]] = Resource.make(apply(options, customizeRequest, compressionHandlers))(_.close()) + /** Creates a backend using the given client. The client is closed when the resource is released. */ def resourceUsingClient( client: HttpClient, customizeRequest: HttpRequest => HttpRequest = identity, diff --git a/effects/zio/src/main/scalajvm/sttp/client4/httpclient/zio/HttpClientZioBackend.scala b/effects/zio/src/main/scalajvm/sttp/client4/httpclient/zio/HttpClientZioBackend.scala index 1a75c1373a..fe198f5d36 100644 --- a/effects/zio/src/main/scalajvm/sttp/client4/httpclient/zio/HttpClientZioBackend.scala +++ b/effects/zio/src/main/scalajvm/sttp/client4/httpclient/zio/HttpClientZioBackend.scala @@ -176,6 +176,7 @@ object HttpClientZioBackend { _.close().ignore ) + /** Creates a backend using the given client. The client is closed when the scope is closed. */ def scopedUsingClient( client: HttpClient, customizeRequest: HttpRequest => HttpRequest = identity, @@ -217,18 +218,7 @@ object HttpClientZioBackend { customizeRequest: HttpRequest => HttpRequest = identity, compressionHandlers: CompressionHandlers[ZioStreams, ZioStreams.BinaryStream] = DefaultCompressionHandlers ): ZLayer[Any, Throwable, SttpClient] = - ZLayer.scoped( - ZIO - .acquireRelease( - ZIO.attempt( - usingClient( - client, - customizeRequest, - compressionHandlers - ) - ) - )(_.close().ignore) - ) + ZLayer.scoped(scopedUsingClient(client, customizeRequest, compressionHandlers)) /** Create a stub backend for testing, which uses the [[Task]] response wrapper, and supports `Stream[Throwable, * ByteBuffer]` streaming. diff --git a/effects/zio1/src/main/scalajvm/sttp/client4/httpclient/zio/HttpClientZioBackend.scala b/effects/zio1/src/main/scalajvm/sttp/client4/httpclient/zio/HttpClientZioBackend.scala index 4fb72f0d35..96c5123b85 100644 --- a/effects/zio1/src/main/scalajvm/sttp/client4/httpclient/zio/HttpClientZioBackend.scala +++ b/effects/zio1/src/main/scalajvm/sttp/client4/httpclient/zio/HttpClientZioBackend.scala @@ -171,6 +171,16 @@ object HttpClientZioBackend { _.close().ignore ) + /** Creates a backend using the given client. The client is closed when the managed resource is released. */ + def managedUsingClient( + client: HttpClient, + customizeRequest: HttpRequest => HttpRequest = identity, + compressionHandlers: CompressionHandlers[ZioStreams, ZioStreams.BinaryStream] = DefaultCompressionHandlers + ): ZManaged[Any, Throwable, WebSocketStreamBackend[Task, ZioStreams]] = + ZManaged.make( + ZIO.effect(HttpClientZioBackend(client, closeClient = true, customizeRequest, compressionHandlers)) + )(_.close().ignore) + def layer( options: BackendOptions = BackendOptions.Default, customizeRequest: HttpRequest => HttpRequest = identity, @@ -203,16 +213,7 @@ object HttpClientZioBackend { customizeRequest: HttpRequest => HttpRequest = identity, compressionHandlers: CompressionHandlers[ZioStreams, ZioStreams.BinaryStream] = DefaultCompressionHandlers ): ZLayer[Any, Throwable, SttpClient] = - ZLayer.fromManaged( - ZManaged - .makeEffect( - usingClient( - client, - customizeRequest, - compressionHandlers - ) - )(_.close().ignore) - ) + ZLayer.fromManaged(managedUsingClient(client, customizeRequest, compressionHandlers)) /** Create a stub backend for testing, which uses the [[Task]] response wrapper, and supports `Stream[Throwable, * ByteBuffer]` streaming. diff --git a/okhttp-backend/monix/src/main/scala/sttp/client4/okhttp/monix/OkHttpMonixBackend.scala b/okhttp-backend/monix/src/main/scala/sttp/client4/okhttp/monix/OkHttpMonixBackend.scala index 74db37f35a..6b963320ae 100644 --- a/okhttp-backend/monix/src/main/scala/sttp/client4/okhttp/monix/OkHttpMonixBackend.scala +++ b/okhttp-backend/monix/src/main/scala/sttp/client4/okhttp/monix/OkHttpMonixBackend.scala @@ -161,6 +161,18 @@ object OkHttpMonixBackend { ): Resource[Task, WebSocketStreamBackend[Task, MonixStreams]] = Resource.make(apply(options, compressionHandlers, webSocketBufferCapacity))(_.close()) + /** Creates a backend using the given client. The client is closed when the resource is released. */ + def resourceUsingClient( + client: OkHttpClient, + compressionHandlers: CompressionHandlers[Any, InputStream] = DefaultCompressionHandlers, + webSocketBufferCapacity: Option[Int] = OkHttpBackend.DefaultWebSocketBufferCapacity + )(implicit + s: Scheduler = Scheduler.global + ): Resource[Task, WebSocketStreamBackend[Task, MonixStreams]] = + Resource.make( + Task.eval(OkHttpMonixBackend(client, closeClient = true, compressionHandlers, webSocketBufferCapacity)(s)) + )(_.close()) + def usingClient( client: OkHttpClient, compressionHandlers: CompressionHandlers[Any, InputStream] = DefaultCompressionHandlers,