All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.wavesplatform.api.http.CompositeHttpService.scala Maven / Gradle / Ivy

The newest version!
package com.wavesplatform.api.http

import akka.http.scaladsl.model.*
import akka.http.scaladsl.model.HttpMethods.*
import akka.http.scaladsl.model.headers.*
import akka.http.scaladsl.server.*
import akka.http.scaladsl.server.Directives.*
import akka.http.scaladsl.server.RouteResult.Complete
import akka.http.scaladsl.server.directives.{DebuggingDirectives, LoggingMagnet}
import com.wavesplatform.settings.RestAPISettings
import com.wavesplatform.utils.ScorexLogging
import kamon.Kamon

import scala.io.Source

case class CompositeHttpService(routes: Seq[ApiRoute], settings: RestAPISettings) extends ScorexLogging {

  private val redirectToSwagger = redirect("/api-docs/index.html", StatusCodes.PermanentRedirect)
  private val swaggerRoute: Route =
    (pathEndOrSingleSlash | path("swagger"))(redirectToSwagger) ~
      pathPrefix("api-docs") {
        pathEndOrSingleSlash(redirectToSwagger) ~
          path("openapi.yaml")(complete(patchedSwaggerJson)) ~
          getFromResourceDirectory("swagger-ui")
      }

  val compositeRoute: Route = extractRequest { _ =>
    Kamon
      .currentSpan()
      .mark("processing.start")

    extendRoute(routes.map(_.route).reduce(_ ~ _)) ~ swaggerRoute ~ complete(
      StatusCodes.NotFound
    )
  }

  val loggingCompositeRoute: Route = Route.seal(DebuggingDirectives.logRequestResult(LoggingMagnet(_ => logRequestResponse))(compositeRoute))

  private val CorsAllowAllOrigin = "origin-from-request"

  private def logRequestResponse(req: HttpRequest)(res: RouteResult): Unit = res match {
    case Complete(resp) =>
      val msg = s"HTTP ${resp.status.value} from ${req.method.value} ${req.uri}"
      if (resp.status == StatusCodes.OK) log.info(msg) else log.warn(msg)
    case _ =>
  }

  private def preflightCorsHeaders(requestOrigin: Option[Origin]): Seq[HttpHeader] =
    requestOrigin
      .flatMap(_.origins.headOption)
      .fold(Seq[HttpHeader]()) { _ =>
        Seq(
          `Access-Control-Allow-Headers`(settings.corsHeaders.accessControlAllowHeaders),
          `Access-Control-Allow-Methods`(settings.corsHeaders.accessControlAllowMethods.flatMap(getForKeyCaseInsensitive))
        )
      }

  private def corsHeaders(requestOrigin: Option[Origin]): Seq[HttpHeader] =
    requestOrigin
      .flatMap(_.origins.headOption)
      .fold(Seq[HttpHeader]()) { requestOriginValue =>
        val responseOrigin =
          settings.corsHeaders.accessControlAllowOrigin match {
            case "*"                => `Access-Control-Allow-Origin`.*
            case CorsAllowAllOrigin => `Access-Control-Allow-Origin`(requestOriginValue)
            case origin             => `Access-Control-Allow-Origin`(origin)
          }
        Seq(responseOrigin, `Access-Control-Allow-Credentials`(settings.corsHeaders.accessControlAllowCredentials))
      }

  private def extendRoute(base: Route): Route = handleAllExceptions {
    optionalHeaderValueByType(Origin) { maybeOrigin =>
      respondWithDefaultHeaders(corsHeaders(maybeOrigin)) {
        options {
          respondWithDefaultHeaders(preflightCorsHeaders(maybeOrigin)) {
            complete(StatusCodes.OK)
          }
        } ~ base
      }
    }
  }

  private[this] lazy val patchedSwaggerJson = {
    import com.wavesplatform.Version
    import com.wavesplatform.account.AddressScheme

    def chainIdString: String =
      if (Character.isAlphabetic(AddressScheme.current.chainId)) AddressScheme.current.chainId.toChar.toString
      else s"#${AddressScheme.current.chainId}"

    HttpEntity(
      MediaType.customWithFixedCharset("text", "x-yaml", HttpCharsets.`UTF-8`, List("yaml")),
      Source
        .fromResource("swagger-ui/openapi.yaml")
        .mkString
        .replace("{{version}}", Version.VersionString)
        .replace("{{chainId}}", chainIdString)
    )
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy