/runtime/src/main/scala/akka/grpc/javadsl/WebHandler.scala

https://github.com/akka/akka-grpc · Scala · 89 lines · 59 code · 8 blank · 22 comment · 3 complexity · c9d239672574d019c5427e5cd4534483 MD5 · raw file

  1. /*
  2. * Copyright (C) 2020 Lightbend Inc. <https://www.lightbend.com>
  3. */
  4. package akka.grpc.javadsl
  5. import java.util
  6. import java.util.concurrent.CompletionStage
  7. import akka.NotUsed
  8. import akka.actor.ClassicActorSystemProvider
  9. import akka.annotation.ApiMayChange
  10. import akka.grpc.javadsl.ServiceHandler.{ concat, unsupportedMediaType }
  11. import akka.http.javadsl.marshalling.Marshaller
  12. import akka.http.javadsl.model.{ HttpRequest, HttpResponse }
  13. import akka.http.javadsl.server.Route
  14. import akka.http.javadsl.server.directives.RouteAdapter
  15. import akka.http.scaladsl.marshalling.{ ToResponseMarshaller, Marshaller => sMarshaller }
  16. import akka.grpc.scaladsl
  17. import akka.http.scaladsl.server.directives.MarshallingDirectives
  18. import akka.japi.{ Function => JFunction }
  19. import akka.stream.Materializer
  20. import akka.stream.javadsl.{ Keep, Sink, Source }
  21. import akka.util.ConstantFun
  22. import ch.megard.akka.http.cors.javadsl.settings.CorsSettings
  23. import ch.megard.akka.http.cors.javadsl.CorsDirectives
  24. @ApiMayChange
  25. object WebHandler {
  26. /**
  27. * Creates a `HttpRequest` to `HttpResponse` handler for gRPC services that can be used in
  28. * for example `Http().bindAndHandleAsync` for the generated partial function handlers:
  29. * - The generated handler supports the `application/grpc-web` and `application/grpc-web-text` media types.
  30. * - CORS is implemented for handled servives, including pre-flight requests and request enforcement.
  31. * - If the request s not a CORS pre-flight request, and has an invalid media type, then a _415: Unsupported Media Type_ response is produced.
  32. * - Otherise if the request is not handled by one of the provided handlers, a _404: Not Found_ response is produced.
  33. */
  34. def grpcWebHandler(
  35. handlers: util.List[JFunction[HttpRequest, CompletionStage[HttpResponse]]],
  36. as: ClassicActorSystemProvider,
  37. mat: Materializer): JFunction[HttpRequest, CompletionStage[HttpResponse]] =
  38. grpcWebHandler(handlers, as, mat, scaladsl.WebHandler.defaultCorsSettings)
  39. // Adapt Marshaller.futureMarshaller(fromResponse) to javadsl
  40. private implicit val csResponseMarshaller: ToResponseMarshaller[CompletionStage[HttpResponse]] = {
  41. import scala.compat.java8.FutureConverters._
  42. // HACK: Only known way to lift this to the scaladsl.model types required for MarshallingDirectives.handleWith
  43. Marshaller.asScalaToResponseMarshaller(
  44. Marshaller
  45. .fromScala(sMarshaller.futureMarshaller(sMarshaller.opaque(ConstantFun.scalaIdentityFunction[HttpResponse])))
  46. .compose[CompletionStage[HttpResponse]](_.toScala))
  47. }
  48. /**
  49. * Creates a `HttpRequest` to `HttpResponse` handler for gRPC services that can be used in
  50. * for example `Http().bindAndHandleAsync` for the generated partial function handlers:
  51. * - The generated handler supports the `application/grpc-web` and `application/grpc-web-text` media types.
  52. * - CORS is implemented for handled servives, including pre-flight requests and request enforcement.
  53. * - If the request s not a CORS pre-flight request, and has an invalid media type, then a _415: Unsupported Media Type_ response is produced.
  54. * - Otherise if the request is not handled by one of the provided handlers, a _404: Not Found_ response is produced.
  55. */
  56. def grpcWebHandler(
  57. handlers: util.List[JFunction[HttpRequest, CompletionStage[HttpResponse]]],
  58. as: ClassicActorSystemProvider,
  59. mat: Materializer,
  60. corsSettings: CorsSettings): JFunction[HttpRequest, CompletionStage[HttpResponse]] = {
  61. import scala.collection.JavaConverters._
  62. val servicesHandler = concat(handlers.asScala.toList: _*)
  63. val servicesRoute = RouteAdapter(MarshallingDirectives.handleWith(servicesHandler.apply(_)))
  64. val handler = asyncHandler(CorsDirectives.cors(corsSettings, () => servicesRoute), as, mat)
  65. (req: HttpRequest) =>
  66. if (scaladsl.ServiceHandler.isGrpcWebRequest(req) || scaladsl.WebHandler.isCorsPreflightRequest(req)) handler(req)
  67. else unsupportedMediaType
  68. }
  69. // Java version of Route.asyncHandler
  70. private def asyncHandler(
  71. route: Route,
  72. as: ClassicActorSystemProvider,
  73. mat: Materializer): HttpRequest => CompletionStage[HttpResponse] = {
  74. val sealedFlow =
  75. route
  76. .seal()
  77. .flow(as.classicSystem, mat)
  78. .toMat(Sink.head[HttpResponse], Keep.right[NotUsed, CompletionStage[HttpResponse]])
  79. (req: HttpRequest) => Source.single(req).runWith(sealedFlow, mat)
  80. }
  81. }