Akka Http live reload routes

Hy!

I’m currently using the

Http()
      .bindAndHandle(routes, "0.0.0.0", 9000)
      .map { binding =>
        logger.info(s"Web server started on 0.0.0.0:9000")
        setupShutdownHook(binding)
        binding
      }

mode to start a webserver. My new feature requirement is to disable routes at runtime. Is there any API/process to give an already binded port a new route?

As I understand the Route is transformed to a Flow[HttpRequest, HttpResponse, Any]. So in theory I could wrap this Flow to a stage which can propagate a mat value, that can be called with a reload(newRoute) but this seems quite a hacky solution, and the original bindAndHandle not giving back the mat value of the inside flow.

(It is not really possible to just “if” the routes with directives, mainly because we want the documentation clean from these “switched out” routes, and also almost all of our routes are generated from tapir. Regenerate and rebind the whole routes “method” would be much easier.)

Hi @tg44,

routes are executed lazily, so can provide routes dynamically from anywhere inside of the routing tree. If routes are generated, I’d suggest to create a new top-level Route that wraps the dynamic part around the generated routes. Maybe like this:

val topLevelRoutes: Route = { ctx =>
  // dynamic condition here
  val realRoute = If (xyz) routes1 else routes2 // or whatever
  realRoute(ctx)
}

Would that work?

Johannes

I came up with this;

def startWebserver(routeGen: () => Future[Route])(
      implicit actorSystem: ActorSystem,
      materializer: Materializer,
      executionContext: ExecutionContext,
  ): Future[Http.ServerBinding] = {
    import JsonBasedRejectionHandler._
    val settings = ServerSettings(actorSystem)
    Http()
      .newServerAt("0.0.0.0", 9000)
      .connectionSource()
      .mapAsyncUnordered(settings.maxConnections) { in =>
        in.handleWith(
          Flow[HttpRequest]
            .mapAsync(1)(in => routeGen().flatMap(_(in)))
            .watchTermination()(Keep.right)
        )
      }
      .to(Sink.ignore)
      .run()
      .map { binding =>
        logger.info(s"Web server started on 0.0.0.0:9000")
        setupShutdownHook(binding)
        binding
      }
  }

Where routeGen returns a cached future value most of the time in my case, and when the super admins turn on features, or when these features expire I reset the cache. (And the documentation only generated to the enabled routes. I think your method would work if I would not use tapir…)