Skip to content

Commit 088bbfa

Browse files
committed
http4s - migrate from Blaze to Ember
1 parent eb121c0 commit 088bbfa

File tree

31 files changed

+200
-233
lines changed

31 files changed

+200
-233
lines changed

build.sbt

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@ lazy val clientTestServer = (projectMatrix in file("client/testserver"))
390390
publish / skip := true,
391391
libraryDependencies ++= Seq(
392392
"org.http4s" %% "http4s-dsl" % Versions.http4s,
393-
"org.http4s" %% "http4s-blaze-server" % Versions.http4sBlazeServer,
393+
"org.http4s" %% "http4s-ember-server" % Versions.http4s,
394394
"org.http4s" %% "http4s-circe" % Versions.http4s,
395395
logback
396396
),
@@ -533,7 +533,7 @@ lazy val perfTests: ProjectMatrix = (projectMatrix in file("perf-tests"))
533533
"io.github.classgraph" % "classgraph" % "4.8.179",
534534
"org.http4s" %% "http4s-core" % Versions.http4s,
535535
"org.http4s" %% "http4s-dsl" % Versions.http4s,
536-
"org.http4s" %% "http4s-blaze-server" % Versions.http4sBlazeServer,
536+
"org.http4s" %% "http4s-ember-server" % Versions.http4s,
537537
"org.typelevel" %%% "cats-effect" % Versions.catsEffect,
538538
logback
539539
),
@@ -1142,7 +1142,7 @@ lazy val swaggerUiBundle: ProjectMatrix = (projectMatrix in file("docs/swagger-u
11421142
name := "tapir-swagger-ui-bundle",
11431143
libraryDependencies ++= Seq(
11441144
"com.softwaremill.sttp.apispec" %% "openapi-circe-yaml" % Versions.sttpApispec,
1145-
"org.http4s" %% "http4s-blaze-server" % Versions.http4sBlazeServer % Test,
1145+
"org.http4s" %% "http4s-ember-server" % Versions.http4s % Test,
11461146
scalaTest.value % Test
11471147
)
11481148
)
@@ -1168,7 +1168,7 @@ lazy val redocBundle: ProjectMatrix = (projectMatrix in file("docs/redoc-bundle"
11681168
name := "tapir-redoc-bundle",
11691169
libraryDependencies ++= Seq(
11701170
"com.softwaremill.sttp.apispec" %% "openapi-circe-yaml" % Versions.sttpApispec,
1171-
"org.http4s" %% "http4s-blaze-server" % Versions.http4sBlazeServer % Test,
1171+
"org.http4s" %% "http4s-ember-server" % Versions.http4s % Test,
11721172
scalaTest.value % Test
11731173
)
11741174
)
@@ -1303,7 +1303,7 @@ lazy val http4sServer: ProjectMatrix = (projectMatrix in file("server/http4s-ser
13031303
scalaVersions = scala2And3Versions,
13041304
settings = commonJvmSettings ++ Seq {
13051305
libraryDependencies ++= Seq(
1306-
"org.http4s" %%% "http4s-blaze-server" % Versions.http4sBlazeServer % Test
1306+
"org.http4s" %%% "http4s-ember-server" % Versions.http4s % Test
13071307
)
13081308
}
13091309
)
@@ -1319,7 +1319,7 @@ lazy val http4sServerZio: ProjectMatrix = (projectMatrix in file("server/http4s-
13191319
name := "tapir-http4s-server-zio",
13201320
libraryDependencies ++= Seq(
13211321
"dev.zio" %% "zio-interop-cats" % Versions.zioInteropCats,
1322-
"org.http4s" %% "http4s-blaze-server" % Versions.http4sBlazeServer % Test
1322+
"org.http4s" %% "http4s-ember-server" % Versions.http4s % Test
13231323
)
13241324
)
13251325
.jvmPlatform(scalaVersions = scala2And3Versions, settings = commonJvmSettings)
@@ -1887,7 +1887,7 @@ lazy val http4sClient: ProjectMatrix = (projectMatrix in file("client/http4s-cli
18871887
name := "tapir-http4s-client",
18881888
libraryDependencies ++= Seq(
18891889
"org.http4s" %% "http4s-core" % Versions.http4s,
1890-
"org.http4s" %% "http4s-blaze-client" % Versions.http4sBlazeClient % Test,
1890+
"org.http4s" %% "http4s-ember-client" % Versions.http4s % Test,
18911891
"com.softwaremill.sttp.shared" %% "fs2" % Versions.sttpShared % Optional
18921892
)
18931893
)
@@ -2049,7 +2049,7 @@ lazy val examples: ProjectMatrix = (projectMatrix in file("examples"))
20492049
"com.github.jwt-scala" %% "jwt-circe" % Versions.jwtScala,
20502050
"org.http4s" %% "http4s-dsl" % Versions.http4s,
20512051
"org.http4s" %% "http4s-circe" % Versions.http4s,
2052-
"org.http4s" %% "http4s-blaze-server" % Versions.http4sBlazeServer,
2052+
"org.http4s" %% "http4s-ember-server" % Versions.http4s,
20532053
"org.mock-server" % "mockserver-netty" % Versions.mockServer,
20542054
"io.opentelemetry" % "opentelemetry-sdk" % Versions.openTelemetry,
20552055
"io.opentelemetry" % "opentelemetry-sdk-metrics" % Versions.openTelemetry,
@@ -2117,7 +2117,7 @@ lazy val documentation: ProjectMatrix = (projectMatrix in file("generated-doc"))
21172117
name := "doc",
21182118
libraryDependencies ++= Seq(
21192119
"org.playframework" %% "play-netty-server" % Versions.playServer,
2120-
"org.http4s" %% "http4s-blaze-server" % Versions.http4sBlazeServer,
2120+
"org.http4s" %% "http4s-ember-server" % Versions.http4s,
21212121
"com.softwaremill.sttp.apispec" %% "openapi-circe-yaml" % Versions.sttpApispec,
21222122
"com.softwaremill.sttp.apispec" %% "asyncapi-circe-yaml" % Versions.sttpApispec
21232123
),

client/http4s-client/src/test/scala/sttp/tapir/client/http4s/Http4sClientTests.scala

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
package sttp.tapir.client.http4s
22

33
import cats.effect.IO
4-
import org.http4s.blaze.client.BlazeClientBuilder
4+
import org.http4s.ember.client.EmberClientBuilder
55
import org.http4s.{Request, Response, Uri}
66
import sttp.tapir.client.tests.ClientTests
77
import sttp.tapir.{DecodeResult, Endpoint}
8-
import scala.concurrent.ExecutionContext.global
98

109
abstract class Http4sClientTests[R] extends ClientTests[R] {
1110
override def send[A, I, E, O](
@@ -35,7 +34,7 @@ abstract class Http4sClientTests[R] extends ClientTests[R] {
3534
}
3635

3736
private def sendAndParseResponse[Result](request: Request[IO], parseResponse: Response[IO] => IO[Result]) =
38-
BlazeClientBuilder[IO](global).resource.use { client =>
37+
EmberClientBuilder.default[IO].build.use { client =>
3938
client.run(request).use(parseResponse)
4039
}
4140
}

client/testserver/src/main/scala/sttp/tapir/client/tests/HttpServer.scala

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ package sttp.tapir.client.tests
33
import cats.effect._
44
import cats.effect.std.Queue
55
import cats.effect.unsafe.implicits.global
6+
import com.comcast.ip4s
67
import cats.implicits._
78
import fs2.{Pipe, Stream}
89
import org.http4s.dsl.io._
910
import org.http4s.headers.{Accept, `Content-Type`}
1011
import org.http4s.server.Router
11-
import org.http4s.blaze.server.BlazeServerBuilder
12+
import org.http4s.ember.server.EmberServerBuilder
1213
import org.http4s.server.middleware._
1314
import org.http4s.server.websocket.WebSocketBuilder2
1415
import org.http4s.websocket.WebSocketFrame
@@ -33,7 +34,7 @@ class HttpServer(port: Port) {
3334

3435
private val logger = LoggerFactory.getLogger(getClass)
3536

36-
private var stopServer: IO[Unit] = _
37+
private val stopServer: Deferred[IO, Unit] = Deferred.unsafe[IO, Unit]
3738

3839
//
3940

@@ -213,22 +214,19 @@ class HttpServer(port: Port) {
213214
//
214215

215216
def start(): Unit = {
216-
val (_, _stopServer) = BlazeServerBuilder[IO]
217-
.withExecutionContext(ExecutionContext.global)
218-
.bindHttp(port)
217+
EmberServerBuilder
218+
.default[IO]
219+
.withPort(ip4s.Port.fromInt(port).get)
219220
.withHttpWebSocketApp(app)
220-
.resource
221-
.map(_.address.getPort)
222-
.allocated
221+
.build
222+
.use(_ => stopServer.get)
223223
.unsafeRunSync()
224224

225-
stopServer = _stopServer
226-
227225
logger.info(s"Server on port $port started")
228226
}
229227

230228
def close(): Unit = {
231-
stopServer.unsafeRunSync()
229+
stopServer.complete(()).unsafeRunSync()
232230
logger.info(s"Server on port $port stopped")
233231
}
234232
}

doc/server/http4s.md

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,11 @@ The capability can be added to the classpath independently of the interpreter th
5252
## Http4s backends
5353

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

5858
```scala
59-
"org.http4s" %% "http4s-blaze-server" % Http4sVersion
59+
"org.http4s" %% "http4s-ember-server" % Http4sVersion
6060
```
6161

6262
## Web sockets
@@ -75,24 +75,21 @@ import sttp.tapir.*
7575
import sttp.tapir.server.http4s.Http4sServerInterpreter
7676
import cats.effect.IO
7777
import org.http4s.HttpRoutes
78-
import org.http4s.blaze.server.BlazeServerBuilder
78+
import org.http4s.ember.server.EmberServerBuilder
7979
import org.http4s.server.Router
8080
import org.http4s.server.websocket.WebSocketBuilder2
8181
import fs2.*
8282
import scala.concurrent.ExecutionContext
8383

84-
given ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global
85-
8684
val wsEndpoint: PublicEndpoint[Unit, Unit, Pipe[IO, String, String], Fs2Streams[IO] with WebSockets] =
8785
endpoint.get.in("count").out(webSocketBody[String, CodecFormat.TextPlain, String, CodecFormat.TextPlain](Fs2Streams[IO]))
8886

8987
val wsRoutes: WebSocketBuilder2[IO] => HttpRoutes[IO] =
9088
Http4sServerInterpreter[IO]().toWebSocketRoutes(wsEndpoint.serverLogicSuccess[IO](_ => ???))
91-
92-
BlazeServerBuilder[IO]
93-
.withExecutionContext(summon[ExecutionContext])
94-
.bindHttp(8080, "localhost")
95-
.withHttpWebSocketApp(wsb => Router("/" -> wsRoutes(wsb)).orNotFound)
89+
90+
EmberServerBuilder
91+
.default[IO]
92+
.withHttpWebSocketApp(wsb => Router("/" -> wsRoutes(wsb)).orNotFound)
9693
```
9794

9895
## Server Sent Events

doc/server/zio-http4s.md

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,11 @@ The capability can be added to the classpath independently of the interpreter th
9999
## Http4s backends
100100

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

105105
```scala
106-
"org.http4s" %% "http4s-blaze-server" % Http4sVersion
106+
"org.http4s" %% "http4s-ember-server" % Http4sVersion
107107
```
108108

109109
## Web sockets
@@ -121,7 +121,7 @@ import sttp.tapir.{CodecFormat, PublicEndpoint}
121121
import sttp.tapir.ztapir.*
122122
import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter
123123
import org.http4s.HttpRoutes
124-
import org.http4s.blaze.server.BlazeServerBuilder
124+
import org.http4s.ember.server.EmberServerBuilder
125125
import org.http4s.server.Router
126126
import org.http4s.server.websocket.WebSocketBuilder2
127127
import scala.concurrent.ExecutionContext
@@ -131,8 +131,6 @@ import zio.stream.Stream
131131

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

134-
given ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global
135-
136134
val wsEndpoint: PublicEndpoint[Unit, Unit, Stream[Throwable, String] => Stream[Throwable, String], ZioStreams with WebSockets] =
137135
endpoint.get.in("count").out(webSocketBody[String, CodecFormat.TextPlain, String, CodecFormat.TextPlain](ZioStreams))
138136

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

142140
val serve: Task[Unit] =
143141
ZIO.executor.flatMap(executor =>
144-
BlazeServerBuilder[Task]
145-
.withExecutionContext(executor.asExecutionContext)
146-
.bindHttp(8080, "localhost")
142+
EmberServerBuilder
143+
.default[Task]
147144
.withHttpWebSocketApp(wsb => Router("/" -> wsRoutes(wsb)).orNotFound)
148-
.serve
149-
.compile
150-
.drain
145+
.build
146+
.useForever
151147
)
152148
```
153149

doc/tutorials/07_cats_effect.md

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -132,11 +132,11 @@ standard code to start a server and handle requests until the application is int
132132
```scala
133133
//> using dep com.softwaremill.sttp.tapir::tapir-core:@VERSION@
134134
//> using dep com.softwaremill.sttp.tapir::tapir-http4s-server:@VERSION@
135-
//> using dep org.http4s::http4s-blaze-server:0.23.16
135+
//> using dep org.http4s::http4s-ember-server:0.23.29
136136

137137
import cats.effect.{ExitCode, IO, IOApp}
138138
import org.http4s.HttpRoutes
139-
import org.http4s.blaze.server.BlazeServerBuilder
139+
import org.http4s.ember.server.EmberServerBuilder
140140
import org.http4s.server.Router
141141
import sttp.tapir.*
142142
import sttp.tapir.server.http4s.Http4sServerInterpreter
@@ -154,12 +154,11 @@ object HelloWorldTapir extends IOApp:
154154
.toRoutes(helloWorldEndpoint)
155155

156156
override def run(args: List[String]): IO[ExitCode] =
157-
BlazeServerBuilder[IO]
158-
.bindHttp(8080, "localhost")
157+
EmberServerBuilder
158+
.default[IO]
159159
.withHttpApp(Router("/" -> helloWorldRoutes).orNotFound)
160-
.resource
161-
.use(_ => IO.never)
162-
.as(ExitCode.Success)
160+
.build
161+
.useForever
163162
```
164163

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

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

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

200199
import cats.effect.{ExitCode, IO, IOApp}
201200
import cats.syntax.all.*
202201
import org.http4s.HttpRoutes
203-
import org.http4s.blaze.server.BlazeServerBuilder
202+
import org.http4s.ember.server.EmberServerBuilder
204203
import org.http4s.server.Router
205204
import sttp.tapir.*
206205
import sttp.tapir.server.http4s.Http4sServerInterpreter
@@ -226,17 +225,16 @@ object HelloWorldTapir extends IOApp:
226225
val allRoutes: HttpRoutes[IO] = helloWorldRoutes <+> swaggerRoutes
227226

228227
override def run(args: List[String]): IO[ExitCode] =
229-
BlazeServerBuilder[IO]
230-
.bindHttp(8080, "localhost")
228+
EmberServerBuilder
229+
.default[IO]
231230
.withHttpApp(Router("/" -> allRoutes).orNotFound)
232-
.resource
233-
.use(_ => IO.never)
234-
.as(ExitCode.Success)
231+
.build
232+
.useForever
235233
```
236234

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

241239
## Other concepts covered so far
242240

docs/redoc-bundle/src/test/scala/sttp/tapir/redoc/bundle/RedocInterpreterTest.scala

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ package sttp.tapir.redoc.bundle
22

33
import cats.effect.IO
44
import cats.effect.unsafe.implicits.global
5+
import com.comcast.ip4s.Port
56
import org.http4s.HttpRoutes
6-
import org.http4s.blaze.server.BlazeServerBuilder
7+
import org.http4s.ember.server.EmberServerBuilder
78
import org.http4s.server.Router
89
import org.scalatest.Assertion
910
import org.scalatest.funsuite.AsyncFunSuite
@@ -66,10 +67,11 @@ class RedocInterpreterTest extends AsyncFunSuite with Matchers {
6667
.fromEndpoints[IO](List(testEndpoint), "The tapir library", "1.0.0")
6768
)
6869

69-
BlazeServerBuilder[IO]
70-
.bindHttp(0, "localhost")
70+
EmberServerBuilder
71+
.default[IO]
72+
.withPort(Port.fromInt(0).get)
7173
.withHttpApp(Router(s"/${context.mkString("/")}" -> redocUIRoutes).orNotFound)
72-
.resource
74+
.build
7375
.use { server =>
7476
IO {
7577
val port = server.address.getPort

docs/swagger-ui-bundle/src/test/scala/sttp/tapir/swagger/bundle/SwaggerInterpreterTest.scala

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ package sttp.tapir.swagger.bundle
22

33
import cats.effect.IO
44
import cats.effect.unsafe.implicits.global
5+
import com.comcast.ip4s.Port
56
import org.http4s.HttpRoutes
6-
import org.http4s.blaze.server.BlazeServerBuilder
7+
import org.http4s.ember.server.EmberServerBuilder
78
import org.http4s.server.Router
89
import org.scalatest.Assertion
910
import org.scalatest.funsuite.AsyncFunSuite
@@ -33,10 +34,11 @@ class SwaggerInterpreterTest extends AsyncFunSuite with Matchers {
3334
.fromEndpoints[IO](List(testEndpoint), "The tapir library", "1.0.0")
3435
)
3536

36-
BlazeServerBuilder[IO]
37-
.bindHttp(0, "localhost")
37+
EmberServerBuilder
38+
.default[IO]
39+
.withPort(Port.fromInt(0).get)
3840
.withHttpApp(Router(s"/${context.mkString("/")}" -> swaggerUIRoutes).orNotFound)
39-
.resource
41+
.build
4042
.use { server =>
4143
IO {
4244
val port = server.address.getPort

0 commit comments

Comments
 (0)