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

sttp.tapir.server.pekkohttp.PekkoHttpServerInterpreter.scala Maven / Gradle / Ivy

The newest version!
package sttp.tapir.server.pekkohttp

import org.apache.pekko.http.scaladsl.model._
import org.apache.pekko.http.scaladsl.server.Directives.{
  complete,
  extractExecutionContext,
  extractMaterializer,
  extractRequestContext,
  handleWebSocketMessages,
  onSuccess,
  reject,
  respondWithHeaders
}
import org.apache.pekko.http.scaladsl.server.Route
import org.apache.pekko.stream.Materializer
import org.apache.pekko.stream.scaladsl.Source
import org.apache.pekko.util.ByteString
import sttp.capabilities.WebSockets
import sttp.capabilities.pekko.PekkoStreams
import sttp.model.Method
import sttp.monad.FutureMonad
import sttp.tapir.server.ServerEndpoint
import sttp.tapir.server.interceptor.RequestResult
import sttp.tapir.server.interceptor.reject.RejectInterceptor
import sttp.tapir.server.interpreter.{BodyListener, FilterServerEndpoints, RequestBody, ServerInterpreter, ToResponseBody}
import sttp.tapir.server.model.ServerResponse
import sttp.tapir.server.pekkohttp.PekkoModel.parseHeadersOrThrowWithoutContentHeaders

import scala.concurrent.{ExecutionContext, Future}

trait PekkoHttpServerInterpreter {

  implicit def executionContext: ExecutionContext

  def pekkoHttpServerOptions: PekkoHttpServerOptions = PekkoHttpServerOptions.default

  def toRoute(se: ServerEndpoint[PekkoStreams with WebSockets, Future]): Route = toRoute(List(se))

  def toRoute(ses: List[ServerEndpoint[PekkoStreams with WebSockets, Future]]): Route =
    toRoute(new PekkoRequestBody(pekkoHttpServerOptions)(_, _), new PekkoToResponseBody()(_, _))(ses)

  protected def toRoute(
      requestBody: (Materializer, ExecutionContext) => RequestBody[Future, PekkoStreams],
      toResponseBody: (Materializer, ExecutionContext) => ToResponseBody[PekkoResponseBody, PekkoStreams]
  )(ses: List[ServerEndpoint[PekkoStreams with WebSockets, Future]]): Route = {
    val filterServerEndpoints = FilterServerEndpoints(ses)
    val interceptors = RejectInterceptor.disableWhenSingleEndpoint(
      pekkoHttpServerOptions.appendInterceptor(PekkoStreamSizeExceptionInterceptor).interceptors,
      ses
    )

    extractExecutionContext { implicit ec =>
      extractMaterializer { implicit mat =>
        implicit val monad: FutureMonad = new FutureMonad()
        implicit val bodyListener: BodyListener[Future, PekkoResponseBody] = new PekkoBodyListener

        val interpreter = new ServerInterpreter(
          filterServerEndpoints,
          requestBody(mat, ec),
          toResponseBody(mat, ec),
          interceptors,
          pekkoHttpServerOptions.deleteFile
        )

        extractRequestContext { ctx =>
          val serverRequest = PekkoServerRequest(ctx)
          onSuccess(interpreter(serverRequest)) {
            case RequestResult.Failure(_)         => reject
            case RequestResult.Response(response) => serverResponseToPekko(response, serverRequest.method)
          }
        }
      }
    }
  }

  private def serverResponseToPekko(response: ServerResponse[PekkoResponseBody], requestMethod: Method): Route = {
    val statusCode = StatusCodes.getForKey(response.code.code).getOrElse(StatusCodes.custom(response.code.code, ""))
    val pekkoHeaders = parseHeadersOrThrowWithoutContentHeaders(response)

    response.body match {
      case Some(Left(flow)) =>
        respondWithHeaders(pekkoHeaders) {
          handleWebSocketMessages(flow)
        }
      case Some(Right(entity)) =>
        complete(HttpResponse(entity = entity, status = statusCode, headers = pekkoHeaders))
      case None =>
        if (requestMethod.is(Method.HEAD) && response.contentLength.isDefined) {
          val contentLength: Long = response.contentLength.getOrElse(0)
          val contentType: ContentType = response.contentType match {
            case Some(t) => ContentType.parse(t).getOrElse(ContentTypes.NoContentType)
            case None    => ContentTypes.NoContentType
          }
          complete(
            HttpResponse(
              status = statusCode,
              headers = pekkoHeaders,
              entity = HttpEntity.Default(contentType, contentLength, Source.empty)
            )
          )
        } else
          response.contentType match {
            case Some(t) =>
              val contentType = ContentType.parse(t).getOrElse(ContentTypes.NoContentType)
              complete(HttpResponse(statusCode, headers = pekkoHeaders, entity = HttpEntity.Strict(contentType, ByteString.empty)))
            case None => complete(HttpResponse(statusCode, headers = pekkoHeaders))
          }
    }
  }
}

object PekkoHttpServerInterpreter {
  def apply()(implicit _ec: ExecutionContext): PekkoHttpServerInterpreter = {
    new PekkoHttpServerInterpreter {
      override implicit def executionContext: ExecutionContext = _ec
    }
  }

  def apply(serverOptions: PekkoHttpServerOptions)(implicit _ec: ExecutionContext): PekkoHttpServerInterpreter = {
    new PekkoHttpServerInterpreter {
      override implicit def executionContext: ExecutionContext = _ec

      override def pekkoHttpServerOptions: PekkoHttpServerOptions = serverOptions
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy