Usage examples

All of the examples are available in the sources in runnable form.

POST a form using the synchronous backend

Required dependencies:

libraryDependencies ++= List("com.softwaremill.sttp.client" %% "core" % "2.1.0-RC1")

Example code:

import sttp.client._

val signup = Some("yes")

val request = basicRequest
  // send the body as form data (x-www-form-urlencoded)
  .body(Map("name" -> "John", "surname" -> "doe"))
  // use an optional parameter in the URI
  .post(uri"https://httpbin.org/post?signup=$signup")

implicit val backend = HttpURLConnectionBackend()
val response = request.send()

println(response.body)
println(response.headers)

GET and parse JSON using the akka-http backend and json4s

Required dependencies:

libraryDependencies ++= List(
  "com.softwaremill.sttp.client" %% "akka-http-backend" % "2.1.0-RC1",
  "com.softwaremill.sttp.client" %% "json4s" % "2.1.0-RC1",
  "org.json4s" %% "json4s-native" % "3.6.0"
)

Example code:

import sttp.client._
import sttp.client.akkahttp._
import sttp.client.json4s._

import scala.concurrent.ExecutionContext.Implicits.global

case class HttpBinResponse(origin: String, headers: Map[String, String])

implicit val serialization = org.json4s.native.Serialization
val request = basicRequest
  .get(uri"https://httpbin.org/get")
  .response(asJson[HttpBinResponse])

implicit val backend = AkkaHttpBackend()
val response: Future[Response[Either[ResponseError[Exception], HttpBinResponse]]] =
  request.send()

for {
  r <- response
} {
  println(s"Got response code: ${r.code}")
  println(r.body)
  backend.close()
}                             

GET and parse JSON using the ZIO async-http-client backend and circe

Required dependencies:

libraryDependencies ++= List(
  "com.softwaremill.sttp.client" %% "async-http-client-backend-zio" % "2.1.0-RC1",
  "com.softwaremill.sttp.client" %% "circe" % "2.1.0-RC1",
  "io.circe" %% "circe-generic" % "0.12.1"
)

Example code:

import sttp.client._
import sttp.client.circe._
import sttp.client.asynchttpclient.zio._
import io.circe.generic.auto._
import zio._
import zio.console.Console

object GetAndParseJsonZioCirce extends App {

  override def run(args: List[String]): ZIO[ZEnv, Nothing, Int] = {

    case class HttpBinResponse(origin: String, headers: Map[String, String])

    val request = basicRequest
      .get(uri"https://httpbin.org/get")
      .response(asJson[HttpBinResponse])

    // create a description of a program, which requires two dependencies in the environment:
    // the SttpClient, and the Console
    val sendAndPrint: ZIO[Console with SttpClient, Throwable, Unit] = for {
      response <- SttpClient.send(request)
      _ <- console.putStrLn(s"Got response code: ${response.code}")
      _ <- console.putStrLn(response.body.toString)
    } yield ()

    // provide an implementation for the SttpClient dependency; other dependencies are
    // provided by Zio
    sendAndPrint.provideCustomLayer(AsyncHttpClientZioBackend.layer()).fold(_ => 1, _ => 0)
  }
}

POST and serialize JSON using the Monix async-http-client backend and circe

Required dependencies:

libraryDependencies ++= List(
  "com.softwaremill.sttp.client" %% "async-http-client-backend-monix" % "2.1.0-RC1",
  "com.softwaremill.sttp.client" %% "circe" % "2.1.0-RC1",
  "io.circe" %% "circe-generic" % "0.12.1"
)

Example code:

import sttp.client._
import sttp.client.circe._
import sttp.client.asynchttpclient.monix._
import io.circe.generic.auto._
import monix.eval.Task

case class Info(x: Int, y: String)

val postTask = AsyncHttpClientMonixBackend().flatMap { implicit backend =>
  val r = basicRequest
    .body(Info(91, "abc"))
    .post(uri"https://httpbin.org/post")

  r.send()
    .flatMap { response =>
      Task(println(s"""Got ${response.code} response, body:\n${response.body}"""))
    }
    .guarantee(backend.close())
}

import monix.execution.Scheduler.Implicits.global
postTask.runSyncUnsafe()

Test an endpoint, which requires multiple query parameters

Required dependencies:

libraryDependencies ++= List("com.softwaremill.sttp.client" %% "core" % "2.1.0-RC1")

Example code:

import sttp.client._
import sttp.client.testing._

implicit val backend = SttpBackendStub.synchronous
  .whenRequestMatches(_.uri.paramsMap.contains("filter"))
  .thenRespond("Filtered")
  .whenRequestMatches(_.uri.path.contains("secret"))
  .thenRespond("42")

val parameters1 = Map("filter" -> "name=mary", "sort" -> "asc")
println(
  basicRequest
    .get(uri"http://example.org?search=true&$parameters1")
    .send()
    .body)

val parameters2 = Map("sort" -> "desc")
println(
  basicRequest
    .get(uri"http://example.org/secret/read?$parameters2")
    .send()
    .body)

Open a websocket using the high-level websocket interface and ZIO

Required dependencies:

libraryDependencies ++= List("com.softwaremill.sttp.client" %% "async-http-client-backend-zio" % "2.1.0-RC1")

Example code:

import sttp.client._
import sttp.client.asynchttpclient.zio._
import sttp.client.ws.WebSocket
import sttp.model.ws.WebSocketFrame
import zio._
import zio.console.Console

object WebsocketZio extends App {
  def useWebsocket(ws: WebSocket[Task]): ZIO[Console, Throwable, Unit] = {
    def send(i: Int) = ws.send(WebSocketFrame.text(s"Hello $i!"))
    val receive = ws.receiveText().flatMap(t => console.putStrLn(s"RECEIVED: $t"))
    send(1) *> send(2) *> receive *> receive *> ws.close
  }

  // create a description of a program, which requires two dependencies in the environment:
  // the SttpClient, and the Console
  val sendAndPrint: ZIO[Console with SttpClient, Throwable, Unit] = for {
    response <- SttpClient.openWebsocket(basicRequest.get(uri"wss://echo.websocket.org"))
    _ <- useWebsocket(response.result)
  } yield ()

  override def run(args: List[String]): ZIO[ZEnv, Nothing, Int] = {
    // provide an implementation for the SttpClient dependency; other dependencies are
    // provided by Zio
    sendAndPrint.provideCustomLayer(AsyncHttpClientZioBackend.layer()).fold(_ => 1, _ => 0)
  }
}

Open a websocket using the high-level websocket interface and Monix

Required dependencies:

libraryDependencies ++= List("com.softwaremill.sttp.client" %% "async-http-client-backend-monix" % "2.1.0-RC1")

Example code:

import monix.eval.Task
import sttp.client._
import sttp.client.ws.{WebSocket, WebSocketResponse}
import sttp.model.ws.WebSocketFrame
import sttp.client.asynchttpclient.monix.{AsyncHttpClientMonixBackend, MonixWebSocketHandler}
import cats.implicits._

object WebsocketMonix extends App {
  import monix.execution.Scheduler.Implicits.global

  def useWebsocket(ws: WebSocket[Task]): Task[Unit] = {
    def send(i: Int) = ws.send(WebSocketFrame.text(s"Hello $i!"))
    val receive = ws.receiveText().flatMap(t => Task(println(s"RECEIVED: $t")))
    send(1) *> send(2) *> receive *> receive *> ws.close
  }

  val websocketTask: Task[Unit] = AsyncHttpClientMonixBackend().flatMap { implicit backend =>
    val response: Task[WebSocketResponse[WebSocket[Task]]] = basicRequest
      .get(uri"wss://echo.websocket.org")
      .openWebsocketF(MonixWebSocketHandler())

    response
      .flatMap(r => useWebsocket(r.result))
      .guarantee(backend.close())
  }

  websocketTask.runSyncUnsafe()
}

Stream request and response bodies using fs2

Required dependencies:

libraryDependencies ++= List("com.softwaremill.sttp.client" %% "async-http-client-backend-fs2" % "2.1.0-RC1")

Example code:

import sttp.client._
import sttp.client.asynchttpclient.fs2.AsyncHttpClientFs2Backend

import java.nio.ByteBuffer
import cats.effect.{ContextShift, IO}
import cats.instances.string._
import fs2.{Stream, Chunk, text}

implicit val cs: ContextShift[IO] = IO.contextShift(scala.concurrent.ExecutionContext.global)

def streamRequestBody(implicit backend: SttpBackend[IO, Stream[IO, ByteBuffer], NothingT]): IO[Unit] = {
  val stream: Stream[IO, ByteBuffer] = Stream.emits(List("Hello, ".getBytes, "world".getBytes)).map(ByteBuffer.wrap)

  basicRequest
    .streamBody(stream)
    .post(uri"https://httpbin.org/post")
    .send()
    .map { response =>
      println(s"RECEIVED:\n${response.body}")
    }
}

def streamResponseBody(implicit backend: SttpBackend[IO, Stream[IO, ByteBuffer], NothingT]): IO[Unit] = {
  basicRequest
    .body("I want a stream!")
    .post(uri"https://httpbin.org/post")
    .response(asStreamAlways[Stream[IO, ByteBuffer]])
    .send()
    .flatMap { response =>
      response.body
        .map(bb => Chunk.array(bb.array))
        .through(text.utf8DecodeC)
        .compile
        .foldMonoid
    }
    .map { body =>
      println(s"RECEIVED:\n$body")
    }
}

val effect = AsyncHttpClientFs2Backend[IO]().flatMap { implicit backend =>
  streamRequestBody.flatMap(_ => streamResponseBody).guarantee(backend.close())
}

effect.unsafeRunSync()

Retry a request using ZIO

Required dependencies:

libraryDependencies ++= List("com.softwaremill.sttp.client" %% "async-http-client-backend-zio" % "2.1.0-RC1")

Example code:

import sttp.client._
import sttp.client.asynchttpclient.zio.AsyncHttpClientZioBackend

import zio.{ZIO, Schedule}
import zio.clock.Clock
import zio.duration._

AsyncHttpClientZioBackend()
  .flatMap { implicit backend =>
    val localhostRequest = basicRequest
      .get(uri"http://localhost/test")
      .response(asStringAlways)

    val sendWithRetries: ZIO[Clock, Throwable, Response[String]] = localhostRequest
      .send()
      .either
      .repeat(
        Schedule.spaced(1.second) *>
          Schedule.recurs(10) *>
          Schedule.doWhile(result => RetryWhen.Default(localhostRequest, result))
      )
      .absolve

    sendWithRetries.ensuring(backend.close().catchAll(_ => ZIO.unit))
  }