Cors

Apache Pekko HTTP’s cors module provides support for the W3C cors standard

Dependency

To use Apache Pekko HTTP Cors, add the module to your project:

sbt
val PekkoHttpVersion = "1.0.1"
libraryDependencies += "org.apache.pekko" %% "pekko-http-cors" % PekkoHttpVersion
Gradle
def versions = [
  ScalaBinary: "2.13"
]
dependencies {
  implementation platform("org.apache.pekko:pekko-http-bom_${versions.ScalaBinary}:1.0.1")

  implementation "org.apache.pekko:pekko-http-cors_${versions.ScalaBinary}"
}
Maven
<properties>
  <scala.binary.version>2.13</scala.binary.version>
</properties>
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.apache.pekko</groupId>
      <artifactId>pekko-http-bom_${scala.binary.version}</artifactId>
      <version>1.0.1</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>
<dependencies>
  <dependency>
    <groupId>org.apache.pekko</groupId>
    <artifactId>pekko-http-cors_${scala.binary.version}</artifactId>
  </dependency>
</dependencies>

Quick Start

The simplest way to enable CORS in your application is to use the cors directive. Settings are passed as a parameter to the directive, with your overrides loaded from the application.conf.

import org.apache.pekko.http.cors.scaladsl.CorsDirectives._

val route: Route = cors() {
  complete(...)
}
import static org.apache.pekko.http.cors.javadsl.CorsDirectives.*;

final Route route = cors(() -> {
    complete(...)
})

The settings can be updated programmatically too.

val settings = CorsSettings(...).withAllowGenericHttpRequests(false)
val strictRoute: Route = cors(settings) {
  complete(...)
}
final CorsSettings settings = CorsSettings.create(...).withAllowGenericHttpRequests(false);
final Route route = cors(settings, () -> {
    complete(...)
});

Rejection

The CORS directives can reject requests using the CorsRejection class. Requests can be either malformed or not allowed to access the resource.

A rejection handler is provided by the library to return meaningful HTTP responses. Read the Pekko documentation to learn more about rejections, or if you need to write your own handler.

Scala
sourceimport org.apache.pekko
import pekko.actor.typed.ActorSystem
import pekko.actor.typed.scaladsl.Behaviors
import pekko.http.scaladsl.Http
import pekko.http.scaladsl.model.StatusCodes
import pekko.http.scaladsl.server.Directives._
import pekko.http.scaladsl.server._

import scala.io.StdIn
import scala.util.{ Failure, Success }

object CorsServerExample {
  def main(args: Array[String]): Unit = {
    implicit val system: ActorSystem[Nothing] = ActorSystem(Behaviors.empty, "cors-server")
    import system.executionContext

    val futureBinding = Http().newServerAt("localhost", 8080).bind(route)

    futureBinding.onComplete {
      case Success(_) =>
        system.log.info("Server online at http://localhost:8080/\nPress RETURN to stop...")
      case Failure(exception) =>
        system.log.error("Failed to bind HTTP endpoint, terminating system", exception)
        system.terminate()
    }

    StdIn.readLine() // let it run until user presses return
    futureBinding
      .flatMap(_.unbind())
      .onComplete(_ => system.terminate())
  }

  def route: Route = {
    import pekko.http.cors.scaladsl.CorsDirectives._

    // Your CORS settings are loaded from `application.conf`

    // Your rejection handler
    val rejectionHandler = corsRejectionHandler.withFallback(RejectionHandler.default)

    // Your exception handler
    val exceptionHandler = ExceptionHandler { case e: NoSuchElementException =>
      complete(StatusCodes.NotFound -> e.getMessage)
    }

    // Combining the two handlers only for convenience
    val handleErrors = handleRejections(rejectionHandler) & handleExceptions(exceptionHandler)

    // Note how rejections and exceptions are handled *before* the CORS directive (in the inner route).
    // This is required to have the correct CORS headers in the response even when an error occurs.
    handleErrors {
      cors() {
        handleErrors {
          path("ping") {
            complete("pong")
          } ~
          path("pong") {
            failWith(new NoSuchElementException("pong not found, try with ping"))
          }
        }
      }
    }
  }
}
Java
sourceimport org.apache.pekko.actor.typed.ActorSystem;
import org.apache.pekko.actor.typed.javadsl.Behaviors;
import org.apache.pekko.http.javadsl.Http;
import org.apache.pekko.http.javadsl.ServerBinding;
import org.apache.pekko.http.javadsl.model.StatusCodes;
import org.apache.pekko.http.javadsl.server.ExceptionHandler;
import org.apache.pekko.http.javadsl.server.RejectionHandler;
import org.apache.pekko.http.javadsl.server.Route;

import java.util.NoSuchElementException;
import java.util.concurrent.CompletionStage;
import java.util.function.Function;
import java.util.function.Supplier;

import static org.apache.pekko.http.cors.javadsl.CorsDirectives.cors;
import static org.apache.pekko.http.cors.javadsl.CorsDirectives.corsRejectionHandler;
import static org.apache.pekko.http.javadsl.server.Directives.*;

public class CorsServerExample {

  public static void main(String[] args) throws Exception {
    final ActorSystem<Void> system = ActorSystem.create(Behaviors.empty(), "cors-server");

    final CorsServerExample app = new CorsServerExample();

    final CompletionStage<ServerBinding> futureBinding =
        Http.get(system).newServerAt("localhost", 8080).bind(app.createRoute());

    futureBinding.whenComplete(
        (binding, exception) -> {
          if (binding != null) {
            system.log().info("Server online at http://localhost:8080/\nPress RETURN to stop...");
          } else {
            system.log().error("Failed to bind HTTP endpoint, terminating system", exception);
            system.terminate();
          }
        });

    System.in.read(); // let it run until user presses return
    futureBinding.thenCompose(ServerBinding::unbind).thenAccept(unbound -> system.terminate());
  }

  private Route createRoute() {

    // Your CORS settings are loaded from `application.conf`

    // Your rejection handler
    final RejectionHandler rejectionHandler =
        corsRejectionHandler().withFallback(RejectionHandler.defaultHandler());

    // Your exception handler
    final ExceptionHandler exceptionHandler =
        ExceptionHandler.newBuilder()
            .match(
                NoSuchElementException.class,
                ex -> complete(StatusCodes.NOT_FOUND, ex.getMessage()))
            .build();

    // Combining the two handlers only for convenience
    final Function<Supplier<Route>, Route> handleErrors =
        inner ->
            allOf(
                s -> handleExceptions(exceptionHandler, s),
                s -> handleRejections(rejectionHandler, s),
                inner);

    // Note how rejections and exceptions are handled *before* the CORS directive (in the inner
    // route).
    // This is required to have the correct CORS headers in the response even when an error occurs.
    return handleErrors.apply(
        () ->
            cors(
                () ->
                    handleErrors.apply(
                        () ->
                            concat(
                                path("ping", () -> complete("pong")),
                                path(
                                    "pong",
                                    () ->
                                        failWith(
                                            new NoSuchElementException(
                                                "pong not found, try with ping")))))));
  }
}

allowGenericHttpRequests / getAllowGenericHttpRequests

If true, allow generic requests (that are outside the scope of the specification) to pass through the directive. Else, strict CORS filtering is applied and any invalid request will be rejected.

allowCredentials / getAllowCredentials

Indicates whether the resource supports user credentials. If true, the header Access-Control-Allow-Credentials is set in the response, indicating the actual request can include user credentials.

Examples of user credentials are: cookies, HTTP authentication or client-side certificates.

allowedOrigins / getAllowedOrigins

List of origins that the CORS filter must allow. Can also be set to * to allow access to the resource from any origin. Controls the content of the Access-Control-Allow-Origin response header: * if parameter is * and credentials are not allowed, a * is set in Access-Control-Allow-Origin. * otherwise, the origins given in the Origin request header are echoed.

Hostname starting with *. will match any sub-domain. The scheme and the port are always strictly matched.

The actual or preflight request is rejected if any of the origins from the request is not allowed.

allowedHeaders / getAllowedHeaders

List of request headers that can be used when making an actual request. Controls the content of the Access-Control-Allow-Headers header in a preflight response: * if parameter is *, the headers from Access-Control-Request-Headers are echoed. * otherwise the parameter list is returned as part of the header.

allowedMethods / getAllowedMethods

List of methods that can be used when making an actual request. The list is returned as part of the Access-Control-Allow-Methods preflight response header.

The preflight request will be rejected if the Access-Control-Request-Method header’s method is not part of the list.

exposedHeaders / getAllowedMethods

List of headers (other than simple response headers) that browsers are allowed to access. If not empty, this list is returned as part of the Access-Control-Expose-Headers header in the actual response.

maxAge / getMaxAge

When set, the amount of seconds the browser is allowed to cache the results of a preflight request. This value is returned as part of the Access-Control-Max-Age preflight response header. If NoneOptionalLong.empty(), the header is not added to the preflight response.

Benchmarks

Benchmarks for Apache Pekko cors are located within the http-bench-jmh project along with instructions on how to run them.

Please look at the original project Akka Http Cors for previous benchmarks with Akka Http.