From 57c2fa6d9061167988ef251d1e137b236e2d8b8c Mon Sep 17 00:00:00 2001 From: Ivan Klass Date: Mon, 25 Nov 2024 10:53:24 +0100 Subject: [PATCH 01/12] http4s - migrate from Blaze to Ember --- build.sbt | 18 +++++------ .../client/http4s/Http4sClientTests.scala | 5 ++- .../sttp/tapir/client/tests/HttpServer.scala | 20 ++++++------ doc/server/http4s.md | 17 ++++------ doc/server/zio-http4s.md | 18 ++++------- doc/tutorials/07_cats_effect.md | 32 +++++++++---------- .../redoc/bundle/RedocInterpreterTest.scala | 10 +++--- .../bundle/SwaggerInterpreterTest.scala | 10 +++--- .../examples/HelloWorldHttp4sServer.scala | 11 +++---- .../examples/ZioEnvExampleHttp4sServer.scala | 22 +++++-------- .../examples/ZioExampleHttp4sServer.scala | 19 ++++------- .../ZioPartialServerLogicHttp4s.scala | 22 ++++++------- .../examples/client/Http4sClientExample.scala | 2 +- .../errors/ErrorUnionTypesHttp4sServer.scala | 11 +++---- ...leEndpointsDocumentationHttp4sServer.scala | 11 +++---- .../RedocContextPathHttp4sServer.scala | 11 +++---- .../security/OAuth2GithubHttp4sServer.scala | 11 +++---- .../streaming/ProxyHttp4sFs2Server.scala | 14 ++++---- .../streaming/StreamingHttp4sFs2Server.scala | 10 +++--- .../StreamingHttp4sFs2ServerOrError.scala | 14 ++++---- .../websocket/WebSocketHttp4sServer.scala | 11 +++---- generated-doc/out/server/http4s.md | 11 +++---- generated-doc/out/server/zio-http4s.md | 16 ++++------ generated-doc/out/tutorials/07_cats_effect.md | 32 +++++++++---------- .../scala/sttp/tapir/perf/http4s/Http4s.scala | 25 +++++++-------- project/Versions.scala | 2 -- .../http4s/Http4sServerInterpreter.scala | 2 +- .../server/http4s/Http4sServerTest.scala | 11 ++++--- .../http4s/Http4sTestServerInterpreter.scala | 13 ++++---- .../ztapir/ZHttp4sTestServerInterpreter.scala | 11 ++++--- .../server/tests/TestServerInterpreter.scala | 10 ++++-- 31 files changed, 199 insertions(+), 233 deletions(-) diff --git a/build.sbt b/build.sbt index b3c1267b04..f6dad79a47 100644 --- a/build.sbt +++ b/build.sbt @@ -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 ), @@ -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 ), @@ -1143,7 +1143,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 ) ) @@ -1169,7 +1169,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 ) ) @@ -1304,7 +1304,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 ) } ) @@ -1320,7 +1320,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) @@ -1888,7 +1888,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 ) ) @@ -2050,7 +2050,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, @@ -2118,7 +2118,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 ), diff --git a/client/http4s-client/src/test/scala/sttp/tapir/client/http4s/Http4sClientTests.scala b/client/http4s-client/src/test/scala/sttp/tapir/client/http4s/Http4sClientTests.scala index 4b81740e65..d460da66e2 100644 --- a/client/http4s-client/src/test/scala/sttp/tapir/client/http4s/Http4sClientTests.scala +++ b/client/http4s-client/src/test/scala/sttp/tapir/client/http4s/Http4sClientTests.scala @@ -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]( @@ -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) } } diff --git a/client/testserver/src/main/scala/sttp/tapir/client/tests/HttpServer.scala b/client/testserver/src/main/scala/sttp/tapir/client/tests/HttpServer.scala index 13b7303794..9fe56619bd 100644 --- a/client/testserver/src/main/scala/sttp/tapir/client/tests/HttpServer.scala +++ b/client/testserver/src/main/scala/sttp/tapir/client/tests/HttpServer.scala @@ -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 @@ -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] // @@ -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") } } diff --git a/doc/server/http4s.md b/doc/server/http4s.md index 184bd97df2..829a0020e3 100644 --- a/doc/server/http4s.md +++ b/doc/server/http4s.md @@ -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 @@ -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 diff --git a/doc/server/zio-http4s.md b/doc/server/zio-http4s.md index 9a69d155a8..f4286b1410 100644 --- a/doc/server/zio-http4s.md +++ b/doc/server/zio-http4s.md @@ -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 @@ -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 @@ -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)) @@ -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 ) ``` diff --git a/doc/tutorials/07_cats_effect.md b/doc/tutorials/07_cats_effect.md index 1c774134c0..ba84b1be71 100644 --- a/doc/tutorials/07_cats_effect.md +++ b/doc/tutorials/07_cats_effect.md @@ -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.30 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 @@ -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, @@ -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`. @@ -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.30 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 @@ -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 diff --git a/docs/redoc-bundle/src/test/scala/sttp/tapir/redoc/bundle/RedocInterpreterTest.scala b/docs/redoc-bundle/src/test/scala/sttp/tapir/redoc/bundle/RedocInterpreterTest.scala index 81e36d3412..97b75ff91a 100644 --- a/docs/redoc-bundle/src/test/scala/sttp/tapir/redoc/bundle/RedocInterpreterTest.scala +++ b/docs/redoc-bundle/src/test/scala/sttp/tapir/redoc/bundle/RedocInterpreterTest.scala @@ -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 @@ -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 diff --git a/docs/swagger-ui-bundle/src/test/scala/sttp/tapir/swagger/bundle/SwaggerInterpreterTest.scala b/docs/swagger-ui-bundle/src/test/scala/sttp/tapir/swagger/bundle/SwaggerInterpreterTest.scala index 0eaad61e02..a1b984ca8b 100644 --- a/docs/swagger-ui-bundle/src/test/scala/sttp/tapir/swagger/bundle/SwaggerInterpreterTest.scala +++ b/docs/swagger-ui-bundle/src/test/scala/sttp/tapir/swagger/bundle/SwaggerInterpreterTest.scala @@ -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 @@ -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 diff --git a/examples/src/main/scala/sttp/tapir/examples/HelloWorldHttp4sServer.scala b/examples/src/main/scala/sttp/tapir/examples/HelloWorldHttp4sServer.scala index 892a7d4d22..2c3a7ea56a 100644 --- a/examples/src/main/scala/sttp/tapir/examples/HelloWorldHttp4sServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/HelloWorldHttp4sServer.scala @@ -3,14 +3,14 @@ //> using dep com.softwaremill.sttp.tapir::tapir-core:1.11.10 //> using dep com.softwaremill.sttp.tapir::tapir-http4s-server:1.11.10 //> using dep com.softwaremill.sttp.client3::core:3.9.8 -//> using dep org.http4s::http4s-blaze-server:0.23.16 +//> using dep org.http4s::http4s-ember-server:0.23.16 package sttp.tapir.examples import cats.effect.* 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.client3.* import sttp.shared.Identity @@ -33,11 +33,10 @@ object HelloWorldHttp4sServer extends IOApp: override def run(args: List[String]): IO[ExitCode] = // starting the server - BlazeServerBuilder[IO] - .withExecutionContext(ec) - .bindHttp(8080, "localhost") + EmberServerBuilder + .default[IO] .withHttpApp(Router("/" -> helloWorldRoutes).orNotFound) - .resource + .build .use { _ => IO { val backend: SttpBackend[Identity, Any] = HttpURLConnectionBackend() diff --git a/examples/src/main/scala/sttp/tapir/examples/ZioEnvExampleHttp4sServer.scala b/examples/src/main/scala/sttp/tapir/examples/ZioEnvExampleHttp4sServer.scala index 410c91b98b..0dcf3ea82d 100644 --- a/examples/src/main/scala/sttp/tapir/examples/ZioEnvExampleHttp4sServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/ZioEnvExampleHttp4sServer.scala @@ -6,7 +6,7 @@ //> using dep com.softwaremill.sttp.tapir::tapir-http4s-server-zio:1.11.10 //> using dep com.softwaremill.sttp.tapir::tapir-swagger-ui-bundle:1.11.10 //> using dep com.softwaremill.sttp.tapir::tapir-zio:1.11.10 -//> using dep org.http4s::http4s-blaze-server:0.23.16 +//> using dep org.http4s::http4s-ember-server:0.23.30 //> using dep dev.zio::zio-interop-cats:23.1.0.3 package sttp.tapir.examples @@ -14,7 +14,7 @@ package sttp.tapir.examples import cats.syntax.all.* import io.circe.generic.auto.* import org.http4s.* -import org.http4s.blaze.server.BlazeServerBuilder +import org.http4s.ember.server.EmberServerBuilder import org.http4s.server.Router import sttp.tapir.PublicEndpoint import sttp.tapir.generic.auto.* @@ -71,17 +71,11 @@ object ZioEnvExampleHttp4sServer extends ZIOAppDefault: .toRoutes // Starting the server - val serve: ZIO[PetService, Throwable, Unit] = { - ZIO.executor.flatMap(executor => - BlazeServerBuilder[RIO[PetService, *]] - .withExecutionContext(executor.asExecutionContext) - .bindHttp(8080, "localhost") - .withHttpApp(Router("/" -> (petRoutes <+> swaggerRoutes)).orNotFound) - .serve - .compile - .drain - ) - - } + val serve: ZIO[PetService, Throwable, Unit] = + EmberServerBuilder + .default[RIO[PetService, *]] + .withHttpApp(Router("/" -> (petRoutes <+> swaggerRoutes)).orNotFound) + .build + .useForever override def run: URIO[Any, ExitCode] = serve.provide(PetService.live).exitCode diff --git a/examples/src/main/scala/sttp/tapir/examples/ZioExampleHttp4sServer.scala b/examples/src/main/scala/sttp/tapir/examples/ZioExampleHttp4sServer.scala index 5c26d28f34..8674d74a63 100644 --- a/examples/src/main/scala/sttp/tapir/examples/ZioExampleHttp4sServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/ZioExampleHttp4sServer.scala @@ -5,7 +5,7 @@ //> using dep com.softwaremill.sttp.tapir::tapir-http4s-server-zio:1.11.10 //> using dep com.softwaremill.sttp.tapir::tapir-swagger-ui-bundle:1.11.10 //> using dep com.softwaremill.sttp.tapir::tapir-zio:1.11.10 -//> using dep org.http4s::http4s-blaze-server:0.23.16 +//> using dep org.http4s::http4s-ember-server:0.23.30 //> using dep dev.zio::zio-interop-cats:23.1.0.3 package sttp.tapir.examples @@ -13,7 +13,7 @@ package sttp.tapir.examples import cats.syntax.all.* import io.circe.generic.auto.* import org.http4s.* -import org.http4s.blaze.server.BlazeServerBuilder +import org.http4s.ember.server.EmberServerBuilder import org.http4s.server.Router import sttp.tapir.PublicEndpoint import sttp.tapir.generic.auto.* @@ -59,15 +59,10 @@ object ZioExampleHttp4sServer extends ZIOAppDefault: .toRoutes // Starting the server - val serve: Task[Unit] = - ZIO.executor.flatMap(executor => - BlazeServerBuilder[Task] - .withExecutionContext(executor.asExecutionContext) - .bindHttp(8080, "localhost") - .withHttpApp(Router("/" -> (petRoutes <+> swaggerRoutes)).orNotFound) - .serve - .compile - .drain - ) + val serve: Task[Unit] = EmberServerBuilder + .default[Task] + .withHttpApp(Router("/" -> (petRoutes <+> swaggerRoutes)).orNotFound) + .build + .useForever override def run: URIO[Any, ExitCode] = serve.exitCode diff --git a/examples/src/main/scala/sttp/tapir/examples/ZioPartialServerLogicHttp4s.scala b/examples/src/main/scala/sttp/tapir/examples/ZioPartialServerLogicHttp4s.scala index 0031aaadf4..a5e1533ab4 100644 --- a/examples/src/main/scala/sttp/tapir/examples/ZioPartialServerLogicHttp4s.scala +++ b/examples/src/main/scala/sttp/tapir/examples/ZioPartialServerLogicHttp4s.scala @@ -4,13 +4,13 @@ //> using dep com.softwaremill.sttp.tapir::tapir-core:1.11.10 //> using dep com.softwaremill.sttp.tapir::tapir-http4s-server-zio:1.11.10 //> using dep com.softwaremill.sttp.tapir::tapir-zio:1.11.10 -//> using dep org.http4s::http4s-blaze-server:0.23.16 +//> using dep org.http4s::http4s-ember-server:0.23.30 //> using dep com.softwaremill.sttp.client3::async-http-client-backend-zio:3.10.1 package sttp.tapir.examples import org.http4s.* -import org.http4s.blaze.server.BlazeServerBuilder +import org.http4s.ember.server.EmberServerBuilder import org.http4s.server.Router import sttp.client3.* import sttp.client3.asynchttpclient.zio.AsyncHttpClientZioBackend @@ -78,16 +78,14 @@ object ZioPartialServerLogicHttp4s extends ZIOAppDefault: // override def run: URIO[Any, ExitCode] = - ZIO.executor.flatMap(executor => - BlazeServerBuilder[RIO[UserService, *]] - .withExecutionContext(executor.asExecutionContext) - .bindHttp(8080, "localhost") - .withHttpApp(Router("/" -> helloWorldRoutes).orNotFound) - .resource - .use(_ => test) - .provide(UserService.live) - .exitCode - ) + EmberServerBuilder + .default[RIO[UserService, *]] + .withHttpApp(Router("/" -> helloWorldRoutes).orNotFound) + .build + .use(_ => test) + .provide(UserService.live) + .exitCode + end ZioPartialServerLogicHttp4s object UserAuthenticationLayer: diff --git a/examples/src/main/scala/sttp/tapir/examples/client/Http4sClientExample.scala b/examples/src/main/scala/sttp/tapir/examples/client/Http4sClientExample.scala index d82f7f91a1..03e18de6b3 100644 --- a/examples/src/main/scala/sttp/tapir/examples/client/Http4sClientExample.scala +++ b/examples/src/main/scala/sttp/tapir/examples/client/Http4sClientExample.scala @@ -4,7 +4,7 @@ //> using dep com.softwaremill.sttp.tapir::tapir-http4s-client:1.11.10 //> using dep com.softwaremill.sttp.tapir::tapir-json-circe:1.11.10 //> using dep org.http4s::http4s-circe:0.23.27 -//> using dep org.http4s::http4s-blaze-server:0.23.16 +//> using dep org.http4s::http4s-ember-server:0.23.30 //> using dep org.http4s::http4s-dsl:0.23.27 package sttp.tapir.examples.client diff --git a/examples/src/main/scala/sttp/tapir/examples/errors/ErrorUnionTypesHttp4sServer.scala b/examples/src/main/scala/sttp/tapir/examples/errors/ErrorUnionTypesHttp4sServer.scala index 026dda492b..2cf944ddb5 100644 --- a/examples/src/main/scala/sttp/tapir/examples/errors/ErrorUnionTypesHttp4sServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/errors/ErrorUnionTypesHttp4sServer.scala @@ -3,7 +3,7 @@ //> using dep com.softwaremill.sttp.tapir::tapir-core:1.11.10 //> using dep com.softwaremill.sttp.tapir::tapir-http4s-server:1.11.10 //> using dep com.softwaremill.sttp.tapir::tapir-json-circe:1.11.10 -//> using dep org.http4s::http4s-blaze-server:0.23.16 +//> using dep org.http4s::http4s-ember-server:0.23.30 //> using dep com.softwaremill.sttp.client3::core:3.9.8 package sttp.tapir.examples.errors @@ -11,7 +11,7 @@ package sttp.tapir.examples.errors import cats.effect.* import io.circe.generic.auto.* import org.http4s.HttpRoutes -import org.http4s.blaze.server.BlazeServerBuilder +import org.http4s.ember.server.EmberServerBuilder import org.http4s.server.Router import sttp.client3.* import sttp.model.StatusCode @@ -71,11 +71,10 @@ object ErrorUnionTypesHttp4sServer extends IOApp: override def run(args: List[String]): IO[ExitCode] = // starting the server - BlazeServerBuilder[IO] - .withExecutionContext(ec) - .bindHttp(8080, "localhost") + EmberServerBuilder + .default[IO] .withHttpApp(Router("/" -> helloWorldRoutes).orNotFound) - .resource + .build .use { _ => IO { val backend: SttpBackend[Identity, Any] = HttpURLConnectionBackend() diff --git a/examples/src/main/scala/sttp/tapir/examples/openapi/MultipleEndpointsDocumentationHttp4sServer.scala b/examples/src/main/scala/sttp/tapir/examples/openapi/MultipleEndpointsDocumentationHttp4sServer.scala index 052ecda0bf..fb71330cd5 100644 --- a/examples/src/main/scala/sttp/tapir/examples/openapi/MultipleEndpointsDocumentationHttp4sServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/openapi/MultipleEndpointsDocumentationHttp4sServer.scala @@ -4,7 +4,7 @@ //> using dep com.softwaremill.sttp.tapir::tapir-json-circe:1.11.10 //> using dep com.softwaremill.sttp.tapir::tapir-swagger-ui-bundle:1.11.10 //> using dep com.softwaremill.sttp.tapir::tapir-http4s-server:1.11.10 -//> using dep org.http4s::http4s-blaze-server:0.23.16 +//> using dep org.http4s::http4s-ember-server:0.23.30 package sttp.tapir.examples.openapi @@ -12,7 +12,7 @@ import cats.effect.* import cats.syntax.all.* import io.circe.generic.auto.* 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.generic.auto.* @@ -73,11 +73,10 @@ object MultipleEndpointsDocumentationHttp4sServer extends IOApp: override def run(args: List[String]): IO[ExitCode] = // starting the server - BlazeServerBuilder[IO] - .withExecutionContext(ec) - .bindHttp(8080, "localhost") + EmberServerBuilder + .default[IO] .withHttpApp(Router("/" -> (routes)).orNotFound) - .resource + .build .use { _ => IO { println("Go to: http://localhost:8080/docs") diff --git a/examples/src/main/scala/sttp/tapir/examples/openapi/RedocContextPathHttp4sServer.scala b/examples/src/main/scala/sttp/tapir/examples/openapi/RedocContextPathHttp4sServer.scala index 19bdcc881e..219e3444b5 100644 --- a/examples/src/main/scala/sttp/tapir/examples/openapi/RedocContextPathHttp4sServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/openapi/RedocContextPathHttp4sServer.scala @@ -3,15 +3,15 @@ //> using dep com.softwaremill.sttp.tapir::tapir-core:1.11.10 //> using dep com.softwaremill.sttp.tapir::tapir-redoc-bundle:1.11.10 //> using dep com.softwaremill.sttp.tapir::tapir-http4s-server:1.11.10 -//> using dep org.http4s::http4s-blaze-server:0.23.16 +//> using dep org.http4s::http4s-ember-server:0.23.30 package sttp.tapir.examples.openapi import cats.effect.* import cats.syntax.all.* import org.http4s.HttpRoutes +import org.http4s.ember.server.EmberServerBuilder import org.http4s.server.Router -import org.http4s.blaze.server.BlazeServerBuilder import sttp.tapir.* import sttp.tapir.redoc.RedocUIOptions import sttp.tapir.redoc.bundle.RedocInterpreter @@ -37,10 +37,9 @@ object RedocContextPathHttp4sServer extends IOApp: override def run(args: List[String]): IO[ExitCode] = // starting the server - BlazeServerBuilder[IO] - .withExecutionContext(ec) - .bindHttp(8080, "localhost") + EmberServerBuilder + .default[IO] .withHttpApp(Router(s"/${contextPath.mkString("/")}" -> routes).orNotFound) - .resource + .build .use { _ => IO.println(s"go to: http://127.0.0.1:8080/${(contextPath ++ docPathPrefix).mkString("/")}") *> IO.never } .as(ExitCode.Success) diff --git a/examples/src/main/scala/sttp/tapir/examples/security/OAuth2GithubHttp4sServer.scala b/examples/src/main/scala/sttp/tapir/examples/security/OAuth2GithubHttp4sServer.scala index 7d119d9928..005d4e6574 100644 --- a/examples/src/main/scala/sttp/tapir/examples/security/OAuth2GithubHttp4sServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/security/OAuth2GithubHttp4sServer.scala @@ -4,7 +4,7 @@ //> using dep com.softwaremill.sttp.tapir::tapir-http4s-server:1.11.10 //> using dep com.softwaremill.sttp.tapir::tapir-json-circe:1.11.10 //> using dep com.softwaremill.sttp.client3::async-http-client-backend-cats:3.10.1 -//> using dep org.http4s::http4s-blaze-server:0.23.16 +//> using dep org.http4s::http4s-ember-server:0.23.30 //> using dep com.github.jwt-scala::jwt-circe:10.0.1 package sttp.tapir.examples.security @@ -13,8 +13,8 @@ import cats.effect.* import cats.syntax.all.* import io.circe.generic.auto.* import org.http4s.HttpRoutes +import org.http4s.ember.server.EmberServerBuilder import org.http4s.server.Router -import org.http4s.blaze.server.BlazeServerBuilder import pdi.jwt.{JwtAlgorithm, JwtCirce, JwtClaim} import sttp.client3.* import sttp.client3.asynchttpclient.cats.AsyncHttpClientCatsBackend @@ -118,11 +118,10 @@ object OAuth2GithubHttp4sServer extends IOApp: // starting the server httpClient .use(backend => - BlazeServerBuilder[IO] - .withExecutionContext(ec) - .bindHttp(8080, "localhost") + EmberServerBuilder + .default[IO] .withHttpApp(Router("/" -> (secretPlaceRoute <+> loginRoute <+> loginGithubRoute(backend))).orNotFound) - .resource + .build .use { _ => IO { println("Go to: http://localhost:8080") diff --git a/examples/src/main/scala/sttp/tapir/examples/streaming/ProxyHttp4sFs2Server.scala b/examples/src/main/scala/sttp/tapir/examples/streaming/ProxyHttp4sFs2Server.scala index 4257115330..cbd4ad24c3 100644 --- a/examples/src/main/scala/sttp/tapir/examples/streaming/ProxyHttp4sFs2Server.scala +++ b/examples/src/main/scala/sttp/tapir/examples/streaming/ProxyHttp4sFs2Server.scala @@ -3,14 +3,14 @@ //> using dep com.softwaremill.sttp.tapir::tapir-core:1.11.10 //> using dep com.softwaremill.sttp.tapir::tapir-http4s-server:1.11.10 //> using dep com.softwaremill.sttp.client3::fs2:3.9.8 -//> using dep org.http4s::http4s-blaze-server:0.23.16 +//> using dep org.http4s::http4s-ember-server:0.23.30 package sttp.tapir.examples.streaming import cats.effect.{ExitCode, IO, IOApp} import fs2.Stream import org.http4s.HttpRoutes -import org.http4s.blaze.server.BlazeServerBuilder +import org.http4s.ember.server.EmberServerBuilder import org.http4s.server.Router import sttp.capabilities.fs2.Fs2Streams import sttp.client3.* @@ -58,10 +58,8 @@ object ProxyHttp4sFs2Server extends IOApp: (for { backend <- HttpClientFs2Backend.resource[IO]() routes = proxyRoutes(backend) - _ <- BlazeServerBuilder[IO] - .bindHttp(8080, "localhost") + _ <- EmberServerBuilder + .default[IO] .withHttpApp(Router("/" -> routes).orNotFound) - .resource - } yield ()) - .use { _ => IO.never } - .as(ExitCode.Success) + .build + } yield ()).useForever diff --git a/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingHttp4sFs2Server.scala b/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingHttp4sFs2Server.scala index 834434b584..bea6c8e3a9 100644 --- a/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingHttp4sFs2Server.scala +++ b/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingHttp4sFs2Server.scala @@ -3,7 +3,7 @@ //> using dep com.softwaremill.sttp.tapir::tapir-core:1.11.10 //> using dep com.softwaremill.sttp.tapir::tapir-http4s-server:1.11.10 //> using dep com.softwaremill.sttp.client3::core:3.9.8 -//> using dep org.http4s::http4s-blaze-server:0.23.16 +//> using dep org.http4s::http4s-ember-server:0.23.30 package sttp.tapir.examples.streaming @@ -11,7 +11,7 @@ import cats.effect.{ExitCode, IO, IOApp} import cats.implicits.* import fs2.{Chunk, Stream} import org.http4s.HttpRoutes -import org.http4s.blaze.server.BlazeServerBuilder +import org.http4s.ember.server.EmberServerBuilder import org.http4s.server.Router import sttp.capabilities.fs2.Fs2Streams import sttp.client3.* @@ -52,10 +52,10 @@ object StreamingHttp4sFs2Server extends IOApp: override def run(args: List[String]): IO[ExitCode] = // starting the server - BlazeServerBuilder[IO] - .bindHttp(8080, "localhost") + EmberServerBuilder + .default[IO] .withHttpApp(Router("/" -> streamingRoutes).orNotFound) - .resource + .build .use { _ => IO { val backend: SttpBackend[Identity, Any] = HttpClientSyncBackend() diff --git a/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingHttp4sFs2ServerOrError.scala b/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingHttp4sFs2ServerOrError.scala index 9f3c26e7ca..5528cb2e1a 100644 --- a/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingHttp4sFs2ServerOrError.scala +++ b/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingHttp4sFs2ServerOrError.scala @@ -2,13 +2,13 @@ //> using dep com.softwaremill.sttp.tapir::tapir-core:1.11.10 //> using dep com.softwaremill.sttp.tapir::tapir-http4s-server:1.11.10 -//> using dep org.http4s::http4s-blaze-server:0.23.16 +//> using dep org.http4s::http4s-ember-server:0.23.30 package sttp.tapir.examples.streaming import cats.effect.* import org.http4s.HttpRoutes -import org.http4s.blaze.server.BlazeServerBuilder +import org.http4s.ember.server.EmberServerBuilder import org.http4s.server.Router import sttp.capabilities.fs2.Fs2Streams import sttp.model.StatusCode @@ -51,10 +51,8 @@ object StreamingHttp4sFs2ServerOrError extends IOApp: // curl -v http://localhost:8080/user/another_user (responds with 404) override def run(args: List[String]): IO[ExitCode] = // starting the server - BlazeServerBuilder[IO] - .withExecutionContext(scala.concurrent.ExecutionContext.global) - .bindHttp(8080, "localhost") + EmberServerBuilder + .default[IO] .withHttpApp(Router("/" -> userDataRoutes).orNotFound) - .resource - .use { _ => IO.never } - .as(ExitCode.Success) + .build + .useForever diff --git a/examples/src/main/scala/sttp/tapir/examples/websocket/WebSocketHttp4sServer.scala b/examples/src/main/scala/sttp/tapir/examples/websocket/WebSocketHttp4sServer.scala index fcaa1fa5a5..20004c71e1 100644 --- a/examples/src/main/scala/sttp/tapir/examples/websocket/WebSocketHttp4sServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/websocket/WebSocketHttp4sServer.scala @@ -6,7 +6,7 @@ //> using dep com.softwaremill.sttp.tapir::tapir-json-circe:1.11.10 //> using dep com.softwaremill.sttp.apispec::asyncapi-circe-yaml:0.10.0 //> using dep com.softwaremill.sttp.client3::async-http-client-backend-fs2:3.10.1 -//> using dep org.http4s::http4s-blaze-server:0.23.16 +//> using dep org.http4s::http4s-ember-server:0.23.16 package sttp.tapir.examples.websocket @@ -14,7 +14,7 @@ import cats.effect.{ExitCode, IO, IOApp} import io.circe.generic.auto.* import fs2.* 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 sttp.apispec.asyncapi.Server @@ -81,11 +81,10 @@ object WebSocketHttp4sServer extends IOApp: override def run(args: List[String]): IO[ExitCode] = // Starting the server - BlazeServerBuilder[IO] - .withExecutionContext(ec) - .bindHttp(8080, "localhost") + EmberServerBuilder + .default[IO] .withHttpWebSocketApp(wsb => Router("/" -> wsRoutes(wsb)).orNotFound) - .resource + .build .flatMap(_ => AsyncHttpClientFs2Backend.resource[IO]()) .use { backend => // Client which interacts with the web socket diff --git a/generated-doc/out/server/http4s.md b/generated-doc/out/server/http4s.md index cd1d2a5208..b58f1b8f60 100644 --- a/generated-doc/out/server/http4s.md +++ b/generated-doc/out/server/http4s.md @@ -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 @@ -75,7 +75,7 @@ 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.* @@ -89,9 +89,8 @@ val wsEndpoint: PublicEndpoint[Unit, Unit, Pipe[IO, String, String], Fs2Streams[ val wsRoutes: WebSocketBuilder2[IO] => HttpRoutes[IO] = Http4sServerInterpreter[IO]().toWebSocketRoutes(wsEndpoint.serverLogicSuccess[IO](_ => ???)) -BlazeServerBuilder[IO] - .withExecutionContext(summon[ExecutionContext]) - .bindHttp(8080, "localhost") +EmberServerBuilder + .default[IO] .withHttpWebSocketApp(wsb => Router("/" -> wsRoutes(wsb)).orNotFound) ``` diff --git a/generated-doc/out/server/zio-http4s.md b/generated-doc/out/server/zio-http4s.md index 63f712042f..62616dcb22 100644 --- a/generated-doc/out/server/zio-http4s.md +++ b/generated-doc/out/server/zio-http4s.md @@ -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 @@ -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 @@ -141,13 +141,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 ) ``` diff --git a/generated-doc/out/tutorials/07_cats_effect.md b/generated-doc/out/tutorials/07_cats_effect.md index 87838ff513..929cc929a3 100644 --- a/generated-doc/out/tutorials/07_cats_effect.md +++ b/generated-doc/out/tutorials/07_cats_effect.md @@ -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:1.11.10 //> using dep com.softwaremill.sttp.tapir::tapir-http4s-server:1.11.10 -//> using dep org.http4s::http4s-blaze-server:0.23.16 +//> using dep org.http4s::http4s-ember-server:0.23.30 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 @@ -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, @@ -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`. @@ -195,12 +194,12 @@ the second step that we need to perform: //> using dep com.softwaremill.sttp.tapir::tapir-core:1.11.10 //> using dep com.softwaremill.sttp.tapir::tapir-http4s-server:1.11.10 //> using dep com.softwaremill.sttp.tapir::tapir-swagger-ui-bundle:1.11.10 -//> using dep org.http4s::http4s-blaze-server:0.23.16 +//> using dep org.http4s::http4s-ember-server:0.23.30 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 @@ -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 diff --git a/perf-tests/src/main/scala/sttp/tapir/perf/http4s/Http4s.scala b/perf-tests/src/main/scala/sttp/tapir/perf/http4s/Http4s.scala index 68eab44f21..f8a3daf28a 100644 --- a/perf-tests/src/main/scala/sttp/tapir/perf/http4s/Http4s.scala +++ b/perf-tests/src/main/scala/sttp/tapir/perf/http4s/Http4s.scala @@ -2,10 +2,11 @@ package sttp.tapir.perf.http4s import cats.effect._ import cats.syntax.all._ +import com.comcast.ip4s import fs2._ import fs2.io.file.{Files, Path => Fs2Path} import org.http4s._ -import org.http4s.blaze.server.BlazeServerBuilder +import org.http4s.ember.server.EmberServerBuilder import org.http4s.dsl._ import org.http4s.implicits._ import org.http4s.server.Router @@ -105,21 +106,17 @@ object Tapir extends Endpoints { object server { val maxConnections = 65536 - val connectorPoolSize: Int = Math.max(2, Runtime.getRuntime.availableProcessors() / 4) def runServer( router: WebSocketBuilder2[IO] => HttpRoutes[IO] - ): IO[ServerRunner.KillSwitch] = - BlazeServerBuilder[IO] - .bindHttp(Port, "localhost") - .withHttpWebSocketApp(wsb => router(wsb).orNotFound) - .withMaxConnections(maxConnections) - .withConnectorPoolSize(connectorPoolSize) - .resource - .allocated - .map(_._2) - .map(_.flatTap { _ => - IO.println("Http4s server closed.") - }) + ): IO[ServerRunner.KillSwitch] = EmberServerBuilder + .default[IO] + .withPort(ip4s.Port.fromInt(Port).get) + .withHttpWebSocketApp(wsb => router(wsb).orNotFound) + .withMaxConnections(maxConnections) + .build + .useForever + .start + .map(_.cancel *> IO.println("Http4s server closed.")) } object TapirServer extends ServerRunner { override def start = server.runServer(Tapir.router(1)) } diff --git a/project/Versions.scala b/project/Versions.scala index a07ffd1f59..b12ffca27c 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -1,7 +1,5 @@ object Versions { val http4s = "0.23.30" - val http4sBlazeServer = "0.23.17" - val http4sBlazeClient = "0.23.17" val catsCore = "2.12.0" val catsEffect = "3.5.7" val circe = "0.14.9" diff --git a/server/http4s-server/src/main/scala/sttp/tapir/server/http4s/Http4sServerInterpreter.scala b/server/http4s-server/src/main/scala/sttp/tapir/server/http4s/Http4sServerInterpreter.scala index 64df3e9f66..902c0788e8 100644 --- a/server/http4s-server/src/main/scala/sttp/tapir/server/http4s/Http4sServerInterpreter.scala +++ b/server/http4s-server/src/main/scala/sttp/tapir/server/http4s/Http4sServerInterpreter.scala @@ -124,7 +124,7 @@ trait Http4sServerInterpreter[F[_]] { new Http4sInvalidWebSocketUse( "Invalid usage of web socket endpoint without WebSocketBuilder2. " + "Use the toWebSocketRoutes/toWebSocketHttp interpreter methods, " + - "and add the result using BlazeServerBuilder.withHttpWebSocketApp(..)." + "and add the result using (Blaze/Ember)ServerBuilder.withHttpWebSocketApp(..)." ) ) } diff --git a/server/http4s-server/src/test/scala/sttp/tapir/server/http4s/Http4sServerTest.scala b/server/http4s-server/src/test/scala/sttp/tapir/server/http4s/Http4sServerTest.scala index c89a8fe03b..dce747b163 100644 --- a/server/http4s-server/src/test/scala/sttp/tapir/server/http4s/Http4sServerTest.scala +++ b/server/http4s-server/src/test/scala/sttp/tapir/server/http4s/Http4sServerTest.scala @@ -4,9 +4,10 @@ import cats.data._ import cats.effect._ import cats.effect.unsafe.implicits.global import cats.syntax.all._ +import com.comcast.ip4s.Port import fs2.Pipe import fs2.Stream -import org.http4s.blaze.server.BlazeServerBuilder +import org.http4s.ember.server.EmberServerBuilder import org.http4s.server.Router import org.http4s.server.ContextMiddleware import org.http4s.ContextRoutes @@ -40,11 +41,11 @@ class Http4sServerTest[R >: Fs2Streams[IO] with WebSockets] extends TestSuite wi val sse2 = ServerSentEvent(randomUUID, randomUUID, randomUUID, Some(Random.nextInt(200))) def assert_get_apiTestRouter_respondsWithExpectedContent[T](routes: HttpRoutes[IO], expectedContext: T): IO[Assertion] = - BlazeServerBuilder[IO] - .withExecutionContext(ExecutionContext.global) - .bindHttp(0, "localhost") + EmberServerBuilder + .default[IO] + .withPort(Port.fromInt(0).get) .withHttpApp(Router("/api" -> routes).orNotFound) - .resource + .build .use { server => val port = server.address.getPort basicRequest.get(uri"http://localhost:$port/api/test/router").send(backend).map(_.body shouldBe Right(expectedContext)) diff --git a/server/http4s-server/src/test/scala/sttp/tapir/server/http4s/Http4sTestServerInterpreter.scala b/server/http4s-server/src/test/scala/sttp/tapir/server/http4s/Http4sTestServerInterpreter.scala index a5852ce240..c8021b20b2 100644 --- a/server/http4s-server/src/test/scala/sttp/tapir/server/http4s/Http4sTestServerInterpreter.scala +++ b/server/http4s-server/src/test/scala/sttp/tapir/server/http4s/Http4sTestServerInterpreter.scala @@ -3,7 +3,8 @@ package sttp.tapir.server.http4s import cats.data.NonEmptyList import cats.effect.{IO, Resource} import cats.syntax.all._ -import org.http4s.blaze.server.BlazeServerBuilder +import com.comcast.ip4s +import org.http4s.ember.server.EmberServerBuilder import org.http4s.server.websocket.WebSocketBuilder2 import org.http4s.{HttpApp, HttpRoutes} import sttp.capabilities.WebSockets @@ -35,11 +36,11 @@ class Http4sTestServerInterpreter extends TestServerInterpreter[IO, Fs2Streams[I val service: WebSocketBuilder2[IO] => HttpApp[IO] = wsb => routes.map(_.apply(wsb)).reduceK.orNotFound - BlazeServerBuilder[IO] - .withExecutionContext(ExecutionContext.global) - .bindHttp(0, "localhost") + EmberServerBuilder + .default[IO] + .withPort(ip4s.Port.fromInt(0).get) .withHttpWebSocketApp(service) - .resource - .map(_.address.getPort()) + .build + .map(_.address.getPort) } } diff --git a/server/http4s-server/zio/src/test/scala/sttp/tapir/server/http4s/ztapir/ZHttp4sTestServerInterpreter.scala b/server/http4s-server/zio/src/test/scala/sttp/tapir/server/http4s/ztapir/ZHttp4sTestServerInterpreter.scala index 9b46b6a198..1246c4310d 100644 --- a/server/http4s-server/zio/src/test/scala/sttp/tapir/server/http4s/ztapir/ZHttp4sTestServerInterpreter.scala +++ b/server/http4s-server/zio/src/test/scala/sttp/tapir/server/http4s/ztapir/ZHttp4sTestServerInterpreter.scala @@ -4,7 +4,8 @@ import cats.data.NonEmptyList import cats.effect.{IO, Resource} import cats._ import cats.syntax.all._ -import org.http4s.blaze.server.BlazeServerBuilder +import com.comcast.ip4s +import org.http4s.ember.server.EmberServerBuilder import org.http4s.server.websocket.WebSocketBuilder2 import org.http4s.{HttpApp, HttpRoutes} import sttp.capabilities.WebSockets @@ -41,11 +42,11 @@ class ZHttp4sTestServerInterpreter extends TestServerInterpreter[Task, ZioStream val service: WebSocketBuilder2[Task] => HttpApp[Task] = wsb => routes.map(_.apply(wsb)).reduceK.orNotFound - BlazeServerBuilder[Task] - .withExecutionContext(ExecutionContext.global) - .bindHttp(0, "localhost") + EmberServerBuilder + .default[Task] + .withPort(ip4s.Port.fromInt(0).get) .withHttpWebSocketApp(service) - .resource + .build .map(_.address.getPort) .mapK(new ~>[Task, IO] { // Converting a ZIO effect to an Cats Effect IO effect diff --git a/server/tests/src/main/scala/sttp/tapir/server/tests/TestServerInterpreter.scala b/server/tests/src/main/scala/sttp/tapir/server/tests/TestServerInterpreter.scala index 8bb4548295..8a87c8c877 100644 --- a/server/tests/src/main/scala/sttp/tapir/server/tests/TestServerInterpreter.scala +++ b/server/tests/src/main/scala/sttp/tapir/server/tests/TestServerInterpreter.scala @@ -1,7 +1,7 @@ package sttp.tapir.server.tests import cats.data.NonEmptyList -import cats.effect.{IO, Resource} +import cats.effect.{Deferred, IO, Resource} import sttp.tapir.server.ServerEndpoint import sttp.tapir.server.interceptor.CustomiseInterceptors import sttp.tapir.tests._ @@ -21,8 +21,12 @@ trait TestServerInterpreter[F[_], +R, OPTIONS, ROUTE] { def serverWithStop( routes: NonEmptyList[ROUTE], gracefulShutdownTimeout: Option[FiniteDuration] = None - ): Resource[IO, (Port, KillSwitch)] = - Resource.eval(server(routes, gracefulShutdownTimeout).allocated) + ): Resource[IO, (Port, KillSwitch)] = for { + stopSignal <- Resource.eval(Deferred[IO, Unit]) + portValue <- Resource.eval(Deferred[IO, Port]) + _ <- server(routes, gracefulShutdownTimeout).use(port => portValue.complete(port) *> stopSignal.get).background + port <- Resource.eval(portValue.get) + } yield (port, stopSignal.complete(()).void) def server(routes: NonEmptyList[ROUTE], gracefulShutdownTimeout: Option[FiniteDuration] = None): Resource[IO, Port] } From e7227bf4870a5dfe6ee8351f280b4e4eb7709865 Mon Sep 17 00:00:00 2001 From: Ivan Klass Date: Fri, 20 Dec 2024 09:46:50 +0100 Subject: [PATCH 02/12] Trying to fix timeout exceptions --- .../src/main/scala/sttp/tapir/client/tests/HttpServer.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/testserver/src/main/scala/sttp/tapir/client/tests/HttpServer.scala b/client/testserver/src/main/scala/sttp/tapir/client/tests/HttpServer.scala index 9fe56619bd..d58b51a47c 100644 --- a/client/testserver/src/main/scala/sttp/tapir/client/tests/HttpServer.scala +++ b/client/testserver/src/main/scala/sttp/tapir/client/tests/HttpServer.scala @@ -220,6 +220,8 @@ class HttpServer(port: Port) { .withHttpWebSocketApp(app) .build .use(_ => stopServer.get) + .background + .useForever .unsafeRunSync() logger.info(s"Server on port $port started") From 20d97f46b108bf2a521a7a408ab831fac3b5bdcb Mon Sep 17 00:00:00 2001 From: Ivan Klass Date: Fri, 20 Dec 2024 09:50:31 +0100 Subject: [PATCH 03/12] a proper server resource cancelling --- .../src/main/scala/sttp/tapir/client/tests/HttpServer.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/testserver/src/main/scala/sttp/tapir/client/tests/HttpServer.scala b/client/testserver/src/main/scala/sttp/tapir/client/tests/HttpServer.scala index d58b51a47c..077d4be405 100644 --- a/client/testserver/src/main/scala/sttp/tapir/client/tests/HttpServer.scala +++ b/client/testserver/src/main/scala/sttp/tapir/client/tests/HttpServer.scala @@ -219,9 +219,9 @@ class HttpServer(port: Port) { .withPort(ip4s.Port.fromInt(port).get) .withHttpWebSocketApp(app) .build - .use(_ => stopServer.get) - .background .useForever + .background + .use(_ => stopServer.get) .unsafeRunSync() logger.info(s"Server on port $port started") From 34d8d6f4f5094f7d79593375c6cbcf2dd4422dcf Mon Sep 17 00:00:00 2001 From: Ivan Klass Date: Fri, 20 Dec 2024 10:14:15 +0100 Subject: [PATCH 04/12] Fix execution style of client.tests.HttpServer --- .../sttp/tapir/client/tests/HttpServer.scala | 43 ++++++------------- 1 file changed, 13 insertions(+), 30 deletions(-) diff --git a/client/testserver/src/main/scala/sttp/tapir/client/tests/HttpServer.scala b/client/testserver/src/main/scala/sttp/tapir/client/tests/HttpServer.scala index 077d4be405..8a9d2a362b 100644 --- a/client/testserver/src/main/scala/sttp/tapir/client/tests/HttpServer.scala +++ b/client/testserver/src/main/scala/sttp/tapir/client/tests/HttpServer.scala @@ -2,9 +2,8 @@ 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 com.comcast.ip4s.Port import fs2.{Pipe, Stream} import org.http4s.dsl.io._ import org.http4s.headers.{Accept, `Content-Type`} @@ -17,16 +16,14 @@ import org.http4s._ import org.slf4j.LoggerFactory import org.typelevel.ci.CIString import scodec.bits.ByteVector -import sttp.tapir.client.tests.HttpServer._ -import scala.concurrent.ExecutionContext +object HttpServer extends ResourceApp.Forever { -object HttpServer { - type Port = Int + private val defaultPort = Port.fromInt(51823).get - def main(args: Array[String]): Unit = { - val port = args.headOption.map(_.toInt).getOrElse(51823) - new HttpServer(port).start() + def run(args: List[String]): Resource[IO, Unit] = { + val port = args.headOption.flatMap(Port.fromString).getOrElse(defaultPort) + new HttpServer(port).build.void } } @@ -34,8 +31,6 @@ class HttpServer(port: Port) { private val logger = LoggerFactory.getLogger(getClass) - private val stopServer: Deferred[IO, Unit] = Deferred.unsafe[IO, Unit] - // private object numParam extends QueryParamDecoderMatcher[Int]("num") @@ -211,24 +206,12 @@ class HttpServer(port: Port) { Router("/" -> corsService).orNotFound } - // + def build: Resource[IO, server.Server] = EmberServerBuilder + .default[IO] + .withPort(port) + .withHttpWebSocketApp(app) + .build + .evalTap(_ => IO(logger.info(s"Server on port $port started"))) + .onFinalize(IO(logger.info(s"Server on port $port stopped"))) - def start(): Unit = { - EmberServerBuilder - .default[IO] - .withPort(ip4s.Port.fromInt(port).get) - .withHttpWebSocketApp(app) - .build - .useForever - .background - .use(_ => stopServer.get) - .unsafeRunSync() - - logger.info(s"Server on port $port started") - } - - def close(): Unit = { - stopServer.complete(()).unsafeRunSync() - logger.info(s"Server on port $port stopped") - } } From e36fd118e10551423eada0c16988ec1712757305 Mon Sep 17 00:00:00 2001 From: Ivan Klass Date: Fri, 20 Dec 2024 13:13:37 +0100 Subject: [PATCH 05/12] Use shutdown timeout --- .../server/http4s/Http4sTestServerInterpreter.scala | 12 +++++++----- .../http4s/ztapir/ZHttp4sTestServerInterpreter.scala | 12 +++++++----- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/server/http4s-server/src/test/scala/sttp/tapir/server/http4s/Http4sTestServerInterpreter.scala b/server/http4s-server/src/test/scala/sttp/tapir/server/http4s/Http4sTestServerInterpreter.scala index c8021b20b2..d012a3d473 100644 --- a/server/http4s-server/src/test/scala/sttp/tapir/server/http4s/Http4sTestServerInterpreter.scala +++ b/server/http4s-server/src/test/scala/sttp/tapir/server/http4s/Http4sTestServerInterpreter.scala @@ -35,11 +35,13 @@ class Http4sTestServerInterpreter extends TestServerInterpreter[IO, Fs2Streams[I ): Resource[IO, Port] = { val service: WebSocketBuilder2[IO] => HttpApp[IO] = wsb => routes.map(_.apply(wsb)).reduceK.orNotFound - - EmberServerBuilder - .default[IO] - .withPort(ip4s.Port.fromInt(0).get) - .withHttpWebSocketApp(service) + gracefulShutdownTimeout + .foldLeft( + EmberServerBuilder + .default[IO] + .withPort(ip4s.Port.fromInt(0).get) + .withHttpWebSocketApp(service) + )(_.withShutdownTimeout) .build .map(_.address.getPort) } diff --git a/server/http4s-server/zio/src/test/scala/sttp/tapir/server/http4s/ztapir/ZHttp4sTestServerInterpreter.scala b/server/http4s-server/zio/src/test/scala/sttp/tapir/server/http4s/ztapir/ZHttp4sTestServerInterpreter.scala index 1246c4310d..edeb529a9e 100644 --- a/server/http4s-server/zio/src/test/scala/sttp/tapir/server/http4s/ztapir/ZHttp4sTestServerInterpreter.scala +++ b/server/http4s-server/zio/src/test/scala/sttp/tapir/server/http4s/ztapir/ZHttp4sTestServerInterpreter.scala @@ -41,11 +41,13 @@ class ZHttp4sTestServerInterpreter extends TestServerInterpreter[Task, ZioStream ): Resource[IO, Port] = { val service: WebSocketBuilder2[Task] => HttpApp[Task] = wsb => routes.map(_.apply(wsb)).reduceK.orNotFound - - EmberServerBuilder - .default[Task] - .withPort(ip4s.Port.fromInt(0).get) - .withHttpWebSocketApp(service) + gracefulShutdownTimeout + .foldLeft( + EmberServerBuilder + .default[Task] + .withPort(ip4s.Port.fromInt(0).get) + .withHttpWebSocketApp(service) + )(_.withShutdownTimeout) .build .map(_.address.getPort) .mapK(new ~>[Task, IO] { From 7e043f7fe07968f9a95625c2a63f9233505fa66e Mon Sep 17 00:00:00 2001 From: Ivan Klass Date: Fri, 20 Dec 2024 13:32:11 +0100 Subject: [PATCH 06/12] fix CI compilation errors that were fine locally --- .../sttp/tapir/server/http4s/Http4sTestServerInterpreter.scala | 2 +- .../server/http4s/ztapir/ZHttp4sTestServerInterpreter.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/http4s-server/src/test/scala/sttp/tapir/server/http4s/Http4sTestServerInterpreter.scala b/server/http4s-server/src/test/scala/sttp/tapir/server/http4s/Http4sTestServerInterpreter.scala index d012a3d473..21b34369b6 100644 --- a/server/http4s-server/src/test/scala/sttp/tapir/server/http4s/Http4sTestServerInterpreter.scala +++ b/server/http4s-server/src/test/scala/sttp/tapir/server/http4s/Http4sTestServerInterpreter.scala @@ -41,7 +41,7 @@ class Http4sTestServerInterpreter extends TestServerInterpreter[IO, Fs2Streams[I .default[IO] .withPort(ip4s.Port.fromInt(0).get) .withHttpWebSocketApp(service) - )(_.withShutdownTimeout) + ) { case (b, t) => b.withShutdownTimeout(t) } .build .map(_.address.getPort) } diff --git a/server/http4s-server/zio/src/test/scala/sttp/tapir/server/http4s/ztapir/ZHttp4sTestServerInterpreter.scala b/server/http4s-server/zio/src/test/scala/sttp/tapir/server/http4s/ztapir/ZHttp4sTestServerInterpreter.scala index edeb529a9e..4909be3500 100644 --- a/server/http4s-server/zio/src/test/scala/sttp/tapir/server/http4s/ztapir/ZHttp4sTestServerInterpreter.scala +++ b/server/http4s-server/zio/src/test/scala/sttp/tapir/server/http4s/ztapir/ZHttp4sTestServerInterpreter.scala @@ -47,7 +47,7 @@ class ZHttp4sTestServerInterpreter extends TestServerInterpreter[Task, ZioStream .default[Task] .withPort(ip4s.Port.fromInt(0).get) .withHttpWebSocketApp(service) - )(_.withShutdownTimeout) + ) { case (b, t) => b.withShutdownTimeout(t) } .build .map(_.address.getPort) .mapK(new ~>[Task, IO] { From 1ad08ef654dff17d5e5927aaaae9025a97bfc673 Mon Sep 17 00:00:00 2001 From: Ivan Klass Date: Mon, 23 Dec 2024 15:07:48 +0100 Subject: [PATCH 07/12] Add explicit test server stop --- build.sbt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index f6dad79a47..4d298f12d6 100644 --- a/build.sbt +++ b/build.sbt @@ -28,6 +28,7 @@ val ideScalaVersion = scala3 lazy val clientTestServerPort = settingKey[Int]("Port to run the client interpreter test server on") lazy val startClientTestServer = taskKey[Unit]("Start a http server used by client interpreter tests") +lazy val stopClientTestServer = taskKey[Unit]("Stop http server used by client interpreter tests") lazy val generateMimeByExtensionDB = taskKey[Unit]("Generate the mime by extension DB") lazy val verifyExamplesCompileUsingScalaCli = taskKey[Unit]("Verify that each example compiles using Scala CLI") @@ -380,7 +381,8 @@ val clientTestServerSettings = Seq( Test / testOptions += Tests.Setup(() => { val port = (clientTestServer2_13 / clientTestServerPort).value PollingUtils.waitUntilServerAvailable(new URL(s"http://localhost:$port")) - }) + }), + Test / testOptions += Tests.Cleanup(() => (clientTestServer2_13 / stopClientTestServer).value) ) lazy val clientTestServer = (projectMatrix in file("client/testserver")) @@ -399,7 +401,8 @@ lazy val clientTestServer = (projectMatrix in file("client/testserver")) reStart / reStartArgs := Seq(s"${(Test / clientTestServerPort).value}"), reStart / fullClasspath := (Test / fullClasspath).value, clientTestServerPort := 51823, - startClientTestServer := reStart.toTask("").value + startClientTestServer := reStart.toTask("").value, + stopClientTestServer := reStop.toTask("").value ) .settings(disableScaladocSettingsWhenScala3) .jvmPlatform(scalaVersions = scala2And3Versions, settings = commonJvmSettings) From c30ffd3bbb790fd203e196a95cd7f020d2f49f0a Mon Sep 17 00:00:00 2001 From: Ivan Klass Date: Mon, 23 Dec 2024 15:14:26 +0100 Subject: [PATCH 08/12] Fix stop task --- build.sbt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index 129534f7b6..0e7fdfa8cb 100644 --- a/build.sbt +++ b/build.sbt @@ -28,7 +28,6 @@ val ideScalaVersion = scala3 lazy val clientTestServerPort = settingKey[Int]("Port to run the client interpreter test server on") lazy val startClientTestServer = taskKey[Unit]("Start a http server used by client interpreter tests") -lazy val stopClientTestServer = taskKey[Unit]("Stop http server used by client interpreter tests") lazy val generateMimeByExtensionDB = taskKey[Unit]("Generate the mime by extension DB") lazy val verifyExamplesCompileUsingScalaCli = taskKey[Unit]("Verify that each example compiles using Scala CLI") @@ -383,7 +382,7 @@ val clientTestServerSettings = Seq( val port = (clientTestServer2_13 / clientTestServerPort).value PollingUtils.waitUntilServerAvailable(new URL(s"http://localhost:$port")) }), - Test / testOptions += Tests.Cleanup(() => (clientTestServer2_13 / stopClientTestServer).value) + Test / testOptions += Tests.Cleanup(() => (clientTestServer2_13 / reStop).value) ) lazy val clientTestServer = (projectMatrix in file("client/testserver")) @@ -402,8 +401,7 @@ lazy val clientTestServer = (projectMatrix in file("client/testserver")) reStart / reStartArgs := Seq(s"${(Test / clientTestServerPort).value}"), reStart / fullClasspath := (Test / fullClasspath).value, clientTestServerPort := 51823, - startClientTestServer := reStart.toTask("").value, - stopClientTestServer := reStop.toTask("").value + startClientTestServer := reStart.toTask("").value ) .settings(disableScaladocSettingsWhenScala3) .jvmPlatform(scalaVersions = scala2And3Versions, settings = commonJvmSettings) From 92044cf088919f3839aec7147f5de57c73f4f4c4 Mon Sep 17 00:00:00 2001 From: Ivan Klass Date: Wed, 25 Dec 2024 20:56:57 +0100 Subject: [PATCH 09/12] set short shutdown timeou by default in tests --- build.sbt | 3 +-- .../sttp/tapir/client/tests/HttpServer.scala | 2 +- .../tapir/server/http4s/Http4sServerTest.scala | 13 +++++++------ .../http4s/Http4sTestServerInterpreter.scala | 15 +++++++-------- .../ztapir/ZHttp4sTestServerInterpreter.scala | 11 +++-------- .../tapir/server/tests/CreateServerTest.scala | 4 +++- .../server/vertx/cats/VertxStubServerTest.scala | 2 +- .../scalajvm/sttp/tapir/tests/TestSuite.scala | 2 +- 8 files changed, 24 insertions(+), 28 deletions(-) diff --git a/build.sbt b/build.sbt index 0e7fdfa8cb..4e9a189212 100644 --- a/build.sbt +++ b/build.sbt @@ -381,8 +381,7 @@ val clientTestServerSettings = Seq( Test / testOptions += Tests.Setup(() => { val port = (clientTestServer2_13 / clientTestServerPort).value PollingUtils.waitUntilServerAvailable(new URL(s"http://localhost:$port")) - }), - Test / testOptions += Tests.Cleanup(() => (clientTestServer2_13 / reStop).value) + }) ) lazy val clientTestServer = (projectMatrix in file("client/testserver")) diff --git a/client/testserver/src/main/scala/sttp/tapir/client/tests/HttpServer.scala b/client/testserver/src/main/scala/sttp/tapir/client/tests/HttpServer.scala index 8a9d2a362b..bf86b920b0 100644 --- a/client/testserver/src/main/scala/sttp/tapir/client/tests/HttpServer.scala +++ b/client/testserver/src/main/scala/sttp/tapir/client/tests/HttpServer.scala @@ -71,7 +71,7 @@ class HttpServer(port: Port) { case r @ POST -> Root / "api" / "echo" / "multipart" => r.decode[multipart.Multipart[IO]] { mp => val parts: Vector[multipart.Part[IO]] = mp.parts - def toString(s: fs2.Stream[IO, Byte]): IO[String] = s.through(fs2.text.utf8Decode).compile.foldMonoid + def toString(s: fs2.Stream[IO, Byte]): IO[String] = s.through(fs2.text.utf8.decode).compile.foldMonoid def partToString(name: String): IO[String] = parts.find(_.name.contains(name)).map(p => toString(p.body)).getOrElse(IO.pure("")) partToString("fruit").product(partToString("amount")).flatMap { case (fruit, amount) => Ok(s"$fruit=$amount") diff --git a/server/http4s-server/src/test/scala/sttp/tapir/server/http4s/Http4sServerTest.scala b/server/http4s-server/src/test/scala/sttp/tapir/server/http4s/Http4sServerTest.scala index dce747b163..6be411d12d 100644 --- a/server/http4s-server/src/test/scala/sttp/tapir/server/http4s/Http4sServerTest.scala +++ b/server/http4s-server/src/test/scala/sttp/tapir/server/http4s/Http4sServerTest.scala @@ -25,12 +25,15 @@ import sttp.tapir.tests.{Test, TestSuite} import sttp.ws.{WebSocket, WebSocketFrame} import java.util.UUID -import scala.concurrent.ExecutionContext import scala.concurrent.duration.DurationInt import scala.util.Random class Http4sServerTest[R >: Fs2Streams[IO] with WebSockets] extends TestSuite with OptionValues { + private val serverBuilder = EmberServerBuilder + .default[IO] + .withPort(Port.fromInt(0).get) + override def tests: Resource[IO, List[Test]] = backendResource.map { backend => implicit val m: CatsMonadError[IO] = new CatsMonadError[IO] @@ -41,9 +44,7 @@ class Http4sServerTest[R >: Fs2Streams[IO] with WebSockets] extends TestSuite wi val sse2 = ServerSentEvent(randomUUID, randomUUID, randomUUID, Some(Random.nextInt(200))) def assert_get_apiTestRouter_respondsWithExpectedContent[T](routes: HttpRoutes[IO], expectedContext: T): IO[Assertion] = - EmberServerBuilder - .default[IO] - .withPort(Port.fromInt(0).get) + serverBuilder .withHttpApp(Router("/api" -> routes).orNotFound) .build .use { server => @@ -110,12 +111,12 @@ class Http4sServerTest[R >: Fs2Streams[IO] with WebSockets] extends TestSuite wi endpoint.out(streamBinaryBody(Fs2Streams[IO])(CodecFormat.OctetStream())), "streaming should send data according to producer stream rate" )((_: Unit) => - IO(Right(fs2.Stream.awakeEvery[IO](1.second).map(_.toString()).through(fs2.text.utf8Encode).interruptAfter(10.seconds))) + IO(Right(fs2.Stream.awakeEvery[IO](1.second).map(_.toString()).through(fs2.text.utf8.encode).interruptAfter(10.seconds))) ) { (backend, baseUri) => basicRequest .response( asStream(Fs2Streams[IO])(bs => { - bs.through(fs2.text.utf8Decode).mapAccumulate(0)((pings, currentTime) => (pings + 1, currentTime)).compile.last + bs.through(fs2.text.utf8.decode).mapAccumulate(0)((pings, currentTime) => (pings + 1, currentTime)).compile.last }) ) .get(baseUri) diff --git a/server/http4s-server/src/test/scala/sttp/tapir/server/http4s/Http4sTestServerInterpreter.scala b/server/http4s-server/src/test/scala/sttp/tapir/server/http4s/Http4sTestServerInterpreter.scala index 21b34369b6..9e6fd2a8f0 100644 --- a/server/http4s-server/src/test/scala/sttp/tapir/server/http4s/Http4sTestServerInterpreter.scala +++ b/server/http4s-server/src/test/scala/sttp/tapir/server/http4s/Http4sTestServerInterpreter.scala @@ -14,7 +14,6 @@ import sttp.tapir.server.http4s.Http4sTestServerInterpreter._ import sttp.tapir.server.tests.TestServerInterpreter import sttp.tapir.tests._ -import scala.concurrent.ExecutionContext import scala.concurrent.duration._ object Http4sTestServerInterpreter { @@ -22,13 +21,16 @@ object Http4sTestServerInterpreter { } class Http4sTestServerInterpreter extends TestServerInterpreter[IO, Fs2Streams[IO] with WebSockets, Http4sServerOptions[IO], Routes] { - implicit val ec: ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global override def route(es: List[ServerEndpoint[Fs2Streams[IO] with WebSockets, IO]], interceptors: Interceptors): Routes = { val serverOptions: Http4sServerOptions[IO] = interceptors(Http4sServerOptions.customiseInterceptors[IO]).options Http4sServerInterpreter(serverOptions).toWebSocketRoutes(es) } + private val anyAvailablePort = ip4s.Port.fromInt(0).get + // for some reason server doesn't exit gracefully in tests, that's why a short interval by default + private val builder = EmberServerBuilder.default[IO].withPort(anyAvailablePort).withShutdownTimeout(10.millis) + override def server( routes: NonEmptyList[Routes], gracefulShutdownTimeout: Option[FiniteDuration] @@ -36,12 +38,9 @@ class Http4sTestServerInterpreter extends TestServerInterpreter[IO, Fs2Streams[I val service: WebSocketBuilder2[IO] => HttpApp[IO] = wsb => routes.map(_.apply(wsb)).reduceK.orNotFound gracefulShutdownTimeout - .foldLeft( - EmberServerBuilder - .default[IO] - .withPort(ip4s.Port.fromInt(0).get) - .withHttpWebSocketApp(service) - ) { case (b, t) => b.withShutdownTimeout(t) } + .foldLeft(builder.withHttpWebSocketApp(service)) { case (b, t) => + b.withShutdownTimeout(t) + } .build .map(_.address.getPort) } diff --git a/server/http4s-server/zio/src/test/scala/sttp/tapir/server/http4s/ztapir/ZHttp4sTestServerInterpreter.scala b/server/http4s-server/zio/src/test/scala/sttp/tapir/server/http4s/ztapir/ZHttp4sTestServerInterpreter.scala index 4909be3500..445b624b92 100644 --- a/server/http4s-server/zio/src/test/scala/sttp/tapir/server/http4s/ztapir/ZHttp4sTestServerInterpreter.scala +++ b/server/http4s-server/zio/src/test/scala/sttp/tapir/server/http4s/ztapir/ZHttp4sTestServerInterpreter.scala @@ -28,7 +28,6 @@ object ZHttp4sTestServerInterpreter { } class ZHttp4sTestServerInterpreter extends TestServerInterpreter[Task, ZioStreams with WebSockets, ServerOptions, Routes] { - implicit val ec: ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global override def route(es: List[ZServerEndpoint[Any, ZioStreams with WebSockets]], interceptors: Interceptors): Routes = { val serverOptions: ServerOptions = interceptors(Http4sServerOptions.customiseInterceptors[Task]).options @@ -39,20 +38,16 @@ class ZHttp4sTestServerInterpreter extends TestServerInterpreter[Task, ZioStream routes: NonEmptyList[Routes], gracefulShutdownTimeout: Option[FiniteDuration] ): Resource[IO, Port] = { - val service: WebSocketBuilder2[Task] => HttpApp[Task] = - wsb => routes.map(_.apply(wsb)).reduceK.orNotFound + val service: WebSocketBuilder2[Task] => HttpApp[IO] = + wsb => routes.map(_.apply(wsb).mapF(_.toIO)).reduceK.orNotFound gracefulShutdownTimeout .foldLeft( EmberServerBuilder - .default[Task] + .default[IO] .withPort(ip4s.Port.fromInt(0).get) .withHttpWebSocketApp(service) ) { case (b, t) => b.withShutdownTimeout(t) } .build .map(_.address.getPort) - .mapK(new ~>[Task, IO] { - // Converting a ZIO effect to an Cats Effect IO effect - def apply[B](fa: Task[B]): IO[B] = IO.fromFuture(Unsafe.unsafe(implicit u => IO(Runtime.default.unsafe.runToFuture(fa)))) - }) } } diff --git a/server/tests/src/main/scala/sttp/tapir/server/tests/CreateServerTest.scala b/server/tests/src/main/scala/sttp/tapir/server/tests/CreateServerTest.scala index d1909c8185..aee0e69252 100644 --- a/server/tests/src/main/scala/sttp/tapir/server/tests/CreateServerTest.scala +++ b/server/tests/src/main/scala/sttp/tapir/server/tests/CreateServerTest.scala @@ -133,7 +133,9 @@ class DefaultCreateServerTest[F[_], +R, OPTIONS, ROUTE]( Test(name)( resources .use { port => - runTest(backend, uri"http://localhost:$port").guarantee(IO(logger.info(s"Tests completed on port $port"))) + runTest(backend, uri"http://localhost:$port").guaranteeCase(exitCase => + IO(logger.info(s"Test on port $port: ${exitCase.getClass.getSimpleName}")) + ) } .unsafeToFuture() ) diff --git a/server/vertx-server/cats/src/test/scala/sttp/tapir/server/vertx/cats/VertxStubServerTest.scala b/server/vertx-server/cats/src/test/scala/sttp/tapir/server/vertx/cats/VertxStubServerTest.scala index bcea7b30a3..54c86129b8 100644 --- a/server/vertx-server/cats/src/test/scala/sttp/tapir/server/vertx/cats/VertxStubServerTest.scala +++ b/server/vertx-server/cats/src/test/scala/sttp/tapir/server/vertx/cats/VertxStubServerTest.scala @@ -12,7 +12,7 @@ import sttp.tapir.server.tests.{CreateServerStubTest, ServerStubStreamingTest, S import scala.concurrent.Future class VertxCatsCreateServerStubTest extends CreateServerStubTest[IO, VertxCatsServerOptions[IO]] { - private val (dispatcher, shutdownDispatcher) = Dispatcher[IO].allocated.unsafeRunSync() + private val (dispatcher, shutdownDispatcher) = Dispatcher.sequential[IO].allocated.unsafeRunSync() override def customiseInterceptors: CustomiseInterceptors[IO, VertxCatsServerOptions[IO]] = VertxCatsServerOptions.customiseInterceptors(dispatcher) diff --git a/tests/src/main/scalajvm/sttp/tapir/tests/TestSuite.scala b/tests/src/main/scalajvm/sttp/tapir/tests/TestSuite.scala index a005d9bb08..7a5bf2ad65 100644 --- a/tests/src/main/scalajvm/sttp/tapir/tests/TestSuite.scala +++ b/tests/src/main/scalajvm/sttp/tapir/tests/TestSuite.scala @@ -11,7 +11,7 @@ trait TestSuite extends AsyncFunSuite with BeforeAndAfterAll { def tests: Resource[IO, List[Test]] def testNameFilter: Option[String] = None // define to run a single test (temporarily for debugging) - protected val (dispatcher, shutdownDispatcher) = Dispatcher[IO].allocated.unsafeRunSync() + protected val (dispatcher, shutdownDispatcher) = Dispatcher.sequential[IO].allocated.unsafeRunSync() // we need to register the tests when the class is constructed, as otherwise scalatest skips it val (allTests, doRelease) = tests.allocated.unsafeRunSync() From e3c0077b33228d5910bc8f635964afb423b5b53c Mon Sep 17 00:00:00 2001 From: Ivan Klass Date: Wed, 25 Dec 2024 21:27:22 +0100 Subject: [PATCH 10/12] fix zio-http4s compilation --- .../ztapir/ZHttp4sTestServerInterpreter.scala | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/server/http4s-server/zio/src/test/scala/sttp/tapir/server/http4s/ztapir/ZHttp4sTestServerInterpreter.scala b/server/http4s-server/zio/src/test/scala/sttp/tapir/server/http4s/ztapir/ZHttp4sTestServerInterpreter.scala index 445b624b92..b355ed3794 100644 --- a/server/http4s-server/zio/src/test/scala/sttp/tapir/server/http4s/ztapir/ZHttp4sTestServerInterpreter.scala +++ b/server/http4s-server/zio/src/test/scala/sttp/tapir/server/http4s/ztapir/ZHttp4sTestServerInterpreter.scala @@ -17,6 +17,7 @@ import sttp.tapir.tests._ import sttp.tapir.ztapir.ZServerEndpoint import zio.{Runtime, Task, Unsafe} import zio.interop.catz._ +import zio.interop.catz.implicits._ import scala.concurrent.ExecutionContext import scala.concurrent.duration.FiniteDuration @@ -29,6 +30,15 @@ object ZHttp4sTestServerInterpreter { class ZHttp4sTestServerInterpreter extends TestServerInterpreter[Task, ZioStreams with WebSockets, ServerOptions, Routes] { + private val anyAvailablePort = ip4s.Port.fromInt(0).get + // for some reason server doesn't exit gracefully in tests, that's why a short interval by default + private val builder = EmberServerBuilder.default[IO].withPort(anyAvailablePort).withShutdownTimeout(10.millis) + + private val taskToIO = new ~>[Task, IO] { + // Converting a ZIO effect to an Cats Effect IO effect + def apply[B](fa: Task[B]): IO[B] = fa.toEffect[IO] + } + override def route(es: List[ZServerEndpoint[Any, ZioStreams with WebSockets]], interceptors: Interceptors): Routes = { val serverOptions: ServerOptions = interceptors(Http4sServerOptions.customiseInterceptors[Task]).options ZHttp4sServerInterpreter(serverOptions).fromWebSocket(es).toRoutes @@ -38,16 +48,14 @@ class ZHttp4sTestServerInterpreter extends TestServerInterpreter[Task, ZioStream routes: NonEmptyList[Routes], gracefulShutdownTimeout: Option[FiniteDuration] ): Resource[IO, Port] = { - val service: WebSocketBuilder2[Task] => HttpApp[IO] = - wsb => routes.map(_.apply(wsb).mapF(_.toIO)).reduceK.orNotFound + val service: WebSocketBuilder2[Task] => HttpApp[Task] = + wsb => routes.map(_.apply(wsb)).reduceK.orNotFound gracefulShutdownTimeout .foldLeft( - EmberServerBuilder - .default[IO] - .withPort(ip4s.Port.fromInt(0).get) - .withHttpWebSocketApp(service) + builder.withHttpWebSocketApp(service) ) { case (b, t) => b.withShutdownTimeout(t) } .build .map(_.address.getPort) + .mapK(taskToIO) } } From d04c81d0166dc48736eed3bce76334bed4dfdd2d Mon Sep 17 00:00:00 2001 From: Ivan Klass Date: Wed, 25 Dec 2024 21:34:26 +0100 Subject: [PATCH 11/12] fix more compile errors --- .../server/http4s/ztapir/ZHttp4sTestServerInterpreter.scala | 2 +- .../sttp/tapir/server/vertx/cats/streams/Fs2StreamTest.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/http4s-server/zio/src/test/scala/sttp/tapir/server/http4s/ztapir/ZHttp4sTestServerInterpreter.scala b/server/http4s-server/zio/src/test/scala/sttp/tapir/server/http4s/ztapir/ZHttp4sTestServerInterpreter.scala index b355ed3794..42e6b6d71b 100644 --- a/server/http4s-server/zio/src/test/scala/sttp/tapir/server/http4s/ztapir/ZHttp4sTestServerInterpreter.scala +++ b/server/http4s-server/zio/src/test/scala/sttp/tapir/server/http4s/ztapir/ZHttp4sTestServerInterpreter.scala @@ -20,7 +20,7 @@ import zio.interop.catz._ import zio.interop.catz.implicits._ import scala.concurrent.ExecutionContext -import scala.concurrent.duration.FiniteDuration +import scala.concurrent.duration.{DurationInt, FiniteDuration} object ZHttp4sTestServerInterpreter { type F[A] = Task[A] diff --git a/server/vertx-server/cats/src/test/scala/sttp/tapir/server/vertx/cats/streams/Fs2StreamTest.scala b/server/vertx-server/cats/src/test/scala/sttp/tapir/server/vertx/cats/streams/Fs2StreamTest.scala index baadbe13f4..39966b12d2 100644 --- a/server/vertx-server/cats/src/test/scala/sttp/tapir/server/vertx/cats/streams/Fs2StreamTest.scala +++ b/server/vertx-server/cats/src/test/scala/sttp/tapir/server/vertx/cats/streams/Fs2StreamTest.scala @@ -18,7 +18,7 @@ import scala.concurrent.duration._ class Fs2StreamTest extends AsyncFlatSpec with Matchers with BeforeAndAfterAll { - private val (dispatcher, shutdownDispatcher) = Dispatcher[IO].allocated.unsafeRunSync() + private val (dispatcher, shutdownDispatcher) = Dispatcher.sequential[IO].allocated.unsafeRunSync() override protected def afterAll(): Unit = { shutdownDispatcher.unsafeRunSync() From ba6aa056ea09587c849a0855de42c416f069ea07 Mon Sep 17 00:00:00 2001 From: Ivan Klass Date: Thu, 26 Dec 2024 08:49:44 +0100 Subject: [PATCH 12/12] add required import --- .../server/http4s/ztapir/ZHttp4sTestServerInterpreter.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/http4s-server/zio/src/test/scala/sttp/tapir/server/http4s/ztapir/ZHttp4sTestServerInterpreter.scala b/server/http4s-server/zio/src/test/scala/sttp/tapir/server/http4s/ztapir/ZHttp4sTestServerInterpreter.scala index 42e6b6d71b..fe1eff6e24 100644 --- a/server/http4s-server/zio/src/test/scala/sttp/tapir/server/http4s/ztapir/ZHttp4sTestServerInterpreter.scala +++ b/server/http4s-server/zio/src/test/scala/sttp/tapir/server/http4s/ztapir/ZHttp4sTestServerInterpreter.scala @@ -16,6 +16,7 @@ import sttp.tapir.server.tests.TestServerInterpreter import sttp.tapir.tests._ import sttp.tapir.ztapir.ZServerEndpoint import zio.{Runtime, Task, Unsafe} +import zio.interop._ import zio.interop.catz._ import zio.interop.catz.implicits._ @@ -32,7 +33,7 @@ class ZHttp4sTestServerInterpreter extends TestServerInterpreter[Task, ZioStream private val anyAvailablePort = ip4s.Port.fromInt(0).get // for some reason server doesn't exit gracefully in tests, that's why a short interval by default - private val builder = EmberServerBuilder.default[IO].withPort(anyAvailablePort).withShutdownTimeout(10.millis) + private val builder = EmberServerBuilder.default[Task].withPort(anyAvailablePort).withShutdownTimeout(10.millis) private val taskToIO = new ~>[Task, IO] { // Converting a ZIO effect to an Cats Effect IO effect