Skip to content

Commit

Permalink
Fixed handling of CORS within Vert.x
Browse files Browse the repository at this point in the history
*Technical changes related to this issue:*
- WIP

*Technical changes NOT related to this issue:*
- added CORSInterceptor example for Vert.x server

Closes #3651
  • Loading branch information
sergiuszkierat committed Jan 2, 2025
1 parent 1412cf2 commit 8be6984
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -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
Expand Down

0 comments on commit 8be6984

Please sign in to comment.