From 8be6984778b436ceff8c2f21254a22a8ed28d007 Mon Sep 17 00:00:00 2001 From: Sergiusz Kierat Date: Tue, 31 Dec 2024 14:06:43 +0100 Subject: [PATCH] Fixed handling of CORS within Vert.x *Technical changes related to this issue:* - WIP *Technical changes NOT related to this issue:* - added CORSInterceptor example for Vert.x server Closes #3651 --- .../security/corsInterceptorVertxServer.scala | 76 +++++++++++++++++++ .../vertx/VertxFutureServerInterpreter.scala | 3 + 2 files changed, 79 insertions(+) create mode 100644 examples/src/main/scala/sttp/tapir/examples/security/corsInterceptorVertxServer.scala diff --git a/examples/src/main/scala/sttp/tapir/examples/security/corsInterceptorVertxServer.scala b/examples/src/main/scala/sttp/tapir/examples/security/corsInterceptorVertxServer.scala new file mode 100644 index 0000000000..738ad4391f --- /dev/null +++ b/examples/src/main/scala/sttp/tapir/examples/security/corsInterceptorVertxServer.scala @@ -0,0 +1,76 @@ +// {cat=Security; effects=Future; server=Vert.x}: CORS interceptor + +//> using dep com.softwaremill.sttp.tapir::tapir-vertx-server:1.11.11 +//> using dep com.softwaremill.sttp.client3::core:3.10.2 + +package sttp.tapir.examples.security + +import io.vertx.core.Vertx +import io.vertx.ext.web.* +import io.vertx.ext.web.handler.CorsHandler +import sttp.client3.UriContext +import sttp.tapir.* +import sttp.tapir.server.interceptor.cors.CORSInterceptor +import sttp.tapir.server.vertx.VertxFutureServerInterpreter.* +import sttp.tapir.server.vertx.{VertxFutureServerInterpreter, VertxFutureServerOptions} +import sttp.client3.* +import sttp.model.{Header, HeaderNames, Method, StatusCode} + +import sttp.model.headers.Origin +import sttp.tapir.* +import sttp.tapir.server.interceptor.cors.{CORSConfig, CORSInterceptor} + + +import scala.concurrent.duration.* +import scala.concurrent.{Await, Future} + +import scala.concurrent.duration.* +import scala.concurrent.{Await, ExecutionContext, Future} + +@main def corsInterceptorVertxServer() = + given ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global + val vertx = Vertx.vertx() + + val server = vertx.createHttpServer() + val router = Router.router(vertx) + + // CORS works with native Vert.x handler +// router.route().handler(CorsHandler.create()) + + val myEndpoint = endpoint.get + .in("path") + .out(plainBody[String]) + .serverLogic(_ => Future(Right("OK"))) + + val corsInterceptor = VertxFutureServerOptions.customiseInterceptors + .corsInterceptor( + CORSInterceptor.default[Future] + ).options + + val attach = VertxFutureServerInterpreter(corsInterceptor).route(myEndpoint) + attach(router) + + // starting the server + val bindAndCheck = server.requestHandler(router).listen(9000).asScala.map { binding => + val backend = HttpClientSyncBackend() + + // Sending preflight request with allowed origin + val preflightResponse = basicRequest + .options(uri"http://localhost:9000/path") + .headers( + Header.origin(Origin.Host("http", "my.origin")), + Header.accessControlRequestMethod(Method.GET) + ) + .send(backend) + + println(s"Preflight response code: ${preflightResponse.code}") + println(s"Preflight response headers: ${preflightResponse.headers}") + + assert(preflightResponse.code == StatusCode.NoContent) + + println("Got expected response for preflight request") + + binding + } + + Await.result(bindAndCheck.flatMap(_.close().asScala), 1.minute) diff --git a/server/vertx-server/src/main/scala/sttp/tapir/server/vertx/VertxFutureServerInterpreter.scala b/server/vertx-server/src/main/scala/sttp/tapir/server/vertx/VertxFutureServerInterpreter.scala index 14fdc4794d..2d0578be0e 100644 --- a/server/vertx-server/src/main/scala/sttp/tapir/server/vertx/VertxFutureServerInterpreter.scala +++ b/server/vertx-server/src/main/scala/sttp/tapir/server/vertx/VertxFutureServerInterpreter.scala @@ -15,6 +15,8 @@ import sttp.tapir.server.vertx.routing.PathMapping.extractRouteDefinition import sttp.tapir.server.vertx.streams.{ReadStreamCompatible, VertxStreams} import scala.concurrent.{ExecutionContext, Future, Promise} +import io.vertx.ext.web.handler.CorsHandler + trait VertxFutureServerInterpreter extends CommonServerInterpreter with VertxErrorHandler { @@ -28,6 +30,7 @@ trait VertxFutureServerInterpreter extends CommonServerInterpreter with VertxErr def route[A, U, I, E, O](e: ServerEndpoint[VertxStreams with WebSockets, Future]): Router => Route = { router => mountWithDefaultHandlers(e)(router, extractRouteDefinition(e.endpoint), vertxFutureServerOptions) .handler(endpointHandler(e)) +// .handler(CorsHandler.create()) } /** Given a Router, creates and mounts a Route matching this endpoint, with default error handling The logic will be executed in a