Skip to content

Commit

Permalink
http4s - migrate from Blaze to Ember
Browse files Browse the repository at this point in the history
  • Loading branch information
ivan-klass committed Nov 30, 2024
1 parent eb121c0 commit 6aa2b66
Show file tree
Hide file tree
Showing 31 changed files with 199 additions and 233 deletions.
18 changes: 9 additions & 9 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ lazy val clientTestServer = (projectMatrix in file("client/testserver"))
publish / skip := true,
libraryDependencies ++= Seq(
"org.http4s" %% "http4s-dsl" % Versions.http4s,
"org.http4s" %% "http4s-blaze-server" % Versions.http4sBlazeServer,
"org.http4s" %% "http4s-ember-server" % Versions.http4s,
"org.http4s" %% "http4s-circe" % Versions.http4s,
logback
),
Expand Down Expand Up @@ -533,7 +533,7 @@ lazy val perfTests: ProjectMatrix = (projectMatrix in file("perf-tests"))
"io.github.classgraph" % "classgraph" % "4.8.179",
"org.http4s" %% "http4s-core" % Versions.http4s,
"org.http4s" %% "http4s-dsl" % Versions.http4s,
"org.http4s" %% "http4s-blaze-server" % Versions.http4sBlazeServer,
"org.http4s" %% "http4s-ember-server" % Versions.http4s,
"org.typelevel" %%% "cats-effect" % Versions.catsEffect,
logback
),
Expand Down Expand Up @@ -1142,7 +1142,7 @@ lazy val swaggerUiBundle: ProjectMatrix = (projectMatrix in file("docs/swagger-u
name := "tapir-swagger-ui-bundle",
libraryDependencies ++= Seq(
"com.softwaremill.sttp.apispec" %% "openapi-circe-yaml" % Versions.sttpApispec,
"org.http4s" %% "http4s-blaze-server" % Versions.http4sBlazeServer % Test,
"org.http4s" %% "http4s-ember-server" % Versions.http4s % Test,
scalaTest.value % Test
)
)
Expand All @@ -1168,7 +1168,7 @@ lazy val redocBundle: ProjectMatrix = (projectMatrix in file("docs/redoc-bundle"
name := "tapir-redoc-bundle",
libraryDependencies ++= Seq(
"com.softwaremill.sttp.apispec" %% "openapi-circe-yaml" % Versions.sttpApispec,
"org.http4s" %% "http4s-blaze-server" % Versions.http4sBlazeServer % Test,
"org.http4s" %% "http4s-ember-server" % Versions.http4s % Test,
scalaTest.value % Test
)
)
Expand Down Expand Up @@ -1303,7 +1303,7 @@ lazy val http4sServer: ProjectMatrix = (projectMatrix in file("server/http4s-ser
scalaVersions = scala2And3Versions,
settings = commonJvmSettings ++ Seq {
libraryDependencies ++= Seq(
"org.http4s" %%% "http4s-blaze-server" % Versions.http4sBlazeServer % Test
"org.http4s" %%% "http4s-ember-server" % Versions.http4s % Test
)
}
)
Expand All @@ -1319,7 +1319,7 @@ lazy val http4sServerZio: ProjectMatrix = (projectMatrix in file("server/http4s-
name := "tapir-http4s-server-zio",
libraryDependencies ++= Seq(
"dev.zio" %% "zio-interop-cats" % Versions.zioInteropCats,
"org.http4s" %% "http4s-blaze-server" % Versions.http4sBlazeServer % Test
"org.http4s" %% "http4s-ember-server" % Versions.http4s % Test
)
)
.jvmPlatform(scalaVersions = scala2And3Versions, settings = commonJvmSettings)
Expand Down Expand Up @@ -1887,7 +1887,7 @@ lazy val http4sClient: ProjectMatrix = (projectMatrix in file("client/http4s-cli
name := "tapir-http4s-client",
libraryDependencies ++= Seq(
"org.http4s" %% "http4s-core" % Versions.http4s,
"org.http4s" %% "http4s-blaze-client" % Versions.http4sBlazeClient % Test,
"org.http4s" %% "http4s-ember-client" % Versions.http4s % Test,
"com.softwaremill.sttp.shared" %% "fs2" % Versions.sttpShared % Optional
)
)
Expand Down Expand Up @@ -2049,7 +2049,7 @@ lazy val examples: ProjectMatrix = (projectMatrix in file("examples"))
"com.github.jwt-scala" %% "jwt-circe" % Versions.jwtScala,
"org.http4s" %% "http4s-dsl" % Versions.http4s,
"org.http4s" %% "http4s-circe" % Versions.http4s,
"org.http4s" %% "http4s-blaze-server" % Versions.http4sBlazeServer,
"org.http4s" %% "http4s-ember-server" % Versions.http4s,
"org.mock-server" % "mockserver-netty" % Versions.mockServer,
"io.opentelemetry" % "opentelemetry-sdk" % Versions.openTelemetry,
"io.opentelemetry" % "opentelemetry-sdk-metrics" % Versions.openTelemetry,
Expand Down Expand Up @@ -2117,7 +2117,7 @@ lazy val documentation: ProjectMatrix = (projectMatrix in file("generated-doc"))
name := "doc",
libraryDependencies ++= Seq(
"org.playframework" %% "play-netty-server" % Versions.playServer,
"org.http4s" %% "http4s-blaze-server" % Versions.http4sBlazeServer,
"org.http4s" %% "http4s-ember-server" % Versions.http4s,
"com.softwaremill.sttp.apispec" %% "openapi-circe-yaml" % Versions.sttpApispec,
"com.softwaremill.sttp.apispec" %% "asyncapi-circe-yaml" % Versions.sttpApispec
),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package sttp.tapir.client.http4s

import cats.effect.IO
import org.http4s.blaze.client.BlazeClientBuilder
import org.http4s.ember.client.EmberClientBuilder
import org.http4s.{Request, Response, Uri}
import sttp.tapir.client.tests.ClientTests
import sttp.tapir.{DecodeResult, Endpoint}
import scala.concurrent.ExecutionContext.global

abstract class Http4sClientTests[R] extends ClientTests[R] {
override def send[A, I, E, O](
Expand Down Expand Up @@ -35,7 +34,7 @@ abstract class Http4sClientTests[R] extends ClientTests[R] {
}

private def sendAndParseResponse[Result](request: Request[IO], parseResponse: Response[IO] => IO[Result]) =
BlazeClientBuilder[IO](global).resource.use { client =>
EmberClientBuilder.default[IO].build.use { client =>
client.run(request).use(parseResponse)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ package sttp.tapir.client.tests
import cats.effect._
import cats.effect.std.Queue
import cats.effect.unsafe.implicits.global
import com.comcast.ip4s
import cats.implicits._
import fs2.{Pipe, Stream}
import org.http4s.dsl.io._
import org.http4s.headers.{Accept, `Content-Type`}
import org.http4s.server.Router
import org.http4s.blaze.server.BlazeServerBuilder
import org.http4s.ember.server.EmberServerBuilder
import org.http4s.server.middleware._
import org.http4s.server.websocket.WebSocketBuilder2
import org.http4s.websocket.WebSocketFrame
Expand All @@ -33,7 +34,7 @@ class HttpServer(port: Port) {

private val logger = LoggerFactory.getLogger(getClass)

private var stopServer: IO[Unit] = _
private val stopServer: Deferred[IO, Unit] = Deferred.unsafe[IO, Unit]

//

Expand Down Expand Up @@ -213,22 +214,19 @@ class HttpServer(port: Port) {
//

def start(): Unit = {
val (_, _stopServer) = BlazeServerBuilder[IO]
.withExecutionContext(ExecutionContext.global)
.bindHttp(port)
EmberServerBuilder
.default[IO]
.withPort(ip4s.Port.fromInt(port).get)
.withHttpWebSocketApp(app)
.resource
.map(_.address.getPort)
.allocated
.build
.use(_ => stopServer.get)
.unsafeRunSync()

stopServer = _stopServer

logger.info(s"Server on port $port started")
}

def close(): Unit = {
stopServer.unsafeRunSync()
stopServer.complete(()).unsafeRunSync()
logger.info(s"Server on port $port stopped")
}
}
17 changes: 7 additions & 10 deletions doc/server/http4s.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,11 @@ The capability can be added to the classpath independently of the interpreter th
## Http4s backends

Http4s integrates with a couple of [server backends](https://http4s.org/v1.0/integrations/), the most popular being
Blaze and Ember. In the [examples](../examples.md) and throughout the docs we use Blaze, but other backends can be used
Blaze and Ember. In the [examples](../examples.md) and throughout the docs we use Ember, but other backends can be used
as well. This means adding another dependency, such as:

```scala
"org.http4s" %% "http4s-blaze-server" % Http4sVersion
"org.http4s" %% "http4s-ember-server" % Http4sVersion
```

## Web sockets
Expand All @@ -75,24 +75,21 @@ import sttp.tapir.*
import sttp.tapir.server.http4s.Http4sServerInterpreter
import cats.effect.IO
import org.http4s.HttpRoutes
import org.http4s.blaze.server.BlazeServerBuilder
import org.http4s.ember.server.EmberServerBuilder
import org.http4s.server.Router
import org.http4s.server.websocket.WebSocketBuilder2
import fs2.*
import scala.concurrent.ExecutionContext

given ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global

val wsEndpoint: PublicEndpoint[Unit, Unit, Pipe[IO, String, String], Fs2Streams[IO] with WebSockets] =
endpoint.get.in("count").out(webSocketBody[String, CodecFormat.TextPlain, String, CodecFormat.TextPlain](Fs2Streams[IO]))

val wsRoutes: WebSocketBuilder2[IO] => HttpRoutes[IO] =
Http4sServerInterpreter[IO]().toWebSocketRoutes(wsEndpoint.serverLogicSuccess[IO](_ => ???))

BlazeServerBuilder[IO]
.withExecutionContext(summon[ExecutionContext])
.bindHttp(8080, "localhost")
.withHttpWebSocketApp(wsb => Router("/" -> wsRoutes(wsb)).orNotFound)

EmberServerBuilder
.default[IO]
.withHttpWebSocketApp(wsb => Router("/" -> wsRoutes(wsb)).orNotFound)
```

## Server Sent Events
Expand Down
18 changes: 7 additions & 11 deletions doc/server/zio-http4s.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,11 @@ The capability can be added to the classpath independently of the interpreter th
## Http4s backends

Http4s integrates with a couple of [server backends](https://http4s.org/v1.0/integrations/), the most popular being
Blaze and Ember. In the [examples](../examples.md) and throughout the docs we use Blaze, but other backends can be used
Blaze and Ember. In the [examples](../examples.md) and throughout the docs we use Ember, but other backends can be used
as well. This means adding another dependency, such as:

```scala
"org.http4s" %% "http4s-blaze-server" % Http4sVersion
"org.http4s" %% "http4s-ember-server" % Http4sVersion
```

## Web sockets
Expand All @@ -121,7 +121,7 @@ import sttp.tapir.{CodecFormat, PublicEndpoint}
import sttp.tapir.ztapir.*
import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter
import org.http4s.HttpRoutes
import org.http4s.blaze.server.BlazeServerBuilder
import org.http4s.ember.server.EmberServerBuilder
import org.http4s.server.Router
import org.http4s.server.websocket.WebSocketBuilder2
import scala.concurrent.ExecutionContext
Expand All @@ -131,8 +131,6 @@ import zio.stream.Stream

def runtime: Runtime[Any] = ??? // provided by ZIOAppDefault

given ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global

val wsEndpoint: PublicEndpoint[Unit, Unit, Stream[Throwable, String] => Stream[Throwable, String], ZioStreams with WebSockets] =
endpoint.get.in("count").out(webSocketBody[String, CodecFormat.TextPlain, String, CodecFormat.TextPlain](ZioStreams))

Expand All @@ -141,13 +139,11 @@ val wsRoutes: WebSocketBuilder2[Task] => HttpRoutes[Task] =

val serve: Task[Unit] =
ZIO.executor.flatMap(executor =>
BlazeServerBuilder[Task]
.withExecutionContext(executor.asExecutionContext)
.bindHttp(8080, "localhost")
EmberServerBuilder
.default[Task]
.withHttpWebSocketApp(wsb => Router("/" -> wsRoutes(wsb)).orNotFound)
.serve
.compile
.drain
.build
.useForever
)
```

Expand Down
32 changes: 15 additions & 17 deletions doc/tutorials/07_cats_effect.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,11 +132,11 @@ standard code to start a server and handle requests until the application is int
```scala
//> using dep com.softwaremill.sttp.tapir::tapir-core:@VERSION@
//> using dep com.softwaremill.sttp.tapir::tapir-http4s-server:@VERSION@
//> using dep org.http4s::http4s-blaze-server:0.23.16
//> using dep org.http4s::http4s-ember-server:0.23.29

import cats.effect.{ExitCode, IO, IOApp}
import org.http4s.HttpRoutes
import org.http4s.blaze.server.BlazeServerBuilder
import org.http4s.ember.server.EmberServerBuilder
import org.http4s.server.Router
import sttp.tapir.*
import sttp.tapir.server.http4s.Http4sServerInterpreter
Expand All @@ -154,12 +154,11 @@ object HelloWorldTapir extends IOApp:
.toRoutes(helloWorldEndpoint)

override def run(args: List[String]): IO[ExitCode] =
BlazeServerBuilder[IO]
.bindHttp(8080, "localhost")
EmberServerBuilder
.default[IO]
.withHttpApp(Router("/" -> helloWorldRoutes).orNotFound)
.resource
.use(_ => IO.never)
.as(ExitCode.Success)
.build
.useForever
```

First of all, you might notice that instead of the `@main` method, we are extending the `IOApp` trait. This is needed,
Expand All @@ -169,8 +168,8 @@ the `IOApp` will handle evaluating the `IO` description and actually running the

Secondly, with http4s we need to use a specific server implementation (http4s itself is only an API to define endpoints -
kind of a middle-man between Tapir and low-level networking code). We can choose from `blaze` and `ember` servers, here
we're using the `blaze` one, which is reflected in the additional dependency and the server configuration constructor:
`BlazeServerBuilder`.
we're using the `ember` one, which is reflected in the additional dependency and the server configuration constructor:
`EmberServerBuilder`.

Finally, we've got the `run` method implementation, which attaches our interpreted route to the root context `/` and
exposes the server on `localhost:8080`.
Expand All @@ -195,12 +194,12 @@ the second step that we need to perform:
//> using dep com.softwaremill.sttp.tapir::tapir-core:@VERSION@
//> using dep com.softwaremill.sttp.tapir::tapir-http4s-server:@VERSION@
//> using dep com.softwaremill.sttp.tapir::tapir-swagger-ui-bundle:@VERSION@
//> using dep org.http4s::http4s-blaze-server:0.23.16
//> using dep org.http4s::http4s-ember-server:0.23.29

import cats.effect.{ExitCode, IO, IOApp}
import cats.syntax.all.*
import org.http4s.HttpRoutes
import org.http4s.blaze.server.BlazeServerBuilder
import org.http4s.ember.server.EmberServerBuilder
import org.http4s.server.Router
import sttp.tapir.*
import sttp.tapir.server.http4s.Http4sServerInterpreter
Expand All @@ -226,17 +225,16 @@ object HelloWorldTapir extends IOApp:
val allRoutes: HttpRoutes[IO] = helloWorldRoutes <+> swaggerRoutes

override def run(args: List[String]): IO[ExitCode] =
BlazeServerBuilder[IO]
.bindHttp(8080, "localhost")
EmberServerBuilder
.default[IO]
.withHttpApp(Router("/" -> allRoutes).orNotFound)
.resource
.use(_ => IO.never)
.as(ExitCode.Success)
.build
.useForever
```

Hence, we first generate endpoint descriptions, which correspond to exposing the Swagger UI (containing the generated
OpenAPI yaml for our `/hello/world` endpoint), which use `IO` to express their server logic. Then, we interpret those
endpoints as `HttpRoutes[IO]`, which we can expose using http4's blaze server.
endpoints as `HttpRoutes[IO]`, which we can expose using http4's ember server.

## Other concepts covered so far

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ package sttp.tapir.redoc.bundle

import cats.effect.IO
import cats.effect.unsafe.implicits.global
import com.comcast.ip4s.Port
import org.http4s.HttpRoutes
import org.http4s.blaze.server.BlazeServerBuilder
import org.http4s.ember.server.EmberServerBuilder
import org.http4s.server.Router
import org.scalatest.Assertion
import org.scalatest.funsuite.AsyncFunSuite
Expand Down Expand Up @@ -66,10 +67,11 @@ class RedocInterpreterTest extends AsyncFunSuite with Matchers {
.fromEndpoints[IO](List(testEndpoint), "The tapir library", "1.0.0")
)

BlazeServerBuilder[IO]
.bindHttp(0, "localhost")
EmberServerBuilder
.default[IO]
.withPort(Port.fromInt(0).get)
.withHttpApp(Router(s"/${context.mkString("/")}" -> redocUIRoutes).orNotFound)
.resource
.build
.use { server =>
IO {
val port = server.address.getPort
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ package sttp.tapir.swagger.bundle

import cats.effect.IO
import cats.effect.unsafe.implicits.global
import com.comcast.ip4s.Port
import org.http4s.HttpRoutes
import org.http4s.blaze.server.BlazeServerBuilder
import org.http4s.ember.server.EmberServerBuilder
import org.http4s.server.Router
import org.scalatest.Assertion
import org.scalatest.funsuite.AsyncFunSuite
Expand Down Expand Up @@ -33,10 +34,11 @@ class SwaggerInterpreterTest extends AsyncFunSuite with Matchers {
.fromEndpoints[IO](List(testEndpoint), "The tapir library", "1.0.0")
)

BlazeServerBuilder[IO]
.bindHttp(0, "localhost")
EmberServerBuilder
.default[IO]
.withPort(Port.fromInt(0).get)
.withHttpApp(Router(s"/${context.mkString("/")}" -> swaggerUIRoutes).orNotFound)
.resource
.build
.use { server =>
IO {
val port = server.address.getPort
Expand Down
Loading

0 comments on commit 6aa2b66

Please sign in to comment.