Usage examples
All of the examples are available in the sources in runnable form.
Use the simple synchronous client
Required dependencies:
libraryDependencies ++= List("com.softwaremill.sttp.client3" %% "core" % "3.10.0")
Example code:
package sttp.client3.examples
import sttp.client3.{Request, Response, SimpleHttpClient, UriContext, asStringAlways, basicRequest}
import java.util.UUID
object SimpleClientGetAndPost extends App {
val client = SimpleHttpClient()
try {
val response: Response[Either[String, String]] = client.send(basicRequest.get(uri"https://httpbin.org/get"))
response.body match {
case Left(body) => println(s"Non-2xx response to GET with code ${response.code}:\n$body")
case Right(body) => println(s"2xx response to GET:\n$body")
}
println("---\n")
//
val request2: Request[String, Any] = basicRequest
.header("X-Correlation-ID", UUID.randomUUID().toString)
.response(asStringAlways)
.body("Hello, world!")
.post(uri"https://httpbin.org/post")
val response2: Response[String] = client.send(request2)
println(s"Response to POST:\n${response2.body}")
} finally client.close()
}
POST a form using the synchronous backend
Required dependencies:
libraryDependencies ++= List("com.softwaremill.sttp.client3" %% "core" % "3.10.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 = HttpClientSyncBackend()
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.10.0",
"com.softwaremill.sttp.client3" %% "json4s" % "3.10.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 http-client backend and circe
Required dependencies:
libraryDependencies ++= List(
"com.softwaremill.sttp.client3" %% "zio" % "3.10.0",
"com.softwaremill.sttp.client3" %% "circe" % "3.10.0",
"io.circe" %% "circe-generic" % "0.14.10"
)
Example code:
package sttp.client3.examples
import io.circe.generic.auto._
import sttp.client3._
import sttp.client3.circe._
import sttp.client3.httpclient.zio.{HttpClientZioBackend, send}
import zio._
object GetAndParseJsonZioCirce extends ZIOAppDefault {
override def run = {
case class HttpBinResponse(origin: String, headers: Map[String, String])
val request = basicRequest
.get(uri"https://httpbin.org/get")
.response(asJson[HttpBinResponse])
for {
response <- send(request)
_ <- Console.printLine(s"Got response code: ${response.code}")
_ <- Console.printLine(response.body.toString)
} yield ()
}.provideLayer(HttpClientZioBackend.layer())
}
GET and parse JSON using the http-client Monix backend and circe, treating deserialization errors as failed effects
Required dependencies:
libraryDependencies ++= List(
"com.softwaremill.sttp.client3" %% "monix" % "3.10.0",
"com.softwaremill.sttp.client3" %% "circe" % "3.10.0",
"io.circe" %% "circe-generic" % "0.14.10"
)
Example code:
package sttp.client3.examples
import io.circe.generic.auto._
import sttp.client3._
import sttp.client3.httpclient.monix.HttpClientMonixBackend
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)
HttpClientMonixBackend
.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.10.0",
"com.softwaremill.sttp.client3" %% "circe" % "3.10.0",
"io.circe" %% "circe-generic" % "0.14.10"
)
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(
HttpClientSyncBackend(),
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 http-client backend and circe
Required dependencies:
libraryDependencies ++= List(
"com.softwaremill.sttp.client3" %% "monix" % "3.10.0",
"com.softwaremill.sttp.client3" %% "circe" % "3.10.0",
"io.circe" %% "circe-generic" % "0.14.10"
)
Example code:
package sttp.client3.examples
object PostSerializeJsonMonixHttpClientCirce extends App {
import sttp.client3._
import sttp.client3.circe._
import sttp.client3.httpclient.monix.HttpClientMonixBackend
import io.circe.generic.auto._
import monix.eval.Task
case class Info(x: Int, y: String)
val postTask = HttpClientMonixBackend().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.10.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" %% "zio" % "3.10.0")
Example code:
package sttp.client3.examples
import sttp.capabilities.WebSockets
import sttp.client3._
import sttp.client3.httpclient.zio.HttpClientZioBackend
import sttp.ws.WebSocket
import zio.{Console, _}
object WebSocketZio extends ZIOAppDefault {
def useWebSocket(ws: WebSocket[Task]): Task[Unit] = {
def send(i: Int) = ws.sendText(s"Hello $i!")
val receive = ws.receiveText().flatMap(t => Console.printLine(s"RECEIVED: $t"))
send(1) *> send(2) *> receive *> receive
}
// create a description of a program, which requires SttpClient dependency in the environment
def sendAndPrint(backend: SttpBackend[Task, WebSockets]): Task[Response[Unit]] =
backend.send(basicRequest.get(uri"wss://ws.postman-echo.com/raw").response(asWebSocketAlways(useWebSocket)))
override def run = {
// provide an implementation for the SttpClient dependency
HttpClientZioBackend.scoped().flatMap(sendAndPrint)
}
}
Open a websocket using FS2 streams
Required dependencies:
libraryDependencies ++= List("com.softwaremill.sttp.client3" %% "fs2" % "3.10.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.httpclient.fs2.HttpClientFs2Backend
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
}
}
HttpClientFs2Backend
.resource[IO]()
.use { backend =>
basicRequest
.response(asWebSocketStream(Fs2Streams[IO])(webSocketFramePipe))
.get(uri"wss://ws.postman-echo.com/raw")
.send(backend)
.void
}
.unsafeRunSync()
}
Test Monix websockets
Required dependencies:
libraryDependencies ++= List("com.softwaremill.sttp.client3" %% "monix" % "3.10.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.httpclient.monix.HttpClientMonixBackend
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] =
HttpClientMonixBackend.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.10.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://ws.postman-echo.com/raw")
.send(backend)
.onComplete(_ => backend.close())
}
Open a websocket using Pekko
Required dependencies:
libraryDependencies ++= List("com.softwaremill.sttp.client3" %% "pekko-http-backend" % "3.10.0")
Example code:
package sttp.client3.examples
import sttp.client3._
import sttp.client3.pekkohttp.PekkoHttpBackend
import sttp.ws.WebSocket
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
object WebSocketPekko 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 = PekkoHttpBackend()
basicRequest
.get(uri"wss://ws.postman-echo.com/raw")
.response(asWebSocket(useWebSocket))
.send(backend)
.onComplete(_ => backend.close())
}
Open a websocket using Monix
Required dependencies:
libraryDependencies ++= List("com.softwaremill.sttp.client3" %% "monix" % "3.10.0")
Example code:
package sttp.client3.examples
import monix.eval.Task
import sttp.client3._
import sttp.client3.httpclient.monix.HttpClientMonixBackend
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
}
HttpClientMonixBackend
.resource()
.use { backend =>
basicRequest
.response(asWebSocket(useWebSocket))
.get(uri"wss://ws.postman-echo.com/raw")
.send(backend)
.void
}
.runSyncUnsafe()
}
Stream request and response bodies using fs2
Required dependencies:
libraryDependencies ++= List("com.softwaremill.sttp.client3" %% "fs2" % "3.10.0")
Example code:
package sttp.client3.examples
import sttp.client3._
import sttp.client3.httpclient.fs2.HttpClientFs2Backend
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.utf8.decodeC).compile.foldMonoid))
.send(backend)
.map { response => println(s"RECEIVED:\n${response.body}") }
}
val effect = HttpClientFs2Backend.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" %% "zio" % "3.10.0")
Example code:
package sttp.client3.examples
import sttp.capabilities.zio.ZioStreams
import sttp.client3._
import zio.Console._
import zio._
import zio.stream._
import sttp.client3.httpclient.zio.{HttpClientZioBackend, SttpClient, send}
object StreamZio extends ZIOAppDefault {
def streamRequestBody: RIO[SttpClient, Unit] = {
val stream: Stream[Throwable, Byte] = ZStream("Hello, world".getBytes.toIndexedSeq: _*)
send(
basicRequest
.streamBody(ZioStreams)(stream)
.post(uri"https://httpbin.org/post")
).flatMap { response => printLine(s"RECEIVED:\n${response.body}") }
}
def streamResponseBody: RIO[SttpClient, Unit] =
send(
basicRequest
.body("I want a stream!")
.post(uri"https://httpbin.org/post")
.response(asStreamAlways(ZioStreams)(_.via(ZPipeline.utf8Decode).runFold("")(_ + _)))
).flatMap { response => printLine(s"RECEIVED:\n${response.body}") }
override def run =
(streamRequestBody *> streamResponseBody).provide(HttpClientZioBackend.layer())
}
Retry a request using ZIO
Required dependencies:
libraryDependencies ++= List("com.softwaremill.sttp.client3" %% "zio" % "3.10.0")
Example code:
package sttp.client3.examples
import sttp.client3._
import sttp.client3.httpclient.zio.HttpClientZioBackend
import zio.{Schedule, Task, ZIO, ZIOAppDefault, durationInt}
object RetryZio extends ZIOAppDefault {
override def run: ZIO[Any, Throwable, Response[String]] = {
HttpClientZioBackend()
.flatMap { backend =>
val localhostRequest = basicRequest
.get(uri"http://localhost/test")
.response(asStringAlways)
val sendWithRetries: Task[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)
}
}
}
GET parsed and raw response bodies
Required dependencies:
libraryDependencies ++= List("com.softwaremill.sttp.client3" %% "core" % "3.10.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] = HttpClientSyncBackend()
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()
}