From a9b0c0e0860f528399eb0f323c78fe3334c2645b Mon Sep 17 00:00:00 2001
From: eikek <eike.kettner@posteo.de>
Date: Mon, 6 Nov 2023 23:43:31 +0100
Subject: [PATCH] Fix http server startup

Closes: #2358
---
 .../docspell/common/util/ResourceUse.scala    | 41 +++++++++++++++++++
 .../main/scala/docspell/joex/JoexServer.scala | 18 ++++----
 .../docspell/restserver/RestServer.scala      | 33 +++++++--------
 3 files changed, 65 insertions(+), 27 deletions(-)
 create mode 100644 modules/common/src/main/scala/docspell/common/util/ResourceUse.scala

diff --git a/modules/common/src/main/scala/docspell/common/util/ResourceUse.scala b/modules/common/src/main/scala/docspell/common/util/ResourceUse.scala
new file mode 100644
index 00000000..3389e344
--- /dev/null
+++ b/modules/common/src/main/scala/docspell/common/util/ResourceUse.scala
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2020 Eike K. & Contributors
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+package docspell.common.util
+
+import cats.effect._
+import cats.syntax.all._
+import fs2._
+import fs2.concurrent.{Signal, SignallingRef}
+
+object ResourceUse {
+  def apply[F[_]: Concurrent, A](
+      resource: Resource[F, A]
+  ): Implicits.UseSyntax[F, A] =
+    new Implicits.UseSyntax(resource)
+
+  object Implicits {
+    implicit final class UseSyntax[F[_]: Concurrent, A](resource: Resource[F, A]) {
+
+      /** Evaluates `resource` endlessly or until the signal turns `true`. */
+      def useUntil(
+          signal: Signal[F, Boolean],
+          returnValue: Ref[F, ExitCode]
+      ): F[ExitCode] = {
+        val server = Stream.resource(resource)
+        val blockUntilTrue = signal.discrete.takeWhile(_ == false).drain
+        val exit = fs2.Stream.eval(returnValue.get)
+        (server *> (blockUntilTrue ++ exit)).compile.lastOrError
+      }
+
+      def useForever(implicit ev: Async[F]): F[ExitCode] = for {
+        termSignal <- SignallingRef.of[F, Boolean](false)
+        exitValue <- Ref.of(ExitCode.Success)
+        rc <- useUntil(termSignal, exitValue)
+      } yield rc
+    }
+  }
+}
diff --git a/modules/joex/src/main/scala/docspell/joex/JoexServer.scala b/modules/joex/src/main/scala/docspell/joex/JoexServer.scala
index e527f811..64d4c9a3 100644
--- a/modules/joex/src/main/scala/docspell/joex/JoexServer.scala
+++ b/modules/joex/src/main/scala/docspell/joex/JoexServer.scala
@@ -14,6 +14,7 @@ import fs2.io.net.Network
 
 import docspell.backend.msg.Topics
 import docspell.common.Pools
+import docspell.common.util.ResourceUse.Implicits._
 import docspell.joex.routes._
 import docspell.pubsub.naive.NaivePubSub
 import docspell.store.Store
@@ -74,15 +75,14 @@ object JoexServer {
 
     Stream
       .resource(app)
-      .flatMap { app =>
-        Stream.resource {
-          EmberServerBuilder
-            .default[F]
-            .withHost(cfg.bind.address)
-            .withPort(cfg.bind.port)
-            .withHttpApp(app.httpApp)
-            .build
-        }
+      .evalMap { app =>
+        EmberServerBuilder
+          .default[F]
+          .withHost(cfg.bind.address)
+          .withPort(cfg.bind.port)
+          .withHttpApp(app.httpApp)
+          .build
+          .useUntil(app.termSig, app.exitRef)
       }
   }.drain
 }
diff --git a/modules/restserver/src/main/scala/docspell/restserver/RestServer.scala b/modules/restserver/src/main/scala/docspell/restserver/RestServer.scala
index 400d5d27..905e9919 100644
--- a/modules/restserver/src/main/scala/docspell/restserver/RestServer.scala
+++ b/modules/restserver/src/main/scala/docspell/restserver/RestServer.scala
@@ -58,24 +58,16 @@ object RestServer {
             Stream(
               restApp.subscriptions,
               restApp.eventConsume(maxConcurrent = 2),
-              Stream.resource {
-                if (cfg.serverOptions.enableHttp2)
-                  EmberServerBuilder
-                    .default[F]
-                    .withHost(cfg.bind.address)
-                    .withPort(cfg.bind.port)
-                    .withMaxConnections(cfg.serverOptions.maxConnections)
-                    .withHttpWebSocketApp(createHttpApp(setting, pubSub, restApp))
-                    .withHttp2
-                    .build
-                else
-                  EmberServerBuilder
-                    .default[F]
-                    .withHost(cfg.bind.address)
-                    .withPort(cfg.bind.port)
-                    .withMaxConnections(cfg.serverOptions.maxConnections)
-                    .withHttpWebSocketApp(createHttpApp(setting, pubSub, restApp))
-                    .build
+              Stream.eval {
+                EmberServerBuilder
+                  .default[F]
+                  .withHost(cfg.bind.address)
+                  .withPort(cfg.bind.port)
+                  .withMaxConnections(cfg.serverOptions.maxConnections)
+                  .withHttpWebSocketApp(createHttpApp(setting, pubSub, restApp))
+                  .toggleHttp2(cfg.serverOptions.enableHttp2)
+                  .build
+                  .useForever
               }
             )
           }
@@ -164,4 +156,9 @@ object RestServer {
       ).pure[F]
     }
   }
+
+  implicit final class EmberServerBuilderExt[F[_]](self: EmberServerBuilder[F]) {
+    def toggleHttp2(flag: Boolean) =
+      if (flag) self.withHttp2 else self.withoutHttp2
+  }
 }