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.client4" %% "core" % "4.0.0-M13")
Example code:
POST a form using the synchronous backend
Required dependencies:
libraryDependencies ++= List("com.softwaremill.sttp.client4" %% "core" % "4.0.0-M13")
Example code:
package sttp.client4.examples
import sttp.client4.httpclient.HttpClientSyncBackend
object PostFormSynchronous extends App {
import sttp.client4._
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.client4" %% "akka-http-backend" % "4.0.0-M13",
"com.softwaremill.sttp.client4" %% "json4s" % "4.0.0-M13",
"org.json4s" %% "json4s-native" % "3.6.0"
)
Example code:
package sttp.client4.examples
object GetAndParseJsonAkkaHttpJson4s extends App {
import scala.concurrent.Future
import sttp.client4._
import sttp.client4.akkahttp._
import sttp.client4.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: Backend[Future] = 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.client4" %% "zio" % "4.0.0-M13",
"com.softwaremill.sttp.client4" %% "circe" % "4.0.0-M13",
"io.circe" %% "circe-generic" % "0.14.6"
)
Example code:
package sttp.client4.examples
import io.circe.generic.auto._
import sttp.client4._
import sttp.client4.circe._
import sttp.client4.httpclient.zio.{send, HttpClientZioBackend}
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.client4" %% "monix" % "4.0.0-M13",
"com.softwaremill.sttp.client4" %% "circe" % "4.0.0-M13",
"io.circe" %% "circe-generic" % "0.14.6"
)
Example code:
package sttp.client4.examples
import io.circe.generic.auto._
import sttp.client4._
import sttp.client4.httpclient.monix.HttpClientMonixBackend
import sttp.client4.circe._
object GetAndParseJsonGetRightMonixCirce extends App {
import monix.execution.Scheduler.Implicits.global
case class HttpBinResponse(origin: String, headers: Map[String, String])
val request: Request[HttpBinResponse] = 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.client4" %% "slf4j-backend" % "4.0.0-M13",
"com.softwaremill.sttp.client4" %% "circe" % "4.0.0-M13",
"io.circe" %% "circe-generic" % "0.14.6"
)
Example code:
package sttp.client4.examples
import io.circe.generic.auto._
import sttp.client4._
import sttp.client4.circe._
import sttp.client4.httpclient.HttpClientSyncBackend
import sttp.client4.logging.slf4j.Slf4jLoggingBackend
import sttp.client4.logging.LogConfig
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: SyncBackend =
Slf4jLoggingBackend(
HttpClientSyncBackend(),
LogConfig(
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.client4" %% "monix" % "4.0.0-M13",
"com.softwaremill.sttp.client4" %% "circe" % "4.0.0-M13",
"io.circe" %% "circe-generic" % "0.14.6"
)
Example code:
package sttp.client4.examples
object PostSerializeJsonMonixHttpClientCirce extends App {
import sttp.client4._
import sttp.client4.circe._
import sttp.client4.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.client4" %% "core" % "4.0.0-M13")
Example code:
package sttp.client4.examples
object TestEndpointMultipleQueryParameters extends App {
import sttp.client4._
import sttp.client4.testing._
val backend = SyncBackendStub
.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.client4" %% "zio" % "4.0.0-M13")
Example code:
package sttp.client4.examples
import sttp.client4._
import sttp.client4.ws.async._
import sttp.client4.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: WebSocketBackend[Task]): Task[Response[Unit]] =
basicRequest.get(uri"wss://ws.postman-echo.com/raw").response(asWebSocketAlways(useWebSocket)).send(backend)
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.client4" %% "fs2" % "4.0.0-M13")
Example code:
package sttp.client4.examples
import cats.effect.IO
import cats.effect.unsafe.IORuntime
import fs2._
import sttp.capabilities.fs2.Fs2Streams
import sttp.client4._
import sttp.client4.ws.stream._
import sttp.client4.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
.get(uri"wss://ws.postman-echo.com/raw")
.response(asWebSocketStream(Fs2Streams[IO])(webSocketFramePipe))
.send(backend)
.void
}
.unsafeRunSync()
}
Test Monix websockets
Required dependencies:
libraryDependencies ++= List("com.softwaremill.sttp.client4" %% "monix" % "4.0.0-M13")
Example code:
package sttp.client4.examples
import monix.eval.Task
import sttp.capabilities.monix.MonixStreams
import sttp.client4._
import sttp.client4.ws.async._
import sttp.client4.httpclient.monix.HttpClientMonixBackend
import sttp.client4.testing.WebSocketStreamBackendStub
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: WebSocketBackend[Task]): Task[Unit] =
basicRequest
.get(uri"wss://echo.websocket.org")
.response(asWebSocket(useWebSocket))
.send(backend)
.void
// the backend stub which we'll use instead of a "real" backend
val stubBackend: WebSocketStreamBackendStub[Task, MonixStreams] =
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.client4" %% "akka-http-backend" % "4.0.0-M13")
Example code:
package sttp.client4.examples
import sttp.client4._
import sttp.client4.ws.async._
import sttp.client4.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
.get(uri"wss://ws.postman-echo.com/raw")
.response(asWebSocket(useWebSocket))
.send(backend)
.onComplete(_ => backend.close())
}
Open a websocket using Pekko
Required dependencies:
libraryDependencies ++= List("com.softwaremill.sttp.client4" %% "pekko-http-backend" % "4.0.0-M13")
Example code:
package sttp.client4.examples
import sttp.client4._
import sttp.client4.ws.async._
import sttp.client4.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.client4" %% "monix" % "4.0.0-M13")
Example code:
package sttp.client4.examples
import monix.eval.Task
import sttp.client4._
import sttp.client4.ws.async._
import sttp.client4.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
.get(uri"wss://ws.postman-echo.com/raw")
.response(asWebSocket(useWebSocket))
.send(backend)
.void
}
.runSyncUnsafe()
}
Stream request and response bodies using fs2
Required dependencies:
libraryDependencies ++= List("com.softwaremill.sttp.client4" %% "fs2" % "4.0.0-M13")
Example code:
package sttp.client4.examples
import sttp.client4._
import sttp.client4.httpclient.fs2.HttpClientFs2Backend
import cats.effect.IO
import cats.instances.string._
import fs2.{text, Stream}
import sttp.capabilities.fs2.Fs2Streams
object StreamFs2 extends App {
def streamRequestBody(backend: StreamBackend[IO, Fs2Streams[IO]]): IO[Unit] = {
val stream: Stream[IO, Byte] = Stream.emits("Hello, world".getBytes)
basicRequest
.post(uri"https://httpbin.org/post")
.streamBody(Fs2Streams[IO])(stream)
.send(backend)
.map(response => println(s"RECEIVED:\n${response.body}"))
}
def streamResponseBody(backend: StreamBackend[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.client4" %% "zio" % "4.0.0-M13")
Example code:
package sttp.client4.examples
import sttp.capabilities.zio.ZioStreams
import sttp.client4._
import zio.Console._
import zio._
import zio.stream._
import sttp.client4.httpclient.zio.{send, HttpClientZioBackend, SttpClient}
object StreamZio extends ZIOAppDefault {
def streamRequestBody: RIO[SttpClient, Unit] = {
val stream: Stream[Throwable, Byte] = ZStream("Hello, world".getBytes.toIndexedSeq: _*)
send(
basicRequest
.post(uri"https://httpbin.org/post")
.streamBody(ZioStreams)(stream)
).flatMap(response => printLine(s"RECEIVED:\n${response.body}"))
}
def streamResponseBody: RIO[SttpClient, Unit] =
send(
basicRequest
.post(uri"https://httpbin.org/post")
.body("I want a stream!")
.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.client4" %% "zio" % "4.0.0-M13")
Example code:
package sttp.client4.examples
import sttp.client4._
import sttp.client4.httpclient.zio.HttpClientZioBackend
import zio.{durationInt, Schedule, Task, ZIO, ZIOAppDefault}
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.client4" %% "core" % "4.0.0-M13")
Example code:
package sttp.client4.examples
import io.circe
import io.circe.generic.auto._
import sttp.client4._
import sttp.client4.circe._
import sttp.client4.httpclient.HttpClientSyncBackend
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: SyncBackend = 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()
}