All Downloads are FREE. Search and download functionalities are using the official Maven repository.

sttp.client3.okhttp.monix.OkHttpMonixBackend.scala Maven / Gradle / Ivy

The newest version!
package sttp.client3.okhttp.monix

import java.io.InputStream
import java.util.concurrent.ArrayBlockingQueue

import cats.effect.Resource
import monix.eval.Task
import monix.execution.Ack.Continue
import monix.execution.{Ack, Scheduler}
import monix.reactive.Observable
import monix.reactive.observers.Subscriber
import okhttp3.{MediaType, OkHttpClient, RequestBody => OkHttpRequestBody}
import okio.BufferedSink
import sttp.capabilities.WebSockets
import sttp.capabilities.monix.MonixStreams
import sttp.client3.impl.monix.{MonixSimpleQueue, MonixWebSockets, TaskMonadAsyncError}
import sttp.client3.internal.ws.SimpleQueue
import sttp.monad.MonadError
import sttp.client3.okhttp.OkHttpBackend.EncodingHandler
import sttp.client3.okhttp.{BodyFromOkHttp, BodyToOkHttp, OkHttpAsyncBackend, OkHttpBackend}
import sttp.client3.testing.SttpBackendStub
import sttp.client3.{SttpBackend, _}
import sttp.ws.{WebSocket, WebSocketFrame}

import scala.concurrent.Future

class OkHttpMonixBackend private (
    client: OkHttpClient,
    closeClient: Boolean,
    customEncodingHandler: EncodingHandler,
    webSocketBufferCapacity: Option[Int]
)(implicit
    s: Scheduler
) extends OkHttpAsyncBackend[Task, MonixStreams, MonixStreams with WebSockets](
      client,
      TaskMonadAsyncError,
      closeClient,
      customEncodingHandler
    ) {
  override val streams: MonixStreams = MonixStreams

  override protected val bodyToOkHttp: BodyToOkHttp[Task, MonixStreams] = new BodyToOkHttp[Task, MonixStreams] {
    override val streams: MonixStreams = MonixStreams

    override def streamToRequestBody(
        stream: streams.BinaryStream,
        mt: MediaType,
        cl: Option[Long]
    ): OkHttpRequestBody = {
      new OkHttpRequestBody() {
        override def writeTo(sink: BufferedSink): Unit = toIterable(stream).foreach(sink.write)
        override def contentType(): MediaType = mt
        override def contentLength(): Long = cl.getOrElse(super.contentLength())
      }
    }
  }

  override protected val bodyFromOkHttp: BodyFromOkHttp[Task, MonixStreams] = new BodyFromOkHttp[Task, MonixStreams] {
    override val streams: MonixStreams = MonixStreams
    override implicit val monad: MonadError[Task] = OkHttpMonixBackend.this.responseMonad

    override def responseBodyToStream(inputStream: InputStream): Observable[Array[Byte]] =
      Observable
        .fromInputStream(Task.now(inputStream))
        .guarantee(Task.eval(inputStream.close()))

    override def compileWebSocketPipe(
        ws: WebSocket[Task],
        pipe: streams.Pipe[WebSocketFrame.Data[_], WebSocketFrame]
    ): Task[Unit] = MonixWebSockets.compilePipe(ws, pipe)
  }

  private def toIterable[T](observable: Observable[T])(implicit s: Scheduler): Iterable[T] =
    new Iterable[T] {
      override def iterator: Iterator[T] =
        new Iterator[T] {
          case object Completed extends Exception

          val blockingQueue = new ArrayBlockingQueue[Either[Throwable, T]](1)

          observable.executeAsync.subscribe(new Subscriber[T] {
            override implicit def scheduler: Scheduler = s

            override def onError(ex: Throwable): Unit = {
              blockingQueue.put(Left(ex))
            }

            override def onComplete(): Unit = {
              blockingQueue.put(Left(Completed))
            }

            override def onNext(elem: T): Future[Ack] = {
              blockingQueue.put(Right(elem))
              Continue
            }
          })

          var value: T = _

          override def hasNext: Boolean =
            blockingQueue.take() match {
              case Left(Completed) => false
              case Right(elem) =>
                value = elem
                true
              case Left(ex) => throw ex
            }

          override def next(): T = value
        }
    }

  override protected def createSimpleQueue[T]: Task[SimpleQueue[Task, T]] =
    Task.eval(new MonixSimpleQueue[T](webSocketBufferCapacity))
}

object OkHttpMonixBackend {
  private def apply(
      client: OkHttpClient,
      closeClient: Boolean,
      customEncodingHandler: EncodingHandler,
      webSocketBufferCapacity: Option[Int]
  )(implicit
      s: Scheduler
  ): SttpBackend[Task, MonixStreams with WebSockets] =
    new FollowRedirectsBackend(
      new OkHttpMonixBackend(client, closeClient, customEncodingHandler, webSocketBufferCapacity)(s)
    )

  def apply(
      options: SttpBackendOptions = SttpBackendOptions.Default,
      customEncodingHandler: EncodingHandler = PartialFunction.empty,
      webSocketBufferCapacity: Option[Int] = OkHttpBackend.DefaultWebSocketBufferCapacity
  )(implicit
      s: Scheduler = Scheduler.global
  ): Task[SttpBackend[Task, MonixStreams with WebSockets]] =
    Task.eval(
      OkHttpMonixBackend(
        OkHttpBackend.defaultClient(DefaultReadTimeout.toMillis, options),
        closeClient = true,
        customEncodingHandler,
        webSocketBufferCapacity
      )(s)
    )

  def resource(
      options: SttpBackendOptions = SttpBackendOptions.Default,
      customEncodingHandler: EncodingHandler = PartialFunction.empty,
      webSocketBufferCapacity: Option[Int] = OkHttpBackend.DefaultWebSocketBufferCapacity
  )(implicit
      s: Scheduler = Scheduler.global
  ): Resource[Task, SttpBackend[Task, MonixStreams with WebSockets]] =
    Resource.make(apply(options, customEncodingHandler, webSocketBufferCapacity))(_.close())

  def usingClient(
      client: OkHttpClient,
      customEncodingHandler: EncodingHandler = PartialFunction.empty,
      webSocketBufferCapacity: Option[Int] = OkHttpBackend.DefaultWebSocketBufferCapacity
  )(implicit s: Scheduler = Scheduler.global): SttpBackend[Task, MonixStreams with WebSockets] =
    OkHttpMonixBackend(client, closeClient = false, customEncodingHandler, webSocketBufferCapacity)(s)

  /** Create a stub backend for testing, which uses the [[Task]] response wrapper, and supports `Observable[ByteBuffer]`
    * streaming.
    *
    * See [[SttpBackendStub]] for details on how to configure stub responses.
    */
  def stub: SttpBackendStub[Task, MonixStreams with WebSockets] = SttpBackendStub(TaskMonadAsyncError)
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy