diff options
Diffstat (limited to 'src/main/scala/xyz/driver/core/app/DriverApp.scala')
-rw-r--r-- | src/main/scala/xyz/driver/core/app/DriverApp.scala | 294 |
1 files changed, 0 insertions, 294 deletions
diff --git a/src/main/scala/xyz/driver/core/app/DriverApp.scala b/src/main/scala/xyz/driver/core/app/DriverApp.scala deleted file mode 100644 index 6dd98e3..0000000 --- a/src/main/scala/xyz/driver/core/app/DriverApp.scala +++ /dev/null @@ -1,294 +0,0 @@ -package xyz.driver.core.app - -import akka.actor.ActorSystem -import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport -import akka.http.scaladsl.model._ -import akka.http.scaladsl.model.headers._ -import akka.http.scaladsl.server.Directives._ -import akka.http.scaladsl.server.RouteResult._ -import akka.http.scaladsl.server._ -import akka.http.scaladsl.{Http, HttpExt} -import akka.stream.ActorMaterializer -import com.typesafe.config.Config -import com.typesafe.scalalogging.Logger -import io.swagger.models.Scheme -import org.slf4j.{LoggerFactory, MDC} -import xyz.driver.core -import xyz.driver.core.rest._ -import xyz.driver.core.stats.SystemStats -import xyz.driver.core.time.Time -import xyz.driver.core.time.provider.{SystemTimeProvider, TimeProvider} -import xyz.driver.tracing.TracingDirectives._ -import xyz.driver.tracing._ - -import scala.concurrent.duration._ -import scala.concurrent.{Await, ExecutionContext} -import scala.util.Try -import scalaz.Scalaz.stringInstance -import scalaz.syntax.equal._ - -class DriverApp( - appName: String, - version: String, - gitHash: String, - modules: Seq[Module], - time: TimeProvider = new SystemTimeProvider(), - log: Logger = Logger(LoggerFactory.getLogger(classOf[DriverApp])), - config: Config = core.config.loadDefaultConfig, - interface: String = "::0", - baseUrl: String = "localhost:8080", - scheme: String = "http", - port: Int = 8080, - tracer: Tracer = NoTracer)(implicit actorSystem: ActorSystem, executionContext: ExecutionContext) { - self => - - implicit private lazy val materializer: ActorMaterializer = ActorMaterializer()(actorSystem) - private lazy val http: HttpExt = Http()(actorSystem) - val appEnvironment: String = config.getString("application.environment") - - def run(): Unit = { - activateServices(modules) - scheduleServicesDeactivation(modules) - bindHttp(modules) - Console.print(s"${this.getClass.getName} App is started\n") - } - - def stop(): Unit = { - http.shutdownAllConnectionPools().onComplete { _ => - Await.result(tracer.close(), 15.seconds) // flush out any remaining traces from the buffer - val terminated = Await.result(actorSystem.terminate(), 30.seconds) - val addressTerminated = if (terminated.addressTerminated) "is" else "is not" - Console.print(s"${this.getClass.getName} App $addressTerminated stopped ") - } - } - - protected lazy val allowedCorsDomainSuffixes: Set[HttpOrigin] = { - import scala.collection.JavaConverters._ - config - .getConfigList("application.cors.allowedOrigins") - .asScala - .map { c => - HttpOrigin(c.getString("scheme"), Host(c.getString("hostSuffix"))) - }(scala.collection.breakOut) - } - - protected lazy val defaultCorsAllowedMethods: Set[HttpMethod] = { - import scala.collection.JavaConverters._ - config.getStringList("application.cors.allowedMethods").asScala.toSet.flatMap(HttpMethods.getForKey) - } - - protected lazy val defaultCorsAllowedOrigin: Origin = { - Origin(allowedCorsDomainSuffixes.to[collection.immutable.Seq]) - } - - protected def corsAllowedOriginHeader(origin: Option[Origin]): HttpHeader = { - val allowedOrigin = - origin - .filter { requestOrigin => - allowedCorsDomainSuffixes.exists { allowedOriginSuffix => - requestOrigin.origins.exists(o => - o.scheme == allowedOriginSuffix.scheme && - o.host.host.address.endsWith(allowedOriginSuffix.host.host.address())) - } - } - .getOrElse(defaultCorsAllowedOrigin) - - `Access-Control-Allow-Origin`(HttpOriginRange(allowedOrigin.origins: _*)) - } - - protected def respondWithAllCorsHeaders: Directive0 = { - respondWithCorsAllowedHeaders tflatMap { _ => - respondWithCorsAllowedMethodHeaders(defaultCorsAllowedMethods) tflatMap { _ => - optionalHeaderValueByType[Origin](()) flatMap { origin => - respondWithHeader(corsAllowedOriginHeader(origin)) - } - } - } - } - - private def extractHeader(request: HttpRequest)(headerName: String): Option[String] = - request.headers.find(_.name().toLowerCase === headerName).map(_.value()) - - protected def defaultOptionsRoute: Route = options { - respondWithAllCorsHeaders { - complete("OK") - } - } - - def appRoute: Route = { - val serviceTypes = modules.flatMap(_.routeTypes) - val swaggerService = new Swagger(baseUrl, Scheme.forValue(scheme) :: Nil, version, serviceTypes, config, log) - val swaggerRoute = swaggerService.routes ~ swaggerService.swaggerUI - val versionRt = versionRoute(version, gitHash, time.currentTime()) - val basicRoutes = new DriverRoute { - override def log: Logger = self.log - override def route: Route = versionRt ~ healthRoute ~ swaggerRoute - } - val combinedRoute = - Route.seal(modules.map(_.route).foldLeft(basicRoutes.routeWithDefaults)(_ ~ _) ~ defaultOptionsRoute) - - (extractHost & extractClientIP & trace(tracer) & handleRejections(authenticationRejectionHandler)) { - case (origin, ip) => - ctx => - val trackingId = extractTrackingId(ctx.request) - MDC.put("trackingId", trackingId) - - val updatedStacktrace = - (extractStacktrace(ctx.request) ++ Array(appName)).mkString("->") - MDC.put("stack", updatedStacktrace) - - storeRequestContextToMdc(ctx.request, origin, ip) - - log.info(s"""Received request ${ctx.request.method.value} ${ctx.request.uri} (trace: $trackingId)""") - - val contextWithTrackingId = - ctx.withRequest( - ctx.request - .addHeader(RawHeader(ContextHeaders.TrackingIdHeader, trackingId)) - .addHeader(RawHeader(ContextHeaders.StacktraceHeader, updatedStacktrace))) - - val logResponses = mapRouteResult { - case c @ Complete(response) => - log.info( - s"Responded to ${ctx.request.method.value} ${ctx.request.uri} " + - s"with ${response.status.toString} (trace: $trackingId)") - c - case r @ Rejected(rejections) => - log.warn( - s"Request ${ctx.request.method.value} ${ctx.request.uri} " + - s"(trace: $trackingId) is rejected:\n${rejections.mkString(",\n")}") - r - } - - respondWithAllCorsHeaders(logResponses(combinedRoute))(contextWithTrackingId) - } - } - - protected def authenticationRejectionHandler: RejectionHandler = - RejectionHandler - .newBuilder() - .handle { - case AuthenticationFailedRejection(_, challenge) => - complete(HttpResponse(StatusCodes.Unauthorized, entity = challenge.realm)) - } - .result() - - protected def bindHttp(modules: Seq[Module]): Unit = { - val _ = http.bindAndHandle(route2HandlerFlow(appRoute), interface, port)(materializer) - } - - private def storeRequestContextToMdc(request: HttpRequest, origin: String, ip: RemoteAddress): Unit = { - - MDC.put("origin", origin) - MDC.put("ip", ip.toOption.map(_.getHostAddress).getOrElse("unknown")) - MDC.put("remoteHost", ip.toOption.map(_.getHostName).getOrElse("unknown")) - - MDC.put( - "xForwardedFor", - extractHeader(request)("x-forwarded-for") - .orElse(extractHeader(request)("x_forwarded_for")) - .getOrElse("unknown")) - MDC.put("remoteAddress", extractHeader(request)("remote-address").getOrElse("unknown")) - MDC.put("userAgent", extractHeader(request)("user-agent").getOrElse("unknown")) - } - - protected def versionRoute(version: String, gitHash: String, startupTime: Time): Route = { - import spray.json._ - import DefaultJsonProtocol._ - import SprayJsonSupport._ - - path("version") { - val currentTime = time.currentTime().millis - complete( - Map( - "version" -> version.toJson, - "gitHash" -> gitHash.toJson, - "modules" -> modules.map(_.name).toJson, - "dependencies" -> collectAppDependencies().toJson, - "startupTime" -> startupTime.millis.toString.toJson, - "serverTime" -> currentTime.toString.toJson, - "uptime" -> (currentTime - startupTime.millis).toString.toJson - ).toJson) - } - } - - protected def collectAppDependencies(): Map[String, String] = { - - def serviceWithLocation(serviceName: String): (String, String) = - serviceName -> Try(config.getString(s"services.$serviceName.baseUrl")).getOrElse("not-detected") - - modules.flatMap(module => module.serviceDiscovery.getUsedServices.map(serviceWithLocation).toSeq).toMap - } - - protected def healthRoute: Route = { - import spray.json._ - import DefaultJsonProtocol._ - import SprayJsonSupport._ - import spray.json._ - - val memoryUsage = SystemStats.memoryUsage - val gcStats = SystemStats.garbageCollectorStats - - path("health") { - complete( - Map( - "availableProcessors" -> SystemStats.availableProcessors.toJson, - "memoryUsage" -> Map( - "free" -> memoryUsage.free.toJson, - "total" -> memoryUsage.total.toJson, - "max" -> memoryUsage.max.toJson - ).toJson, - "gcStats" -> Map( - "garbageCollectionTime" -> gcStats.garbageCollectionTime.toJson, - "totalGarbageCollections" -> gcStats.totalGarbageCollections.toJson - ).toJson, - "fileSystemSpace" -> SystemStats.fileSystemSpace.map { f => - Map( - "path" -> f.path.toJson, - "freeSpace" -> f.freeSpace.toJson, - "totalSpace" -> f.totalSpace.toJson, - "usableSpace" -> f.usableSpace.toJson) - }.toJson, - "operatingSystem" -> SystemStats.operatingSystemStats.toJson - )) - } - } - - /** - * Initializes services - */ - protected def activateServices(services: Seq[Module]): Unit = { - services.foreach { service => - Console.print(s"Service ${service.name} starts ...") - try { - service.activate() - } catch { - case t: Throwable => - log.error(s"Service ${service.name} failed to activate", t) - Console.print(" Failed! (check log)") - } - Console.print(" Done\n") - } - } - - /** - * Schedules services to be deactivated on the app shutdown - */ - protected def scheduleServicesDeactivation(services: Seq[Module]): Unit = { - Runtime.getRuntime.addShutdownHook(new Thread() { - override def run(): Unit = { - services.foreach { service => - Console.print(s"Service ${service.name} shutting down ...\n") - try { - service.deactivate() - } catch { - case t: Throwable => - log.error(s"Service ${service.name} failed to deactivate", t) - Console.print(" Failed! (check log)") - } - Console.print(s"Service ${service.name} is shut down\n") - } - } - }) - } -} |