Skip to content

Commit

Permalink
[v3] Add Pekko support (#1920)
Browse files Browse the repository at this point in the history
- Originally authored by @mdedetrich
  • Loading branch information
kciesielski authored Aug 9, 2023
1 parent e954e04 commit 15d710d
Show file tree
Hide file tree
Showing 21 changed files with 1,262 additions and 7 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ core/native/local.sbt

.metals/
.bloop/
project/metals.sbt
metals.sbt
.bsp/
.java-version

Expand Down
37 changes: 33 additions & 4 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ val akkaHttp = "com.typesafe.akka" %% "akka-http" % "10.2.10"
val akkaStreamVersion = "2.6.20"
val akkaStreams = "com.typesafe.akka" %% "akka-stream" % akkaStreamVersion

val pekkoHttp = "org.apache.pekko" %% "pekko-http" % "1.0.0"
val pekkoStreamVersion = "1.0.1"
val pekkoStreams = "org.apache.pekko" %% "pekko-stream" % pekkoStreamVersion

val scalaTest = libraryDependencies ++= Seq("freespec", "funsuite", "flatspec", "wordspec", "shouldmatchers").map(m =>
"org.scalatest" %%% s"scalatest-$m" % "3.2.15" % Test
)
Expand All @@ -155,7 +159,7 @@ val zio1InteropRsVersion = "1.3.12"
val zio2InteropRsVersion = "2.0.1"

val sttpModelVersion = "1.5.5"
val sttpSharedVersion = "1.3.15"
val sttpSharedVersion = "1.3.16"

val logback = "ch.qos.logback" % "logback-classic" % "1.4.5"

Expand Down Expand Up @@ -196,6 +200,7 @@ lazy val allAggregates = projectsWithOptionalNative ++
zio1.projectRefs ++
zio.projectRefs ++
akkaHttpBackend.projectRefs ++
pekkoHttpBackend.projectRefs ++
asyncHttpClientBackend.projectRefs ++
asyncHttpClientFutureBackend.projectRefs ++
asyncHttpClientScalazBackend.projectRefs ++
Expand Down Expand Up @@ -541,6 +546,25 @@ lazy val akkaHttpBackend = (projectMatrix in file("akka-http-backend"))
scalaVersions = scala2alive
)

//-- pekko
lazy val pekkoHttpBackend = (projectMatrix in file("pekko-http-backend"))
.settings(commonJvmSettings)
.settings(testServerSettings)
.settings(
name := "pekko-http-backend",
libraryDependencies ++= Seq(
pekkoHttp,
// provided as we don't want to create a transitive dependency on a specific streams version,
// just as akka-http doesn't
pekkoStreams % "provided",
"com.softwaremill.sttp.shared" %% "pekko" % sttpSharedVersion
)
)
.dependsOn(core % compileAndTest)
.jvmPlatform(
scalaVersions = scala2alive ++ scala3
)

//-- async http client
lazy val asyncHttpClientBackend = (projectMatrix in file("async-http-client-backend"))
.settings(commonJvmSettings)
Expand Down Expand Up @@ -1001,7 +1025,8 @@ lazy val examples = (projectMatrix in file("examples"))
libraryDependencies ++= dependenciesFor(scalaVersion.value)(
"io.circe" %% "circe-generic" % circeVersion(_),
_ => "org.json4s" %% "json4s-native" % json4sVersion,
_ => akkaStreams,
_ => akkaStreams.exclude("org.scala-lang.modules", "scala-java8-compat_2.12"),
_ => pekkoStreams,
_ => logback
)
)
Expand All @@ -1010,6 +1035,7 @@ lazy val examples = (projectMatrix in file("examples"))
core,
asyncHttpClientZioBackend,
akkaHttpBackend,
pekkoHttpBackend,
asyncHttpClientFs2Backend,
json4s,
circe,
Expand All @@ -1036,7 +1062,8 @@ lazy val docs: ProjectMatrix = (projectMatrix in file("generated-docs")) // impo
"BRAVE_OPENTRACING_VERSION" -> braveOpentracingVersion,
"ZIPKIN_SENDER_OKHTTP_VERSION" -> zipkinSenderOkHttpVersion,
"AKKA_STREAM_VERSION" -> akkaStreamVersion,
"CIRCE_VERSION" -> circeVersion(None)
"CIRCE_VERSION" -> circeVersion(None),
"PEKKO_STREAM_VERSION" -> pekkoStreamVersion
),
mdocOut := file("generated-docs/out"),
mdocExtraArguments := Seq("--clean-target"),
Expand All @@ -1053,13 +1080,15 @@ lazy val docs: ProjectMatrix = (projectMatrix in file("generated-docs")) // impo
"io.opentracing.brave" % "brave-opentracing" % braveOpentracingVersion,
"io.zipkin.reporter2" % "zipkin-sender-okhttp3" % zipkinSenderOkHttpVersion,
"io.opentelemetry" % "opentelemetry-semconv" % "1.2.0-alpha",
akkaStreams
akkaStreams,
pekkoStreams
),
evictionErrorLevel := Level.Info
)
.dependsOn(
core % "compile->test",
akkaHttpBackend,
pekkoHttpBackend,
json4s,
circe,
sprayJson,
Expand Down
123 changes: 123 additions & 0 deletions docs/backends/pekko.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Pekko backend

This backend is based on [pekko-http](https://pekko.apache.org/docs/pekko-http/current/). To use, add the following dependency to your project:

```
"com.softwaremill.sttp.client3" %% "pekko-http-backend" % "@VERSION@"
```

A fully **asynchronous** backend. Uses the `Future` effect to return responses. There are also [other `Future`-based backends](future.md), which don't depend on Pekko.

Note that you'll also need an explicit dependency on pekko-streams, as pekko-http doesn't depend on any specific pekko-streams version. So you'll also need to add, for example:

```
"org.apache.pekko" %% "pekko-stream" % "@PEKKO_STREAM_VERSION@"
```

Next you'll need to add create the backend instance:

```scala mdoc:compile-only
import sttp.client3.pekkohttp._
val backend = PekkoHttpBackend()
```

or, if you'd like to use an existing actor system:

```scala mdoc:compile-only
import sttp.client3.pekkohttp._
import org.apache.pekko.actor.ActorSystem

val actorSystem: ActorSystem = ???
val backend = PekkoHttpBackend.usingActorSystem(actorSystem)
```

This backend supports sending and receiving [pekko-streams](https://pekko.apache.org/docs/pekko/current/stream/index.html) streams. The streams capability is represented as `sttp.client3.pekkohttp.PekkoStreams`.

To set the request body as a stream:

```scala mdoc:compile-only
import sttp.capabilities.pekko.PekkoStreams
import sttp.client3._

import org.apache.pekko
import pekko.stream.scaladsl.Source
import pekko.util.ByteString

val source: Source[ByteString, Any] = ???

basicRequest
.post(uri"...")
.streamBody(PekkoStreams)(source)
```

To receive the response body as a stream:

```scala mdoc:compile-only
import scala.concurrent.Future
import sttp.capabilities.pekko.PekkoStreams
import sttp.client3._
import sttp.client3.pekkohttp.PekkoHttpBackend

import org.apache.pekko
import pekko.stream.scaladsl.Source
import pekko.util.ByteString

val backend = PekkoHttpBackend()

val response: Future[Response[Either[String, Source[ByteString, Any]]]] =
basicRequest
.post(uri"...")
.response(asStreamUnsafe(PekkoStreams))
.send(backend)
```

The pekko-http backend support both regular and streaming [websockets](../websockets.md).

## Testing

Apart from testing using [the stub](../testing.md), you can create a backend using any `HttpRequest => Future[HttpResponse]` function, or an pekko-http `Route`.

That way, you can "mock" a server that the backend will talk to, without starting any actual server or making any HTTP calls.

If your application provides a client library for its dependants to use, this is a great way to ensure that the client actually matches the routes exposed by your application:

```scala mdoc:compile-only
import sttp.client3.pekkohttp._
import org.apache.pekko
import pekko.http.scaladsl.server.Route
import pekko.actor.ActorSystem

val route: Route = ???
implicit val system: ActorSystem = ???

val backend = PekkoHttpBackend.usingClient(system, http = PekkoHttpClient.stubFromRoute(route))
```

## WebSockets

Non-standard behavior:

* pekko always automatically responds with a `Pong` to a `Ping` message
* `WebSocketFrame.Ping` and `WebSocketFrame.Pong` frames are ignored; instead, you can configure automatic [keep-alive pings](https://pekko.apache.org/docs/pekko-http/current/client-side/websocket-support.html#automatic-keep-alive-ping-support)

## Server-sent events

Received data streams can be parsed to a stream of server-sent events (SSE):

```scala mdoc:compile-only
import scala.concurrent.Future

import org.apache.pekko.stream.scaladsl.Source

import sttp.capabilities.pekko.PekkoStreams
import sttp.client3.pekkohttp.PekkoHttpServerSentEvents
import sttp.model.sse.ServerSentEvent
import sttp.client3._

def processEvents(source: Source[ServerSentEvent, Any]): Future[Unit] = ???

basicRequest
.get(uri"...")
.response(asStream(PekkoStreams)(stream =>
processEvents(stream.via(PekkoHttpServerSentEvents.parse))))
```
15 changes: 15 additions & 0 deletions docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,21 @@ Example code:
:language: scala
```

## Open a websocket using Pekko

Required dependencies:

```scala
libraryDependencies ++= List("com.softwaremill.sttp.client3" %% "pekko-http-backend" % "@VERSION@")
```

Example code:

```eval_rst
.. literalinclude:: ../../examples/src/main/scala/sttp/client3/examples/WebSocketPekko.scala
:language: scala
```

## Open a websocket using Monix

Required dependencies:
Expand Down
3 changes: 2 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Welcome!
[sttp client](https://github.com/softwaremill/sttp) is an open-source library which provides a clean, programmer-friendly API to describe HTTP
requests and how to handle responses. Requests are sent using one of the backends, which wrap other Scala or Java HTTP client implementations. The backends can integrate with a variety of Scala stacks, providing both synchronous and asynchronous, procedural and functional interfaces.

Backend implementations include ones based on [akka-http](https://doc.akka.io/docs/akka-http/current/scala/http/), [http4s](https://http4s.org), [OkHttp](http://square.github.io/okhttp/), and HTTP clients which ship with Java. They integrate with [Akka](https://akka.io), [Monix](https://monix.io), [fs2](https://github.com/functional-streams-for-scala/fs2), [cats-effect](https://github.com/typelevel/cats-effect), [scalaz](https://github.com/scalaz/scalaz) and [ZIO](https://github.com/zio/zio). Supported Scala versions include 2.11, 2.12, 2.13 and 3, Scala.JS and Scala Native.
Backend implementations include ones based on [akka-http](https://doc.akka.io/docs/akka-http/current/scala/http/), [pekko-http](https://pekko.apache.org/docs/pekko-http/current/), [http4s](https://http4s.org), [OkHttp](http://square.github.io/okhttp/), and HTTP clients which ship with Java. They integrate with [Akka](https://akka.io), [Monix](https://monix.io), [fs2](https://github.com/functional-streams-for-scala/fs2), [cats-effect](https://github.com/typelevel/cats-effect), [scalaz](https://github.com/scalaz/scalaz) and [ZIO](https://github.com/zio/zio). Supported Scala versions include 2.11, 2.12, 2.13 and 3, Scala.JS and Scala Native.

Here's a quick example of sttp client in action:

Expand Down Expand Up @@ -124,6 +124,7 @@ We offer commercial support for sttp and related technologies, as well as develo
backends/start_stop
backends/synchronous
backends/akka
backends/pekko
backends/future
backends/monix
backends/catseffect
Expand Down
29 changes: 29 additions & 0 deletions examples/src/main/scala/sttp/client3/examples/WebSocketPekko.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
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())
}
15 changes: 15 additions & 0 deletions generated-docs/out/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,21 @@ Example code:
:language: scala
```

## Open a websocket using Pekko

Required dependencies:

```scala
libraryDependencies ++= List("com.softwaremill.sttp.client3" %% "pekko-http-backend" % "@VERSION@")
```

Example code:

```eval_rst
.. literalinclude:: ../../examples/src/main/scala/sttp/client3/examples/WebSocketPekko.scala
:language: scala
```

## Open a websocket using Monix

Required dependencies:
Expand Down
Loading

0 comments on commit 15d710d

Please sign in to comment.