Repository: quarkusio/quarkus
Issue: CORS is broken with Quarkus and http2

## Describe the bug
Duplicate access-control* headers with http 2. Browsers do not allow these and so connections fail.

### Expected behavior
Single access-control* headers for each type matching the Quarkus CORS configuration. Works as expected with http 1.1

### Actual behavior
Duplicate access-control* headers with http 2. There should only be a single header of each type returned.

With http1/1:

```
[user@localhost test]$ curl -v -H "Origin: http://google.com" http://localhost:8080/q/openapi
*   Trying ::1...
* TCP_NODELAY set
* connect to ::1 port 8080 failed: Connection refused
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /q/openapi HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.61.1
> Accept: */*
> Origin: http://google.com
> 
< HTTP/1.1 200 OK
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Credentials: true
< Access-Control-Allow-Methods: GET, HEAD, OPTIONS
< Access-Control-Max-Age: 86400
< Access-Control-Allow-Headers: Content-Type, Authorization
< Content-Type: application/yaml;charset=UTF-8
< content-length: 7494
```

With http2:

```bash
[user@localhost test]$ curl -v -H "Origin: http://google.com" --http2-prior-knowledge http://localhost:8080/q/openapi
*   Trying ::1...
* TCP_NODELAY set
* connect to ::1 port 8080 failed: Connection refused
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x56116f1a64c0)
> GET /q/openapi HTTP/2
> Host: localhost:8080
> User-Agent: curl/7.61.1
> Accept: */*
> Origin: http://google.com
> 
* Connection state changed (MAX_CONCURRENT_STREAMS == 100)!
< HTTP/2 200 
< access-control-allow-origin: http://google.com
< access-control-allow-credentials: true
< access-control-allow-origin: *
< access-control-allow-credentials: true
< access-control-allow-methods: GET, HEAD, OPTIONS
< access-control-max-age: 86400
< access-control-allow-headers: Content-Type, Authorization
< content-type: application/yaml;charset=UTF-8
< content-length: 7494
```
`access-control-allow-origin`, `access-control-allow-credentials` are both duplicated.

In curl I am forcing unencrypted http2 (h2c) since this is how my cloud load balancer connects to the Quarkus service. There should be no need for any extra headers to be added.

Although I'm showing an example here with openapi, the problem shows on all endpoints.

## Environment (please complete the following information):

```
# Enable cross-origin requests.
quarkus.http.cors=true
quarkus.http.cors.origins=*
quarkus.http.cors.methods=*
quarkus.http.cors.access-control-max-age=24H
quarkus.http.cors.access-control-allow-credentials=true
```

### Output of `uname -a` or `ver`

### Output of `java -version`
openjdk version "11.0.11" 2021-04-20 LTS
OpenJDK Runtime Environment 18.9 (build 11.0.11+9-LTS)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.11+9-LTS, mixed mode, sharing)

### GraalVM version (if different from Java)
N/A occurs in both Java build and native image.

### Quarkus version or git rev
2.0.1

### Build tool (ie. output of `mvnw --version` or `gradlew --version`)
Gradle 6.9


Changed files:
extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiHandler.java

diff --git a/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiHandler.java b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiHandler.java
index 17490611311..c83b162277b 100644
--- a/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiHandler.java
+++ b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiHandler.java
@@ -1,16 +1,16 @@
 package io.quarkus.smallrye.openapi.runtime;
 
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 import io.quarkus.arc.Arc;
 import io.smallrye.openapi.runtime.io.Format;
 import io.vertx.core.Handler;
+import io.vertx.core.MultiMap;
 import io.vertx.core.buffer.Buffer;
 import io.vertx.core.http.HttpMethod;
 import io.vertx.core.http.HttpServerRequest;
 import io.vertx.core.http.HttpServerResponse;
+import io.vertx.core.http.impl.headers.HeadersMultiMap;
 import io.vertx.ext.web.RoutingContext;
 
 /**
@@ -21,14 +21,14 @@ public class OpenApiHandler implements Handler<RoutingContext> {
     private volatile OpenApiDocumentService openApiDocumentService;
     private static final String ALLOWED_METHODS = "GET, HEAD, OPTIONS";
     private static final String QUERY_PARAM_FORMAT = "format";
-    private static final Map<String, String> RESPONSE_HEADERS = new HashMap<>();
+    private static final MultiMap RESPONSE_HEADERS = new HeadersMultiMap();
 
     static {
-        RESPONSE_HEADERS.put("Access-Control-Allow-Origin", "*");
-        RESPONSE_HEADERS.put("Access-Control-Allow-Credentials", "true");
-        RESPONSE_HEADERS.put("Access-Control-Allow-Methods", ALLOWED_METHODS);
-        RESPONSE_HEADERS.put("Access-Control-Allow-Headers", "Content-Type, Authorization");
-        RESPONSE_HEADERS.put("Access-Control-Max-Age", "86400");
+        RESPONSE_HEADERS.add("access-control-allow-origin", "*");
+        RESPONSE_HEADERS.add("access-control-allow-credentials", "true");
+        RESPONSE_HEADERS.add("access-control-allow-methods", ALLOWED_METHODS);
+        RESPONSE_HEADERS.add("access-control-allow-headers", "Content-Type, Authorization");
+        RESPONSE_HEADERS.add("access-control-max-age", "86400");
     }
 
     @Override