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
-
source
import 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
-
source
import 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 None
OptionalLong.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.