diff --git a/build.sbt b/build.sbt index 96e381ee2f..5287a256f7 100644 --- a/build.sbt +++ b/build.sbt @@ -438,7 +438,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 ), @@ -591,7 +591,7 @@ lazy val perfTestsE2e: ProjectMatrix = (projectMatrix in file("perf-tests/perf-t "io.github.classgraph" % "classgraph" % "4.8.184", "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 ), @@ -1318,7 +1318,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 ) ) @@ -1344,7 +1344,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 ) ) @@ -1370,7 +1370,7 @@ lazy val scalarBundle: ProjectMatrix = (projectMatrix in file("docs/scalar-bundl name := "tapir-scalar-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 ) ) @@ -1505,7 +1505,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 ) } ) @@ -1527,7 +1527,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) @@ -2105,7 +2105,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 ) ) @@ -2357,7 +2357,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, @@ -2436,7 +2436,7 @@ lazy val documentation: ProjectMatrix = (projectMatrix in file("generated-doc")) dependencyOverrides += "com.lihaoyi" %% "upickle" % Versions.upickle3, 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 ee293971e8..d903b91090 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,6 +1,6 @@ package sttp.tapir.client.http4s -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} @@ -44,7 +44,7 @@ abstract class Http4sClientTests[R] extends ClientTests[R] { private implicit val ioRT: IORuntime = cats.effect.unsafe.implicits.global private def sendAndParseResponse[Result](request: Request[IO], parseResponse: Response[IO] => IO[Result]) = - BlazeClientBuilder[IO](global).resource + 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 1887308136..65900a9536 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,11 +3,12 @@ package sttp.tapir.client.tests import cats.effect._ import cats.effect.std.Queue import cats.implicits._ +import com.comcast.ip4s.Port 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 @@ -16,18 +17,19 @@ import org.slf4j.LoggerFactory import org.typelevel.ci.CIString import scodec.bits.ByteVector -import scala.concurrent.ExecutionContext +import scala.concurrent.duration._ object HttpServer extends ResourceApp.Forever { - type Port = Int + + private val defaultPort = Port.fromInt(51823).get def run(args: List[String]): Resource[IO, Unit] = { - val port = args.headOption.map(_.toInt).getOrElse(51823) + val port = args.headOption.flatMap(Port.fromString).getOrElse(defaultPort) new HttpServer(port).build.void } } -class HttpServer(port: HttpServer.Port) { +class HttpServer(port: Port) { private val logger = LoggerFactory.getLogger(getClass) @@ -228,13 +230,12 @@ class HttpServer(port: HttpServer.Port) { Router("/" -> corsService).orNotFound } - // - - def build: Resource[IO, server.Server] = BlazeServerBuilder[IO] - .withExecutionContext(ExecutionContext.global) - .bindHttp(port) + def build: Resource[IO, server.Server] = EmberServerBuilder + .default[IO] + .withPort(port) .withHttpWebSocketApp(app) - .resource + .withIdleTimeout(5.seconds) + .build .evalTap(_ => IO(logger.info(s"Server on port $port started"))) .onFinalize(IO(logger.info(s"Server on port $port stopped"))) } diff --git a/doc/server/http4s.md b/doc/server/http4s.md index c5db5d59b1..2b53209c38 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) ``` ```{note} diff --git a/doc/server/zio-http4s.md b/doc/server/zio-http4s.md index 0bf2f7852a..30bccbeb95 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)) @@ -140,15 +138,11 @@ val wsRoutes: WebSocketBuilder2[Task] => HttpRoutes[Task] = ZHttp4sServerInterpreter().fromWebSocket(wsEndpoint.zServerLogic(_ => ???)).toRoutes val serve: Task[Unit] = - ZIO.executor.flatMap(executor => - BlazeServerBuilder[Task] - .withExecutionContext(executor.asExecutionContext) - .bindHttp(8080, "localhost") - .withHttpWebSocketApp(wsb => Router("/" -> wsRoutes(wsb)).orNotFound) - .serve - .compile - .drain - ) + EmberServerBuilder + .default[Task] + .withHttpWebSocketApp(wsb => Router("/" -> wsRoutes(wsb)).orNotFound) + .build + .useForever ``` ## Server Sent Events diff --git a/doc/tutorials/07_cats_effect.md b/doc/tutorials/07_cats_effect.md index 2024e2bc7d..00a3bc2703 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.34 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.34 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,16 +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 + .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 c292df893b..827b66d853 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 @@ -67,10 +68,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/scalar-bundle/src/test/scala/sttp/tapir/scalar/bundle/ScalarInterpreterTest.scala b/docs/scalar-bundle/src/test/scala/sttp/tapir/scalar/bundle/ScalarInterpreterTest.scala index e753f2a040..d0df6c5fb1 100644 --- a/docs/scalar-bundle/src/test/scala/sttp/tapir/scalar/bundle/ScalarInterpreterTest.scala +++ b/docs/scalar-bundle/src/test/scala/sttp/tapir/scalar/bundle/ScalarInterpreterTest.scala @@ -2,8 +2,9 @@ package sttp.tapir.scalar.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 @@ -67,10 +68,11 @@ class ScalarInterpreterTest 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("/")}" -> scalarUIRoutes).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 916f0cab4f..4c298f65ff 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 @@ -34,10 +35,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 859f8a2aae..e018febae4 100644 --- a/examples/src/main/scala/sttp/tapir/examples/HelloWorldHttp4sServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/HelloWorldHttp4sServer.scala @@ -3,22 +3,20 @@ //> using dep com.softwaremill.sttp.tapir::tapir-core:1.13.23 //> using dep com.softwaremill.sttp.tapir::tapir-http4s-server:1.13.23 //> using dep com.softwaremill.sttp.client4::core:4.0.0-RC3 -//> using dep org.http4s::http4s-blaze-server:0.23.16 +//> using dep org.http4s::http4s-ember-server:0.23.34 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.client4.* import sttp.client4.httpclient.HttpClientSyncBackend import sttp.tapir.* import sttp.tapir.server.http4s.Http4sServerInterpreter -import scala.concurrent.ExecutionContext - object HelloWorldHttp4sServer extends IOApp: // the endpoint: single fixed path input ("hello"), single query parameter // corresponds to: GET /hello?name=... @@ -29,15 +27,12 @@ object HelloWorldHttp4sServer extends IOApp: val helloWorldRoutes: HttpRoutes[IO] = Http4sServerInterpreter[IO]().toRoutes(helloWorld.serverLogic(name => IO(s"Hello, $name!".asRight[Unit]))) - implicit val ec: ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global - 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: SyncBackend = HttpClientSyncBackend() diff --git a/examples/src/main/scala/sttp/tapir/examples/ZioEnvExampleHttp4sServer.scala b/examples/src/main/scala/sttp/tapir/examples/ZioEnvExampleHttp4sServer.scala index 8d34b6de98..c1f4c18507 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.13.23 //> using dep com.softwaremill.sttp.tapir::tapir-swagger-ui-bundle:1.13.23 //> using dep com.softwaremill.sttp.tapir::tapir-zio:1.13.23 -//> using dep org.http4s::http4s-blaze-server:0.23.16 +//> using dep org.http4s::http4s-ember-server:0.23.34 //> 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 90dd49af87..f4fb4d3477 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.13.23 //> using dep com.softwaremill.sttp.tapir::tapir-swagger-ui-bundle:1.13.23 //> using dep com.softwaremill.sttp.tapir::tapir-zio:1.13.23 -//> using dep org.http4s::http4s-blaze-server:0.23.16 +//> using dep org.http4s::http4s-ember-server:0.23.34 //> 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 abc977d219..55d183ce90 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.13.23 //> using dep com.softwaremill.sttp.tapir::tapir-http4s-server-zio:1.13.23 //> using dep com.softwaremill.sttp.tapir::tapir-zio:1.13.23 -//> using dep org.http4s::http4s-blaze-server:0.23.16 +//> using dep org.http4s::http4s-ember-server:0.23.34 //> using dep com.softwaremill.sttp.client4::zio:4.0.0-RC3 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.client4.* import sttp.client4.httpclient.zio.HttpClientZioBackend @@ -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 8713797519..5d7794c346 100644 --- a/examples/src/main/scala/sttp/tapir/examples/client/Http4sClientExample.scala +++ b/examples/src/main/scala/sttp/tapir/examples/client/Http4sClientExample.scala @@ -3,9 +3,9 @@ //> using dep com.softwaremill.sttp.tapir::tapir-core:1.13.23 //> using dep com.softwaremill.sttp.tapir::tapir-http4s-client:1.13.23 //> using dep com.softwaremill.sttp.tapir::tapir-json-circe:1.13.23 -//> using dep org.http4s::http4s-circe:0.23.27 -//> using dep org.http4s::http4s-blaze-server:0.23.16 -//> using dep org.http4s::http4s-dsl:0.23.27 +//> using dep org.http4s::http4s-circe:0.23.34 +//> using dep org.http4s::http4s-ember-server:0.23.34 +//> using dep org.http4s::http4s-dsl:0.23.34 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 69b3911134..4defb0047d 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.13.23 //> using dep com.softwaremill.sttp.tapir::tapir-http4s-server:1.13.23 //> using dep com.softwaremill.sttp.tapir::tapir-json-circe:1.13.23 -//> using dep org.http4s::http4s-blaze-server:0.23.16 +//> using dep org.http4s::http4s-ember-server:0.23.34 //> using dep com.softwaremill.sttp.client4::core:4.0.0-RC3 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.client4.* import sttp.client4.httpclient.HttpClientSyncBackend @@ -67,15 +67,12 @@ object ErrorUnionTypesHttp4sServer extends IOApp: // converting an endpoint to a route (providing server-side logic); extension method comes from imported packages val helloWorldRoutes: HttpRoutes[IO] = Http4sServerInterpreter[IO]().toRoutes(helloServerEndpoint) - implicit val ec: ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global - 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: SyncBackend = HttpClientSyncBackend() 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 938f01f762..984804ec20 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.13.23 //> using dep com.softwaremill.sttp.tapir::tapir-swagger-ui-bundle:1.13.23 //> using dep com.softwaremill.sttp.tapir::tapir-http4s-server:1.13.23 -//> using dep org.http4s::http4s-blaze-server:0.23.16 +//> using dep org.http4s::http4s-ember-server:0.23.34 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.* @@ -43,7 +43,6 @@ object MultipleEndpointsDocumentationHttp4sServer extends IOApp: ) // server-side logic - implicit val ec: ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global val books = new AtomicReference( Vector( @@ -73,16 +72,10 @@ object MultipleEndpointsDocumentationHttp4sServer extends IOApp: override def run(args: List[String]): IO[ExitCode] = // starting the server - BlazeServerBuilder[IO] - .withExecutionContext(ec) - .bindHttp(8080, "localhost") - .withHttpApp(Router("/" -> (routes)).orNotFound) - .resource - .use { _ => - IO { - println("Go to: http://localhost:8080/docs") - println("Press any key to exit ...") - scala.io.StdIn.readLine() - } - } + EmberServerBuilder + .default[IO] + .withHttpApp(Router("/" -> routes).orNotFound) + .build + .evalTap(_ => IO.println("Go to: http://localhost:8080/docs")) + .surround(IO.println("Press any key to exit ...") *> IO.readLine) .as(ExitCode.Success) 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 f69d422ece..d07ebf8639 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.13.23 //> using dep com.softwaremill.sttp.tapir::tapir-redoc-bundle:1.13.23 //> using dep com.softwaremill.sttp.tapir::tapir-http4s-server:1.13.23 -//> using dep org.http4s::http4s-blaze-server:0.23.16 +//> using dep org.http4s::http4s-ember-server:0.23.34 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/openapi/ScalarContextPathHttp4sServer.scala b/examples/src/main/scala/sttp/tapir/examples/openapi/ScalarContextPathHttp4sServer.scala index 53a7c1fe98..77268d1dfd 100644 --- a/examples/src/main/scala/sttp/tapir/examples/openapi/ScalarContextPathHttp4sServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/openapi/ScalarContextPathHttp4sServer.scala @@ -3,15 +3,15 @@ //> using dep com.softwaremill.sttp.tapir::tapir-core:1.13.23 //> using dep com.softwaremill.sttp.tapir::tapir-scalar-bundle:1.13.23 //> using dep com.softwaremill.sttp.tapir::tapir-http4s-server:1.13.23 -//> using dep org.http4s::http4s-blaze-server:0.23.16 +//> using dep org.http4s::http4s-ember-server:0.23.34 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.scalar.ScalarUIOptions import sttp.tapir.scalar.bundle.ScalarInterpreter @@ -36,10 +36,10 @@ object ScalarContextPathHttp4sServer extends IOApp: implicit val ec: ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global override def run(args: List[String]): IO[ExitCode] = - BlazeServerBuilder[IO] - .withExecutionContext(ec) - .bindHttp(8080, "localhost") + // starting the server + 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 5afcd8e4b5..093138e67f 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.13.23 //> using dep com.softwaremill.sttp.tapir::tapir-json-circe:1.13.23 //> using dep com.softwaremill.sttp.client4::cats:4.0.25 -//> using dep org.http4s::http4s-blaze-server:0.23.16 +//> using dep org.http4s::http4s-ember-server:0.23.34 //> 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.client4.* import sttp.client4.httpclient.cats.HttpClientCatsBackend @@ -118,17 +118,13 @@ 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 - .use { _ => - IO { - println("Go to: http://localhost:8080") - println("Press any key to exit ...") - scala.io.StdIn.readLine() - } + .build + .evalTap(_ => IO.println("Go to: http://localhost:8080")) + .surround { + IO.println("Press any key to exit ...") *> IO.readLine } ) .as(ExitCode.Success) 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 5a2e9e2fa3..b447cc56d7 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.13.23 //> using dep com.softwaremill.sttp.tapir::tapir-http4s-server:1.13.23 //> using dep com.softwaremill.sttp.client4::fs2:4.0.0-RC3 -//> using dep org.http4s::http4s-blaze-server:0.23.16 +//> using dep org.http4s::http4s-ember-server:0.23.34 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.client4.* @@ -58,8 +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 + .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 9a404a2ee4..bde67a0fac 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.13.23 //> using dep com.softwaremill.sttp.tapir::tapir-http4s-server:1.13.23 //> using dep com.softwaremill.sttp.client4::core:4.0.0-RC3 -//> using dep org.http4s::http4s-blaze-server:0.23.16 +//> using dep org.http4s::http4s-ember-server:0.23.34 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.client4.* @@ -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: SyncBackend = 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 f1a215d4d0..4a60afe631 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.13.23 //> using dep com.softwaremill.sttp.tapir::tapir-http4s-server:1.13.23 -//> using dep org.http4s::http4s-blaze-server:0.23.16 +//> using dep org.http4s::http4s-ember-server:0.23.34 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 @@ -50,9 +50,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 + .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 21a5fbed6b..302641abce 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.13.23 //> using dep com.softwaremill.sttp.apispec::asyncapi-circe-yaml:0.10.0 //> using dep com.softwaremill.sttp.client4::fs2:4.0.0-RC3 -//> using dep org.http4s::http4s-blaze-server:0.23.16 +//> using dep org.http4s::http4s-ember-server:0.23.34 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 @@ -82,11 +82,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(_ => HttpClientFs2Backend.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 6d43f12087..abc76a508e 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,23 +75,20 @@ 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") + +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 788c1630fa..9f4ab5f3e1 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 @@ -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)) @@ -140,15 +138,11 @@ val wsRoutes: WebSocketBuilder2[Task] => HttpRoutes[Task] = ZHttp4sServerInterpreter().fromWebSocket(wsEndpoint.zServerLogic(_ => ???)).toRoutes val serve: Task[Unit] = - ZIO.executor.flatMap(executor => - BlazeServerBuilder[Task] - .withExecutionContext(executor.asExecutionContext) - .bindHttp(8080, "localhost") - .withHttpWebSocketApp(wsb => Router("/" -> wsRoutes(wsb)).orNotFound) - .serve - .compile - .drain - ) + EmberServerBuilder + .default[Task] + .withHttpWebSocketApp(wsb => Router("/" -> wsRoutes(wsb)).orNotFound) + .build + .useForever ``` ## Server Sent Events diff --git a/generated-doc/out/tutorials/07_cats_effect.md b/generated-doc/out/tutorials/07_cats_effect.md index 3175fcb4df..63187a2e4f 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.13.23 //> using dep com.softwaremill.sttp.tapir::tapir-http4s-server:1.13.23 -//> using dep org.http4s::http4s-blaze-server:0.23.16 +//> using dep org.http4s::http4s-ember-server:0.23.34 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.13.23 //> using dep com.softwaremill.sttp.tapir::tapir-http4s-server:1.13.23 //> using dep com.softwaremill.sttp.tapir::tapir-swagger-ui-bundle:1.13.23 -//> using dep org.http4s::http4s-blaze-server:0.23.16 +//> using dep org.http4s::http4s-ember-server:0.23.34 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,16 +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 + .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/perf-tests-e2e/src/main/scala/sttp/tapir/perf/http4s/Http4s.scala b/perf-tests/perf-tests-e2e/src/main/scala/sttp/tapir/perf/http4s/Http4s.scala index 7fb0fa795c..159c479e4d 100644 --- a/perf-tests/perf-tests-e2e/src/main/scala/sttp/tapir/perf/http4s/Http4s.scala +++ b/perf-tests/perf-tests-e2e/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,14 +106,13 @@ 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]): Resource[IO, Unit] = - BlazeServerBuilder[IO] - .bindHttp(Port, "localhost") + EmberServerBuilder + .default[IO] + .withPort(ip4s.Port.fromInt(Port).get) .withHttpWebSocketApp(wsb => router(wsb).orNotFound) .withMaxConnections(maxConnections) - .withConnectorPoolSize(connectorPoolSize) - .resource + .build .map(_ => ()) .onFinalize(IO.println("Http4s server closed.")) } diff --git a/project/Versions.scala b/project/Versions.scala index b71a44f6c7..48ddb977a8 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -1,7 +1,5 @@ object Versions { val http4s = "0.23.34" - val http4sBlazeServer = "0.23.17" - val http4sBlazeClient = "0.23.17" val catsCore = "2.13.0" val catsEffect = "3.7.0" val circe = "0.14.15" diff --git a/server/http4s-server/src/main/scala/sttp/tapir/server/http4s/Http4sRequestBody.scala b/server/http4s-server/src/main/scala/sttp/tapir/server/http4s/Http4sRequestBody.scala index 46b12e1869..fd0bb6271a 100644 --- a/server/http4s-server/src/main/scala/sttp/tapir/server/http4s/Http4sRequestBody.scala +++ b/server/http4s-server/src/main/scala/sttp/tapir/server/http4s/Http4sRequestBody.scala @@ -11,6 +11,7 @@ import sttp.capabilities.fs2.Fs2Streams import sttp.model.{Header, Part} import sttp.tapir.model.ServerRequest import sttp.tapir.server.interpreter.{RawValue, RequestBody} +import sttp.tapir.server.model.InvalidMultipartBodyException import sttp.tapir.{FileRange, InputStreamRange, RawBodyType, RawPart} private[http4s] class Http4sRequestBody[F[_]: Async]( @@ -57,7 +58,7 @@ private[http4s] class Http4sRequestBody[F[_]: Async]( .decode(limitedMedia(http4sRequest(serverRequest), maxBytes), strict = false) .value .flatMap { - case Left(failure) => Sync[F].raiseError(failure) + case Left(failure) => Sync[F].raiseError(InvalidMultipartBodyException(failure)) case Right(mp) => val rawPartsF: Vector[F[RawPart]] = mp.parts .flatMap(part => part.name.flatMap(name => m.partType(name)).map((part, _)).toList) 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 0c87d53a45..8db66e49cf 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 @@ -134,7 +134,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 7427d80840..814a23bf75 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,7 +4,6 @@ import cats.effect._ import cats.effect.unsafe.implicits.global import cats.syntax.all._ import fs2.Pipe -import org.http4s.blaze.server.BlazeServerBuilder import org.http4s.server.Router import org.http4s.server.ContextMiddleware import org.http4s.server.websocket.WebSocketBuilder2 @@ -24,7 +23,6 @@ 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 @@ -39,14 +37,10 @@ class Http4sServerTest[R >: Fs2Streams[IO] with WebSockets] extends TestSuite wi val sse1 = ServerSentEvent(randomUUID, randomUUID, randomUUID, Some(Random.nextInt(200))) 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") - .withHttpApp(Router("/api" -> routes).orNotFound) - .resource - .use { server => - val port = server.address.getPort + def assert_get_apiTestRouter_respondsWithExpectedContent[T](routes: HttpRoutes[IO], expectedContent: T): IO[Assertion] = + interpreter + .server(_ => Router("/api" -> routes)) + .use { port => basicRequest.get(uri"http://localhost:$port/api/test/router").send(backend).map(_.body shouldBe Right(expectedContext)) } @@ -157,11 +151,8 @@ class Http4sServerTest[R >: Fs2Streams[IO] with WebSockets] extends TestSuite wi // middleware to add the context to each request (so here string constant) val middleware: ContextMiddleware[IO, String] = ContextMiddleware.const(expectedContext) - BlazeServerBuilder[IO] - .withExecutionContext(ExecutionContext.global) - .bindHttp(0, "localhost") - .withHttpWebSocketApp(wsb => middleware(routesWithContext(wsb)).orNotFound) - .resource + interpreter + .buildServer(wsb => middleware(routesWithContext(wsb)).orNotFound) .use { server => val port = server.address.getPort basicRequest @@ -190,6 +181,7 @@ class Http4sServerTest[R >: Fs2Streams[IO] with WebSockets] extends TestSuite wi createServerTest, Fs2Streams[IO], autoPing = true, + autoPongAtEndpoint = false, handlePong = false, decodeCloseRequests = false // when a close frame is received, http4s cancels the stream, so sometimes the close frames are never processed 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 5d39fed08e..7444205d31 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 @@ -1,7 +1,9 @@ package sttp.tapir.server.http4s import cats.effect.{IO, Resource} -import org.http4s.blaze.server.BlazeServerBuilder +import com.comcast.ip4s +import org.http4s.ember.server.EmberServerBuilder +import org.http4s.server.Server import org.http4s.server.websocket.WebSocketBuilder2 import org.http4s.{HttpApp, HttpRoutes} import sttp.capabilities.WebSockets @@ -11,7 +13,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 { @@ -19,25 +20,39 @@ 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 + private val serverBuilder = EmberServerBuilder + .default[IO] + .withPort(anyAvailablePort) + // Keep ember's default idle timeout (60s). A short idle timeout cancels in-flight chunked-response + // writes under load, truncating the response before the terminal chunk and causing the client to see + // an EOF mid-read. See https://github.com/http4s/http4s/issues/6427. Teardown is handled by + // withShutdownTimeout(0) below, so the short timeout is not needed for test speed. + .withAdditionalSocketOptions( + List(fs2.io.net.SocketOption.noDelay(true)) // https://github.com/http4s/http4s/issues/7668 + ) + + def buildServer( + makeService: WebSocketBuilder2[IO] => HttpApp[IO], + gracefulShutdownTimeout: Option[FiniteDuration] = None + ): Resource[IO, Server] = + serverBuilder + .withHttpWebSocketApp(makeService) + .withShutdownTimeout( + gracefulShutdownTimeout.getOrElse(0.seconds) // no need to wait unless it's explicitly required by test + ) + .build + override def server( route: Routes, gracefulShutdownTimeout: Option[FiniteDuration] - ): Resource[IO, Port] = { - val service: WebSocketBuilder2[IO] => HttpApp[IO] = - wsb => route(wsb).orNotFound - - BlazeServerBuilder[IO] - .withExecutionContext(ExecutionContext.global) - .bindHttp(0, "localhost") - .withHttpWebSocketApp(service) - .resource - .map(_.address.getPort()) - } + ): Resource[IO, Port] = + buildServer(wsb => route(wsb).orNotFound, gracefulShutdownTimeout) + .map(_.address.getPort) } diff --git a/server/http4s-server/zio/src/test/scala/sttp/tapir/server/http4s/ztapir/ZHttp4sServerTest.scala b/server/http4s-server/zio/src/test/scala/sttp/tapir/server/http4s/ztapir/ZHttp4sServerTest.scala index bc99739685..ab9d849e19 100644 --- a/server/http4s-server/zio/src/test/scala/sttp/tapir/server/http4s/ztapir/ZHttp4sServerTest.scala +++ b/server/http4s-server/zio/src/test/scala/sttp/tapir/server/http4s/ztapir/ZHttp4sServerTest.scala @@ -59,6 +59,7 @@ class ZHttp4sServerTest extends TestSuite with OptionValues { createServerTest, ZioStreams, autoPing = true, + autoPongAtEndpoint = false, handlePong = false, decodeCloseRequests = false // when a close frame is received, http4s cancels the stream, so sometimes the close frames are never processed 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 e88bc43e62..813a546c99 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 @@ -2,7 +2,8 @@ package sttp.tapir.server.http4s.ztapir import cats.effect.{IO, Resource} import cats._ -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 @@ -17,8 +18,7 @@ import zio.interop._ 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] @@ -28,6 +28,17 @@ object ZHttp4sTestServerInterpreter { class ZHttp4sTestServerInterpreter extends TestServerInterpreter[Task, ZioStreams with WebSockets, ServerOptions, Routes] { + private val anyAvailablePort = ip4s.Port.fromInt(0).get + // Keep ember's default idle timeout (60s): a short one cancels in-flight chunked-response writes under + // load, truncating the response and causing the client to see an EOF mid-read (http4s#6427). Teardown is + // handled by withShutdownTimeout(0) below. + private val serverBuilder = EmberServerBuilder + .default[Task] + .withPort(anyAvailablePort) + .withAdditionalSocketOptions( + List(fs2.io.net.SocketOption.noDelay(true)) // https://github.com/http4s/http4s/issues/7668 + ) + 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 @@ -39,12 +50,12 @@ class ZHttp4sTestServerInterpreter extends TestServerInterpreter[Task, ZioStream ): Resource[IO, Port] = { val service: WebSocketBuilder2[Task] => HttpApp[Task] = wsb => route(wsb).orNotFound - - BlazeServerBuilder[Task] - .withExecutionContext(ExecutionContext.global) - .bindHttp(0, "localhost") + serverBuilder .withHttpWebSocketApp(service) - .resource + .withShutdownTimeout( + gracefulShutdownTimeout.getOrElse(0.seconds) // no need to wait unless it's explicitly required by test + ) + .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/ServerWebSocketTests.scala b/server/tests/src/main/scala/sttp/tapir/server/tests/ServerWebSocketTests.scala index b0e3a85bf9..1f38ed47fe 100644 --- a/server/tests/src/main/scala/sttp/tapir/server/tests/ServerWebSocketTests.scala +++ b/server/tests/src/main/scala/sttp/tapir/server/tests/ServerWebSocketTests.scala @@ -29,6 +29,8 @@ abstract class ServerWebSocketTests[F[_], S <: Streams[S], OPTIONS, ROUTE]( val streams: S, autoPing: Boolean, handlePong: Boolean, + // some servers (e.g. http4s Ember) can pong on pings automatically without proxying them to endpoint logic + autoPongAtEndpoint: Boolean = true, // Disabled for example for vert.x, which sometimes drops connection without returning Close expectCloseResponse: Boolean = true, frameConcatenation: Boolean = true, @@ -74,7 +76,11 @@ abstract class ServerWebSocketTests[F[_], S <: Streams[S], OPTIONS, ROUTE]( .map(_.last) .value .asInstanceOf[Option[Either[WebSocketFrame, String]]] - .forall(_ == Left(WebSocketFrame.Close(1000, "normal closure"))) + .forall { + case Left(WebSocketFrame.Close(1000, "normal closure")) if decodeCloseRequests => true + case Left(WebSocketFrame.Close(1000, "" | "normal closure")) if !decodeCloseRequests => true + case _ => false + } ) } }, @@ -166,7 +172,7 @@ abstract class ServerWebSocketTests[F[_], S <: Streams[S], OPTIONS, ROUTE]( endpoint.out( webSocketBody[String, CodecFormat.TextPlain, String, CodecFormat.TextPlain](streams) .autoPing(None) - .autoPongOnPing(true) + .autoPongOnPing(autoPongAtEndpoint) ), "pong on ping" )((_: Unit) => pureResult(stringEcho.asRight[Unit])) { (backend, baseUri) => @@ -182,13 +188,15 @@ abstract class ServerWebSocketTests[F[_], S <: Streams[S], OPTIONS, ROUTE]( } yield List(m1, m2) }) .send(backend) - .map((r: Response[Either[String, List[WebSocketFrame]]]) => - assert( - r.body.value exists { - case WebSocketFrame.Pong(array) => array sameElements "test-ping-text".getBytes - case _ => false - }, - s"Missing Pong(test-ping-text) in ${r.body}" + .flatMap((r: Response[Either[String, List[WebSocketFrame]]]) => + IO( + assert( + r.body.value exists { + case WebSocketFrame.Pong(array) => array sameElements "test-ping-text".getBytes + case _ => false + }, + s"Missing Pong(test-ping-text) in ${r.body}" + ) ) ) }, @@ -202,7 +210,7 @@ abstract class ServerWebSocketTests[F[_], S <: Streams[S], OPTIONS, ROUTE]( if (expectCloseResponse) ws.eitherClose(ws.receiveText()).map(Some(_)) else IO.pure(None) }) .send(backend) - .map(r => assert(r.body.forall(_.left.map(_.statusCode) == Left(1000)))) + .flatMap(r => IO(assert(r.body.forall(_.left.map(_.statusCode) == Left(1000))))) }, testServer( endpoint diff --git a/server/vertx-server/cats/src/main/scala/sttp/tapir/server/vertx/cats/streams/fs2.scala b/server/vertx-server/cats/src/main/scala/sttp/tapir/server/vertx/cats/streams/fs2.scala index 20deee310e..2d70d6a9a9 100644 --- a/server/vertx-server/cats/src/main/scala/sttp/tapir/server/vertx/cats/streams/fs2.scala +++ b/server/vertx-server/cats/src/main/scala/sttp/tapir/server/vertx/cats/streams/fs2.scala @@ -47,17 +47,17 @@ object fs2 { _ <- GenSpawn[F].start( stream .evalMap({ chunk => - val buffer = fn(chunk) state.get.flatMap { case StreamState(None, handler, _, _) => - Sync[F].delay(handler.handle(buffer)) + Sync[F].delay(handler.handle(fn(chunk))) case StreamState(Some(promise), _, _, _) => for { _ <- promise.get // Handler in state may be updated since the moment when we wait // promise so let's get more recent version. updatedState <- state.get - } yield updatedState.handler.handle(buffer) + _ <- Sync[F].delay(updatedState.handler.handle(fn(chunk))) + } yield () } }) .onFinalizeCase({