Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Pekko support #1781

Merged
merged 7 commits into from
Aug 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,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.16" % Test
)
Expand Down Expand Up @@ -193,6 +197,7 @@ lazy val allAggregates = projectsWithOptionalNative ++
zio1.projectRefs ++
zio.projectRefs ++
akkaHttpBackend.projectRefs ++
pekkoHttpBackend.projectRefs ++
asyncHttpClientBackend.projectRefs ++
asyncHttpClientFutureBackend.projectRefs ++
asyncHttpClientScalazBackend.projectRefs ++
Expand Down Expand Up @@ -527,6 +532,25 @@ lazy val akkaHttpBackend = (projectMatrix in file("akka-http-backend"))
scalaVersions = scala2
)

//-- 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 = scala2 ++ scala3
)

//-- async http client
lazy val asyncHttpClientBackend = (projectMatrix in file("async-http-client-backend"))
.settings(commonJvmSettings)
Expand Down Expand Up @@ -973,7 +997,8 @@ lazy val examples = (projectMatrix in file("examples"))
libraryDependencies ++= Seq(
"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 @@ -982,6 +1007,7 @@ lazy val examples = (projectMatrix in file("examples"))
core,
asyncHttpClientZioBackend,
akkaHttpBackend,
pekkoHttpBackend,
asyncHttpClientFs2Backend,
json4s,
circe,
Expand All @@ -1008,6 +1034,7 @@ lazy val docs: ProjectMatrix = (projectMatrix in file("generated-docs")) // impo
"BRAVE_OPENTRACING_VERSION" -> braveOpentracingVersion,
"ZIPKIN_SENDER_OKHTTP_VERSION" -> zipkinSenderOkHttpVersion,
"AKKA_STREAM_VERSION" -> akkaStreamVersion,
"PEKKO_STREAM_VERSION" -> pekkoStreamVersion,
"CIRCE_VERSION" -> circeVersion
),
mdocOut := file("generated-docs/out"),
Expand All @@ -1025,13 +1052,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.client4" %% "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.client4.pekkohttp._
val backend = PekkoHttpBackend()
```

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

```scala mdoc:compile-only
import sttp.client4.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.client4.pekkohttp.PekkoStreams`.

To set the request body as a stream:

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

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.client4._
import sttp.client4.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.client4.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.client4.pekkohttp.PekkoHttpServerSentEvents
import sttp.model.sse.ServerSentEvent
import sttp.client4._

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.client4" %% "pekko-http-backend" % "@VERSION@")
```

Example code:

```eval_rst
.. literalinclude:: ../../examples/src/main/scala/sttp/client4/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 lower-level 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 the HTTP client that is shipped with Java, as well as 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/). 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.12, 2.13 and 3, Scala.JS and Scala Native; supported Java versions include 11+.
Backend implementations include the HTTP client that is shipped with Java, as well as 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/). 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.12, 2.13 and 3, Scala.JS and Scala Native; supported Java versions include 11+.

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

Expand Down Expand Up @@ -123,6 +123,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/client4/examples/WebSocketPekko.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package sttp.client4.examples

import sttp.client4._
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())
}
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.client4" %% "pekko-http-backend" % "@VERSION@")
```

Example code:

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

## Open a websocket using Monix

Required dependencies:
Expand Down
Loading