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..ccf68fdd46 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 @@ -16,25 +16,22 @@ 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 { +object HttpServer extends ResourceApp.Forever { type Port = Int - def main(args: Array[String]): Unit = { + def run(args: List[String]): Resource[IO, Unit] = { val port = args.headOption.map(_.toInt).getOrElse(51823) - new HttpServer(port).start() + new HttpServer(port).build.void } } -class HttpServer(port: Port) { +class HttpServer(port: HttpServer.Port) { private val logger = LoggerFactory.getLogger(getClass) - private var stopServer: IO[Unit] = _ - // private object numParam extends QueryParamDecoderMatcher[Int]("num") @@ -212,23 +209,11 @@ class HttpServer(port: Port) { // - def start(): Unit = { - val (_, _stopServer) = BlazeServerBuilder[IO] + def build: Resource[IO, server.Server] = BlazeServerBuilder[IO] .withExecutionContext(ExecutionContext.global) .bindHttp(port) .withHttpWebSocketApp(app) .resource - .map(_.address.getPort) - .allocated - .unsafeRunSync() - - stopServer = _stopServer - - logger.info(s"Server on port $port started") - } - - def close(): Unit = { - stopServer.unsafeRunSync() - logger.info(s"Server on port $port stopped") - } + .evalTap(_ => IO(logger.info(s"Server on port $port started"))) + .onFinalize(IO(logger.info(s"Server on port $port stopped"))) } diff --git a/doc/tutorials/07_cats_effect.md b/doc/tutorials/07_cats_effect.md index 1c774134c0..2024e2bc7d 100644 --- a/doc/tutorials/07_cats_effect.md +++ b/doc/tutorials/07_cats_effect.md @@ -230,8 +230,7 @@ object HelloWorldTapir extends IOApp: .bindHttp(8080, "localhost") .withHttpApp(Router("/" -> allRoutes).orNotFound) .resource - .use(_ => IO.never) - .as(ExitCode.Success) + .useForever ``` Hence, we first generate endpoint descriptions, which correspond to exposing the Swagger UI (containing the generated 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 31cfe89d25..8ea90f2f64 100644 --- a/examples/src/main/scala/sttp/tapir/examples/streaming/ProxyHttp4sFs2Server.scala +++ b/examples/src/main/scala/sttp/tapir/examples/streaming/ProxyHttp4sFs2Server.scala @@ -62,6 +62,4 @@ object ProxyHttp4sFs2Server extends IOApp: .bindHttp(8080, "localhost") .withHttpApp(Router("/" -> routes).orNotFound) .resource - } yield ()) - .use { _ => IO.never } - .as(ExitCode.Success) + } yield ()).useForever 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 706be684f4..4d8e6079de 100644 --- a/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingHttp4sFs2ServerOrError.scala +++ b/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingHttp4sFs2ServerOrError.scala @@ -56,5 +56,4 @@ object StreamingHttp4sFs2ServerOrError extends IOApp: .bindHttp(8080, "localhost") .withHttpApp(Router("/" -> userDataRoutes).orNotFound) .resource - .use { _ => IO.never } - .as(ExitCode.Success) + .useForever diff --git a/generated-doc/out/tutorials/07_cats_effect.md b/generated-doc/out/tutorials/07_cats_effect.md index a136c6dbe6..4ac6669866 100644 --- a/generated-doc/out/tutorials/07_cats_effect.md +++ b/generated-doc/out/tutorials/07_cats_effect.md @@ -230,8 +230,7 @@ object HelloWorldTapir extends IOApp: .bindHttp(8080, "localhost") .withHttpApp(Router("/" -> allRoutes).orNotFound) .resource - .use(_ => IO.never) - .as(ExitCode.Success) + .useForever ``` Hence, we first generate endpoint descriptions, which correspond to exposing the Swagger UI (containing the generated diff --git a/perf-tests/src/main/scala/sttp/tapir/perf/apis/ServerRunner.scala b/perf-tests/src/main/scala/sttp/tapir/perf/apis/ServerRunner.scala index 1a34cddf9e..45aae40248 100644 --- a/perf-tests/src/main/scala/sttp/tapir/perf/apis/ServerRunner.scala +++ b/perf-tests/src/main/scala/sttp/tapir/perf/apis/ServerRunner.scala @@ -1,11 +1,11 @@ package sttp.tapir.perf.apis -import cats.effect.{ExitCode, IO, IOApp} +import cats.effect.{IO, Resource, ResourceApp} import scala.reflect.runtime.universe trait ServerRunner { - def start: IO[ServerRunner.KillSwitch] + def runServer: Resource[IO, Unit] } /** Can be used as a Main object to run a single server using its short name. Running perfTests/runMain @@ -13,36 +13,30 @@ trait ServerRunner { * useful when you want to guarantee that the server runs in a different JVM than test runner, so that memory and CPU metrics are recorded * only in the scope of the server JVM. */ -object ServerRunner extends IOApp { - type KillSwitch = IO[Unit] - val NoopKillSwitch = IO.pure(IO.unit) +object ServerRunner extends ResourceApp.Forever { + private val runtimeMirror = universe.runtimeMirror(getClass.getClassLoader) + private val requireArg: Resource[IO, Unit] = Resource.raiseError( + new IllegalArgumentException(s"Unspecified server name. Use one of: ${TypeScanner.allServers}"): Throwable + ) + private def notInstantiated(name: ServerName)(e: Throwable): IO[ServerRunner] = IO.raiseError( + new IllegalArgumentException( + s"ERROR! Could not find object ${name.fullName} or it doesn't extend ServerRunner", e + ) + ) - def run(args: List[String]): IO[ExitCode] = { - val shortServerName = args.headOption.getOrElse { - throw new IllegalArgumentException(s"Unspecified server name. Use one of: ${TypeScanner.allServers}") - } - for { - killSwitch <- startServerByTypeName(ServerName.fromShort(shortServerName)) - _ <- IO.never.guarantee(killSwitch) - } yield ExitCode.Success - } + def run(args: List[String]): Resource[IO, Unit] = + args.headOption.map(ServerName.fromShort).map(startServerByTypeName).getOrElse(requireArg) - def startServerByTypeName(serverName: ServerName): IO[ServerRunner.KillSwitch] = { + def startServerByTypeName(serverName: ServerName): Resource[IO, Unit] = serverName match { - case ExternalServerName => NoopKillSwitch - case _ => - try { + case ExternalServerName => Resource.unit + case _ => Resource.eval( + IO({ val moduleSymbol = runtimeMirror.staticModule(serverName.fullName) val moduleMirror = runtimeMirror.reflectModule(moduleSymbol) - val instance: ServerRunner = moduleMirror.instance.asInstanceOf[ServerRunner] - instance.start - } catch { - case e: Throwable => - IO.raiseError( - new IllegalArgumentException(s"ERROR! Could not find object ${serverName.fullName} or it doesn't extend ServerRunner", e) - ) - } + moduleMirror.instance.asInstanceOf[ServerRunner] + }).handleErrorWith(notInstantiated(serverName)) + ).flatMap(_.runServer) } - } } diff --git a/perf-tests/src/main/scala/sttp/tapir/perf/apis/TypeScanner.scala b/perf-tests/src/main/scala/sttp/tapir/perf/apis/TypeScanner.scala index 9c6d86fe91..07ce8d724b 100644 --- a/perf-tests/src/main/scala/sttp/tapir/perf/apis/TypeScanner.scala +++ b/perf-tests/src/main/scala/sttp/tapir/perf/apis/TypeScanner.scala @@ -4,7 +4,6 @@ import io.github.classgraph.ClassGraph import scala.jdk.CollectionConverters._ import scala.reflect.ClassTag -import scala.util.{Failure, Success, Try} import sttp.tapir.perf.Common._ 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..7fb0fa795c 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 @@ -106,26 +106,21 @@ 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] = + def runServer(router: WebSocketBuilder2[IO] => HttpRoutes[IO]): Resource[IO, Unit] = 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.") - }) + .map(_ => ()) + .onFinalize(IO.println("Http4s server closed.")) } -object TapirServer extends ServerRunner { override def start = server.runServer(Tapir.router(1)) } -object TapirMultiServer extends ServerRunner { override def start = server.runServer(Tapir.router(128)) } +object TapirServer extends ServerRunner { override def runServer = server.runServer(Tapir.router(1)) } +object TapirMultiServer extends ServerRunner { override def runServer = server.runServer(Tapir.router(128)) } object TapirInterceptorMultiServer extends ServerRunner { - override def start = server.runServer(Tapir.router(128, withServerLog = true)) + override def runServer = server.runServer(Tapir.router(128, withServerLog = true)) } -object VanillaServer extends ServerRunner { override def start = server.runServer(Vanilla.router(1)) } -object VanillaMultiServer extends ServerRunner { override def start = server.runServer(Vanilla.router(128)) } +object VanillaServer extends ServerRunner { override def runServer = server.runServer(Vanilla.router(1)) } +object VanillaMultiServer extends ServerRunner { override def runServer = server.runServer(Vanilla.router(128)) } diff --git a/perf-tests/src/main/scala/sttp/tapir/perf/netty/cats/NettyCats.scala b/perf-tests/src/main/scala/sttp/tapir/perf/netty/cats/NettyCats.scala index 10639eb7a9..a88a013541 100644 --- a/perf-tests/src/main/scala/sttp/tapir/perf/netty/cats/NettyCats.scala +++ b/perf-tests/src/main/scala/sttp/tapir/perf/netty/cats/NettyCats.scala @@ -10,7 +10,6 @@ import sttp.tapir.perf.apis._ import sttp.tapir.server.ServerEndpoint import sttp.tapir.server.netty.cats.NettyCatsServer import sttp.tapir.server.netty.cats.NettyCatsServerOptions -import sttp.ws.WebSocketFrame import sttp.capabilities.fs2.Fs2Streams import scala.concurrent.duration._ @@ -33,27 +32,26 @@ object NettyCats { Tapir.wsResponseStream.evalMap(_ => IO.realTime.map(_.toMillis)).concurrently(in.as(())) } ) - def runServer(endpoints: List[ServerEndpoint[Any, IO]], withServerLog: Boolean = false): IO[ServerRunner.KillSwitch] = { + def runServer(endpoints: List[ServerEndpoint[Any, IO]], withServerLog: Boolean = false): Resource[IO, Unit] = { val declaredPort = Port val declaredHost = "0.0.0.0" - (for { + for { dispatcher <- Dispatcher.parallel[IO] serverOptions = buildOptions(NettyCatsServerOptions.customiseInterceptors(dispatcher), withServerLog) - server <- NettyCatsServer.io() - _ <- - Resource.make( - server - .port(declaredPort) - .host(declaredHost) - .addEndpoints(wsServerEndpoint :: endpoints) - .start() - )(binding => binding.stop()) - } yield ()).allocated.map(_._2) + server <- NettyCatsServer.io().map(_.options(serverOptions)) + _ <- Resource.make( + server + .port(declaredPort) + .host(declaredHost) + .addEndpoints(wsServerEndpoint :: endpoints) + .start() + )(_.stop()) + } yield () } } -object TapirServer extends ServerRunner { override def start = NettyCats.runServer(Tapir.genEndpointsIO(1)) } -object TapirMultiServer extends ServerRunner { override def start = NettyCats.runServer(Tapir.genEndpointsIO(128)) } +object TapirServer extends ServerRunner { override def runServer = NettyCats.runServer(Tapir.genEndpointsIO(1)) } +object TapirMultiServer extends ServerRunner { override def runServer = NettyCats.runServer(Tapir.genEndpointsIO(128)) } object TapirInterceptorMultiServer extends ServerRunner { - override def start = NettyCats.runServer(Tapir.genEndpointsIO(128), withServerLog = true) + override def runServer = NettyCats.runServer(Tapir.genEndpointsIO(128), withServerLog = true) } diff --git a/perf-tests/src/main/scala/sttp/tapir/perf/netty/future/NettyFuture.scala b/perf-tests/src/main/scala/sttp/tapir/perf/netty/future/NettyFuture.scala index 97c1fef7dc..0af9f02339 100644 --- a/perf-tests/src/main/scala/sttp/tapir/perf/netty/future/NettyFuture.scala +++ b/perf-tests/src/main/scala/sttp/tapir/perf/netty/future/NettyFuture.scala @@ -1,6 +1,6 @@ package sttp.tapir.perf.netty.future -import cats.effect.IO +import cats.effect.{IO, Resource} import sttp.tapir.perf.apis._ import sttp.tapir.perf.Common._ import sttp.tapir.server.netty.{NettyFutureServer, NettyFutureServerBinding, NettyFutureServerOptions} @@ -14,7 +14,7 @@ object Tapir extends Endpoints object NettyFuture { - def runServer(endpoints: List[ServerEndpoint[Any, Future]], withServerLog: Boolean = false): IO[ServerRunner.KillSwitch] = { + def runServer(endpoints: List[ServerEndpoint[Any, Future]], withServerLog: Boolean = false): Resource[IO, Unit] = { val declaredPort = Port val declaredHost = "0.0.0.0" val serverOptions = buildOptions(NettyFutureServerOptions.customiseInterceptors, withServerLog) @@ -29,13 +29,12 @@ object NettyFuture { .start() ) ) - - serverBinding.map(b => IO.fromFuture(IO(b.stop()))) + Resource.make(serverBinding)(b => IO.fromFuture(IO(b.stop()))).map(_ => ()) } } -object TapirServer extends ServerRunner { override def start = NettyFuture.runServer(Tapir.genEndpointsFuture(1)) } -object TapirMultiServer extends ServerRunner { override def start = NettyFuture.runServer(Tapir.genEndpointsFuture(128)) } +object TapirServer extends ServerRunner { override def runServer = NettyFuture.runServer(Tapir.genEndpointsFuture(1)) } +object TapirMultiServer extends ServerRunner { override def runServer = NettyFuture.runServer(Tapir.genEndpointsFuture(128)) } object TapirInterceptorMultiServer extends ServerRunner { - override def start = NettyFuture.runServer(Tapir.genEndpointsFuture(128), withServerLog = true) + override def runServer = NettyFuture.runServer(Tapir.genEndpointsFuture(128), withServerLog = true) } diff --git a/perf-tests/src/main/scala/sttp/tapir/perf/nima/Nima.scala b/perf-tests/src/main/scala/sttp/tapir/perf/nima/Nima.scala index f6b1dbeafa..a45c182f43 100644 --- a/perf-tests/src/main/scala/sttp/tapir/perf/nima/Nima.scala +++ b/perf-tests/src/main/scala/sttp/tapir/perf/nima/Nima.scala @@ -1,6 +1,6 @@ package sttp.tapir.perf.nima -import cats.effect.IO +import cats.effect.{IO, Resource} import io.helidon.webserver.WebServer import sttp.shared.Identity import sttp.tapir.perf.apis._ @@ -14,27 +14,29 @@ object Tapir extends Endpoints { object Nima { - def runServer(endpoints: List[ServerEndpoint[Any, Identity]], withServerLog: Boolean = false): IO[ServerRunner.KillSwitch] = { + def runServer(endpoints: List[ServerEndpoint[Any, Identity]], withServerLog: Boolean = false): Resource[IO, Unit] = { val declaredPort = Port val serverOptions = buildOptions(NimaServerOptions.customiseInterceptors, withServerLog) // Starting Nima server val handler = NimaServerInterpreter(serverOptions).toHandler(endpoints) - val server = WebServer - .builder() - .routing { builder => - builder.any(handler) - () - } - .port(declaredPort) - .build() - .start() - IO(IO { val _ = server.stop() }) + val startServer = IO { + WebServer + .builder() + .routing { builder => + builder.any(handler) + () + } + .port(declaredPort) + .build() + .start() + } + Resource.make(startServer)(server => IO(server.stop()).void).map(_ => ()) } } -object TapirServer extends ServerRunner { override def start = Nima.runServer(Tapir.genEndpointsNId(1)) } -object TapirMultiServer extends ServerRunner { override def start = Nima.runServer(Tapir.genEndpointsNId(128)) } +object TapirServer extends ServerRunner { override def runServer = Nima.runServer(Tapir.genEndpointsNId(1)) } +object TapirMultiServer extends ServerRunner { override def runServer = Nima.runServer(Tapir.genEndpointsNId(128)) } object TapirInterceptorMultiServer extends ServerRunner { - override def start = Nima.runServer(Tapir.genEndpointsNId(128), withServerLog = true) + override def runServer = Nima.runServer(Tapir.genEndpointsNId(128), withServerLog = true) } diff --git a/perf-tests/src/main/scala/sttp/tapir/perf/pekko/PekkoHttp.scala b/perf-tests/src/main/scala/sttp/tapir/perf/pekko/PekkoHttp.scala index 0abad119a8..15552d34b2 100644 --- a/perf-tests/src/main/scala/sttp/tapir/perf/pekko/PekkoHttp.scala +++ b/perf-tests/src/main/scala/sttp/tapir/perf/pekko/PekkoHttp.scala @@ -1,6 +1,6 @@ package sttp.tapir.perf.pekko -import cats.effect.IO +import cats.effect.{IO, Resource} import org.apache.pekko.actor.ActorSystem import org.apache.pekko.http.scaladsl.Http import org.apache.pekko.http.scaladsl.model.HttpEntity @@ -14,7 +14,7 @@ import sttp.tapir.perf.apis._ import sttp.tapir.server.pekkohttp.{PekkoHttpServerInterpreter, PekkoHttpServerOptions} import scala.concurrent.duration._ -import scala.concurrent.{ExecutionContext, ExecutionContextExecutor, Future} +import scala.concurrent.{ExecutionContext, Future} object PekkoCommon { // Define a source that emits the current timestamp every 100 milliseconds @@ -103,27 +103,25 @@ object Tapir extends Endpoints { } object PekkoHttp { - def runServer(router: ActorSystem => Route): IO[ServerRunner.KillSwitch] = { - // We need to create a new actor system each time server is run - implicit val actorSystem: ActorSystem = ActorSystem("tapir-pekko-http") - implicit val executionContext: ExecutionContextExecutor = actorSystem.dispatcher - IO.fromFuture( - IO( - Http() - .newServerAt("127.0.0.1", Port) - .bind(router(actorSystem)) - .map { binding => - IO.fromFuture(IO(binding.unbind().flatMap(_ => actorSystem.terminate()))).void - } - ) - ) - } + private val actorSystem = Resource.make(IO(ActorSystem("tapir-pekko-http")))( + aSystem => IO.fromFuture(IO(aSystem.terminate())).void + ) + + private def http(route: Route)(implicit aSystem: ActorSystem) = Resource.make( + IO.fromFuture(IO(Http().newServerAt("127.0.0.1", Port).bind(route))) + )( + binding => IO.fromFuture(IO(binding.unbind())).void + ) + + // We need to create a new actor system each time server is run + def runServer(router: ActorSystem => Route): Resource[IO, Unit] = + actorSystem.flatMap { implicit aSystem: ActorSystem => http(router(aSystem)) }.map(_ => ()) } -object TapirServer extends ServerRunner { override def start = PekkoHttp.runServer(Tapir.router(1)) } -object TapirMultiServer extends ServerRunner { override def start = PekkoHttp.runServer(Tapir.router(128)) } +object TapirServer extends ServerRunner { override def runServer = PekkoHttp.runServer(Tapir.router(1)) } +object TapirMultiServer extends ServerRunner { override def runServer = PekkoHttp.runServer(Tapir.router(128)) } object TapirInterceptorMultiServer extends ServerRunner { - override def start = PekkoHttp.runServer(Tapir.router(128, withServerLog = true)) + override def runServer = PekkoHttp.runServer(Tapir.router(128, withServerLog = true)) } -object VanillaServer extends ServerRunner { override def start = PekkoHttp.runServer(Vanilla.router(1)) } -object VanillaMultiServer extends ServerRunner { override def start = PekkoHttp.runServer(Vanilla.router(128)) } +object VanillaServer extends ServerRunner { override def runServer = PekkoHttp.runServer(Vanilla.router(1)) } +object VanillaMultiServer extends ServerRunner { override def runServer = PekkoHttp.runServer(Vanilla.router(128)) } diff --git a/perf-tests/src/main/scala/sttp/tapir/perf/play/Play.scala b/perf-tests/src/main/scala/sttp/tapir/perf/play/Play.scala index c3e576696c..24ebbda49c 100644 --- a/perf-tests/src/main/scala/sttp/tapir/perf/play/Play.scala +++ b/perf-tests/src/main/scala/sttp/tapir/perf/play/Play.scala @@ -1,6 +1,6 @@ package sttp.tapir.perf.play -import cats.effect.IO +import cats.effect.{IO, Resource} import org.apache.pekko.actor.ActorSystem import org.apache.pekko.util.ByteString import play.api.Mode @@ -87,28 +87,36 @@ object Tapir extends Endpoints { object Play { - def runServer(routes: ActorSystem => Routes): IO[ServerRunner.KillSwitch] = { - implicit lazy val perfActorSystem: ActorSystem = ActorSystem(s"tapir-play") - val playRouter = - Router.from( - List(routes(perfActorSystem)).reduce((a: Routes, b: Routes) => { - val handler: PartialFunction[RequestHeader, Handler] = { case request => - a.applyOrElse(request, b) - } - handler - }) - ) - val components = new DefaultPekkoHttpServerComponents { + private val actorSystem = Resource.make( + IO(ActorSystem("tapir-play")) + )( + aSystem => IO.fromFuture(IO(aSystem.terminate())).void + ) + + private def httpServer(routes: Routes, actSys: ActorSystem) = Resource.make(IO { + val server = new DefaultPekkoHttpServerComponents { override lazy val serverConfig: ServerConfig = ServerConfig(port = Some(Port), address = "127.0.0.1", mode = Mode.Test) - override lazy val actorSystem: ActorSystem = perfActorSystem - override def router: Router = playRouter + override lazy val actorSystem: ActorSystem = actSys + override def router: Router = Router.from(routes) } - IO(components.server).map(server => IO(server.stop())) - } + server.server + })(server => IO(server.stop())) + + def runServer(routes: ActorSystem => Routes): Resource[IO, Unit] = actorSystem.flatMap { + aSystem => httpServer( + List(routes(aSystem)).reduce((a: Routes, b: Routes) => { + val handler: PartialFunction[RequestHeader, Handler] = { case request => + a.applyOrElse(request, b) + } + handler + }), + aSystem + ) + }.map(_ => ()) } -object TapirServer extends ServerRunner { override def start = Play.runServer(Tapir.router(1)) } -object TapirMultiServer extends ServerRunner { override def start = Play.runServer(Tapir.router(128)) } -object TapirInterceptorMultiServer extends ServerRunner { override def start = Play.runServer(Tapir.router(128, withServerLog = true)) } -object VanillaServer extends ServerRunner { override def start = Play.runServer(Vanilla.router(1)) } -object VanillaMultiServer extends ServerRunner { override def start = Play.runServer(Vanilla.router(128)) } +object TapirServer extends ServerRunner { override def runServer = Play.runServer(Tapir.router(1)) } +object TapirMultiServer extends ServerRunner { override def runServer = Play.runServer(Tapir.router(128)) } +object TapirInterceptorMultiServer extends ServerRunner { override def runServer = Play.runServer(Tapir.router(128, withServerLog = true)) } +object VanillaServer extends ServerRunner { override def runServer = Play.runServer(Vanilla.router(1)) } +object VanillaMultiServer extends ServerRunner { override def runServer = Play.runServer(Vanilla.router(128)) } diff --git a/perf-tests/src/main/scala/sttp/tapir/perf/vertx/Vertx.scala b/perf-tests/src/main/scala/sttp/tapir/perf/vertx/Vertx.scala index d19955fe27..7f90c17376 100644 --- a/perf-tests/src/main/scala/sttp/tapir/perf/vertx/Vertx.scala +++ b/perf-tests/src/main/scala/sttp/tapir/perf/vertx/Vertx.scala @@ -152,7 +152,7 @@ object Vanilla extends Endpoints { } object VertxRunner { - def runServer(route: Vertx => Router => Route, wsRoute: Option[Vertx => Router => Route] = None): IO[ServerRunner.KillSwitch] = { + def runServer(route: Vertx => Router => Route, wsRoute: Option[Vertx => Router => Route] = None): Resource[IO, Unit] = Resource .make(IO.delay(Vertx.vertx()))(vertx => IO.delay(vertx.close()).void) .flatMap { vertx => @@ -162,10 +162,7 @@ object VertxRunner { wsRoute.foreach(r => r(vertx).apply(router)) route(vertx).apply(router): Unit Resource.make(listenIO)(s => vertxFutureToIo(s.close()).void) - } - .allocated - .map(_._2) - } + }.map(_ => ()) private def vertxFutureToIo[A](future: => VFuture[A]): IO[A] = IO.async[A] { cb => @@ -178,10 +175,10 @@ object VertxRunner { } } -object TapirServer extends ServerRunner { override def start = VertxRunner.runServer(Tapir.route(1)) } -object TapirMultiServer extends ServerRunner { override def start = VertxRunner.runServer(Tapir.route(128)) } +object TapirServer extends ServerRunner { override def runServer = VertxRunner.runServer(Tapir.route(1)) } +object TapirMultiServer extends ServerRunner { override def runServer = VertxRunner.runServer(Tapir.route(128)) } object TapirInterceptorMultiServer extends ServerRunner { - override def start = VertxRunner.runServer(Tapir.route(128, withServerLog = true)) + override def runServer = VertxRunner.runServer(Tapir.route(128, withServerLog = true)) } -object VanillaServer extends ServerRunner { override def start = VertxRunner.runServer(Vanilla.route(1), Some(Vanilla.webSocketHandler)) } -object VanillaMultiServer extends ServerRunner { override def start = VertxRunner.runServer(Vanilla.route(128)) } +object VanillaServer extends ServerRunner { override def runServer = VertxRunner.runServer(Vanilla.route(1), Some(Vanilla.webSocketHandler)) } +object VanillaMultiServer extends ServerRunner { override def runServer = VertxRunner.runServer(Vanilla.route(128)) } diff --git a/perf-tests/src/main/scala/sttp/tapir/perf/vertx/cats/VertxCats.scala b/perf-tests/src/main/scala/sttp/tapir/perf/vertx/cats/VertxCats.scala index 7ef97951d5..5a0fda4ce1 100644 --- a/perf-tests/src/main/scala/sttp/tapir/perf/vertx/cats/VertxCats.scala +++ b/perf-tests/src/main/scala/sttp/tapir/perf/vertx/cats/VertxCats.scala @@ -1,6 +1,6 @@ package sttp.tapir.perf.vertx.cats -import cats.effect.IO +import cats.effect.{IO, Resource} import cats.effect.std.Dispatcher import fs2.Stream import io.vertx.core.Vertx @@ -37,10 +37,9 @@ object Tapir extends Endpoints { class VertxCatsRunner(numRoutes: Int, withServerLog: Boolean = false) { - def start: IO[ServerRunner.KillSwitch] = - Dispatcher.parallel[IO].allocated.flatMap { case (dispatcher, releaseDispatcher) => - VertxRunner.runServer(Tapir.route(dispatcher, withServerLog)(numRoutes)).map(releaseVertx => releaseVertx >> releaseDispatcher) - } + def runServer: Resource[IO, Unit] = Dispatcher.parallel[IO].flatMap { dispatcher => + VertxRunner.runServer(Tapir.route(dispatcher, withServerLog)(numRoutes)) + } } object TapirServer extends VertxCatsRunner(numRoutes = 1) with ServerRunner 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..389613fbb5 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 @@ -15,7 +15,9 @@ 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._ import scala.concurrent.ExecutionContext import scala.concurrent.duration.FiniteDuration @@ -27,7 +29,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 @@ -49,7 +50,7 @@ class ZHttp4sTestServerInterpreter extends TestServerInterpreter[Task, ZioStream .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)))) + def apply[B](fa: Task[B]): IO[B] = fa.toEffect[IO] }) } } 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/tests/src/main/scala/sttp/tapir/server/tests/ServerCancellationTests.scala b/server/tests/src/main/scala/sttp/tapir/server/tests/ServerCancellationTests.scala index 3d3435a343..8b07aabd66 100644 --- a/server/tests/src/main/scala/sttp/tapir/server/tests/ServerCancellationTests.scala +++ b/server/tests/src/main/scala/sttp/tapir/server/tests/ServerCancellationTests.scala @@ -35,9 +35,7 @@ class ServerCancellationTests[F[_], OPTIONS, ROUTE](createServerTest: CreateServ val resp: IO[_] = basicRequest.get(uri"$baseUri").readTimeout(300.millis).send(backend) resp - .map { case result => - fail(s"Expected cancellation, but received a result: $result") - } + .map(result => fail(s"Expected cancellation, but received a result: $result")) .handleErrorWith { case _: SttpClientException.TimeoutException => // expected, this is how we trigged client-side cancellation IO( 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..f1f03f953d 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).evalTap(portValue.complete).surround(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] } diff --git a/server/zio-http-server/src/test/scala/sttp/tapir/server/ziohttp/ZioHttpTestServerInterpreter.scala b/server/zio-http-server/src/test/scala/sttp/tapir/server/ziohttp/ZioHttpTestServerInterpreter.scala index 38af1fb3d5..8280d62abd 100644 --- a/server/zio-http-server/src/test/scala/sttp/tapir/server/ziohttp/ZioHttpTestServerInterpreter.scala +++ b/server/zio-http-server/src/test/scala/sttp/tapir/server/ziohttp/ZioHttpTestServerInterpreter.scala @@ -28,7 +28,6 @@ class ZioHttpTestServerInterpreter( ZioHttpInterpreter(serverOptions).toHttp(es) } - // Needs to manually call killSwitch, because serverWithStop uses `allocated` override def server( routes: NonEmptyList[Routes[Any, Response]], gracefulShutdownTimeout: Option[FiniteDuration] = None