Repository: quarkusio/quarkus
Issue: Response head already sent Exception

### Describe the bug

When using lazy JWT authentication with http permissions observing below exceptions if JWT token in the request is expired.

```
[io.ver.ext.web.RoutingContext] (vert.x-eventloop-thread-22) Unhandled exception in router: java.lang.IllegalStateException: Response head already sent
or
[io.ver.ext.web.RoutingContext] (vert.x-eventloop-thread-0) Unhandled exception in router: java.lang.IllegalStateException: Response has already been written
```

**Quarkus Extensions used:** cdi, security, smallrye-context-propagation, smallrye-jwt, smallrye-openapi, swagger-ui, vertx, vertx-web

Config used:

```
quarkus.http.auth.proactive=false
quarkus.http.auth.permission.permit.paths=/q/*,/api/hello
quarkus.http.auth.permission.permit.policy=permit
quarkus.http.auth.permission.loggedin.paths=/api/*
quarkus.http.auth.permission.loggedin.policy=authenticated
```



### Expected behavior

No Runtime exceptions

### Actual behavior

Observing below exception

```
2021-08-24 17:00:00,950 ERROR [io.ver.ext.web.RoutingContext] (vert.x-eventloop-thread-22) Unhandled exception in router: java.lang.IllegalStateException: Response head already sent
	at io.vertx.core.http.impl.Http1xServerResponse.checkHeadWritten(Http1xServerResponse.java:675)
	at io.vertx.core.http.impl.Http1xServerResponse.setStatusCode(Http1xServerResponse.java:144)
	at io.quarkus.vertx.http.runtime.QuarkusErrorHandler.handle(QuarkusErrorHandler.java:77)
	at io.quarkus.vertx.http.runtime.QuarkusErrorHandler.handle(QuarkusErrorHandler.java:24)
	at io.vertx.ext.web.impl.RouteState.handleFailure(RouteState.java:1133)
	at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:148)
	at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:133)
	at io.vertx.ext.web.impl.RoutingContextImpl.doFail(RoutingContextImpl.java:591)
	at io.vertx.ext.web.impl.RoutingContextImpl.fail(RoutingContextImpl.java:184)
	at io.vertx.ext.web.impl.RoutingContextImpl.fail(RoutingContextImpl.java:173)
	at io.quarkus.vertx.http.runtime.security.HttpAuthorizer$3.accept(HttpAuthorizer.java:140)
	at io.quarkus.vertx.http.runtime.security.HttpAuthorizer$3.accept(HttpAuthorizer.java:137)
```

Observed below exception as well though not able to reproduce it always

```
2021-08-24 14:02:19,090 ERROR [io.ver.ext.web.RoutingContext] (vert.x-eventloop-thread-0) Unhandled exception in router: java.lang.IllegalStateException: Response has already been written
	at io.vertx.core.http.impl.Http1xServerResponse.checkValid(Http1xServerResponse.java:669)
	at io.vertx.core.http.impl.Http1xServerResponse.endHandler(Http1xServerResponse.java:310)
	at io.vertx.ext.web.impl.RoutingContextImpl.getEndHandlers(RoutingContextImpl.java:573)
	at io.vertx.ext.web.impl.RoutingContextImpl.addEndHandler(RoutingContextImpl.java:436)
	at io.quarkus.vertx.web.runtime.RouteHandler.handle(RouteHandler.java:90)
	at io.quarkus.vertx.web.runtime.RouteHandler.handle(RouteHandler.java:22)
	at io.vertx.ext.web.impl.RouteState.handleFailure(RouteState.java:1133)
	at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:148)
	at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:133)
	at io.vertx.ext.web.impl.RoutingContextImpl.doFail(RoutingContextImpl.java:591)
	at io.vertx.ext.web.impl.RoutingContextImpl.fail(RoutingContextImpl.java:184)
	at io.vertx.ext.web.impl.RoutingContextImpl.fail(RoutingContextImpl.java:173)
	at io.quarkus.vertx.http.runtime.security.HttpAuthorizer$3.accept(HttpAuthorizer.java:140)
	at io.quarkus.vertx.http.runtime.security.HttpAuthorizer$3.accept(HttpAuthorizer.java:137)
```

### How to Reproduce?

https://github.com/MM87037/routing-error

Start the service and run below command
```
curl "http://localhost:8080/api/user" -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJUZXN0Iiwic3ViIjoidW5tVW93TlBRNVEzdVh4WDNPRVkwTnRTRnNOWFZ6SlZnYVZyNm40MWNYbyIsInByZWZlcnJlZF91c2VybmFtZSI6IlRlc3RVc2VyQGNvbXBhbnkuY29tIiwiYXVkIjoiZjkzZWUxYTgtYmExYS00NDk0LWJmZTEtNjQzODIzMDIyNWVhIiwiaWF0IjoxNjI5NzMzMzE1LCJleHAiOjE2Mjk3MzY5MTUsIm5hbWUiOiJUZXN0IFVzZXIiLCJyb2xlcyI6WyJ3cml0ZSIsInJlYWQiLCJyZWxlYXNlIl0sImp0aSI6ImUzMGMxNWZjLTA0YzEtNDM0Yi1iZTZjLTU0N2E3Zjk4MjI0MiJ9.f52Q1JrH4JjeDbwn4NthuBYursCjeRrgd4uvCx0wGpilxaOVm7nvHlFkTKbiRofpEl8kE4YEangEGpiXPfn33Q5fZ_C2TjgQyrbNu9-IbSeDStLxIbhawW-NjyTI3EQY0zUrN9aOHvOWDitNTUVjBxHCIf1WTmyL15X6XT1qBczpTZKr-kkFs2L0Gxereu_gNxWgUPcyfC0vTOhjt3prZcpksv2W26gpKdTdJBmXm0xeOlkdxeIao_2kiJWTxJ22aUSLCESodNex-QV-40EjUz7B4YHSfEJ16mUjgLAOcpIwjjjZAvdHV92oa6alIgbbb35VjeqlPmKc29gQJNQP1Q'
```

or
Run maven build
```
mvn clean verify
```

### Output of `uname -a` or `ver`

Windows 10 Git Bash or Linux ids-slpdf-service-117-qd7hx 3.10.0-1160.36.2.el7.x86_64 #1 SMP Thu Jul 8 02:53:40 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

### Output of `java -version`

openjdk version "11.0.12" 2021-07-20

### GraalVM version (if different from Java)

_No response_

### Quarkus version or git rev

2.0 and above

### Build tool (ie. output of `mvnw --version` or `gradlew --version`)

maven 3.8.1

### Additional information

_No response_

Changed files:
extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/mutiny/NdjsonMultiRouteTest.java
extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/QuarkusErrorHandler.java
extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/mutiny/SSEMultiRouteTest.java

diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/QuarkusErrorHandler.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/QuarkusErrorHandler.java
index 04fae63b0f3..6484de2a3f3 100644
--- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/QuarkusErrorHandler.java
+++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/QuarkusErrorHandler.java
@@ -41,40 +41,49 @@ public QuarkusErrorHandler(boolean showStack) {
 
     @Override
     public void handle(RoutingContext event) {
-        if (event.failure() == null) {
-            event.response().setStatusCode(event.statusCode());
-            event.response().end();
-            return;
-        }
-        //this can happen if there is no auth mechanisms
-        if (event.failure() instanceof UnauthorizedException) {
-            HttpAuthenticator authenticator = event.get(HttpAuthenticator.class.getName());
-            if (authenticator != null) {
-                authenticator.sendChallenge(event).subscribe().with(new Consumer<Boolean>() {
-                    @Override
-                    public void accept(Boolean aBoolean) {
-                        event.response().end();
-                    }
-                }, new Consumer<Throwable>() {
-                    @Override
-                    public void accept(Throwable throwable) {
-                        event.fail(throwable);
-                    }
-                });
-            } else {
+        try {
+            if (event.failure() == null) {
+                event.response().setStatusCode(event.statusCode());
+                event.response().end();
+                return;
+            }
+            //this can happen if there is no auth mechanisms
+            if (event.failure() instanceof UnauthorizedException) {
+                HttpAuthenticator authenticator = event.get(HttpAuthenticator.class.getName());
+                if (authenticator != null) {
+                    authenticator.sendChallenge(event).subscribe().with(new Consumer<Boolean>() {
+                        @Override
+                        public void accept(Boolean aBoolean) {
+                            event.response().end();
+                        }
+                    }, new Consumer<Throwable>() {
+                        @Override
+                        public void accept(Throwable throwable) {
+                            event.fail(throwable);
+                        }
+                    });
+                } else {
+                    event.response().setStatusCode(HttpResponseStatus.UNAUTHORIZED.code()).end();
+                }
+                return;
+            }
+            if (event.failure() instanceof ForbiddenException) {
+                event.response().setStatusCode(HttpResponseStatus.FORBIDDEN.code()).end();
+                return;
+            }
+            if (event.failure() instanceof AuthenticationFailedException) {
+                //generally this should be handled elsewhere
+                //but if we get to this point bad things have happened
+                //so it is better to send a response than to hang
                 event.response().setStatusCode(HttpResponseStatus.UNAUTHORIZED.code()).end();
+                return;
+            }
+        } catch (IllegalStateException e) {
+            //ignore this if the response is already started
+            if (!event.response().ended()) {
+                //could be that just the head is committed
+                event.response().end();
             }
-            return;
-        }
-        if (event.failure() instanceof ForbiddenException) {
-            event.response().setStatusCode(HttpResponseStatus.FORBIDDEN.code()).end();
-            return;
-        }
-        if (event.failure() instanceof AuthenticationFailedException) {
-            //generally this should be handled elsewhere
-            //but if we get to this point bad things have happened
-            //so it is better to send a response than to hang
-            event.response().setStatusCode(HttpResponseStatus.UNAUTHORIZED.code()).end();
             return;
         }
 
@@ -100,6 +109,15 @@ public void accept(Throwable throwable) {
         } else {
             log.errorf(exception, "HTTP Request to %s failed, error id: %s", event.request().uri(), uuid);
         }
+        //we have logged the error
+        //now lets see if we can actually send a response
+        //if not we just return
+        if (event.response().ended()) {
+            return;
+        } else if (event.response().headWritten()) {
+            event.response().end();
+            return;
+        }
         String accept = event.request().getHeader("Accept");
         if (accept != null && accept.contains("application/json")) {
             event.response().headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json; charset=utf-8");
diff --git a/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/mutiny/NdjsonMultiRouteTest.java b/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/mutiny/NdjsonMultiRouteTest.java
index 97e31d96090..8c2d6528967 100644
--- a/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/mutiny/NdjsonMultiRouteTest.java
+++ b/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/mutiny/NdjsonMultiRouteTest.java
@@ -47,7 +47,7 @@ public void testNdjsonMultiRoute() {
         // We get the item followed by the exception
         when().get("/hello-and-fail").then().statusCode(200)
                 .body(containsString("\\"Hello\\""))
-                .body(containsString("boom"));
+                .body(not(containsString("boom")));
 
         when().get("/void").then().statusCode(204).body(hasLength(0));
 
diff --git a/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/mutiny/SSEMultiRouteTest.java b/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/mutiny/SSEMultiRouteTest.java
index 6c998dbf1a1..3dd1656631f 100644
--- a/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/mutiny/SSEMultiRouteTest.java
+++ b/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/mutiny/SSEMultiRouteTest.java
@@ -44,7 +44,7 @@ public void testSSEMultiRoute() {
         // We get the item followed by the exception
         when().get("/hello-and-fail").then().statusCode(200)
                 .body(containsString("id: 0"))
-                .body(containsString("boom"));
+                .body(not(containsString("boom")));
 
         when().get("/buffer").then().statusCode(200)
                 .body(is("data: Buffer\\nid: 0\\n\\n"))