From 96955c901f7a6ca3f51f3c319189e37dc26b3f4b Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Fri, 12 Jul 2019 14:43:58 +1000 Subject: [PATCH] Use Vert.x as the basis for all HTTP This moves to the new Vert.x based Undertow branch. All HTTP trafic is not routed through Vert.x, with Undertow providing a Servlet implementation on top of this. This allows for a common HTTP layer where all HTTP endpoints can be configured in a unified manner, including things like security, logging etc. --- azure-pipelines.yml | 2 +- bom/runtime/pom.xml | 68 ++-- .../configuration/ConfigInstantiator.java | 7 +- .../configuration/ssl/ServerSslConfig.java | 184 +-------- core/test-extension/deployment/pom.xml | 4 +- core/test-extension/runtime/pom.xml | 12 +- devtools/common/pom.xml | 4 +- .../java/io/quarkus/maven/it/JarRunnerIT.java | 17 +- .../io/quarkus/maven/it/MojoTestBase.java | 2 +- .../deployment/pom.xml | 2 +- extensions/elytron-security-oauth2/pom.xml | 2 +- .../elytron-security-oauth2/runtime/pom.xml | 2 +- .../runtime/auth/OAuth2AuthMechanism.java | 11 +- .../io/quarkus/security/test/CustomAuth.java | 19 +- extensions/keycloak/deployment/pom.xml | 4 +- extensions/keycloak/runtime/pom.xml | 4 +- extensions/kotlin/runtime/pom.xml | 2 +- extensions/kubernetes/runtime/pom.xml | 2 +- extensions/kubernetes/spi/pom.xml | 2 +- extensions/mailer/pom.xml | 2 +- extensions/reactive-pg-client/pom.xml | 2 +- .../jwt/runtime/auth/JWTAuthMechanism.java | 8 +- extensions/swagger-ui/runtime/pom.xml | 2 +- .../HotReplacementWebsocketEndpoint.java | 2 +- .../UndertowWebsocketProcessor.java | 6 +- .../deployment/WebsocketHotReloadSetup.java | 2 - .../undertow-websockets/runtime/pom.xml | 4 +- .../websockets/runtime/ExecutorSupplier.java | 14 + .../runtime/UndertowWebsocketRecorder.java | 9 +- .../websockets/runtime/WorkerSupplier.java | 15 - extensions/undertow/common/pom.xml | 40 -- .../graal/ALPNManagerSubstitution.java | 18 - .../runtime/graal/ChannelsSubstitution.java | 124 ------ .../graal/URLResourceSubstitution.java | 104 ----- .../runtime/graal/XnioSubstitution.java | 42 -- extensions/undertow/deployment/pom.xml | 10 +- .../deployment/UndertowBuildStep.java | 57 +-- .../devmode/UndertowHotReplacementSetup.java | 60 +-- extensions/undertow/pom.xml | 3 +- extensions/undertow/runtime/pom.xml | 16 +- .../runtime/DelegatingResourceManager.java | 16 - .../undertow/runtime/HttpBuildConfig.java | 18 - .../runtime/KnownPathResourceManager.java | 30 +- .../runtime/UndertowDeploymentRecorder.java | 192 +++------- .../runtime/filters/CORSRecorder.java | 41 -- extensions/vertx-web/deployment/pom.xml | 4 + .../web/deployment/DefaultRouteBuildItem.java | 21 + .../vertx/web/deployment/FilterBuildItem.java | 21 + .../web/deployment/VertxWebProcessor.java | 29 +- .../devmode/VertxHotReplacementSetup.java | 9 +- .../web/CORSDisabledHandlerTestCase.java} | 6 +- .../web/CORSFullConfigHandlerTestCase.java} | 6 +- .../vertx/web/CORSHandlerTestCase.java} | 8 +- .../SecureSocketWithKeyStoreTestCase.java | 8 +- .../java/io/quarkus/vertx/web/TestRoute.java | 14 + .../resources/application-keystore.properties | 0 .../resources/cors-config-full.properties | 0 .../src/test/resources/cors-config.properties | 0 .../src/test/resources/keystore.jks | Bin .../vertx/web/runtime/HttpConfiguration.java} | 50 +-- .../web/runtime/VertxHttpConfiguration.java | 33 -- .../vertx/web/runtime/VertxWebRecorder.java | 358 +++++++++++++++--- .../vertx/web/runtime/cors}/CORSConfig.java | 2 +- .../vertx/web/runtime/cors}/CORSFilter.java | 75 ++-- .../vertx/web/runtime/cors/CORSRecorder.java | 18 + .../quarkus/vertx/runtime/VertxRecorder.java | 12 +- .../vertx/runtime/VertxProducerTest.java | 6 +- .../bootstrap/maven-plugin/pom.xml | 2 +- .../elytron-security-oauth2/pom.xml | 2 +- integration-tests/keycloak/pom.xml | 17 +- integration-tests/mongodb-client/pom.xml | 2 +- .../it/reactive/pg/client/FruitResource.java | 2 +- .../it/vertx/VertxProducerResource.java | 3 +- test-framework/amazon-lambda/pom.xml | 8 +- .../lambda/test/LambdaResourceManager.java | 83 ++-- .../test/common/TestResourceManager.java | 6 +- 76 files changed, 796 insertions(+), 1196 deletions(-) create mode 100644 extensions/undertow-websockets/runtime/src/main/java/io/quarkus/undertow/websockets/runtime/ExecutorSupplier.java delete mode 100644 extensions/undertow-websockets/runtime/src/main/java/io/quarkus/undertow/websockets/runtime/WorkerSupplier.java delete mode 100644 extensions/undertow/common/pom.xml delete mode 100644 extensions/undertow/common/src/main/java/io/quarkus/undertow/common/runtime/graal/ALPNManagerSubstitution.java delete mode 100644 extensions/undertow/common/src/main/java/io/quarkus/undertow/common/runtime/graal/ChannelsSubstitution.java delete mode 100644 extensions/undertow/common/src/main/java/io/quarkus/undertow/common/runtime/graal/URLResourceSubstitution.java delete mode 100644 extensions/undertow/common/src/main/java/io/quarkus/undertow/common/runtime/graal/XnioSubstitution.java delete mode 100644 extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/HttpBuildConfig.java delete mode 100644 extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/filters/CORSRecorder.java create mode 100644 extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/DefaultRouteBuildItem.java create mode 100644 extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/FilterBuildItem.java rename extensions/{undertow/deployment/src/test/java/io/quarkus/undertow/test/CORSDisabledServletTestCase.java => vertx-web/deployment/src/test/java/io/quarkus/vertx/web/CORSDisabledHandlerTestCase.java} (90%) rename extensions/{undertow/deployment/src/test/java/io/quarkus/undertow/test/CORSFullConfigServletTestCase.java => vertx-web/deployment/src/test/java/io/quarkus/vertx/web/CORSFullConfigHandlerTestCase.java} (95%) rename extensions/{undertow/deployment/src/test/java/io/quarkus/undertow/test/CORSServletTestCase.java => vertx-web/deployment/src/test/java/io/quarkus/vertx/web/CORSHandlerTestCase.java} (92%) rename extensions/{undertow/deployment/src/test/java/io/quarkus/undertow/test => vertx-web/deployment/src/test/java/io/quarkus/vertx/web}/SecureSocketWithKeyStoreTestCase.java (79%) create mode 100644 extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/TestRoute.java rename extensions/{undertow => vertx-web}/deployment/src/test/resources/application-keystore.properties (100%) rename extensions/{undertow => vertx-web}/deployment/src/test/resources/cors-config-full.properties (100%) rename extensions/{undertow => vertx-web}/deployment/src/test/resources/cors-config.properties (100%) rename extensions/{undertow => vertx-web}/deployment/src/test/resources/keystore.jks (100%) rename extensions/{undertow/runtime/src/main/java/io/quarkus/undertow/runtime/HttpConfig.java => vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/HttpConfiguration.java} (85%) delete mode 100644 extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/VertxHttpConfiguration.java rename extensions/{undertow/runtime/src/main/java/io/quarkus/undertow/runtime/filters => vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/cors}/CORSConfig.java (97%) rename extensions/{undertow/runtime/src/main/java/io/quarkus/undertow/runtime/filters => vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/cors}/CORSFilter.java (60%) create mode 100644 extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/cors/CORSRecorder.java diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 637fbfad68f1a..fa2327079b292 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -43,7 +43,7 @@ jobs: displayName: 'Maven Build' inputs: goals: 'install' - options: '-B --settings azure-mvn-settings.xml -Dnative-image.docker-build -Dtest-postgresql -Dtest-elasticsearch -Dtest-dynamodb -Ddynamodb-local.port=8000 -Dnative-image.xmx=6g -Dnative -Dno-format' + options: '-B --settings azure-mvn-settings.xml -Dnative-image.docker-build -Dtest-postgresql -Dtest-elasticsearch -Dtest-dynamodb -Ddynamodb-local.port=8000 -Dnative-image.xmx=6g -Dnative -Dno-format' - job: Windows_Build timeoutInMinutes: 60 diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index e666acbef3ab0..02f3c8452c89b 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -23,8 +23,7 @@ 0.1.5 0.2.0 0.34.0 - 2.0.23.Final - 3.7.2.Final + 3.0.0.Alpha2 1.0.0.Final 1.3 1.0-RC1 @@ -152,6 +151,7 @@ 3.1.0 6.0.1 + 1.0.0.Alpha1 3.1.7 0.1.0 0.2.0 @@ -160,6 +160,7 @@ 2.2.0 2.23.2 5.1.8.RELEASE + 2.4.4.Final @@ -843,14 +844,31 @@ ${tika.version} - io.undertow - undertow-core - ${undertow.version} + io.quarkus.http + quarkus-http-core + ${quarkus-http.version} + - io.undertow - undertow-servlet - ${undertow.version} + io.quarkus.http + quarkus-http-vertx-backend + ${quarkus-http.version} + + + io.netty + netty-all + + + + + io.quarkus.http + quarkus-http-core + ${quarkus-http.version} + + + io.quarkus.http + quarkus-http-servlet + ${quarkus-http.version} org.jboss.spec.javax.annotation @@ -859,9 +877,9 @@ - io.undertow - undertow-websockets-jsr - ${undertow.version} + io.quarkus.http + quarkus-http-websockets-jsr + ${quarkus-http.version} org.jboss.spec.javax.annotation @@ -1473,23 +1491,6 @@ arc ${project.version} - - org.jboss.xnio - xnio-api - ${xnio.version} - - - org.wildfly.client - wildfly-client-config - - - - - org.jboss.xnio - xnio-nio - ${xnio.version} - - org.wildfly.common wildfly-common @@ -2042,9 +2043,9 @@ ${keycloak.version} - org.keycloak - keycloak-undertow-adapter - ${keycloak.version} + io.quarkus.keycloak + quarkus-keycloak-adapter + ${quarkus-keycloak.version} org.keycloak @@ -2100,6 +2101,11 @@ jbpm-flow ${kogito.version} + + org.mvel + mvel2 + ${mvel2.version} + org.kie.kogito jbpm-bpmn2 diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigInstantiator.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigInstantiator.java index a4d07b0b1f2fc..9de8249c3be62 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigInstantiator.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigInstantiator.java @@ -11,6 +11,7 @@ import org.eclipse.microprofile.config.ConfigProvider; +import io.quarkus.runtime.annotations.ConfigGroup; import io.quarkus.runtime.annotations.ConfigItem; import io.smallrye.config.SmallRyeConfig; @@ -35,12 +36,12 @@ private static void handleObject(String prefix, Object o, SmallRyeConfig config) try { Class cls = o.getClass(); - if (!cls.getName().endsWith("Config")) { + if (!cls.getName().endsWith("Config") && !cls.getName().endsWith("Configuration")) { return; } - for (Field field : cls.getFields()) { + for (Field field : cls.getDeclaredFields()) { ConfigItem configItem = field.getDeclaredAnnotation(ConfigItem.class); - if (configItem == null) { + if (configItem == null || field.getType().isAnnotationPresent(ConfigGroup.class)) { Object newInstance = field.getType().newInstance(); field.set(o, newInstance); handleObject(prefix + "." + dashify(field.getName()), newInstance, config); diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ssl/ServerSslConfig.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ssl/ServerSslConfig.java index c91592bd41ff8..add35db86c59e 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ssl/ServerSslConfig.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ssl/ServerSslConfig.java @@ -1,36 +1,6 @@ package io.quarkus.runtime.configuration.ssl; -import static java.lang.Math.min; - -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.GeneralSecurityException; -import java.security.KeyStore; -import java.security.PrivateKey; -import java.security.cert.X509Certificate; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Iterator; import java.util.List; -import java.util.Optional; -import java.util.OptionalInt; - -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.X509ExtendedKeyManager; - -import org.jboss.logging.Logger; -import org.wildfly.common.iteration.CodePointIterator; -import org.wildfly.security.pem.Pem; -import org.wildfly.security.pem.PemEntry; -import org.wildfly.security.ssl.CipherSuiteSelector; -import org.wildfly.security.ssl.Protocol; -import org.wildfly.security.ssl.ProtocolSelector; -import org.wildfly.security.ssl.SSLContextBuilder; import io.quarkus.runtime.annotations.ConfigGroup; import io.quarkus.runtime.annotations.ConfigItem; @@ -50,163 +20,13 @@ public class ServerSslConfig { * The cipher suites to use. If none is given, a reasonable default is selected. */ @ConfigItem - public Optional cipherSuites; + public List cipherSuites; /** * The list of protocols to explicitly enable. */ @DefaultConverter @ConfigItem(defaultValue = "TLSv1.3,TLSv1.2") - public List protocols; - - /** - * The SSL provider name to use. If none is given, the platform default is used. - */ - @ConfigItem - public Optional providerName; - - /** - * The SSL session cache size. If not given, the platform default is used. - */ - @ConfigItem - public OptionalInt sessionCacheSize; - - /** - * The SSL session cache timeout. If not given, the platform default is used. - */ - @ConfigItem - public Optional sessionTimeout; - - /** - * Get an {@code SSLContext} for this server configuration. - * - * @return the {@code SSLContext}, or {@code null} if SSL should not be configured - * @throws GeneralSecurityException if something failed in the context setup - */ - public SSLContext toSSLContext() throws GeneralSecurityException, IOException { - //TODO: static fields break config - Logger log = Logger.getLogger("io.quarkus.configuration.ssl"); - final Optional certFile = certificate.file; - final Optional keyFile = certificate.keyFile; - final Optional keyStoreFile = certificate.keyStoreFile; - final String keystorePassword = certificate.keyStorePassword; - final KeyStore keyStore; - if (certFile.isPresent() && keyFile.isPresent()) { - keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); - keyStore.load(null, keystorePassword.toCharArray()); - final Path certPath = certFile.get(); - final Iterator> certItr = Pem.parsePemContent(load(certPath)); - final ArrayList certList = new ArrayList<>(); - while (certItr.hasNext()) { - final PemEntry item = certItr.next(); - final X509Certificate cert = item.tryCast(X509Certificate.class); - if (cert != null) { - certList.add(cert); - } else { - log.warnf("Ignoring non-certificate in certificate file \"%s\" (the type was %s)", certPath, - item.getEntry().getClass()); - } - } - if (certList.isEmpty()) { - log.warnf("No certificate found in file \"%s\"", certPath); - } - final Path keyPath = keyFile.get(); - final Iterator> keyItr = Pem.parsePemContent(load(keyPath)); - final PrivateKey privateKey; - for (;;) { - if (!keyItr.hasNext()) { - log.warnf("No key found in file \"%s\"", keyPath); - return null; - } - final PemEntry next = keyItr.next(); - final PrivateKey entryKey = next.tryCast(PrivateKey.class); - if (entryKey != null) { - privateKey = entryKey; - break; - } - log.warnf("Ignoring non-key in key file \"%s\" (the type was %s)", keyPath, next.getEntry().getClass()); - } - if (keyItr.hasNext()) { - log.warnf("Ignoring extra content in key file \"%s\"", keyPath); - } - //Entry password needs to match the keystore password - keyStore.setEntry("default", new KeyStore.PrivateKeyEntry(privateKey, certList.toArray(new X509Certificate[0])), - new KeyStore.PasswordProtection(keystorePassword.toCharArray())); - } else if (keyStoreFile.isPresent()) { - final Path keyStorePath = keyStoreFile.get(); - final Optional keyStoreFileType = certificate.keyStoreFileType; - final String type; - if (keyStoreFileType.isPresent()) { - type = keyStoreFileType.get(); - } else { - final String pathName = keyStorePath.toString(); - if (pathName.endsWith(".jks")) { - type = "jks"; - } else if (pathName.endsWith(".jceks")) { - type = "jceks"; - } else if (pathName.endsWith(".p12") || pathName.endsWith(".pkcs12") || pathName.endsWith(".pfx")) { - type = "pkcs12"; - } else { - // not sure, just guess - type = "jks"; - } - } - keyStore = KeyStore.getInstance(type); - - final InputStream keystoreAsResource = this.getClass().getClassLoader() - .getResourceAsStream(keyStorePath.toString()); - - if (keystoreAsResource != null) { - try (InputStream is = keystoreAsResource) { - keyStore.load(is, null); - } - } else { - try (InputStream is = Files.newInputStream(keyStorePath)) { - keyStore.load(is, null); - } - } - - } else { - return null; - } - final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - keyManagerFactory.init(keyStore, keystorePassword.toCharArray()); - final SSLContextBuilder sslContextBuilder = new SSLContextBuilder(); - sslContextBuilder.setCipherSuiteSelector(cipherSuites.orElse(CipherSuiteSelector.openSslDefault())); - ProtocolSelector protocolSelector; - if (protocols.isEmpty()) { - protocolSelector = ProtocolSelector.defaultProtocols(); - } else { - protocolSelector = ProtocolSelector.empty().add(protocols.toArray(new Protocol[0])); - } - sslContextBuilder.setProtocolSelector(protocolSelector); - sslContextBuilder.setKeyManager((X509ExtendedKeyManager) keyManagerFactory.getKeyManagers()[0]); - if (sessionCacheSize.isPresent()) { - sslContextBuilder.setSessionCacheSize(sessionCacheSize.getAsInt()); - } - if (sessionTimeout.isPresent()) { - sslContextBuilder.setSessionTimeout((int) min(Integer.MAX_VALUE, sessionTimeout.get().getSeconds())); - } - if (providerName.isPresent()) { - sslContextBuilder.setProviderName(providerName.get()); - } - return sslContextBuilder.build().create(); - } + public List protocols; - static CodePointIterator load(final Path path) throws IOException { - final int size = Math.toIntExact(Files.size(path)); - char[] chars = new char[size]; - int c = 0; - try (InputStream is = Files.newInputStream(path)) { - try (InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8)) { - while (c < size) { - final int res = isr.read(chars, c, size - c); - if (res == -1) - break; - c += res; - } - } - } - return CodePointIterator.ofChars(chars, 0, c); - } } diff --git a/core/test-extension/deployment/pom.xml b/core/test-extension/deployment/pom.xml index 3d56e24c01066..286e948f21a74 100644 --- a/core/test-extension/deployment/pom.xml +++ b/core/test-extension/deployment/pom.xml @@ -14,8 +14,8 @@ - io.undertow - undertow-servlet + io.quarkus.http + quarkus-http-servlet io.quarkus diff --git a/core/test-extension/runtime/pom.xml b/core/test-extension/runtime/pom.xml index 2c78d80f4e89d..efa99821c9cfd 100644 --- a/core/test-extension/runtime/pom.xml +++ b/core/test-extension/runtime/pom.xml @@ -35,16 +35,8 @@ svm - io.undertow - undertow-servlet - - - org.jboss.xnio - xnio-nio - - - org.jboss.xnio - xnio-api + io.quarkus.http + quarkus-http-servlet io.quarkus diff --git a/devtools/common/pom.xml b/devtools/common/pom.xml index aab70ef494149..abdcc3f57a2b8 100644 --- a/devtools/common/pom.xml +++ b/devtools/common/pom.xml @@ -68,8 +68,8 @@ maven-core - io.undertow - undertow-websockets-jsr + io.quarkus.http + quarkus-http-websockets-jsr org.junit.jupiter diff --git a/devtools/maven/src/test/java/io/quarkus/maven/it/JarRunnerIT.java b/devtools/maven/src/test/java/io/quarkus/maven/it/JarRunnerIT.java index 79c94d32f6ee7..9d1a3e38ec8c5 100644 --- a/devtools/maven/src/test/java/io/quarkus/maven/it/JarRunnerIT.java +++ b/devtools/maven/src/test/java/io/quarkus/maven/it/JarRunnerIT.java @@ -45,15 +45,18 @@ public void testThatJarRunnerConsoleOutputWorksCorrectly() throws MavenInvocatio processBuilder.redirectOutput(output); processBuilder.redirectError(output); Process process = processBuilder.start(); - // Wait until server up - await() - .pollDelay(1, TimeUnit.SECONDS) - .atMost(1, TimeUnit.MINUTES).until(() -> getHttpResponse("/app/hello/package", 200)); + try { + // Wait until server up + await() + .pollDelay(1, TimeUnit.SECONDS) + .atMost(1, TimeUnit.MINUTES).until(() -> getHttpResponse("/app/hello/package", 200)); - String logs = FileUtils.readFileToString(output, "UTF-8"); + String logs = FileUtils.readFileToString(output, "UTF-8"); - assertThatOutputWorksCorrectly(logs); + assertThatOutputWorksCorrectly(logs); + } finally { + process.destroy(); + } - process.destroy(); } } diff --git a/devtools/maven/src/test/java/io/quarkus/maven/it/MojoTestBase.java b/devtools/maven/src/test/java/io/quarkus/maven/it/MojoTestBase.java index 891777f19c7fa..f5586665a7c7d 100644 --- a/devtools/maven/src/test/java/io/quarkus/maven/it/MojoTestBase.java +++ b/devtools/maven/src/test/java/io/quarkus/maven/it/MojoTestBase.java @@ -276,7 +276,7 @@ public static void assertThatOutputWorksCorrectly(String logs) { assertThat(logs.contains(infoLogLevel)).isTrue(); Predicate datePattern = Pattern.compile("\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2},\\d{3}\\s").asPredicate(); assertThat(datePattern.test(logs)).isTrue(); - assertThat(logs.contains("features: [cdi, resteasy, undertow-websockets]")).isTrue(); + assertThat(logs.contains("features: [cdi, resteasy, undertow-websockets, vertx, vertx-web]")).isTrue(); assertThat(logs.contains("JBoss Threads version")).isFalse(); } } diff --git a/extensions/elytron-security-oauth2/deployment/pom.xml b/extensions/elytron-security-oauth2/deployment/pom.xml index 3bf834826b24d..37cb8e61390de 100644 --- a/extensions/elytron-security-oauth2/deployment/pom.xml +++ b/extensions/elytron-security-oauth2/deployment/pom.xml @@ -55,4 +55,4 @@ - \ No newline at end of file + diff --git a/extensions/elytron-security-oauth2/pom.xml b/extensions/elytron-security-oauth2/pom.xml index 8db2a0bfd1fa7..bef01c1c3b282 100644 --- a/extensions/elytron-security-oauth2/pom.xml +++ b/extensions/elytron-security-oauth2/pom.xml @@ -20,4 +20,4 @@ - \ No newline at end of file + diff --git a/extensions/elytron-security-oauth2/runtime/pom.xml b/extensions/elytron-security-oauth2/runtime/pom.xml index 1aad856df2788..9c5e2dc86341d 100644 --- a/extensions/elytron-security-oauth2/runtime/pom.xml +++ b/extensions/elytron-security-oauth2/runtime/pom.xml @@ -44,4 +44,4 @@ - \ No newline at end of file + diff --git a/extensions/elytron-security-oauth2/runtime/src/main/java/io/quarkus/elytron/security/oauth2/runtime/auth/OAuth2AuthMechanism.java b/extensions/elytron-security-oauth2/runtime/src/main/java/io/quarkus/elytron/security/oauth2/runtime/auth/OAuth2AuthMechanism.java index 0a2561c69ab89..dc301c3f39fa4 100644 --- a/extensions/elytron-security-oauth2/runtime/src/main/java/io/quarkus/elytron/security/oauth2/runtime/auth/OAuth2AuthMechanism.java +++ b/extensions/elytron-security-oauth2/runtime/src/main/java/io/quarkus/elytron/security/oauth2/runtime/auth/OAuth2AuthMechanism.java @@ -1,9 +1,8 @@ package io.quarkus.elytron.security.oauth2.runtime.auth; -import static io.undertow.util.Headers.WWW_AUTHENTICATE; -import static io.undertow.util.StatusCodes.UNAUTHORIZED; - import io.undertow.UndertowLogger; +import io.undertow.httpcore.HttpHeaderNames; +import io.undertow.httpcore.StatusCodes; import io.undertow.security.api.AuthenticationMechanism; import io.undertow.security.api.SecurityContext; import io.undertow.security.idm.Account; @@ -32,7 +31,7 @@ public OAuth2AuthMechanism(IdentityManager identityManager) { */ @Override public AuthenticationMechanismOutcome authenticate(HttpServerExchange exchange, SecurityContext securityContext) { - String authHeader = exchange.getRequestHeaders().getFirst("Authorization"); + String authHeader = exchange.getRequestHeader("Authorization"); String bearerToken = authHeader != null ? authHeader.substring(7) : null; if (bearerToken != null) { try { @@ -63,8 +62,8 @@ public AuthenticationMechanismOutcome authenticate(HttpServerExchange exchange, @Override public ChallengeResult sendChallenge(HttpServerExchange exchange, SecurityContext securityContext) { - exchange.getResponseHeaders().add(WWW_AUTHENTICATE, "Bearer {token}"); + exchange.addResponseHeader(HttpHeaderNames.WWW_AUTHENTICATE, "Bearer {token}"); UndertowLogger.SECURITY_LOGGER.debugf("Sending Bearer {token} challenge for %s", exchange); - return new ChallengeResult(true, UNAUTHORIZED); + return new ChallengeResult(true, StatusCodes.UNAUTHORIZED); } } diff --git a/extensions/elytron-security/deployment/src/test/java/io/quarkus/security/test/CustomAuth.java b/extensions/elytron-security/deployment/src/test/java/io/quarkus/security/test/CustomAuth.java index f8377993322a9..4cf2c93ba232c 100644 --- a/extensions/elytron-security/deployment/src/test/java/io/quarkus/security/test/CustomAuth.java +++ b/extensions/elytron-security/deployment/src/test/java/io/quarkus/security/test/CustomAuth.java @@ -1,19 +1,19 @@ package io.quarkus.security.test; import static io.undertow.UndertowMessages.MESSAGES; -import static io.undertow.util.Headers.AUTHORIZATION; -import static io.undertow.util.Headers.BASIC; -import static io.undertow.util.Headers.WWW_AUTHENTICATE; -import static io.undertow.util.StatusCodes.UNAUTHORIZED; +import static io.undertow.httpcore.HttpHeaderNames.AUTHORIZATION; +import static io.undertow.httpcore.HttpHeaderNames.BASIC; +import static io.undertow.httpcore.HttpHeaderNames.WWW_AUTHENTICATE; +import static io.undertow.httpcore.StatusCodes.UNAUTHORIZED; import java.io.IOException; -import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Locale; import org.jboss.logging.Logger; +import io.netty.buffer.ByteBuf; import io.undertow.UndertowLogger; import io.undertow.security.api.AuthenticationMechanism; import io.undertow.security.api.SecurityContext; @@ -37,7 +37,7 @@ public class CustomAuth implements AuthenticationMechanism { @Override public AuthenticationMechanismOutcome authenticate(HttpServerExchange exchange, SecurityContext securityContext) { - List authHeaders = exchange.getRequestHeaders().get(AUTHORIZATION); + List authHeaders = exchange.getRequestHeaders(AUTHORIZATION); log.info("CustomAuth, authHeaders: " + authHeaders); if (authHeaders != null) { for (String current : authHeaders) { @@ -46,10 +46,9 @@ public AuthenticationMechanismOutcome authenticate(HttpServerExchange exchange, String base64Challenge = current.substring(PREFIX_LENGTH); String plainChallenge = null; try { - ByteBuffer decode = FlexBase64.decode(base64Challenge); + ByteBuf decode = FlexBase64.decode(base64Challenge); - plainChallenge = new String(decode.array(), decode.arrayOffset(), decode.limit(), - StandardCharsets.UTF_8); + plainChallenge = decode.toString(StandardCharsets.UTF_8); UndertowLogger.SECURITY_LOGGER.infof("Found basic auth header %s (decoded using charset %s) in %s", plainChallenge, StandardCharsets.UTF_8, exchange); } catch (IOException e) { @@ -94,7 +93,7 @@ public AuthenticationMechanismOutcome authenticate(HttpServerExchange exchange, @Override public ChallengeResult sendChallenge(HttpServerExchange exchange, SecurityContext securityContext) { - exchange.getResponseHeaders().add(WWW_AUTHENTICATE, "BASIC realm=CUSTOM"); + exchange.addResponseHeader(WWW_AUTHENTICATE, "BASIC realm=CUSTOM"); UndertowLogger.SECURITY_LOGGER.infof("Sending basic auth challenge for %s", exchange); return new ChallengeResult(true, UNAUTHORIZED); } diff --git a/extensions/keycloak/deployment/pom.xml b/extensions/keycloak/deployment/pom.xml index 331273d4374c9..2f1f5680bc70b 100644 --- a/extensions/keycloak/deployment/pom.xml +++ b/extensions/keycloak/deployment/pom.xml @@ -39,8 +39,8 @@ jackson-databind - org.keycloak - keycloak-undertow-adapter + io.quarkus.keycloak + quarkus-keycloak-adapter diff --git a/extensions/keycloak/runtime/pom.xml b/extensions/keycloak/runtime/pom.xml index f4488e2c948ab..bb38edbbc13bf 100644 --- a/extensions/keycloak/runtime/pom.xml +++ b/extensions/keycloak/runtime/pom.xml @@ -31,8 +31,8 @@ quarkus-elytron-security - org.keycloak - keycloak-undertow-adapter + io.quarkus.keycloak + quarkus-keycloak-adapter org.keycloak diff --git a/extensions/kotlin/runtime/pom.xml b/extensions/kotlin/runtime/pom.xml index 53ad46804d434..c4e3306cfcd50 100644 --- a/extensions/kotlin/runtime/pom.xml +++ b/extensions/kotlin/runtime/pom.xml @@ -21,4 +21,4 @@ - \ No newline at end of file + diff --git a/extensions/kubernetes/runtime/pom.xml b/extensions/kubernetes/runtime/pom.xml index e4298a772f397..4e4ed2a10b4b3 100644 --- a/extensions/kubernetes/runtime/pom.xml +++ b/extensions/kubernetes/runtime/pom.xml @@ -35,4 +35,4 @@ - \ No newline at end of file + diff --git a/extensions/kubernetes/spi/pom.xml b/extensions/kubernetes/spi/pom.xml index 42e7d170422a4..621c65564a9cc 100644 --- a/extensions/kubernetes/spi/pom.xml +++ b/extensions/kubernetes/spi/pom.xml @@ -18,4 +18,4 @@ quarkus-core-deployment - \ No newline at end of file + diff --git a/extensions/mailer/pom.xml b/extensions/mailer/pom.xml index de5b3ac479566..8de49c17f036c 100644 --- a/extensions/mailer/pom.xml +++ b/extensions/mailer/pom.xml @@ -22,4 +22,4 @@ - \ No newline at end of file + diff --git a/extensions/reactive-pg-client/pom.xml b/extensions/reactive-pg-client/pom.xml index 99304de005a25..e8edcb40056d3 100644 --- a/extensions/reactive-pg-client/pom.xml +++ b/extensions/reactive-pg-client/pom.xml @@ -22,4 +22,4 @@ - \ No newline at end of file + diff --git a/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/auth/JWTAuthMechanism.java b/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/auth/JWTAuthMechanism.java index 1111c3579995b..a94c74d922d1d 100644 --- a/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/auth/JWTAuthMechanism.java +++ b/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/auth/JWTAuthMechanism.java @@ -1,7 +1,7 @@ package io.quarkus.smallrye.jwt.runtime.auth; -import static io.undertow.util.Headers.WWW_AUTHENTICATE; -import static io.undertow.util.StatusCodes.UNAUTHORIZED; +import static io.undertow.httpcore.HttpHeaderNames.WWW_AUTHENTICATE; +import static io.undertow.httpcore.StatusCodes.UNAUTHORIZED; import javax.enterprise.inject.spi.CDI; @@ -78,7 +78,7 @@ private void preparePrincipalProducer(JsonWebToken jwtPrincipal) { @Override public ChallengeResult sendChallenge(HttpServerExchange exchange, SecurityContext securityContext) { - exchange.getResponseHeaders().add(WWW_AUTHENTICATE, "Bearer {token}"); + exchange.addResponseHeader(WWW_AUTHENTICATE, "Bearer {token}"); UndertowLogger.SECURITY_LOGGER.debugf("Sending Bearer {token} challenge for %s", exchange); return new ChallengeResult(true, UNAUTHORIZED); } @@ -93,7 +93,7 @@ private static class UndertowBearerTokenExtractor extends AbstractBearerTokenExt @Override protected String getHeaderValue(String headerName) { - return httpExchange.getRequestHeaders().getFirst(headerName); + return httpExchange.getRequestHeader(headerName); } @Override diff --git a/extensions/swagger-ui/runtime/pom.xml b/extensions/swagger-ui/runtime/pom.xml index ec91eb055bb7d..651f21d23f45e 100644 --- a/extensions/swagger-ui/runtime/pom.xml +++ b/extensions/swagger-ui/runtime/pom.xml @@ -49,4 +49,4 @@ - \ No newline at end of file + diff --git a/extensions/undertow-websockets/deployment/src/main/java/io/quarkus/undertow/websockets/deployment/HotReplacementWebsocketEndpoint.java b/extensions/undertow-websockets/deployment/src/main/java/io/quarkus/undertow/websockets/deployment/HotReplacementWebsocketEndpoint.java index 1ca6d1df8e79b..23f7d54932f50 100644 --- a/extensions/undertow-websockets/deployment/src/main/java/io/quarkus/undertow/websockets/deployment/HotReplacementWebsocketEndpoint.java +++ b/extensions/undertow-websockets/deployment/src/main/java/io/quarkus/undertow/websockets/deployment/HotReplacementWebsocketEndpoint.java @@ -26,9 +26,9 @@ import javax.websocket.server.ServerEndpointConfig; import org.jboss.logging.Logger; -import org.xnio.IoUtils; import io.quarkus.deployment.devmode.HotReplacementContext; +import io.undertow.util.IoUtils; @ServerEndpoint(value = HotReplacementWebsocketEndpoint.QUARKUS_HOT_RELOAD, configurator = HotReplacementWebsocketEndpoint.ServerConfigurator.class) public class HotReplacementWebsocketEndpoint { diff --git a/extensions/undertow-websockets/deployment/src/main/java/io/quarkus/undertow/websockets/deployment/UndertowWebsocketProcessor.java b/extensions/undertow-websockets/deployment/src/main/java/io/quarkus/undertow/websockets/deployment/UndertowWebsocketProcessor.java index bf7599674a9c0..4d3c4f55e7a14 100644 --- a/extensions/undertow-websockets/deployment/src/main/java/io/quarkus/undertow/websockets/deployment/UndertowWebsocketProcessor.java +++ b/extensions/undertow-websockets/deployment/src/main/java/io/quarkus/undertow/websockets/deployment/UndertowWebsocketProcessor.java @@ -26,13 +26,13 @@ import io.quarkus.deployment.annotations.ExecutionTime; import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.CombinedIndexBuildItem; +import io.quarkus.deployment.builditem.ExecutorBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.ServiceStartBuildItem; import io.quarkus.deployment.builditem.substrate.ReflectiveClassBuildItem; import io.quarkus.deployment.builditem.substrate.ServiceProviderBuildItem; import io.quarkus.runtime.annotations.ConfigRoot; import io.quarkus.undertow.deployment.ServletContextAttributeBuildItem; -import io.quarkus.undertow.deployment.UndertowBuildItem; import io.quarkus.undertow.websockets.runtime.UndertowWebsocketRecorder; import io.undertow.websockets.jsr.JsrWebSocketFilter; import io.undertow.websockets.jsr.UndertowContainerProvider; @@ -147,8 +147,8 @@ private void registerForReflection(BuildProducer refle @BuildStep @Record(ExecutionTime.RUNTIME_INIT) - ServiceStartBuildItem setupWorker(UndertowWebsocketRecorder recorder, UndertowBuildItem undertow) { - recorder.setupWorker(undertow.getUndertow()); + ServiceStartBuildItem setupWorker(UndertowWebsocketRecorder recorder, ExecutorBuildItem exec) { + recorder.setupWorker(exec.getExecutorProxy()); return new ServiceStartBuildItem("Websockets"); } diff --git a/extensions/undertow-websockets/deployment/src/main/java/io/quarkus/undertow/websockets/deployment/WebsocketHotReloadSetup.java b/extensions/undertow-websockets/deployment/src/main/java/io/quarkus/undertow/websockets/deployment/WebsocketHotReloadSetup.java index c55e1775296e3..517257292e73e 100644 --- a/extensions/undertow-websockets/deployment/src/main/java/io/quarkus/undertow/websockets/deployment/WebsocketHotReloadSetup.java +++ b/extensions/undertow-websockets/deployment/src/main/java/io/quarkus/undertow/websockets/deployment/WebsocketHotReloadSetup.java @@ -15,7 +15,6 @@ import io.quarkus.deployment.devmode.HotReplacementContext; import io.quarkus.deployment.devmode.HotReplacementSetup; import io.quarkus.undertow.runtime.UndertowDeploymentRecorder; -import io.quarkus.undertow.websockets.runtime.WorkerSupplier; import io.undertow.Handlers; import io.undertow.predicate.Predicates; import io.undertow.server.HandlerWrapper; @@ -71,7 +70,6 @@ public void run() { //this will likely change //but we create a servlet deployment that lasts for the life of the server WebSocketDeploymentInfo info = new WebSocketDeploymentInfo(); - info.setWorker(new WorkerSupplier()); info.addEndpoint(HotReplacementWebsocketEndpoint.class); DeploymentInfo d = new DeploymentInfo(); diff --git a/extensions/undertow-websockets/runtime/pom.xml b/extensions/undertow-websockets/runtime/pom.xml index 30ddd7254b191..e0ef12a8301a1 100644 --- a/extensions/undertow-websockets/runtime/pom.xml +++ b/extensions/undertow-websockets/runtime/pom.xml @@ -27,8 +27,8 @@ quarkus-undertow - io.undertow - undertow-websockets-jsr + io.quarkus.http + quarkus-http-websockets-jsr diff --git a/extensions/undertow-websockets/runtime/src/main/java/io/quarkus/undertow/websockets/runtime/ExecutorSupplier.java b/extensions/undertow-websockets/runtime/src/main/java/io/quarkus/undertow/websockets/runtime/ExecutorSupplier.java new file mode 100644 index 0000000000000..6bfbf57575b8b --- /dev/null +++ b/extensions/undertow-websockets/runtime/src/main/java/io/quarkus/undertow/websockets/runtime/ExecutorSupplier.java @@ -0,0 +1,14 @@ +package io.quarkus.undertow.websockets.runtime; + +import java.util.concurrent.Executor; +import java.util.function.Supplier; + +public class ExecutorSupplier implements Supplier { + + static volatile Executor executor; + + @Override + public Executor get() { + return executor; + } +} diff --git a/extensions/undertow-websockets/runtime/src/main/java/io/quarkus/undertow/websockets/runtime/UndertowWebsocketRecorder.java b/extensions/undertow-websockets/runtime/src/main/java/io/quarkus/undertow/websockets/runtime/UndertowWebsocketRecorder.java index 0d45a1e41720c..40d68d28ba7a7 100644 --- a/extensions/undertow-websockets/runtime/src/main/java/io/quarkus/undertow/websockets/runtime/UndertowWebsocketRecorder.java +++ b/extensions/undertow-websockets/runtime/src/main/java/io/quarkus/undertow/websockets/runtime/UndertowWebsocketRecorder.java @@ -2,6 +2,7 @@ import java.util.HashSet; import java.util.Set; +import java.util.concurrent.Executor; import javax.websocket.Endpoint; import javax.websocket.server.ServerApplicationConfig; @@ -9,9 +10,7 @@ import org.jboss.logging.Logger; -import io.quarkus.runtime.RuntimeValue; import io.quarkus.runtime.annotations.Recorder; -import io.undertow.Undertow; import io.undertow.websockets.jsr.WebSocketDeploymentInfo; @Recorder @@ -19,15 +18,15 @@ public class UndertowWebsocketRecorder { private static final Logger log = Logger.getLogger(UndertowWebsocketRecorder.class); - public void setupWorker(RuntimeValue undertow) { - WorkerSupplier.worker = undertow.getValue().getWorker(); + public void setupWorker(Executor executor) { + ExecutorSupplier.executor = executor; } @SuppressWarnings("unchecked") public WebSocketDeploymentInfo createDeploymentInfo(Set annotatedEndpoints, Set endpoints, Set serverApplicationConfigClasses) { WebSocketDeploymentInfo container = new WebSocketDeploymentInfo(); - container.setWorker(new WorkerSupplier()); + container.setExecutor(new ExecutorSupplier()); Set> allScannedEndpointImplementations = new HashSet<>(); for (String i : endpoints) { try { diff --git a/extensions/undertow-websockets/runtime/src/main/java/io/quarkus/undertow/websockets/runtime/WorkerSupplier.java b/extensions/undertow-websockets/runtime/src/main/java/io/quarkus/undertow/websockets/runtime/WorkerSupplier.java deleted file mode 100644 index 54a7dfee0a975..0000000000000 --- a/extensions/undertow-websockets/runtime/src/main/java/io/quarkus/undertow/websockets/runtime/WorkerSupplier.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.quarkus.undertow.websockets.runtime; - -import java.util.function.Supplier; - -import org.xnio.XnioWorker; - -public class WorkerSupplier implements Supplier { - - static volatile XnioWorker worker; - - @Override - public XnioWorker get() { - return worker; - } -} diff --git a/extensions/undertow/common/pom.xml b/extensions/undertow/common/pom.xml deleted file mode 100644 index 7165e6c641c7a..0000000000000 --- a/extensions/undertow/common/pom.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - quarkus-undertow-parent - io.quarkus - 999-SNAPSHOT - - 4.0.0 - - quarkus-undertow-common-substitutions - Quarkus - Undertow - Common - - Some common Undertow 2 substitutions that can be shared with Camel. - - - - - com.oracle.substratevm - svm - - - io.undertow - undertow-servlet - - - org.jboss.xnio - xnio-nio - - - org.jboss.xnio - xnio-api - - - io.quarkus - quarkus-core - - - diff --git a/extensions/undertow/common/src/main/java/io/quarkus/undertow/common/runtime/graal/ALPNManagerSubstitution.java b/extensions/undertow/common/src/main/java/io/quarkus/undertow/common/runtime/graal/ALPNManagerSubstitution.java deleted file mode 100644 index cdbb258dd1f3e..0000000000000 --- a/extensions/undertow/common/src/main/java/io/quarkus/undertow/common/runtime/graal/ALPNManagerSubstitution.java +++ /dev/null @@ -1,18 +0,0 @@ -package io.quarkus.undertow.common.runtime.graal; - -import javax.net.ssl.SSLEngine; - -import com.oracle.svm.core.annotate.Substitute; -import com.oracle.svm.core.annotate.TargetClass; - -import io.undertow.protocols.alpn.ALPNProvider; -import io.undertow.protocols.alpn.OpenSSLAlpnProvider; - -@TargetClass(className = "io.undertow.protocols.alpn.ALPNManager") -public final class ALPNManagerSubstitution { - - @Substitute - public ALPNProvider getProvider(SSLEngine engine) { - return new OpenSSLAlpnProvider(); - } -} diff --git a/extensions/undertow/common/src/main/java/io/quarkus/undertow/common/runtime/graal/ChannelsSubstitution.java b/extensions/undertow/common/src/main/java/io/quarkus/undertow/common/runtime/graal/ChannelsSubstitution.java deleted file mode 100644 index 73f2b8c6b4994..0000000000000 --- a/extensions/undertow/common/src/main/java/io/quarkus/undertow/common/runtime/graal/ChannelsSubstitution.java +++ /dev/null @@ -1,124 +0,0 @@ -package io.quarkus.undertow.common.runtime.graal; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; -import java.nio.channels.ReadableByteChannel; - -import org.xnio.channels.StreamSourceChannel; - -import com.oracle.svm.core.annotate.Delete; -import com.oracle.svm.core.annotate.Substitute; -import com.oracle.svm.core.annotate.TargetClass; - -@TargetClass(className = "org.xnio.channels.Channels") -public final class ChannelsSubstitution { - - @Delete() - private static ByteBuffer DRAIN_BUFFER = null; - - /** - * Attempt to drain the given number of bytes from the stream source channel. - * - * @param channel the channel to drain - * @param count the number of bytes - * @return the number of bytes drained, 0 if reading the channel would block, or -1 if the EOF was reached - * @throws IOException if an error occurs - */ - @Substitute - public static long drain(StreamSourceChannel channel, long count) throws IOException { - long total = 0L, lres; - int ires; - ByteBuffer buffer = ByteBuffer.allocate(1024); - for (;;) { - if (count == 0L) - return total; - if ((long) buffer.limit() > count) - buffer.limit((int) count); - ires = channel.read(buffer); - buffer.clear(); - switch (ires) { - case -1: - return total == 0L ? -1L : total; - case 0: - return total; - default: - total += (long) ires; - count -= (long) ires; - } - } - } - - /** - * Attempt to drain the given number of bytes from the readable byte channel. - * - * @param channel the channel to drain - * @param count the number of bytes - * @return the number of bytes drained, 0 if reading the channel would block, or -1 if the EOF was reached - * @throws IOException if an error occurs - */ - @Substitute - public static long drain(ReadableByteChannel channel, long count) throws IOException { - if (channel instanceof StreamSourceChannel) { - return drain((StreamSourceChannel) channel, count); - } else { - long total = 0L, lres; - int ires; - ByteBuffer buffer = ByteBuffer.allocate(1024); - for (;;) { - if (count == 0L) - return total; - if ((long) buffer.limit() > count) - buffer.limit((int) count); - ires = channel.read(buffer); - buffer.clear(); - switch (ires) { - case -1: - return total == 0L ? -1L : total; - case 0: - return total; - default: - total += (long) ires; - count -= (long) ires; - } - } - } - } - - /** - * Attempt to drain the given number of bytes from the file channel. This does nothing more than force a - * read of bytes in the file. - * - * @param channel the channel to drain - * @param position the position to drain from - * @param count the number of bytes - * @return the number of bytes drained, 0 if reading the channel would block, or -1 if the EOF was reached - * @throws IOException if an error occurs - */ - @Substitute - public static long drain(FileChannel channel, long position, long count) throws IOException { - if (channel instanceof StreamSourceChannel) { - return drain((StreamSourceChannel) channel, count); - } else { - long total = 0L, lres; - int ires; - ByteBuffer buffer = ByteBuffer.allocate(1024); - for (;;) { - if (count == 0L) - return total; - if ((long) buffer.limit() > count) - buffer.limit((int) count); - ires = channel.read(buffer); - buffer.clear(); - switch (ires) { - case -1: - return total == 0L ? -1L : total; - case 0: - return total; - default: - total += (long) ires; - } - } - } - } -} diff --git a/extensions/undertow/common/src/main/java/io/quarkus/undertow/common/runtime/graal/URLResourceSubstitution.java b/extensions/undertow/common/src/main/java/io/quarkus/undertow/common/runtime/graal/URLResourceSubstitution.java deleted file mode 100644 index f86c0b077cc9d..0000000000000 --- a/extensions/undertow/common/src/main/java/io/quarkus/undertow/common/runtime/graal/URLResourceSubstitution.java +++ /dev/null @@ -1,104 +0,0 @@ -package io.quarkus.undertow.common.runtime.graal; - -import java.io.IOException; -import java.net.URISyntaxException; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.Collections; -import java.util.Date; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Map; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; - -import com.oracle.svm.core.annotate.Alias; -import com.oracle.svm.core.annotate.Substitute; -import com.oracle.svm.core.annotate.TargetClass; - -/** - * TODO: this is pretty horrible - *

- * This class is a bit of a hack to allow static resource to work correctly with Substrate. - *

- * The class path is iterated at image build time, and any web resources have their content - * length and last modified times stored in a Map. - *

- * This seems like a simple enough solution for now, but hopefully we can do something better in the future. - * - * Another possibility is just not supporting embedded resources, and requiring resources to be served from - * the file system (or extracted tmp dir) - */ -@TargetClass(className = "io.undertow.server.handlers.resource.URLResource") -final class URLResourceSubstitution { - - @Alias - private boolean connectionOpened = false; - @Alias - private Date lastModified; - @Alias - private Long contentLength; - - @Alias - private String path; - - @Substitute - private void openConnection() { - if (!connectionOpened) { - connectionOpened = true; - ResourceInfo res = ResourceInfo.RESOURCES.get(path.startsWith("/") ? path : ("/" + path)); - if (res != null) { - contentLength = res.contentLength; - lastModified = new Date(res.lastModified); - } - } - } - - static final class ResourceInfo { - - private static final String META_INF_RESOURCES = "META-INF/resources"; - static final Map RESOURCES; - - private final long lastModified; - private final long contentLength; - - ResourceInfo(long lastModified, long contentLength) { - this.lastModified = lastModified; - this.contentLength = contentLength; - } - - static { - - ClassLoader cl = URLResourceSubstitution.class.getClassLoader(); - Map map = new HashMap<>(); - URLClassLoader ucl = (URLClassLoader) cl; - for (URL res : ucl.getURLs()) { - if (res.getProtocol().equals("file") && res.getPath().endsWith(".jar")) { - try (JarFile file = new JarFile(res.toURI().getPath())) { - Enumeration e = file.entries(); - while (e.hasMoreElements()) { - JarEntry entry = e.nextElement(); - if (entry.getName().startsWith(META_INF_RESOURCES)) { - map.put(entry.getName().substring(META_INF_RESOURCES.length()), - new ResourceInfo(entry.getLastModifiedTime().toMillis(), entry.getSize())); - } - } - - } catch (IOException | URISyntaxException e) { - throw new RuntimeException(e); - } - } - } - RESOURCES = Collections.unmodifiableMap(map); - } - - @Override - public String toString() { - return "ResourceInfo{" + - "lastModified=" + lastModified + - ", contentLength=" + contentLength + - '}'; - } - } - -} diff --git a/extensions/undertow/common/src/main/java/io/quarkus/undertow/common/runtime/graal/XnioSubstitution.java b/extensions/undertow/common/src/main/java/io/quarkus/undertow/common/runtime/graal/XnioSubstitution.java deleted file mode 100644 index 95766ca91baf6..0000000000000 --- a/extensions/undertow/common/src/main/java/io/quarkus/undertow/common/runtime/graal/XnioSubstitution.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.quarkus.undertow.common.runtime.graal; - -import java.io.Closeable; - -import org.xnio.IoUtils; -import org.xnio.Xnio; -import org.xnio.management.XnioProviderMXBean; -import org.xnio.management.XnioServerMXBean; -import org.xnio.management.XnioWorkerMXBean; -import org.xnio.nio.NioXnioProvider; - -import com.oracle.svm.core.annotate.Substitute; -import com.oracle.svm.core.annotate.TargetClass; - -@TargetClass(className = "org.xnio.Xnio") -final class XnioSubstitution { - - @Substitute - public static Xnio getInstance() { - return new NioXnioProvider().getInstance(); - } - - @Substitute - public static Xnio getInstance(final ClassLoader classLoader) { - return new NioXnioProvider().getInstance(); - } - - @Substitute - protected static Closeable register(XnioProviderMXBean providerMXBean) { - return IoUtils.nullCloseable(); - } - - @Substitute - protected static Closeable register(XnioWorkerMXBean workerMXBean) { - return IoUtils.nullCloseable(); - } - - @Substitute - protected static Closeable register(XnioServerMXBean serverMXBean) { - return IoUtils.nullCloseable(); - } -} diff --git a/extensions/undertow/deployment/pom.xml b/extensions/undertow/deployment/pom.xml index 42d4400e69647..fb0105ac9ba2c 100644 --- a/extensions/undertow/deployment/pom.xml +++ b/extensions/undertow/deployment/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - quarkus-undertow-parent + quarkus-http-parent io.quarkus 999-SNAPSHOT @@ -14,8 +14,8 @@ - io.undertow - undertow-servlet + io.quarkus.http + quarkus-http-servlet io.quarkus @@ -29,6 +29,10 @@ io.quarkus quarkus-undertow + + io.quarkus + quarkus-vertx-web-deployment + io.quarkus quarkus-kubernetes-spi diff --git a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/UndertowBuildStep.java b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/UndertowBuildStep.java index 92994d066395f..e4486eb896479 100644 --- a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/UndertowBuildStep.java +++ b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/UndertowBuildStep.java @@ -90,29 +90,25 @@ import io.quarkus.deployment.builditem.ServiceStartBuildItem; import io.quarkus.deployment.builditem.ShutdownContextBuildItem; import io.quarkus.deployment.builditem.substrate.ReflectiveClassBuildItem; -import io.quarkus.deployment.builditem.substrate.RuntimeReinitializedClassBuildItem; -import io.quarkus.deployment.builditem.substrate.SubstrateConfigBuildItem; import io.quarkus.deployment.builditem.substrate.SubstrateResourceBuildItem; import io.quarkus.deployment.recording.RecorderContext; import io.quarkus.deployment.util.ServiceUtil; -import io.quarkus.kubernetes.spi.KubernetesPortBuildItem; import io.quarkus.runtime.RuntimeValue; -import io.quarkus.undertow.runtime.HttpBuildConfig; -import io.quarkus.undertow.runtime.HttpConfig; import io.quarkus.undertow.runtime.HttpSessionContext; import io.quarkus.undertow.runtime.ServletProducer; import io.quarkus.undertow.runtime.ServletSecurityInfoProxy; import io.quarkus.undertow.runtime.ServletSecurityInfoSubstitution; import io.quarkus.undertow.runtime.UndertowDeploymentRecorder; import io.quarkus.undertow.runtime.UndertowHandlersConfServletExtension; -import io.quarkus.undertow.runtime.filters.CORSRecorder; -import io.undertow.Undertow; +import io.quarkus.vertx.web.deployment.DefaultRouteBuildItem; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.FilterInfo; import io.undertow.servlet.api.HttpMethodSecurityInfo; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.api.ServletSecurityInfo; import io.undertow.servlet.handlers.DefaultServlet; +import io.vertx.core.Handler; +import io.vertx.core.http.HttpServerRequest; //TODO: break this up, it is getting too big public class UndertowBuildStep { @@ -136,27 +132,14 @@ public ServiceStartBuildItem boot(UndertowDeploymentRecorder recorder, ServletDeploymentManagerBuildItem servletDeploymentManagerBuildItem, List wrappers, ShutdownContextBuildItem shutdown, - Consumer undertowProducer, - LaunchModeBuildItem launchMode, - ExecutorBuildItem executorBuildItem, - CORSRecorder corsRecorder, - HttpConfig config) throws Exception { - corsRecorder.setHttpConfig(config); - RuntimeValue ut = recorder.startUndertow(shutdown, executorBuildItem.getExecutorProxy(), + Consumer undertowProducer, + ExecutorBuildItem executorBuildItem) throws Exception { + Handler ut = recorder.startUndertow(shutdown, executorBuildItem.getExecutorProxy(), servletDeploymentManagerBuildItem.getDeploymentManager(), - config, wrappers.stream().map(HttpHandlerWrapperBuildItem::getValue).collect(Collectors.toList()), - launchMode.getLaunchMode()); - undertowProducer.accept(new UndertowBuildItem(ut)); - return new ServiceStartBuildItem("undertow"); - } + wrappers.stream().map(HttpHandlerWrapperBuildItem::getValue).collect(Collectors.toList())); - @BuildStep() - @Record(STATIC_INIT) - public void buildCorsFilter(CORSRecorder corsRecorder, HttpBuildConfig buildConfig, - BuildProducer extensionProducer) { - if (buildConfig.corsEnabled) { - extensionProducer.produce(new ServletExtensionBuildItem(corsRecorder.buildCORSExtension())); - } + undertowProducer.accept(new DefaultRouteBuildItem(ut)); + return new ServiceStartBuildItem("undertow"); } @BuildStep @@ -173,25 +156,6 @@ public void register(RegistrationContext registrationContext) { listeners.produce(new ListenerBuildItem(HttpSessionContext.class.getName())); } - @BuildStep - SubstrateConfigBuildItem config() { - return SubstrateConfigBuildItem.builder() - .addRuntimeInitializedClass("io.undertow.server.protocol.ajp.AjpServerResponseConduit") - .addRuntimeInitializedClass("io.undertow.server.protocol.ajp.AjpServerRequestConduit") - .build(); - } - - @BuildStep - void runtimeReinit(BuildProducer producer) { - producer.produce(new RuntimeReinitializedClassBuildItem("org.wildfly.common.net.HostName")); - producer.produce(new RuntimeReinitializedClassBuildItem("org.wildfly.common.os.Process")); - } - - @BuildStep - public void kubernetes(HttpConfig config, BuildProducer portProducer) { - portProducer.produce(new KubernetesPortBuildItem(config.port, "http")); - } - /** * Register the undertow-handlers.conf file */ @@ -285,8 +249,7 @@ public ServletDeploymentManagerBuildItem build(List servlets, ObjectSubstitutionBuildItem.Holder holder = new ObjectSubstitutionBuildItem.Holder(ServletSecurityInfo.class, ServletSecurityInfoProxy.class, ServletSecurityInfoSubstitution.class); substitutions.produce(new ObjectSubstitutionBuildItem(holder)); - reflectiveClasses.accept(new ReflectiveClassBuildItem(false, false, DefaultServlet.class.getName(), - "io.undertow.server.protocol.http.HttpRequestParser$$generated")); + reflectiveClasses.accept(new ReflectiveClassBuildItem(false, false, DefaultServlet.class.getName())); WebMetaData webMetaData = webMetadataBuildItem.getWebMetaData(); final IndexView index = combinedIndexBuildItem.getIndex(); diff --git a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/devmode/UndertowHotReplacementSetup.java b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/devmode/UndertowHotReplacementSetup.java index c473e53fe2778..ee1377329170b 100644 --- a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/devmode/UndertowHotReplacementSetup.java +++ b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/devmode/UndertowHotReplacementSetup.java @@ -7,26 +7,14 @@ import io.quarkus.deployment.devmode.HotReplacementContext; import io.quarkus.deployment.devmode.HotReplacementSetup; -import io.quarkus.deployment.devmode.ReplacementDebugPage; import io.quarkus.undertow.runtime.UndertowDeploymentRecorder; -import io.undertow.server.HandlerWrapper; -import io.undertow.server.HttpHandler; -import io.undertow.server.HttpServerExchange; -import io.undertow.util.Headers; public class UndertowHotReplacementSetup implements HotReplacementSetup { protected static final String META_INF_SERVICES = "META-INF/resources"; - private volatile long nextUpdate; - private HotReplacementContext context; - - private static final long TWO_SECONDS = 2000; @Override public void setupHotDeployment(HotReplacementContext context) { - this.context = context; - HandlerWrapper wrapper = createHandlerWrapper(); - UndertowDeploymentRecorder.addHotDeploymentWrapper(wrapper); List resources = new ArrayList<>(); for (Path i : context.getResourcesDir()) { Path resolved = i.resolve(META_INF_SERVICES); @@ -39,56 +27,10 @@ public void setupHotDeployment(HotReplacementContext context) { @Override public void handleFailedInitialStart() { - UndertowDeploymentRecorder.startServerAfterFailedStart(); } @Override public void close() { - UndertowDeploymentRecorder.stopDevMode(); - } - - private HandlerWrapper createHandlerWrapper() { - return new HandlerWrapper() { - @Override - public HttpHandler wrap(HttpHandler handler) { - return new HttpHandler() { - @Override - public void handleRequest(HttpServerExchange exchange) throws Exception { - if (exchange.isInIoThread()) { - exchange.dispatch(this); - return; - } - handleHotDeploymentRequest(exchange, handler); - } - }; - } - }; - } - - void handleHotDeploymentRequest(HttpServerExchange exchange, HttpHandler next) throws Exception { - if (nextUpdate < System.currentTimeMillis()) { - synchronized (this) { - if (nextUpdate < System.currentTimeMillis()) { - context.doScan(true); - // we update at most once every 2s - nextUpdate = System.currentTimeMillis() + TWO_SECONDS; - } - } - } else if (context.isTest()) { - //always scan if this is a test - context.doScan(true); - } - if (context.getDeploymentProblem() != null) { - handleDeploymentProblem(exchange, context.getDeploymentProblem()); - return; - } - next.handleRequest(exchange); - } - - private void handleDeploymentProblem(HttpServerExchange exchange, final Throwable exception) { - String bodyText = ReplacementDebugPage.generateHtml(exception); - exchange.setStatusCode(500); - exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/html; charset=UTF-8"); - exchange.getResponseSender().send(bodyText); + UndertowDeploymentRecorder.setHotDeploymentResources(null); } } diff --git a/extensions/undertow/pom.xml b/extensions/undertow/pom.xml index 29386017df719..8dbe242e23404 100644 --- a/extensions/undertow/pom.xml +++ b/extensions/undertow/pom.xml @@ -10,13 +10,12 @@ 4.0.0 - quarkus-undertow-parent + quarkus-http-parent Quarkus - Undertow pom deployment runtime - common diff --git a/extensions/undertow/runtime/pom.xml b/extensions/undertow/runtime/pom.xml index 18dc090299d42..d68cdcddd12b3 100644 --- a/extensions/undertow/runtime/pom.xml +++ b/extensions/undertow/runtime/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - quarkus-undertow-parent + quarkus-http-parent io.quarkus 999-SNAPSHOT @@ -19,23 +19,23 @@ io.quarkus - quarkus-undertow-common-substitutions + quarkus-vertx-web com.oracle.substratevm svm - io.undertow - undertow-servlet + io.quarkus.http + quarkus-http-servlet - org.jboss.xnio - xnio-nio + io.quarkus.http + quarkus-http-vertx-backend - org.jboss.xnio - xnio-api + io.quarkus.http + quarkus-http-core io.quarkus diff --git a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/DelegatingResourceManager.java b/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/DelegatingResourceManager.java index 1e5d762933035..6b8a91911c764 100644 --- a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/DelegatingResourceManager.java +++ b/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/DelegatingResourceManager.java @@ -5,7 +5,6 @@ import java.util.List; import io.undertow.server.handlers.resource.Resource; -import io.undertow.server.handlers.resource.ResourceChangeListener; import io.undertow.server.handlers.resource.ResourceManager; public class DelegatingResourceManager implements ResourceManager { @@ -27,21 +26,6 @@ public Resource getResource(String path) throws IOException { return null; } - @Override - public boolean isResourceChangeListenerSupported() { - return false; - } - - @Override - public void registerResourceChangeListener(ResourceChangeListener listener) { - - } - - @Override - public void removeResourceChangeListener(ResourceChangeListener listener) { - - } - @Override public void close() throws IOException { for (ResourceManager i : delegates) { diff --git a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/HttpBuildConfig.java b/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/HttpBuildConfig.java deleted file mode 100644 index b3685ca5dd731..0000000000000 --- a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/HttpBuildConfig.java +++ /dev/null @@ -1,18 +0,0 @@ -package io.quarkus.undertow.runtime; - -import io.quarkus.runtime.annotations.ConfigItem; -import io.quarkus.runtime.annotations.ConfigPhase; -import io.quarkus.runtime.annotations.ConfigRoot; - -/** - * Configuration which applies to the HTTP server at build time. - */ -@ConfigRoot(name = "http", phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED) -public class HttpBuildConfig { - - /** - * Enable the CORS filter. - */ - @ConfigItem(name = "cors") - public boolean corsEnabled = false; -} diff --git a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/KnownPathResourceManager.java b/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/KnownPathResourceManager.java index 8a400a0bf39eb..eae7915b6a41a 100644 --- a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/KnownPathResourceManager.java +++ b/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/KnownPathResourceManager.java @@ -2,6 +2,7 @@ import java.io.File; import java.io.IOException; +import java.io.OutputStream; import java.net.URL; import java.nio.file.Path; import java.util.ArrayList; @@ -13,11 +14,9 @@ import java.util.Set; import java.util.TreeSet; -import io.undertow.io.IoCallback; -import io.undertow.io.Sender; +import io.undertow.httpcore.OutputChannel; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.resource.Resource; -import io.undertow.server.handlers.resource.ResourceChangeListener; import io.undertow.server.handlers.resource.ResourceManager; import io.undertow.util.ETag; import io.undertow.util.MimeMappings; @@ -69,21 +68,6 @@ public Resource getResource(String path) throws IOException { return underlying.getResource(path); } - @Override - public boolean isResourceChangeListenerSupported() { - return underlying.isResourceChangeListenerSupported(); - } - - @Override - public void registerResourceChangeListener(ResourceChangeListener listener) { - underlying.registerResourceChangeListener(listener); - } - - @Override - public void removeResourceChangeListener(ResourceChangeListener listener) { - underlying.removeResourceChangeListener(listener); - } - @Override public void close() throws IOException { underlying.close(); @@ -155,8 +139,14 @@ public String getContentType(MimeMappings mimeMappings) { } @Override - public void serve(Sender sender, HttpServerExchange exchange, IoCallback completionCallback) { - completionCallback.onException(exchange, sender, new IOException("Cannot serve directory")); + public void serveBlocking(OutputStream outputStream, HttpServerExchange exchange) throws IOException { + throw new IOException("Cannot serve directory"); + } + + @Override + public void serveAsync(OutputChannel stream, HttpServerExchange exchange) { + exchange.setStatusCode(500); + exchange.endExchange(); } @Override diff --git a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentRecorder.java b/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentRecorder.java index 4b775e6cc9ec3..8b2491f0d49a0 100644 --- a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentRecorder.java +++ b/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentRecorder.java @@ -1,8 +1,6 @@ package io.quarkus.undertow.runtime; -import java.io.Closeable; import java.io.IOException; -import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.file.Path; import java.security.SecureRandom; @@ -14,9 +12,7 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutorService; import java.util.function.Supplier; -import java.util.stream.Collectors; -import javax.net.ssl.SSLContext; import javax.servlet.AsyncEvent; import javax.servlet.AsyncListener; import javax.servlet.DispatcherType; @@ -28,26 +24,22 @@ import javax.servlet.ServletException; import org.jboss.logging.Logger; -import org.wildfly.common.net.Inet; -import org.xnio.Xnio; -import org.xnio.XnioWorker; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.PooledByteBufAllocator; import io.quarkus.arc.InjectableContext; import io.quarkus.arc.ManagedContext; import io.quarkus.arc.runtime.BeanContainer; -import io.quarkus.runtime.ExecutorRecorder; import io.quarkus.runtime.LaunchMode; import io.quarkus.runtime.RuntimeValue; import io.quarkus.runtime.ShutdownContext; -import io.quarkus.runtime.ThreadPoolConfig; -import io.quarkus.runtime.Timing; import io.quarkus.runtime.annotations.Recorder; -import io.quarkus.runtime.configuration.ConfigInstantiator; -import io.undertow.Undertow; +import io.undertow.httpcore.BufferAllocator; +import io.undertow.httpcore.StatusCodes; +import io.undertow.server.DefaultExchangeHandler; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; -import io.undertow.server.handlers.CanonicalPathHandler; import io.undertow.server.handlers.PathHandler; import io.undertow.server.handlers.ResponseCodeHandler; import io.undertow.server.handlers.resource.CachingResourceManager; @@ -75,7 +67,9 @@ import io.undertow.servlet.handlers.ServletRequestContext; import io.undertow.servlet.spec.HttpServletRequestImpl; import io.undertow.util.AttachmentKey; -import io.undertow.util.StatusCodes; +import io.undertow.vertx.VertxHttpExchange; +import io.vertx.core.Handler; +import io.vertx.core.http.HttpServerRequest; /** * Provides the runtime methods to bootstrap Undertow. This class is present in the final uber-jar, @@ -93,7 +87,6 @@ public void handleRequest(HttpServerExchange exchange) throws Exception { } }; - private static volatile Undertow undertow; private static final List hotDeploymentWrappers = new CopyOnWriteArrayList<>(); private static volatile List hotDeploymentResourcePaths; private static volatile HttpHandler currentRoot = ResponseCodeHandler.HANDLE_404; @@ -102,34 +95,49 @@ public void handleRequest(HttpServerExchange exchange) throws Exception { private static final AttachmentKey REQUEST_CONTEXT = AttachmentKey .create(InjectableContext.ContextState.class); - public static void setHotDeploymentResources(List resources) { - hotDeploymentResourcePaths = resources; - } + /** + * TODO: configuration + */ + protected static final int BUFFER_SIZE = 8 * 1024; - public static void startServerAfterFailedStart() { - try { - HttpConfig config = new HttpConfig(); - ConfigInstantiator.handleObject(config); + //TODO: clean this up + private static BufferAllocator ALLOCATOR = new BufferAllocator() { + @Override + public ByteBuf allocateBuffer() { + return PooledByteBufAllocator.DEFAULT.buffer(BUFFER_SIZE); + } - ThreadPoolConfig threadPoolConfig = new ThreadPoolConfig(); - ConfigInstantiator.handleObject(threadPoolConfig); + @Override + public ByteBuf allocateBuffer(boolean direct) { + if (direct) { + return PooledByteBufAllocator.DEFAULT.directBuffer(BUFFER_SIZE); + } else { + return PooledByteBufAllocator.DEFAULT.heapBuffer(BUFFER_SIZE); + } + } - ExecutorService service = ExecutorRecorder.createDevModeExecutorForFailedStart(threadPoolConfig); - //we can't really do - doServerStart(config, LaunchMode.DEVELOPMENT, config.ssl.toSSLContext(), service); - } catch (Exception e) { - throw new RuntimeException(e); + @Override + public ByteBuf allocateBuffer(int bufferSize) { + return PooledByteBufAllocator.DEFAULT.buffer(bufferSize); } - } - public static void stopDevMode() { - //TODO: we need a cleaner abstraction for all this - if (undertow != null) { - undertow.stop(); - undertow = null; + @Override + public ByteBuf allocateBuffer(boolean direct, int bufferSize) { + if (direct) { + return PooledByteBufAllocator.DEFAULT.directBuffer(bufferSize); + } else { + return PooledByteBufAllocator.DEFAULT.heapBuffer(bufferSize); + } + } + + @Override + public int getBufferSize() { + return BUFFER_SIZE; } - hotDeploymentResourcePaths = null; - hotDeploymentWrappers.clear(); + }; + + public static void setHotDeploymentResources(List resources) { + hotDeploymentResourcePaths = resources; } public RuntimeValue createDeployment(String name, Set knownFile, Set knownDirectories, @@ -169,7 +177,9 @@ public RuntimeValue createDeployment(String name, Set kn d.addWelcomePages("index.html", "index.htm"); d.addServlet(new ServletInfo(ServletPathMatches.DEFAULT_SERVLET_NAME, DefaultServlet.class).setAsyncSupported(true)); - + for (HandlerWrapper i : hotDeploymentWrappers) { + d.addOuterHandlerChainWrapper(i); + } context.addShutdownTask(new Runnable() { @Override public void run() { @@ -184,11 +194,6 @@ public void run() { } public static SocketAddress getHttpAddress() { - for (Undertow.ListenerInfo info : undertow.getListenerInfo()) { - if (info.getProtcol().equals("http") && info.getSslContext() == null) { - return info.getAddress(); - } - } return null; } @@ -291,40 +296,10 @@ public void addServletInitParameter(RuntimeValue info, String na info.getValue().addInitParameter(name, value); } - public Closeable undertowDevModeCloseTask() { - //task that is - return new Closeable() { - @Override - public void close() throws IOException { - if (undertow != null) { - undertow.stop(); - undertow = null; - } - } - }; - } - - public RuntimeValue startUndertow(ShutdownContext shutdown, ExecutorService executorService, - DeploymentManager manager, HttpConfig config, - List wrappers, LaunchMode launchMode) throws Exception { + public Handler startUndertow(ShutdownContext shutdown, ExecutorService executorService, + DeploymentManager manager, + List wrappers) throws Exception { - if (undertow == null) { - SSLContext context = config.ssl.toSSLContext(); - doServerStart(config, launchMode, context, executorService); - - if (launchMode != LaunchMode.DEVELOPMENT) { - //in development mode undertow should not be shut down - shutdown.addShutdownTask(new Runnable() { - @Override - public void run() { - XnioWorker worker = undertow.getWorker(); - undertow.stop(); - worker.shutdown(); - undertow = null; - } - }); - } - } shutdown.addShutdownTask(new Runnable() { @Override public void run() { @@ -347,69 +322,20 @@ public void run() { } currentRoot = main; - Timing.setHttpServer(String.format( - "Listening on: " + undertow.getListenerInfo().stream().map(l -> { - String address; - if (l.getAddress() instanceof InetSocketAddress) { - InetSocketAddress inetAddress = (InetSocketAddress) l.getAddress(); - address = Inet.toURLString(inetAddress.getAddress(), true) + ":" + inetAddress.getPort(); - } else { - address = l.getAddress().toString(); - } - return l.getProtcol() + "://" + address; - }).collect(Collectors.joining(", ")))); - - return new RuntimeValue<>(undertow); + DefaultExchangeHandler defaultHandler = new DefaultExchangeHandler(ROOT_HANDLER); + return new Handler() { + @Override + public void handle(HttpServerRequest event) { + VertxHttpExchange exchange = new VertxHttpExchange(event, ALLOCATOR, executorService); + defaultHandler.handle(exchange); + } + }; } public static void addHotDeploymentWrapper(HandlerWrapper handlerWrapper) { hotDeploymentWrappers.add(handlerWrapper); } - /** - * Used for quarkus:run, where we want undertow to start very early in the process. - *

- * This enables recovery from errors on boot. In a normal boot undertow is one of the last things start, so there would - * be no chance to use hot deployment to fix the error. In development mode we start Undertow early, so any error - * on boot can be corrected via the hot deployment handler - */ - private static void doServerStart(HttpConfig config, LaunchMode launchMode, SSLContext sslContext, ExecutorService executor) - throws ServletException { - if (undertow == null) { - int port = config.determinePort(launchMode); - int sslPort = config.determineSslPort(launchMode); - log.debugf("Starting Undertow on port %d", port); - HttpHandler rootHandler = new CanonicalPathHandler(ROOT_HANDLER); - for (HandlerWrapper i : hotDeploymentWrappers) { - rootHandler = i.wrap(rootHandler); - } - - XnioWorker.Builder workerBuilder = Xnio.getInstance().createWorkerBuilder() - .setExternalExecutorService(executor); - - Undertow.Builder builder = Undertow.builder() - .addHttpListener(port, config.host) - .setHandler(rootHandler); - if (config.ioThreads.isPresent()) { - workerBuilder.setWorkerIoThreads(config.ioThreads.getAsInt()); - } else if (launchMode.isDevOrTest()) { - //we limit the number of IO and worker threads in development and testing mode - workerBuilder.setWorkerIoThreads(2); - } else { - workerBuilder.setWorkerIoThreads(Runtime.getRuntime().availableProcessors() * 2); - } - XnioWorker worker = workerBuilder.build(); - builder.setWorker(worker); - if (sslContext != null) { - log.debugf("Starting Undertow HTTPS listener on port %d", sslPort); - builder.addHttpsListener(sslPort, config.host, sslContext); - } - undertow = builder - .build(); - undertow.start(); - } - } - public Supplier servletContextSupplier() { return new ServletContextSupplier(); } diff --git a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/filters/CORSRecorder.java b/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/filters/CORSRecorder.java deleted file mode 100644 index f939b60758f5d..0000000000000 --- a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/filters/CORSRecorder.java +++ /dev/null @@ -1,41 +0,0 @@ -package io.quarkus.undertow.runtime.filters; - -import javax.servlet.DispatcherType; -import javax.servlet.ServletContext; - -import io.quarkus.runtime.annotations.Recorder; -import io.quarkus.undertow.runtime.HttpConfig; -import io.undertow.servlet.ServletExtension; -import io.undertow.servlet.api.DeploymentInfo; -import io.undertow.servlet.api.FilterInfo; -import io.undertow.servlet.util.ImmediateInstanceFactory; - -@Recorder -public class CORSRecorder { - - public CORSServletExtension buildCORSExtension() { - return new CORSServletExtension(); - } - - public void setHttpConfig(HttpConfig config) { - CORSFilter.corsConfig = config.cors; - } - - public static class CORSServletExtension implements ServletExtension { - - @Override - public void handleDeployment(DeploymentInfo deploymentInfo, ServletContext servletContext) { - CORSFilter filter = new CORSFilter(); - ImmediateInstanceFactory filterFactory = new ImmediateInstanceFactory<>(filter); - FilterInfo filterInfo = new FilterInfo(CORSFilter.class.getName(), CORSFilter.class, filterFactory); - filterInfo.setAsyncSupported(true); - deploymentInfo.addFilter(filterInfo); - // Mappings - deploymentInfo.addFilterServletNameMapping(CORSFilter.class.getName(), "*", DispatcherType.ERROR); - deploymentInfo.addFilterServletNameMapping(CORSFilter.class.getName(), "*", DispatcherType.FORWARD); - deploymentInfo.addFilterServletNameMapping(CORSFilter.class.getName(), "*", DispatcherType.INCLUDE); - deploymentInfo.addFilterServletNameMapping(CORSFilter.class.getName(), "*", DispatcherType.REQUEST); - deploymentInfo.addFilterServletNameMapping(CORSFilter.class.getName(), "*", DispatcherType.ASYNC); - } - } -} diff --git a/extensions/vertx-web/deployment/pom.xml b/extensions/vertx-web/deployment/pom.xml index 8d468cd37906b..044b66a7ed628 100644 --- a/extensions/vertx-web/deployment/pom.xml +++ b/extensions/vertx-web/deployment/pom.xml @@ -22,6 +22,10 @@ io.quarkus quarkus-vertx-web + + io.quarkus + quarkus-kubernetes-spi + io.quarkus diff --git a/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/DefaultRouteBuildItem.java b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/DefaultRouteBuildItem.java new file mode 100644 index 0000000000000..dd2eac0a0f37d --- /dev/null +++ b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/DefaultRouteBuildItem.java @@ -0,0 +1,21 @@ +package io.quarkus.vertx.web.deployment; + +import io.quarkus.builder.item.SimpleBuildItem; +import io.vertx.core.Handler; +import io.vertx.core.http.HttpServerRequest; + +/** + * A build item that represents a handler for the default route + */ +public final class DefaultRouteBuildItem extends SimpleBuildItem { + + private final Handler handler; + + public DefaultRouteBuildItem(Handler handler) { + this.handler = handler; + } + + public Handler getHandler() { + return handler; + } +} diff --git a/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/FilterBuildItem.java b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/FilterBuildItem.java new file mode 100644 index 0000000000000..c9342674ebac0 --- /dev/null +++ b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/FilterBuildItem.java @@ -0,0 +1,21 @@ +package io.quarkus.vertx.web.deployment; + +import io.quarkus.builder.item.MultiBuildItem; +import io.vertx.core.Handler; +import io.vertx.ext.web.RoutingContext; + +/** + * A handler that is applied to every route + */ +public final class FilterBuildItem extends MultiBuildItem { + + final Handler handler; + + public FilterBuildItem(Handler handler) { + this.handler = handler; + } + + public Handler getHandler() { + return handler; + } +} diff --git a/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java index f2ba7052bc065..07e702bea4f90 100644 --- a/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java +++ b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java @@ -1,10 +1,12 @@ package io.quarkus.vertx.web.deployment; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; import javax.inject.Singleton; @@ -49,13 +51,15 @@ import io.quarkus.gizmo.MethodCreator; import io.quarkus.gizmo.MethodDescriptor; import io.quarkus.gizmo.ResultHandle; +import io.quarkus.kubernetes.spi.KubernetesPortBuildItem; import io.quarkus.vertx.deployment.VertxBuildItem; import io.quarkus.vertx.web.Route; import io.quarkus.vertx.web.RoutingExchange; +import io.quarkus.vertx.web.runtime.HttpConfiguration; import io.quarkus.vertx.web.runtime.RouterProducer; import io.quarkus.vertx.web.runtime.RoutingExchangeImpl; -import io.quarkus.vertx.web.runtime.VertxHttpConfiguration; import io.quarkus.vertx.web.runtime.VertxWebRecorder; +import io.quarkus.vertx.web.runtime.cors.CORSRecorder; import io.vertx.core.Handler; import io.vertx.ext.web.RoutingContext; @@ -71,13 +75,25 @@ class VertxWebProcessor { private static final DotName ROUTING_EXCHANGE = DotName.createSimple(RoutingExchange.class.getName()); private static final String HANDLER_SUFFIX = "_RouteHandler"; - VertxHttpConfiguration vertxHttpConfiguration; + HttpConfiguration httpConfiguration; + + @BuildStep + @Record(ExecutionTime.RUNTIME_INIT) + FilterBuildItem cors(CORSRecorder recorder, + HttpConfiguration configuration) { + return new FilterBuildItem(recorder.corsHandler(configuration)); + } @BuildStep FeatureBuildItem feature() { return new FeatureBuildItem(FeatureBuildItem.VERTX_WEB); } + @BuildStep + public void kubernetes(HttpConfiguration config, BuildProducer portProducer) { + portProducer.produce(new KubernetesPortBuildItem(config.port, "http")); + } + @BuildStep AdditionalBeanBuildItem additionalBeans() { return AdditionalBeanBuildItem.unremovableOf(RouterProducer.class); @@ -131,7 +147,9 @@ ServiceStartBuildItem build(VertxWebRecorder recorder, BeanContainerBuildItem be LaunchModeBuildItem launchMode, BuildProducer reflectiveClasses, ShutdownContextBuildItem shutdown, - VertxBuildItem vertx) { + VertxBuildItem vertx, + Optional defaultRoute, + List filters) throws IOException { ClassOutput classOutput = new ClassOutput() { @Override @@ -148,9 +166,10 @@ public void write(String name, byte[] data) { routeConfigs.put(handlerClass, routes); reflectiveClasses.produce(new ReflectiveClassBuildItem(false, false, handlerClass)); } - recorder.configureRouter(vertx.getVertx(), beanContainer.getValue(), routeConfigs, vertxHttpConfiguration, + recorder.configureRouter(vertx.getVertx(), beanContainer.getValue(), routeConfigs, + filters.stream().map(FilterBuildItem::getHandler).collect(Collectors.toList()), httpConfiguration, launchMode.getLaunchMode(), - shutdown); + shutdown, defaultRoute.map(DefaultRouteBuildItem::getHandler).orElse(null)); return new ServiceStartBuildItem("vertx-web"); } diff --git a/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/devmode/VertxHotReplacementSetup.java b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/devmode/VertxHotReplacementSetup.java index 7f18535ecca5d..7e42aec954acb 100644 --- a/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/devmode/VertxHotReplacementSetup.java +++ b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/devmode/VertxHotReplacementSetup.java @@ -20,9 +20,14 @@ public void setupHotDeployment(HotReplacementContext context) { VertxWebRecorder.setHotReplacement(this::handleHotReplacementRequest); } + @Override + public void handleFailedInitialStart() { + VertxWebRecorder.startServerAfterFailedStart(); + } + void handleHotReplacementRequest(RoutingContext routingContext) { - if (nextUpdate > System.currentTimeMillis()) { + if (nextUpdate > System.currentTimeMillis() && !hotReplacementContext.isTest()) { if (hotReplacementContext.getDeploymentProblem() != null) { handleDeploymentProblem(routingContext, hotReplacementContext.getDeploymentProblem()); return; @@ -32,7 +37,7 @@ void handleHotReplacementRequest(RoutingContext routingContext) { } boolean restart = false; synchronized (this) { - if (nextUpdate < System.currentTimeMillis()) { + if (nextUpdate < System.currentTimeMillis() || hotReplacementContext.isTest()) { try { restart = hotReplacementContext.doScan(true); } catch (Exception e) { diff --git a/extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/CORSDisabledServletTestCase.java b/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/CORSDisabledHandlerTestCase.java similarity index 90% rename from extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/CORSDisabledServletTestCase.java rename to extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/CORSDisabledHandlerTestCase.java index d6cbde6687fd6..d47a5ffc1747d 100644 --- a/extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/CORSDisabledServletTestCase.java +++ b/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/CORSDisabledHandlerTestCase.java @@ -1,4 +1,4 @@ -package io.quarkus.undertow.test; +package io.quarkus.vertx.web; import static io.restassured.RestAssured.given; import static org.hamcrest.core.IsNull.nullValue; @@ -11,12 +11,12 @@ import io.quarkus.test.QuarkusUnitTest; -public class CORSDisabledServletTestCase { +public class CORSDisabledHandlerTestCase { @RegisterExtension static QuarkusUnitTest runner = new QuarkusUnitTest() .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) - .addClasses(TestServlet.class)); + .addClasses(TestRoute.class)); @Test @DisplayName("Doesn't return CORS headers if not configured") diff --git a/extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/CORSFullConfigServletTestCase.java b/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/CORSFullConfigHandlerTestCase.java similarity index 95% rename from extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/CORSFullConfigServletTestCase.java rename to extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/CORSFullConfigHandlerTestCase.java index 40b470acc0ead..47968001bfb46 100644 --- a/extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/CORSFullConfigServletTestCase.java +++ b/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/CORSFullConfigHandlerTestCase.java @@ -1,4 +1,4 @@ -package io.quarkus.undertow.test; +package io.quarkus.vertx.web; import static io.restassured.RestAssured.given; @@ -10,12 +10,12 @@ import io.quarkus.test.QuarkusUnitTest; -public class CORSFullConfigServletTestCase { +public class CORSFullConfigHandlerTestCase { @RegisterExtension static QuarkusUnitTest runner = new QuarkusUnitTest() .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) - .addClasses(TestServlet.class) + .addClasses(TestRoute.class) .addAsResource("cors-config-full.properties", "application.properties")); @Test diff --git a/extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/CORSServletTestCase.java b/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/CORSHandlerTestCase.java similarity index 92% rename from extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/CORSServletTestCase.java rename to extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/CORSHandlerTestCase.java index 4cf4c91765549..d4ea9ac642deb 100644 --- a/extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/CORSServletTestCase.java +++ b/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/CORSHandlerTestCase.java @@ -1,4 +1,4 @@ -package io.quarkus.undertow.test; +package io.quarkus.vertx.web; import static io.restassured.RestAssured.given; import static org.hamcrest.core.Is.is; @@ -11,12 +11,12 @@ import io.quarkus.test.QuarkusUnitTest; -public class CORSServletTestCase { +public class CORSHandlerTestCase { @RegisterExtension static QuarkusUnitTest runner = new QuarkusUnitTest() .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) - .addClasses(TestServlet.class) + .addClasses(TestRoute.class) .addAsResource("cors-config.properties", "application.properties")); @Test @@ -52,7 +52,7 @@ public void corsNoPreflightTestServlet() { .header("Access-Control-Allow-Origin", origin) .header("Access-Control-Allow-Methods", methods) .header("Access-Control-Allow-Headers", headers) - .body(is("test servlet")); + .body(is("test route")); } } diff --git a/extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/SecureSocketWithKeyStoreTestCase.java b/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/SecureSocketWithKeyStoreTestCase.java similarity index 79% rename from extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/SecureSocketWithKeyStoreTestCase.java rename to extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/SecureSocketWithKeyStoreTestCase.java index d5c5fad3d57e1..7209782a2015c 100644 --- a/extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/SecureSocketWithKeyStoreTestCase.java +++ b/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/SecureSocketWithKeyStoreTestCase.java @@ -1,4 +1,4 @@ -package io.quarkus.undertow.test; +package io.quarkus.vertx.web; import static io.restassured.RestAssured.given; import static org.hamcrest.Matchers.is; @@ -15,16 +15,16 @@ public class SecureSocketWithKeyStoreTestCase { @RegisterExtension static QuarkusUnitTest runner = QuarkusUnitTest.withSecuredConnection() .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) - .addClass(TestServlet.class) + .addClass(TestRoute.class) .addAsResource("application-keystore.properties", "application.properties") .addAsResource("keystore.jks")); @Test public void testSecureConnectionAvailable() { - given() + given().baseUri("https://localhost:" + System.getProperty("quarkus.http.testSslPort", "8444")) .relaxedHTTPSValidation() .when().get("/test").then() .statusCode(200) - .body(is("test servlet")); + .body(is("test route")); } } diff --git a/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/TestRoute.java b/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/TestRoute.java new file mode 100644 index 0000000000000..3ba4be98a1a42 --- /dev/null +++ b/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/TestRoute.java @@ -0,0 +1,14 @@ +package io.quarkus.vertx.web; + +import static io.vertx.core.http.HttpMethod.GET; +import static io.vertx.core.http.HttpMethod.OPTIONS; + +import io.vertx.ext.web.RoutingContext; + +public class TestRoute { + + @Route(path = "/test", methods = { GET, OPTIONS }) + void getRoutes(RoutingContext context) { + context.response().setStatusCode(200).end("test route"); + } +} diff --git a/extensions/undertow/deployment/src/test/resources/application-keystore.properties b/extensions/vertx-web/deployment/src/test/resources/application-keystore.properties similarity index 100% rename from extensions/undertow/deployment/src/test/resources/application-keystore.properties rename to extensions/vertx-web/deployment/src/test/resources/application-keystore.properties diff --git a/extensions/undertow/deployment/src/test/resources/cors-config-full.properties b/extensions/vertx-web/deployment/src/test/resources/cors-config-full.properties similarity index 100% rename from extensions/undertow/deployment/src/test/resources/cors-config-full.properties rename to extensions/vertx-web/deployment/src/test/resources/cors-config-full.properties diff --git a/extensions/undertow/deployment/src/test/resources/cors-config.properties b/extensions/vertx-web/deployment/src/test/resources/cors-config.properties similarity index 100% rename from extensions/undertow/deployment/src/test/resources/cors-config.properties rename to extensions/vertx-web/deployment/src/test/resources/cors-config.properties diff --git a/extensions/undertow/deployment/src/test/resources/keystore.jks b/extensions/vertx-web/deployment/src/test/resources/keystore.jks similarity index 100% rename from extensions/undertow/deployment/src/test/resources/keystore.jks rename to extensions/vertx-web/deployment/src/test/resources/keystore.jks diff --git a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/HttpConfig.java b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/HttpConfiguration.java similarity index 85% rename from extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/HttpConfig.java rename to extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/HttpConfiguration.java index 5f4aa286fd983..2db92deff6388 100644 --- a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/HttpConfig.java +++ b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/HttpConfiguration.java @@ -1,4 +1,4 @@ -package io.quarkus.undertow.runtime; +package io.quarkus.vertx.web.runtime; import java.util.OptionalInt; @@ -7,25 +7,22 @@ import io.quarkus.runtime.annotations.ConfigPhase; import io.quarkus.runtime.annotations.ConfigRoot; import io.quarkus.runtime.configuration.ssl.ServerSslConfig; -import io.quarkus.undertow.runtime.filters.CORSConfig; +import io.quarkus.vertx.web.runtime.cors.CORSConfig; -/** - * Configuration which applies to the HTTP server. - */ @ConfigRoot(phase = ConfigPhase.RUN_TIME) -public class HttpConfig { +public class HttpConfiguration { /** - * The HTTP port + * Enable the CORS filter. */ - @ConfigItem(defaultValue = "8080") - public int port; + @ConfigItem(name = "cors", defaultValue = "false") + public boolean corsEnabled; /** - * The HTTPS port + * The HTTP port */ - @ConfigItem(defaultValue = "8443") - public int sslPort; + @ConfigItem(defaultValue = "8080") + public int port; /** * The HTTP port used to run tests @@ -33,11 +30,6 @@ public class HttpConfig { @ConfigItem(defaultValue = "8081") public int testPort; - /** - * The HTTPS port used to run tests - */ - @ConfigItem(defaultValue = "8444") - public int testSslPort; /** * The HTTP host */ @@ -45,22 +37,34 @@ public class HttpConfig { public String host; /** - * The number if IO threads used to perform IO. This will be automatically set to a reasonable value based on - * the number of CPU cores if it is not provided + * The HTTPS port */ - @ConfigItem - public OptionalInt ioThreads; + @ConfigItem(defaultValue = "8443") + public int sslPort; /** - * The SSL config + * The HTTPS port used to run tests */ - public ServerSslConfig ssl; + @ConfigItem(defaultValue = "8444") + public int testSslPort; /** * The CORS config */ public CORSConfig cors; + /** + * The SSL config + */ + public ServerSslConfig ssl; + + /** + * The number if IO threads used to perform IO. This will be automatically set to a reasonable value based on + * the number of CPU cores if it is not provided + */ + @ConfigItem + public OptionalInt ioThreads; + public int determinePort(LaunchMode launchMode) { return launchMode == LaunchMode.TEST ? testPort : port; } diff --git a/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/VertxHttpConfiguration.java b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/VertxHttpConfiguration.java deleted file mode 100644 index 3728e5cc3fda4..0000000000000 --- a/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/VertxHttpConfiguration.java +++ /dev/null @@ -1,33 +0,0 @@ -package io.quarkus.vertx.web.runtime; - -import io.quarkus.runtime.LaunchMode; -import io.quarkus.runtime.annotations.ConfigItem; -import io.quarkus.runtime.annotations.ConfigPhase; -import io.quarkus.runtime.annotations.ConfigRoot; - -@ConfigRoot(phase = ConfigPhase.RUN_TIME) -public class VertxHttpConfiguration { - - /** - * The HTTP port - */ - @ConfigItem(defaultValue = "8080") - public int port; - - /** - * The HTTP port used to run tests - */ - @ConfigItem(defaultValue = "8081") - public int testPort; - - /** - * The HTTP host - */ - @ConfigItem(defaultValue = "0.0.0.0") - public String host; - - public int determinePort(LaunchMode launchMode) { - return launchMode == LaunchMode.TEST ? testPort : port; - } - -} diff --git a/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/VertxWebRecorder.java b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/VertxWebRecorder.java index 36c7a79d810be..521b91abea421 100644 --- a/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/VertxWebRecorder.java +++ b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/VertxWebRecorder.java @@ -1,12 +1,21 @@ package io.quarkus.vertx.web.runtime; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.lang.reflect.InvocationTargetException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; import javax.enterprise.event.Event; @@ -19,12 +28,26 @@ import io.quarkus.runtime.ShutdownContext; import io.quarkus.runtime.Timing; import io.quarkus.runtime.annotations.Recorder; +import io.quarkus.runtime.configuration.ConfigInstantiator; +import io.quarkus.runtime.configuration.ssl.ServerSslConfig; +import io.quarkus.vertx.runtime.VertxConfiguration; +import io.quarkus.vertx.runtime.VertxRecorder; import io.quarkus.vertx.web.Route; +import io.vertx.core.AsyncResult; +import io.vertx.core.Context; +import io.vertx.core.DeploymentOptions; +import io.vertx.core.Future; import io.vertx.core.Handler; +import io.vertx.core.Verticle; import io.vertx.core.Vertx; +import io.vertx.core.buffer.Buffer; import io.vertx.core.http.HttpMethod; import io.vertx.core.http.HttpServer; import io.vertx.core.http.HttpServerOptions; +import io.vertx.core.http.HttpServerRequest; +import io.vertx.core.net.JksOptions; +import io.vertx.core.net.PemKeyCertOptions; +import io.vertx.core.net.PfxOptions; import io.vertx.ext.web.Router; import io.vertx.ext.web.RoutingContext; import io.vertx.ext.web.handler.BodyHandler; @@ -41,22 +64,45 @@ public static void setHotReplacement(Handler handler) { private static volatile Handler hotReplacementHandler; private static volatile Router router; - private static volatile HttpServer server; + + private static volatile Runnable closeTask; public static void shutDownDevMode() { - if (server != null) { - server.close(); - server = null; - } + closeTask.run(); + closeTask = null; router = null; hotReplacementHandler = null; } + public static void startServerAfterFailedStart() { + VertxConfiguration vertxConfiguration = new VertxConfiguration(); + ConfigInstantiator.handleObject(vertxConfiguration); + VertxRecorder.initialize(vertxConfiguration); + + try { + HttpConfiguration config = new HttpConfiguration(); + ConfigInstantiator.handleObject(config); + + router = Router.router(VertxRecorder.getVertx()); + if (hotReplacementHandler != null) { + router.route().blockingHandler(hotReplacementHandler); + } + + //we can't really do + doServerStart(VertxRecorder.getVertx(), config, LaunchMode.DEVELOPMENT); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + public void configureRouter(RuntimeValue vertx, BeanContainer container, Map> routeHandlers, - VertxHttpConfiguration vertxHttpConfiguration, LaunchMode launchMode, ShutdownContext shutdown) { + List> filters, + HttpConfiguration httpConfiguration, LaunchMode launchMode, ShutdownContext shutdown, + Handler defaultRoute) throws IOException { - List appRoutes = initialize(vertx.getValue(), vertxHttpConfiguration, routeHandlers, - launchMode); + List appRoutes = initialize(vertx.getValue(), httpConfiguration, routeHandlers, filters, + launchMode, defaultRoute); container.instance(RouterProducer.class).initialize(router); if (launchMode == LaunchMode.DEVELOPMENT) { @@ -68,16 +114,19 @@ public void run() { } } }); + } else { + shutdown.addShutdownTask(closeTask); } } - List initialize(Vertx vertx, VertxHttpConfiguration vertxHttpConfiguration, + List initialize(Vertx vertx, HttpConfiguration httpConfiguration, Map> routeHandlers, - LaunchMode launchMode) { + List> filters, + LaunchMode launchMode, + Handler defaultRoute) throws IOException { List routes = new ArrayList<>(); if (router == null) { router = Router.router(vertx); - router.route().handler(BodyHandler.create()); if (hotReplacementHandler != null) { router.route().blockingHandler(hotReplacementHandler); } @@ -85,57 +134,202 @@ List initialize(Vertx vertx, VertxHttpConfiguration vert for (Entry> entry : routeHandlers.entrySet()) { Handler handler = createHandler(entry.getKey()); for (Route route : entry.getValue()) { - routes.add(addRoute(router, handler, route)); + routes.add(addRoute(router, handler, route, filters)); } } // Make it also possible to register the route handlers programmatically Event event = Arc.container().beanManager().getEvent(); event.select(Router.class).fire(router); + for (Handler i : filters) { + if (i != null) { + router.route().handler(i); + } + } + if (defaultRoute != null) { + //TODO: can we skip the router if no other routes? + router.route().handler(new Handler() { + @Override + public void handle(RoutingContext event) { + defaultRoute.handle(event.request()); + } + }); + } + // Start the server - if (server == null) { - CountDownLatch latch = new CountDownLatch(1); - // Http server configuration - HttpServerOptions httpServerOptions = createHttpServerOptions(vertxHttpConfiguration, launchMode); - event.select(HttpServerOptions.class).fire(httpServerOptions); - AtomicReference failure = new AtomicReference<>(); - server = vertx.createHttpServer(httpServerOptions).requestHandler(router) - .listen(ar -> { - if (ar.succeeded()) { - // TODO log proper message - Timing.setHttpServer(String.format( - "Listening on: http://%s:%s", httpServerOptions.getHost(), httpServerOptions.getPort())); - - } else { - // We can't throw an exception from here as we are on the event loop. - // We store the failure in a reference. - // The reference will be checked in the main thread, and the failure re-thrown. - failure.set(ar.cause()); + if (closeTask == null) { + doServerStart(vertx, httpConfiguration, launchMode); + } + return routes; + } + + private static void doServerStart(Vertx vertx, HttpConfiguration httpConfiguration, LaunchMode launchMode) + throws IOException { + CountDownLatch latch = new CountDownLatch(1); + // Http server configuration + HttpServerOptions httpServerOptions = createHttpServerOptions(httpConfiguration, launchMode); + HttpServerOptions sslConfig = createSslOptions(httpConfiguration, launchMode); + + int ioThreads = httpConfiguration.ioThreads.orElse(Runtime.getRuntime().availableProcessors() * 2); + CompletableFuture futureResult = new CompletableFuture<>(); + vertx.deployVerticle(new Supplier() { + @Override + public Verticle get() { + return new WebDeploymentVerticle(httpConfiguration.determinePort(launchMode), + httpConfiguration.determineSslPort(launchMode), httpConfiguration.host, httpServerOptions, sslConfig, + router); + } + }, new DeploymentOptions().setInstances(ioThreads), new Handler>() { + @Override + public void handle(AsyncResult event) { + if (event.failed()) { + futureResult.completeExceptionally(event.cause()); + } else { + futureResult.complete(event.result()); + } + } + }); + try { + + String deploymentId = futureResult.get(); + closeTask = new Runnable() { + @Override + public void run() { + CountDownLatch latch = new CountDownLatch(1); + vertx.undeploy(deploymentId, new Handler>() { + @Override + public void handle(AsyncResult event) { + latch.countDown(); } - latch.countDown(); }); - try { - latch.await(); - if (failure.get() != null) { - throw new IllegalStateException("Unable to start the HTTP server", failure.get()); + try { + latch.await(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + router = null; + closeTask = null; + } + }; + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException("Unable to start HTTP server", e); + } + + // TODO log proper message + Timing.setHttpServer(String.format( + "Listening on: http://%s:%s", httpServerOptions.getHost(), httpServerOptions.getPort())); + } + + /** + * Get an {@code HttpServerOptions} for this server configuration, or null if SSL should not be enabled + * + */ + private static HttpServerOptions createSslOptions(HttpConfiguration httpConfiguration, LaunchMode launchMode) + throws IOException { + ServerSslConfig sslConfig = httpConfiguration.ssl; + //TODO: static fields break config + Logger log = Logger.getLogger("io.quarkus.configuration.ssl"); + final Optional certFile = sslConfig.certificate.file; + final Optional keyFile = sslConfig.certificate.keyFile; + final Optional keyStoreFile = sslConfig.certificate.keyStoreFile; + final String keystorePassword = sslConfig.certificate.keyStorePassword; + final HttpServerOptions serverOptions = new HttpServerOptions(); + if (certFile.isPresent() && keyFile.isPresent()) { + PemKeyCertOptions pemKeyCertOptions = new PemKeyCertOptions() + .setCertPath(certFile.get().toAbsolutePath().toString()) + .setKeyPath(keyFile.get().toAbsolutePath().toString()); + serverOptions.setPemKeyCertOptions(pemKeyCertOptions); + } else if (keyStoreFile.isPresent()) { + final Path keyStorePath = keyStoreFile.get(); + final Optional keyStoreFileType = sslConfig.certificate.keyStoreFileType; + final String type; + if (keyStoreFileType.isPresent()) { + type = keyStoreFileType.get().toLowerCase(); + } else { + final String pathName = keyStorePath.toString(); + if (pathName.endsWith(".p12") || pathName.endsWith(".pkcs12") || pathName.endsWith(".pfx")) { + type = "pkcs12"; + } else { + // assume jks + type = "jks"; } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new IllegalStateException("Unable to start the HTTP server", e); } + + //load the data + byte[] data; + final InputStream keystoreAsResource = Thread.currentThread().getContextClassLoader() + .getResourceAsStream(keyStorePath.toString()); + + if (keystoreAsResource != null) { + try (InputStream is = keystoreAsResource) { + data = doRead(is); + } + } else { + try (InputStream is = Files.newInputStream(keyStorePath)) { + data = doRead(is); + } + } + + switch (type) { + case "pkcs12": { + PfxOptions options = new PfxOptions() + .setPassword(keystorePassword) + .setValue(Buffer.buffer(data)); + serverOptions.setPfxKeyCertOptions(options); + break; + } + case "jks": { + JksOptions options = new JksOptions() + .setPassword(keystorePassword) + .setValue(Buffer.buffer(data)); + serverOptions.setKeyStoreOptions(options); + break; + } + default: + throw new IllegalArgumentException("Unknown keystore type: " + type + " valid types are jks or pkcs12"); + } + + } else { + return null; } - return routes; + + for (String i : sslConfig.cipherSuites) { + if (!i.isEmpty()) { + serverOptions.addEnabledCipherSuite(i); + } + } + + for (String i : sslConfig.protocols) { + if (!i.isEmpty()) { + serverOptions.addEnabledSecureTransportProtocol(i); + } + } + serverOptions.setSsl(true); + serverOptions.setHost(httpConfiguration.host); + serverOptions.setPort(httpConfiguration.determineSslPort(launchMode)); + return serverOptions; } - private HttpServerOptions createHttpServerOptions(VertxHttpConfiguration vertxHttpConfiguration, LaunchMode launchMode) { + private static byte[] doRead(InputStream is) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buf = new byte[1024]; + int r; + while ((r = is.read(buf)) > 0) { + out.write(buf, 0, r); + } + return out.toByteArray(); + } + + private static HttpServerOptions createHttpServerOptions(HttpConfiguration httpConfiguration, LaunchMode launchMode) { // TODO other config properties HttpServerOptions options = new HttpServerOptions(); - options.setHost(vertxHttpConfiguration.host); - options.setPort(vertxHttpConfiguration.determinePort(launchMode)); + options.setHost(httpConfiguration.host); + options.setPort(httpConfiguration.determinePort(launchMode)); return options; } - private io.vertx.ext.web.Route addRoute(Router router, Handler handler, Route routeAnnotation) { + private io.vertx.ext.web.Route addRoute(Router router, Handler handler, Route routeAnnotation, + List> filters) { io.vertx.ext.web.Route route; if (!routeAnnotation.regex().isEmpty()) { route = router.routeWithRegex(routeAnnotation.regex()); @@ -162,6 +356,13 @@ private io.vertx.ext.web.Route addRoute(Router router, Handler h route.consumes(consumes); } } + + for (Handler i : filters) { + if (i != null) { + route.handler(i); + } + } + route.handler(BodyHandler.create()); switch (routeAnnotation.type()) { case NORMAL: route.handler(handler); @@ -196,4 +397,73 @@ private Handler createHandler(String handlerClassName) { } } + private static class WebDeploymentVerticle implements Verticle { + + private final int port; + private final int httpsPort; + private final String host; + private HttpServer httpServer; + private HttpServer httpsServer; + private Vertx vertx; + private final HttpServerOptions httpOptions; + private final HttpServerOptions httpsOptions; + private final Router router; + + public WebDeploymentVerticle(int port, int httpsPort, String host, HttpServerOptions httpOptions, + HttpServerOptions httpsOptions, Router router) { + this.port = port; + this.httpsPort = httpsPort; + this.host = host; + this.httpOptions = httpOptions; + this.httpsOptions = httpsOptions; + this.router = router; + } + + @Override + public Vertx getVertx() { + return vertx; + } + + @Override + public void init(Vertx vertx, Context context) { + this.vertx = vertx; + } + + @Override + public void start(Future startFuture) throws Exception { + final AtomicInteger remainingCount = new AtomicInteger(httpsOptions != null ? 2 : 1); + Handler> doneHandler = event -> { + if (remainingCount.decrementAndGet() == 0) { + startFuture.complete(); + } + }; + httpServer = vertx.createHttpServer(httpOptions); + httpServer.requestHandler(router); + httpServer.listen(port, host, doneHandler); + if (httpsOptions != null) { + httpsServer = vertx.createHttpServer(httpsOptions); + httpsServer.requestHandler(router); + httpsServer.listen(httpsPort, host, doneHandler); + } + } + + @Override + public void stop(Future stopFuture) throws Exception { + httpServer.close(new Handler>() { + @Override + public void handle(AsyncResult event) { + if (httpsServer != null) { + httpsServer.close(new Handler>() { + @Override + public void handle(AsyncResult event) { + stopFuture.complete(); + } + }); + } else { + stopFuture.complete(); + } + } + }); + } + } } diff --git a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/filters/CORSConfig.java b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/cors/CORSConfig.java similarity index 97% rename from extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/filters/CORSConfig.java rename to extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/cors/CORSConfig.java index 3a40c50a9963f..f4734493a6206 100644 --- a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/filters/CORSConfig.java +++ b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/cors/CORSConfig.java @@ -1,4 +1,4 @@ -package io.quarkus.undertow.runtime.filters; +package io.quarkus.vertx.web.runtime.cors; import java.util.Optional; diff --git a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/filters/CORSFilter.java b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/cors/CORSFilter.java similarity index 60% rename from extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/filters/CORSFilter.java rename to extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/cors/CORSFilter.java index dc1997a948483..c06ac32f00859 100644 --- a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/filters/CORSFilter.java +++ b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/cors/CORSFilter.java @@ -1,23 +1,20 @@ -package io.quarkus.undertow.runtime.filters; +package io.quarkus.vertx.web.runtime.cors; -import java.io.IOException; import java.util.Arrays; import java.util.Objects; import java.util.stream.Collectors; -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import io.vertx.core.Handler; +import io.vertx.core.http.HttpMethod; +import io.vertx.core.http.HttpServerRequest; +import io.vertx.core.http.HttpServerResponse; +import io.vertx.ext.web.RoutingContext; -public class CORSFilter implements Filter { +public class CORSFilter implements Handler { // This is set in the recorder at runtime. // Must be static because the filter is created(deployed) at build time and runtime config is still not available - static CORSConfig corsConfig; + final CORSConfig corsConfig; private static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin"; private static final String ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials"; @@ -28,18 +25,34 @@ public class CORSFilter implements Filter { private static final String ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers"; private static final String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers"; - public CORSFilter() { + public CORSFilter(CORSConfig corsConfig) { + this.corsConfig = corsConfig; + } + + private void processHeaders(HttpServerResponse response, String requestedHeaders, String allowedHeaders) { + String validHeaders = Arrays.stream(requestedHeaders.split(",")) + .filter(allowedHeaders::contains) + .collect(Collectors.joining(",")); + if (!validHeaders.isEmpty()) + response.headers().set(ACCESS_CONTROL_ALLOW_HEADERS, validHeaders); + } + + private void processMethods(HttpServerResponse response, String requestedMethods, String allowedMethods) { + String validMethods = Arrays.stream(requestedMethods.split(",")) + .filter(allowedMethods::contains) + .collect(Collectors.joining(",")); + if (!validMethods.isEmpty()) + response.headers().set(ACCESS_CONTROL_ALLOW_METHODS, validMethods); } @Override - public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) - throws IOException, ServletException { + public void handle(RoutingContext event) { Objects.requireNonNull(corsConfig, "CORS config is not set"); - HttpServletRequest request = (HttpServletRequest) servletRequest; - HttpServletResponse response = (HttpServletResponse) servletResponse; + HttpServerRequest request = event.request(); + HttpServerResponse response = event.response(); String origin = request.getHeader(ORIGIN); if (origin == null) { - chain.doFilter(servletRequest, servletResponse); + event.next(); } else { String requestedMethods = request.getHeader(ACCESS_CONTROL_REQUEST_METHOD); if (requestedMethods != null) { @@ -52,30 +65,14 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo String allowedOrigins = corsConfig.origins.orElse(null); boolean allowsOrigin = allowedOrigins == null || allowedOrigins.contains(origin); if (allowsOrigin) - response.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN, origin); - response.setHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"); - corsConfig.exposedHeaders.ifPresent(exposed -> response.setHeader(ACCESS_CONTROL_EXPOSE_HEADERS, exposed)); - if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { - response.flushBuffer(); + response.headers().set(ACCESS_CONTROL_ALLOW_ORIGIN, origin); + response.headers().set(ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"); + corsConfig.exposedHeaders.ifPresent(exposed -> response.headers().set(ACCESS_CONTROL_EXPOSE_HEADERS, exposed)); + if (request.method().equals(HttpMethod.OPTIONS)) { + response.end(); } else { - chain.doFilter(servletRequest, servletResponse); + event.next(); } } } - - private void processHeaders(HttpServletResponse response, String requestedHeaders, String allowedHeaders) { - String validHeaders = Arrays.stream(requestedHeaders.split(",")) - .filter(allowedHeaders::contains) - .collect(Collectors.joining(",")); - if (!validHeaders.isEmpty()) - response.setHeader(ACCESS_CONTROL_ALLOW_HEADERS, validHeaders); - } - - private void processMethods(HttpServletResponse response, String requestedMethods, String allowedMethods) { - String validMethods = Arrays.stream(requestedMethods.split(",")) - .filter(allowedMethods::contains) - .collect(Collectors.joining(",")); - if (!validMethods.isEmpty()) - response.setHeader(ACCESS_CONTROL_ALLOW_METHODS, validMethods); - } } diff --git a/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/cors/CORSRecorder.java b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/cors/CORSRecorder.java new file mode 100644 index 0000000000000..ae820b9d9efc3 --- /dev/null +++ b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/cors/CORSRecorder.java @@ -0,0 +1,18 @@ +package io.quarkus.vertx.web.runtime.cors; + +import io.quarkus.runtime.annotations.Recorder; +import io.quarkus.vertx.web.runtime.HttpConfiguration; +import io.vertx.core.Handler; +import io.vertx.ext.web.RoutingContext; + +@Recorder +public class CORSRecorder { + + public Handler corsHandler(HttpConfiguration configuration) { + if (configuration.corsEnabled) { + return new CORSFilter(configuration.cors); + } + return null; + } + +} diff --git a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/VertxRecorder.java b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/VertxRecorder.java index 3af3f928b0caa..7d95452266ab0 100644 --- a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/VertxRecorder.java +++ b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/VertxRecorder.java @@ -64,7 +64,11 @@ public void run() { return new RuntimeValue(vertx); } - void initialize(VertxConfiguration conf) { + public static Vertx getVertx() { + return vertx; + } + + public static void initialize(VertxConfiguration conf) { if (vertx != null) { return; } @@ -106,7 +110,7 @@ void initialize(VertxConfiguration conf) { messageConsumers = new ArrayList<>(); } - private VertxOptions convertToVertxOptions(VertxConfiguration conf) { + private static VertxOptions convertToVertxOptions(VertxConfiguration conf) { VertxOptions options = new VertxOptions(); // Order matters, as the cluster options modifies the event bus options. setEventBusOptions(conf, options); @@ -157,7 +161,7 @@ void destroy() { } } - private void initializeClusterOptions(VertxConfiguration conf, VertxOptions options) { + private static void initializeClusterOptions(VertxConfiguration conf, VertxOptions options) { ClusterConfiguration cluster = conf.cluster; options.getEventBusOptions().setClustered(cluster.clustered); options.getEventBusOptions().setClusterPingReplyInterval(cluster.pingReplyInterval.toMillis()); @@ -174,7 +178,7 @@ private void initializeClusterOptions(VertxConfiguration conf, VertxOptions opti } } - private void setEventBusOptions(VertxConfiguration conf, VertxOptions options) { + private static void setEventBusOptions(VertxConfiguration conf, VertxOptions options) { EventBusConfiguration eb = conf.eventbus; EventBusOptions opts = new EventBusOptions(); opts.setAcceptBacklog(eb.acceptBacklog.orElse(-1)); diff --git a/extensions/vertx/runtime/src/test/java/io/quarkus/vertx/runtime/VertxProducerTest.java b/extensions/vertx/runtime/src/test/java/io/quarkus/vertx/runtime/VertxProducerTest.java index ff68a136e1a33..3546787599a59 100644 --- a/extensions/vertx/runtime/src/test/java/io/quarkus/vertx/runtime/VertxProducerTest.java +++ b/extensions/vertx/runtime/src/test/java/io/quarkus/vertx/runtime/VertxProducerTest.java @@ -30,7 +30,7 @@ public void tearDown() throws Exception { @Test public void shouldNotFailWithoutConfig() { - recorder.initialize(null); + VertxRecorder.initialize(null); producer.initialize(VertxRecorder.vertx); verifyProducer(); } @@ -55,7 +55,7 @@ public void shouldNotFailWithDefaultConfig() { configuration.workerPoolSize = 10; configuration.warningExceptionTime = Duration.ofSeconds(1); configuration.internalBlockingPoolSize = 5; - recorder.initialize(configuration); + VertxRecorder.initialize(configuration); producer.initialize(VertxRecorder.vertx); verifyProducer(); } @@ -76,7 +76,7 @@ public void shouldEnableClustering() { configuration.cluster = cc; try { - recorder.initialize(configuration); + VertxRecorder.initialize(configuration); fail("It should not have a cluster manager on the classpath, and so fail the creation"); } catch (IllegalStateException e) { assertTrue(e.getMessage().contains("No ClusterManagerFactory")); diff --git a/independent-projects/bootstrap/maven-plugin/pom.xml b/independent-projects/bootstrap/maven-plugin/pom.xml index c7b5c1943aba9..5485051beeb7e 100644 --- a/independent-projects/bootstrap/maven-plugin/pom.xml +++ b/independent-projects/bootstrap/maven-plugin/pom.xml @@ -66,4 +66,4 @@ junit - \ No newline at end of file + diff --git a/integration-tests/elytron-security-oauth2/pom.xml b/integration-tests/elytron-security-oauth2/pom.xml index 5241088127bc7..239584d5c5b3c 100644 --- a/integration-tests/elytron-security-oauth2/pom.xml +++ b/integration-tests/elytron-security-oauth2/pom.xml @@ -127,4 +127,4 @@ - \ No newline at end of file + diff --git a/integration-tests/keycloak/pom.xml b/integration-tests/keycloak/pom.xml index 6bb4d1c866670..81d46bd859765 100644 --- a/integration-tests/keycloak/pom.xml +++ b/integration-tests/keycloak/pom.xml @@ -90,12 +90,18 @@ maven-surefire-plugin false + + ${keycloak.url} + maven-failsafe-plugin false + + ${keycloak.url} + @@ -205,13 +211,10 @@ - - direct - - 8080 - - - + + http://localhost:8180 + + diff --git a/integration-tests/mongodb-client/pom.xml b/integration-tests/mongodb-client/pom.xml index d4f5545d3328d..a6d1f65a41fef 100644 --- a/integration-tests/mongodb-client/pom.xml +++ b/integration-tests/mongodb-client/pom.xml @@ -129,4 +129,4 @@ - \ No newline at end of file + diff --git a/integration-tests/reactive-pg-client/src/main/java/io/quarkus/it/reactive/pg/client/FruitResource.java b/integration-tests/reactive-pg-client/src/main/java/io/quarkus/it/reactive/pg/client/FruitResource.java index 459948bad1f70..eababbb7341fb 100644 --- a/integration-tests/reactive-pg-client/src/main/java/io/quarkus/it/reactive/pg/client/FruitResource.java +++ b/integration-tests/reactive-pg-client/src/main/java/io/quarkus/it/reactive/pg/client/FruitResource.java @@ -34,7 +34,7 @@ void setupDb() { @GET @Produces(MediaType.APPLICATION_JSON) public CompletionStage listFruits() { - return client.query("SELECT * FROM fruits").thenApply(pgRowSet -> { + return client.query("SELECT * FROM fruits").thenApplyAsync(pgRowSet -> { JsonArray jsonArray = new JsonArray(); for (Row row : pgRowSet) { jsonArray.add(toJson(row)); diff --git a/integration-tests/vertx/src/main/java/io/quarkus/it/vertx/VertxProducerResource.java b/integration-tests/vertx/src/main/java/io/quarkus/it/vertx/VertxProducerResource.java index c81fdfc8e018a..2105e3d05fb1e 100644 --- a/integration-tests/vertx/src/main/java/io/quarkus/it/vertx/VertxProducerResource.java +++ b/integration-tests/vertx/src/main/java/io/quarkus/it/vertx/VertxProducerResource.java @@ -5,6 +5,7 @@ import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import java.util.function.Function; import javax.inject.Inject; import javax.ws.rs.GET; @@ -57,7 +58,7 @@ public CompletionStage eb() { } }); - return future; + return future.thenApplyAsync(Function.identity()); } } diff --git a/test-framework/amazon-lambda/pom.xml b/test-framework/amazon-lambda/pom.xml index e6c4be2d11363..888c086e4ad62 100644 --- a/test-framework/amazon-lambda/pom.xml +++ b/test-framework/amazon-lambda/pom.xml @@ -24,8 +24,12 @@ quarkus-test-common - io.undertow - undertow-core + io.quarkus.http + quarkus-http-core + + + io.quarkus.http + quarkus-http-vertx-backend com.fasterxml.jackson.core diff --git a/test-framework/amazon-lambda/src/main/java/io/quarkus/amazon/lambda/test/LambdaResourceManager.java b/test-framework/amazon-lambda/src/main/java/io/quarkus/amazon/lambda/test/LambdaResourceManager.java index 16b32dfd2c468..933baad98fd8a 100644 --- a/test-framework/amazon-lambda/src/main/java/io/quarkus/amazon/lambda/test/LambdaResourceManager.java +++ b/test-framework/amazon-lambda/src/main/java/io/quarkus/amazon/lambda/test/LambdaResourceManager.java @@ -1,6 +1,8 @@ package io.quarkus.amazon.lambda.test; +import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.Map; import java.util.concurrent.CompletableFuture; @@ -12,12 +14,10 @@ import io.quarkus.amazon.lambda.runtime.FunctionError; import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; import io.undertow.Undertow; -import io.undertow.io.Receiver; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.RoutingHandler; import io.undertow.server.handlers.BlockingHandler; -import io.undertow.util.HttpString; public class LambdaResourceManager implements QuarkusTestResourceLifecycleManager { @@ -40,8 +40,8 @@ public void handleRequest(HttpServerExchange exchange) throws Exception { return; } } - exchange.getResponseHeaders().put(new HttpString(AmazonLambdaApi.LAMBDA_RUNTIME_AWS_REQUEST_ID), req.id); - exchange.getResponseSender().send(req.json); + exchange.addResponseHeader(AmazonLambdaApi.LAMBDA_RUNTIME_AWS_REQUEST_ID, req.id); + exchange.writeAsync(req.json); } }); routingHandler.add("POST", AmazonLambdaApi.API_PATH_INVOCATION + "{req}" + AmazonLambdaApi.API_PATH_RESPONSE, @@ -49,12 +49,13 @@ public void handleRequest(HttpServerExchange exchange) throws Exception { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { String id = exchange.getQueryParameters().get("req").getFirst(); - exchange.getRequestReceiver().receiveFullString(new Receiver.FullStringCallback() { - @Override - public void handle(HttpServerExchange exchange, String message) { - LambdaClient.REQUESTS.get(id).complete(message); - } - }); + byte[] data = new byte[1024]; + int r; + ByteArrayOutputStream bao = new ByteArrayOutputStream(); + while ((r = exchange.getInputStream().read(data)) > 0) { + bao.write(data, 0, r); + } + LambdaClient.REQUESTS.get(id).complete(new String(bao.toByteArray(), StandardCharsets.UTF_8)); } }); @@ -63,41 +64,47 @@ public void handle(HttpServerExchange exchange, String message) { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { String id = exchange.getQueryParameters().get("req").getFirst(); - exchange.getRequestReceiver().receiveFullString(new Receiver.FullStringCallback() { - @Override - public void handle(HttpServerExchange exchange, String message) { - ObjectMapper mapper = new ObjectMapper(); - try { - FunctionError result = mapper.readerFor(FunctionError.class).readValue(message); - - LambdaClient.REQUESTS.get(id).completeExceptionally( - new LambdaException(result.getErrorType(), result.getErrorMessage())); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - }); - } - }); - routingHandler.add("POST", AmazonLambdaApi.API_PATH_INIT_ERROR, new HttpHandler() { - @Override - public void handleRequest(HttpServerExchange exchange) throws Exception { - exchange.getRequestReceiver().receiveFullString(new Receiver.FullStringCallback() { - @Override - public void handle(HttpServerExchange exchange, String message) { + byte[] data = new byte[1024]; + int r; + ByteArrayOutputStream bao = new ByteArrayOutputStream(); + while ((r = exchange.getInputStream().read(data)) > 0) { + bao.write(data, 0, r); + } + String body = new String(bao.toByteArray(), StandardCharsets.UTF_8); ObjectMapper mapper = new ObjectMapper(); try { - FunctionError result = mapper.readerFor(FunctionError.class).readValue(message); - LambdaClient.problem = new LambdaException(result.getErrorType(), result.getErrorMessage()); - LambdaStartedNotifier.started = true; - for (Map.Entry> e : LambdaClient.REQUESTS.entrySet()) { - e.getValue().completeExceptionally(LambdaClient.problem); - } + FunctionError result = mapper.readerFor(FunctionError.class).readValue(body); + + LambdaClient.REQUESTS.get(id).completeExceptionally( + new LambdaException(result.getErrorType(), result.getErrorMessage())); } catch (IOException e) { throw new RuntimeException(e); } } }); + routingHandler.add("POST", AmazonLambdaApi.API_PATH_INIT_ERROR, new HttpHandler() { + @Override + public void handleRequest(HttpServerExchange exchange) throws Exception { + + byte[] data = new byte[1024]; + int r; + ByteArrayOutputStream bao = new ByteArrayOutputStream(); + while ((r = exchange.getInputStream().read(data)) > 0) { + bao.write(data, 0, r); + } + String body = new String(bao.toByteArray(), StandardCharsets.UTF_8); + + ObjectMapper mapper = new ObjectMapper(); + try { + FunctionError result = mapper.readerFor(FunctionError.class).readValue(body); + LambdaClient.problem = new LambdaException(result.getErrorType(), result.getErrorMessage()); + LambdaStartedNotifier.started = true; + for (Map.Entry> e : LambdaClient.REQUESTS.entrySet()) { + e.getValue().completeExceptionally(LambdaClient.problem); + } + } catch (IOException e) { + throw new RuntimeException(e); + } } }); undertow = Undertow.builder().addHttpListener(PORT, "localhost") diff --git a/test-framework/common/src/main/java/io/quarkus/test/common/TestResourceManager.java b/test-framework/common/src/main/java/io/quarkus/test/common/TestResourceManager.java index 396acf6d7b77a..1e0f562f038fc 100644 --- a/test-framework/common/src/main/java/io/quarkus/test/common/TestResourceManager.java +++ b/test-framework/common/src/main/java/io/quarkus/test/common/TestResourceManager.java @@ -48,7 +48,11 @@ public Map start() { oldSystemProps = new HashMap<>(); for (Map.Entry i : ret.entrySet()) { oldSystemProps.put(i.getKey(), System.getProperty(i.getKey())); - System.setProperty(i.getKey(), i.getValue()); + if (i.getValue() == null) { + System.clearProperty(i.getKey()); + } else { + System.setProperty(i.getKey(), i.getValue()); + } } return ret; }