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.client3" %% "core" % "3.3.0")
Example code:
package sttp.client3.examples
object PostFormSynchronous extends App {
import sttp.client3._
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")
val backend = HttpURLConnectionBackend()
val response = request.send(backend)
println(response.body)
println(response.headers)
}
GET and parse JSON using the akka-http backend and json4s¶
Required dependencies:
libraryDependencies ++= List(
"com.softwaremill.sttp.client3" %% "akka-http-backend" % "3.3.0",
"com.softwaremill.sttp.client3" %% "json4s" % "3.3.0",
"org.json4s" %% "json4s-native" % "3.6.0"
)
Example code:
package sttp.client3.examples
object GetAndParseJsonAkkaHttpJson4s extends App {
import scala.concurrent.Future
import sttp.client3._
import sttp.client3.akkahttp._
import sttp.client3.json4s._
import scala.concurrent.ExecutionContext.Implicits.global
case class HttpBinResponse(origin: String, headers: Map[String, String])
implicit val serialization = org.json4s.native.Serialization
implicit val formats = org.json4s.DefaultFormats
val request = basicRequest
.get(uri"https://httpbin.org/get")
.response(asJson[HttpBinResponse])
val backend: SttpBackend[Future, Any] = AkkaHttpBackend()
val response: Future[Response[Either[ResponseException[String, Exception], HttpBinResponse]]] =
request.send(backend)
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.client3" %% "async-http-client-backend-zio" % "3.3.0",
"com.softwaremill.sttp.client3" %% "circe" % "3.3.0",
"io.circe" %% "circe-generic" % "0.13.0"
)
Example code:
package sttp.client3.examples
import sttp.client3._
import sttp.client3.circe._
import sttp.client3.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, ExitCode] = {
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 <- 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())
.exitCode
}
}
GET and parse JSON using the async-http-client Monix backend and circe, treating deserialization errors as failed effects¶
Required dependencies:
libraryDependencies ++= List(
"com.softwaremill.sttp.client3" %% "async-http-client-backend-monix" % "3.3.0",
"com.softwaremill.sttp.client3" %% "circe" % "3.3.0",
"io.circe" %% "circe-generic" % "0.13.0"
)
Example code:
package sttp.client3.examples
import io.circe.generic.auto._
import sttp.client3._
import sttp.client3.asynchttpclient.monix.AsyncHttpClientMonixBackend
import sttp.client3.circe._
object GetAndParseJsonGetRightMonixCirce extends App {
import monix.execution.Scheduler.Implicits.global
case class HttpBinResponse(origin: String, headers: Map[String, String])
val request: Request[HttpBinResponse, Any] = basicRequest
.get(uri"https://httpbin.org/get")
.response(asJson[HttpBinResponse].getRight)
AsyncHttpClientMonixBackend
.resource()
.use { backend =>
request.send(backend).map { response: Response[HttpBinResponse] =>
println(s"Got response code: ${response.code}")
println(response.body)
}
}
.runSyncUnsafe()
}
Log requests & responses using slf4j¶
Required dependencies:
libraryDependencies ++= List(
"com.softwaremill.sttp.client3" %% "slf4j-backend" % "3.3.0",
"com.softwaremill.sttp.client3" %% "circe" % "3.3.0",
"io.circe" %% "circe-generic" % "0.13.0"
)
Example code:
package sttp.client3.examples
import io.circe.generic.auto._
import sttp.client3._
import sttp.client3.circe._
import sttp.client3.logging.slf4j.Slf4jLoggingBackend
object LogRequestsSlf4j extends App {
case class HttpBinResponse(origin: String, headers: Map[String, String])
val request = basicRequest
.get(uri"https://httpbin.org/get")
.response(asJson[HttpBinResponse].getRight)
val backend: SttpBackend[Identity, Any] =
Slf4jLoggingBackend(
HttpURLConnectionBackend(),
includeTiming = true,
logRequestBody = false,
logResponseBody = false
)
try {
val response: Response[HttpBinResponse] = request.send(backend)
println("Done! " + response.code)
} finally backend.close()
}
POST and serialize JSON using the Monix async-http-client backend and circe¶
Required dependencies:
libraryDependencies ++= List(
"com.softwaremill.sttp.client3" %% "async-http-client-backend-monix" % "3.3.0",
"com.softwaremill.sttp.client3" %% "circe" % "3.3.0",
"io.circe" %% "circe-generic" % "0.13.0"
)
Example code:
package sttp.client3.examples
object PostSerializeJsonMonixAsyncHttpClientCirce extends App {
import sttp.client3._
import sttp.client3.circe._
import sttp.client3.asynchttpclient.monix._
import io.circe.generic.auto._
import monix.eval.Task
case class Info(x: Int, y: String)
val postTask = AsyncHttpClientMonixBackend().flatMap { backend =>
val r = basicRequest
.body(Info(91, "abc"))
.post(uri"https://httpbin.org/post")
r.send(backend)
.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.client3" %% "core" % "3.3.0")
Example code:
package sttp.client3.examples
object TestEndpointMultipleQueryParameters extends App {
import sttp.client3._
import sttp.client3.testing._
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(backend)
.body
)
val parameters2 = Map("sort" -> "desc")
println(
basicRequest
.get(uri"http://example.org/secret/read?$parameters2")
.send(backend)
.body
)
}
Open a websocket using ZIO¶
Required dependencies:
libraryDependencies ++= List("com.softwaremill.sttp.client3" %% "async-http-client-backend-zio" % "3.3.0")
Example code:
package sttp.client3.examples
import sttp.client3._
import sttp.client3.asynchttpclient.zio._
import sttp.ws.WebSocket
import zio._
import zio.console.Console
object WebSocketZio extends App {
def useWebSocket(ws: WebSocket[RIO[Console, *]]): RIO[Console, Unit] = {
def send(i: Int) = ws.sendText(s"Hello $i!")
val receive = ws.receiveText().flatMap(t => console.putStrLn(s"RECEIVED: $t"))
send(1) *> send(2) *> receive *> receive
}
// create a description of a program, which requires two dependencies in the environment:
// the SttpClient, and the Console
val sendAndPrint: RIO[Console with SttpClient, Response[Unit]] =
sendR(basicRequest.get(uri"wss://echo.websocket.org").response(asWebSocketAlways(useWebSocket)))
override def run(args: List[String]): ZIO[ZEnv, Nothing, ExitCode] = {
// provide an implementation for the SttpClient dependency; other dependencies are
// provided by Zio
sendAndPrint
.provideCustomLayer(AsyncHttpClientZioBackend.layer())
.exitCode
}
}
Open a websocket using FS2 streams¶
Required dependencies:
libraryDependencies ++= List("com.softwaremill.sttp.client3" %% "async-http-client-backend-fs2 % "3.3.0")
Example code:
package sttp.client3.examples
import cats.effect.IO
import cats.effect.unsafe.IORuntime
import fs2._
import sttp.capabilities.fs2.Fs2Streams
import sttp.client3._
import sttp.client3.asynchttpclient.fs2.AsyncHttpClientFs2Backend
import sttp.ws.WebSocketFrame
object WebSocketStreamFs2 extends App {
implicit val runtime: IORuntime = cats.effect.unsafe.implicits.global
def webSocketFramePipe: Pipe[IO, WebSocketFrame.Data[_], WebSocketFrame] = { input =>
Stream.emit(WebSocketFrame.text("1")) ++ input.flatMap {
case WebSocketFrame.Text("10", _, _) =>
println("Received 10 messages, sending close frame")
Stream.emit(WebSocketFrame.close)
case WebSocketFrame.Text(n, _, _) =>
println(s"Received $n messages, replying with $n+1")
Stream.emit(WebSocketFrame.text((n.toInt + 1).toString))
case _ => Stream.empty // ignoring
}
}
AsyncHttpClientFs2Backend
.resource[IO]()
.use { backend =>
basicRequest
.response(asWebSocketStream(Fs2Streams[IO])(webSocketFramePipe))
.get(uri"wss://echo.websocket.org")
.send(backend)
.void
}
.unsafeRunSync()
}
Test Monix websockets¶
Required dependencies:
libraryDependencies ++= List("com.softwaremill.sttp.client3" %% "async-http-client-backend-monix" % "3.3.0")
Example code:
package sttp.client3.examples
import monix.eval.Task
import sttp.capabilities.WebSockets
import sttp.capabilities.monix.MonixStreams
import sttp.client3._
import sttp.client3.asynchttpclient.monix.AsyncHttpClientMonixBackend
import sttp.client3.testing.SttpBackendStub
import sttp.model.StatusCode
import sttp.ws.{WebSocket, WebSocketFrame}
import sttp.ws.testing.WebSocketStub
object WebSocketTesting extends App {
// the web socket-handling logic
def useWebSocket(ws: WebSocket[Task]): Task[Unit] = {
def send(i: Int) = ws.sendText(s"Hello $i!")
val receive = ws.receiveText().flatMap(t => Task(println(s"RECEIVED [$t]")))
send(1) *> send(2) *> receive *> receive
}
// the request description
def openWebSocket(backend: SttpBackend[Task, WebSockets]): Task[Unit] = {
basicRequest
.response(asWebSocket(useWebSocket))
.get(uri"wss://echo.websocket.org")
.send(backend)
.void
}
// the backend stub which we'll use instead of a "real" backend
val stubBackend: SttpBackendStub[Task, MonixStreams with WebSockets] =
AsyncHttpClientMonixBackend.stub
.whenRequestMatches(_.uri.toString().contains("echo.websocket.org"))
.thenRespond(
WebSocketStub.noInitialReceive.thenRespond {
case WebSocketFrame.Text(payload, _, _) =>
List(WebSocketFrame.text(s"response to: $payload"))
case _ => Nil // ignoring other types of messages
},
StatusCode.SwitchingProtocols
)
// running the test
import monix.execution.Scheduler.Implicits.global
openWebSocket(stubBackend).runSyncUnsafe()
}
Open a websocket using Akka¶
Required dependencies:
libraryDependencies ++= List("com.softwaremill.sttp.client3" %% "akka-http-backend" % "3.3.0")
Example code:
package sttp.client3.examples
import sttp.client3._
import sttp.client3.akkahttp.AkkaHttpBackend
import sttp.ws.WebSocket
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
object WebSocketAkka extends App {
def useWebSocket(ws: WebSocket[Future]): Future[Unit] = {
def send(i: Int) = ws.sendText(s"Hello $i!")
def receive() = ws.receiveText().map(t => println(s"RECEIVED: $t"))
for {
_ <- send(1)
_ <- send(2)
_ <- receive()
_ <- receive()
} yield ()
}
val backend = AkkaHttpBackend()
basicRequest
.response(asWebSocket(useWebSocket))
.get(uri"wss://echo.websocket.org")
.send(backend)
.onComplete(_ => backend.close())
}
Open a websocket using Monix¶
Required dependencies:
libraryDependencies ++= List("com.softwaremill.sttp.client3" %% "async-http-client-backend-monix" % "3.3.0")
Example code:
package sttp.client3.examples
import monix.eval.Task
import sttp.client3._
import sttp.client3.asynchttpclient.monix.AsyncHttpClientMonixBackend
import sttp.ws.WebSocket
object WebSocketMonix extends App {
import monix.execution.Scheduler.Implicits.global
def useWebSocket(ws: WebSocket[Task]): Task[Unit] = {
def send(i: Int) = ws.sendText(s"Hello $i!")
val receive = ws.receiveText().flatMap(t => Task(println(s"RECEIVED: $t")))
send(1) *> send(2) *> receive *> receive
}
AsyncHttpClientMonixBackend
.resource()
.use { backend =>
basicRequest
.response(asWebSocket(useWebSocket))
.get(uri"wss://echo.websocket.org")
.send(backend)
.void
}
.runSyncUnsafe()
}
Stream request and response bodies using fs2¶
Required dependencies:
libraryDependencies ++= List("com.softwaremill.sttp.client3" %% "async-http-client-backend-fs2" % "3.3.0")
Example code:
package sttp.client3.examples
import sttp.client3._
import sttp.client3.asynchttpclient.fs2.AsyncHttpClientFs2Backend
import cats.effect.IO
import cats.instances.string._
import fs2.{Stream, text}
import sttp.capabilities.fs2.Fs2Streams
object StreamFs2 extends App {
def streamRequestBody(backend: SttpBackend[IO, Fs2Streams[IO]]): IO[Unit] = {
val stream: Stream[IO, Byte] = Stream.emits("Hello, world".getBytes)
basicRequest
.streamBody(Fs2Streams[IO])(stream)
.post(uri"https://httpbin.org/post")
.send(backend)
.map { response => println(s"RECEIVED:\n${response.body}") }
}
def streamResponseBody(backend: SttpBackend[IO, Fs2Streams[IO]]): IO[Unit] = {
basicRequest
.body("I want a stream!")
.post(uri"https://httpbin.org/post")
.response(asStreamAlways(Fs2Streams[IO])(_.chunks.through(text.utf8DecodeC).compile.foldMonoid))
.send(backend)
.map { response => println(s"RECEIVED:\n${response.body}") }
}
val effect = AsyncHttpClientFs2Backend.resource[IO]().use { backend =>
streamRequestBody(backend).flatMap(_ => streamResponseBody(backend))
}
effect.unsafeRunSync()(cats.effect.unsafe.implicits.global)
}
Stream request and response bodies using zio-stream¶
Required dependencies:
libraryDependencies ++= List("com.softwaremill.sttp.client3" %% "async-http-client-backend-zio" % "3.3.0")
Example code:
package sttp.client3.examples
import sttp.capabilities.zio.ZioStreams
import sttp.client3._
import sttp.client3.asynchttpclient.zio.{AsyncHttpClientZioBackend, SttpClient, send}
import zio._
import zio.console._
import zio.stream._
object StreamZio extends App {
def streamRequestBody: RIO[Console with SttpClient, Unit] = {
val stream: Stream[Throwable, Byte] = Stream("Hello, world".getBytes: _*)
send(
basicRequest
.streamBody(ZioStreams)(stream)
.post(uri"https://httpbin.org/post")
).flatMap { response => putStrLn(s"RECEIVED:\n${response.body}") }
}
def streamResponseBody: RIO[Console with SttpClient, Unit] = {
send(
basicRequest
.body("I want a stream!")
.post(uri"https://httpbin.org/post")
.response(asStreamAlways(ZioStreams)(_.transduce(Transducer.utf8Decode).fold("")(_ + _)))
).flatMap { response => putStrLn(s"RECEIVED:\n${response.body}") }
}
override def run(args: List[String]): ZIO[ZEnv, Nothing, ExitCode] = {
(streamRequestBody *> streamResponseBody)
.provideCustomLayer(AsyncHttpClientZioBackend.layer())
.exitCode
}
}
Retry a request using ZIO¶
Required dependencies:
libraryDependencies ++= List("com.softwaremill.sttp.client3" %% "async-http-client-backend-zio" % "3.3.0")
Example code:
package sttp.client3.examples
import sttp.client3._
import sttp.client3.asynchttpclient.zio.AsyncHttpClientZioBackend
import zio.{ExitCode, Schedule, ZIO}
import zio.clock.Clock
import zio.duration._
object RetryZio extends zio.App {
override def run(args: List[String]): ZIO[zio.ZEnv, Nothing, ExitCode] = {
AsyncHttpClientZioBackend().flatMap { backend =>
val localhostRequest = basicRequest
.get(uri"http://localhost/test")
.response(asStringAlways)
val sendWithRetries: ZIO[Clock, Throwable, Response[String]] = localhostRequest
.send(backend)
.either
.repeat(
Schedule.spaced(1.second) *>
Schedule.recurs(10) *>
Schedule.recurWhile(result => RetryWhen.Default(localhostRequest, result))
)
.absolve
sendWithRetries.ensuring(backend.close().ignore)
}.exitCode
}
}
GET parsed and raw response bodies¶
Required dependencies:
libraryDependencies ++= List("com.softwaremill.sttp.client3" %% "core" % "3.3.0")
Example code:
package sttp.client3.examples
import io.circe
import io.circe.generic.auto._
import sttp.client3._
import sttp.client3.circe._
object GetRawResponseBodySynchronous extends App {
case class HttpBinResponse(origin: String, headers: Map[String, String])
val request = basicRequest
.get(uri"https://httpbin.org/get")
.response(asBoth(asJson[HttpBinResponse], asStringAlways))
val backend: SttpBackend[Identity, Any] = HttpURLConnectionBackend()
try {
val response: Response[(Either[ResponseException[String, circe.Error], HttpBinResponse], String)] =
request.send(backend)
val (parsed, raw) = response.body
println("Got response - parsed: " + parsed)
println("Got response - raw: " + raw)
} finally backend.close()
}