Skip to content

Commit

Permalink
chore: use akka-http native CORS feature (#1928)
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastian-alfers authored Apr 2, 2024
1 parent 950d251 commit 8c4ae60
Show file tree
Hide file tree
Showing 9 changed files with 23 additions and 57 deletions.
19 changes: 1 addition & 18 deletions docs/src/main/paradox/server/grpc-web.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,24 +33,7 @@ To serve a gRPC service with Akka gRPC, it is recommended to serve the
native gRPC protocol on a different port than gRPC-Web, as the two protocols
will likely require a different security story. You can use
@apidoc[WebHandler.grpcWebHandler](WebHandler$) to serve your
gRPC-Web endpoint with basic CORS infrastructure in place. To use CORS,
you will need to add the akka-http-cors dependency to your project.

The Akka dependencies are available from Akka's library repository. To access them there, you need to configure the URL for this repository.

@@repository [sbt,Maven,Gradle] {
id="akka-repository"
name="Akka library repository"
url="https://repo.akka.io/maven"
}

Additionally, add the dependency as below.

@@dependency[sbt,Maven,Gradle] {
group="ch.megard"
artifact="akka-http-cors_2.13"
version="0.4.2"
}
gRPC-Web endpoint with basic CORS infrastructure in place. To use CORS please have a look at our [akka-http docs](https://doc.akka.io/docs/akka-http/current/routing-dsl/directives/cors-directives/cors.html).

And then serve the handlers with @apidoc[WebHandler.grpcWebHandler](WebHandler$) like this:

Expand Down
1 change: 0 additions & 1 deletion plugin-tester-java/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ def scalaBinaryVersion = "${scalaVersion.major}.${scalaVersion.minor}"
def akkaVersion = "2.9.2"

dependencies {
implementation group: 'ch.megard', name: "akka-http-cors_${scalaBinaryVersion}", version: '1.1.3'
testImplementation "com.typesafe.akka:akka-stream-testkit_${scalaBinaryVersion}:${akkaVersion}"
implementation "com.typesafe.akka:akka-pki_${scalaBinaryVersion}:${akkaVersion}"
testImplementation "org.scalatest:scalatest_${scalaBinaryVersion}:3.2.12"
Expand Down
5 changes: 0 additions & 5 deletions plugin-tester-java/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,6 @@
<artifactId>akka-pki_2.13</artifactId>
<version>${akka.version}</version>
</dependency>
<dependency>
<groupId>ch.megard</groupId>
<artifactId>akka-http-cors_2.13</artifactId>
<version>${akka.http.cors.version}</version>
</dependency>

<!-- Needed for the generated client -->
<dependency>
Expand Down
1 change: 0 additions & 1 deletion plugin-tester-scala/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ def scalaBinaryVersion = "${scalaVersion.major}.${scalaVersion.minor}"
def akkaVersion = "2.9.2"

dependencies {
implementation group: 'ch.megard', name: "akka-http-cors_${scalaBinaryVersion}", version: '1.1.3'
implementation "com.typesafe.akka:akka-pki_${scalaBinaryVersion}:${akkaVersion}"
testImplementation "com.typesafe.akka:akka-stream-testkit_${scalaBinaryVersion}:${akkaVersion}"
testImplementation "com.typesafe.akka:akka-actor-testkit-typed_${scalaBinaryVersion}:${akkaVersion}"
Expand Down
6 changes: 0 additions & 6 deletions plugin-tester-scala/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,6 @@
<artifactId>akka-grpc-runtime_2.13</artifactId>
<version>${akka.grpc.project.version}</version>
</dependency>
<dependency>
<groupId>ch.megard</groupId>
<artifactId>akka-http-cors_2.13</artifactId>
<version>${akka.http.cors.version}</version>
</dependency>

<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-stream-testkit_2.13</artifactId>
Expand Down
5 changes: 0 additions & 5 deletions project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,6 @@ object Dependencies {
val akkaDiscovery = "com.typesafe.akka" %% "akka-discovery" % Versions.akka
val akkaSlf4j = "com.typesafe.akka" %% "akka-slf4j" % Versions.akka

val akkaHttpCors = ("ch.megard" %% "akka-http-cors" % "1.2.0") // Apache v2
.excludeAll(ExclusionRule(organization = "com.typesafe.akka")) // needed to not pull in 2.13 deps for Scala 3

val scalapbCompilerPlugin = "com.thesamet.scalapb" %% "compilerplugin" % scalapb.compiler.Version.scalapbVersion
val scalapbRuntime = ("com.thesamet.scalapb" %% "scalapb-runtime" % scalapb.compiler.Version.scalapbVersion)
.exclude("io.grpc", "grpc-netty")
Expand Down Expand Up @@ -117,7 +114,6 @@ object Dependencies {
Compile.akkaHttpCore,
Compile.akkaHttp,
Compile.akkaDiscovery,
Compile.akkaHttpCors % "provided",
Test.akkaTestkit,
Test.akkaStreamTestkit,
Test.scalaTest,
Expand Down Expand Up @@ -151,7 +147,6 @@ object Dependencies {
// usually automatically added by `suggestedDependencies`, which doesn't work with ReflectiveCodeGen
Compile.grpcStub,
Compile.akkaPki,
Compile.akkaHttpCors,
Runtime.logback,
Test.scalaTest,
Test.scalaTestPlusJunit,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Use akka-http native CORS

ProblemFilters.exclude[IncompatibleMethTypeProblem]("akka.grpc.javadsl.WebHandler.grpcWebHandler")
ProblemFilters.exclude[DirectMissingMethodProblem]("akka.grpc.scaladsl.WebHandler.grpcWebHandler$default$3")
ProblemFilters.exclude[DirectMissingMethodProblem]("akka.grpc.scaladsl.WebHandler.grpcWebHandler")
ProblemFilters.exclude[DirectMissingMethodProblem]("akka.grpc.scaladsl.WebHandler.defaultCorsSettings")
12 changes: 7 additions & 5 deletions runtime/src/main/scala/akka/grpc/javadsl/WebHandler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ import akka.japi.function.{ Function => JFunction }
import akka.stream.Materializer
import akka.stream.javadsl.{ Keep, Sink, Source }
import akka.util.ConstantFun
import ch.megard.akka.http.cors.javadsl.settings.CorsSettings
import ch.megard.akka.http.cors.javadsl.CorsDirectives
import akka.http.javadsl.settings.CorsSettings
import akka.http.javadsl.server.Directives.cors

@ApiMayChange
object WebHandler {
Expand All @@ -39,8 +39,10 @@ object WebHandler {
def grpcWebHandler(
handlers: util.List[JFunction[HttpRequest, CompletionStage[HttpResponse]]],
as: ClassicActorSystemProvider,
mat: Materializer): JFunction[HttpRequest, CompletionStage[HttpResponse]] =
grpcWebHandler(handlers, as, mat, scaladsl.WebHandler.defaultCorsSettings)
mat: Materializer): JFunction[HttpRequest, CompletionStage[HttpResponse]] = {
val defaultCorsSettings = scaladsl.WebHandler.defaultCorsSettings(as)
grpcWebHandler(handlers, as, mat, defaultCorsSettings)
}

// Adapt Marshaller.futureMarshaller(fromResponse) to javadsl
private implicit val csResponseMarshaller: ToResponseMarshaller[CompletionStage[HttpResponse]] = {
Expand Down Expand Up @@ -69,7 +71,7 @@ object WebHandler {

val servicesHandler = concatOrNotFound(handlers.asScala.toList: _*)
val servicesRoute = RouteAdapter(MarshallingDirectives.handleWith(servicesHandler.apply(_)))
val handler = asyncHandler(CorsDirectives.cors(corsSettings, () => servicesRoute), as, mat)
val handler = asyncHandler(cors(corsSettings, () => servicesRoute), as, mat)
(req: HttpRequest) =>
if (scaladsl.ServiceHandler.isGrpcWebRequest(req) || scaladsl.WebHandler.isCorsPreflightRequest(req)) handler(req)
else unsupportedMediaType
Expand Down
25 changes: 9 additions & 16 deletions runtime/src/main/scala/akka/grpc/scaladsl/WebHandler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,19 @@ import akka.http.scaladsl.model.{ HttpMethods, HttpRequest, HttpResponse }
import akka.http.scaladsl.model.headers._
import akka.http.scaladsl.server.Route
import akka.http.scaladsl.server.directives.MarshallingDirectives.handleWith
import ch.megard.akka.http.cors.scaladsl.CorsDirectives.cors
import ch.megard.akka.http.cors.scaladsl.model.HttpHeaderRange
import ch.megard.akka.http.cors.scaladsl.settings.CorsSettings
import akka.http.scaladsl.settings.CorsSettings
import akka.http.scaladsl.server.Directives.cors

@ApiMayChange
object WebHandler {

/** Default CORS settings to use for grpc-web */
val defaultCorsSettings: CorsSettings = CorsSettings.defaultSettings
def defaultCorsSettings(as: ClassicActorSystemProvider): CorsSettings = CorsSettings(as)
.withAllowCredentials(true)
.withAllowedMethods(immutable.Seq(HttpMethods.POST, HttpMethods.OPTIONS))
.withExposedHeaders(immutable.Seq(headers.`Status`.name, headers.`Status-Message`.name, `Content-Encoding`.name))
.withAllowedHeaders(
HttpHeaderRange(
"x-user-agent",
"x-grpc-web",
`Content-Type`.name,
Accept.name,
"grpc-timeout",
`Accept-Encoding`.name))
.withAllowedMethods(immutable.Set(HttpMethods.POST, HttpMethods.OPTIONS))
.withExposedHeaders(immutable.Set(headers.`Status`.name, headers.`Status-Message`.name, `Content-Encoding`.name))
.withAllowedHeaders(immutable
.Set("x-user-agent", "x-grpc-web", `Content-Type`.name, Accept.name, "grpc-timeout", `Accept-Encoding`.name))

private[grpc] def isCorsPreflightRequest(r: jmodel.HttpRequest): Boolean =
r.method == HttpMethods.OPTIONS && r.getHeader(classOf[Origin]).isPresent && r
Expand All @@ -48,8 +41,8 @@ object WebHandler {
* - Otherise if the request is not handled by one of the provided handlers, a _404: Not Found_ response is produced.
*/
def grpcWebHandler(handlers: PartialFunction[HttpRequest, Future[HttpResponse]]*)(
implicit as: ClassicActorSystemProvider,
corsSettings: CorsSettings = defaultCorsSettings): HttpRequest => Future[HttpResponse] = {
implicit as: ClassicActorSystemProvider): HttpRequest => Future[HttpResponse] = {
val corsSettings: CorsSettings = defaultCorsSettings(as)
implicit val system = as.classicSystem
val servicesHandler = ServiceHandler.concat(handlers: _*)
Route.toFunction(cors(corsSettings) {
Expand Down

0 comments on commit 8c4ae60

Please sign in to comment.