diff --git a/dist/platform/pom.xml b/dist/platform/pom.xml index cd9ffec360..6c03c6374d 100644 --- a/dist/platform/pom.xml +++ b/dist/platform/pom.xml @@ -72,7 +72,11 @@ httpPort httpPort2 - httpsPort2 + httpPort2b + httpPort3 + httpPort3b + httpPort4 + httpPort4b @@ -96,16 +100,6 @@ - - org.apache.maven.plugins - maven-surefire-plugin - - - ${httpPort2} - ${httpsPort2} - - - org.apache.maven.plugins maven-failsafe-plugin @@ -125,7 +119,11 @@ ${httpPort} ${httpPort2} - ${httpsPort2} + ${httpPort2b} + ${httpPort2} + ${httpPort2b} + ${httpPort2} + ${httpPort2b} diff --git a/dist/platform/src/test/java/cloud/piranha/dist/platform/PlatformPiranhaBuilderTest.java b/dist/platform/src/test/java/cloud/piranha/dist/platform/PlatformPiranhaBuilderTest.java index 1931a93fd6..f409b6a627 100644 --- a/dist/platform/src/test/java/cloud/piranha/dist/platform/PlatformPiranhaBuilderTest.java +++ b/dist/platform/src/test/java/cloud/piranha/dist/platform/PlatformPiranhaBuilderTest.java @@ -75,7 +75,7 @@ void testHttpPort2() throws Exception { PlatformPiranha piranha = new PlatformPiranhaBuilder() .extensionClass(PlatformExtension.class) .httpPort(-1) - .httpsPort(Integer.parseInt(System.getProperty("httpsPort2"))) + .httpsPort(Integer.parseInt(System.getProperty("httpPort2b"))) .build(); piranha.start(); Thread.sleep(5000); @@ -99,13 +99,13 @@ void testHttpsPort2() throws Exception { .extensionClass(PlatformExtension.class) .httpsKeystoreFile("src/main/zip/etc/keystore.jks") .httpsKeystorePassword("password") - .httpPort(8228) - .httpsPort(8338) + .httpPort(Integer.parseInt(System.getProperty("httpPort3"))) + .httpsPort(Integer.parseInt(System.getProperty("httpPort3b"))) .build(); piranha.start(); Thread.sleep(5000); SocketFactory factory = SSLSocketFactory.getDefault(); - try (SSLSocket socket = (SSLSocket) factory.createSocket("localhost", 8338)) { + try (SSLSocket socket = (SSLSocket) factory.createSocket("localhost", Integer.parseInt(System.getProperty("httpPort3b")))) { assertNotNull(socket.getOutputStream()); assertNotNull(socket.getSSLParameters()); assertEquals("TLSv1.3", socket.getSSLParameters().getProtocols()[0]); @@ -123,12 +123,12 @@ void testHttpsPort2() throws Exception { void testDefaultExtensionClass() throws Exception { PlatformPiranha piranha = new PlatformPiranhaBuilder() .extensionClass(PlatformExtension.class.getName()) - .httpPort(8080) + .httpPort(Integer.parseInt(System.getProperty("httpPort4"))) .verbose(true) .build(); piranha.start(); Thread.sleep(5000); - try (Socket socket = new Socket("localhost", 8080)) { + try (Socket socket = new Socket("localhost", Integer.parseInt(System.getProperty("httpPort4")))) { assertNotNull(socket.getOutputStream()); } catch (ConnectException e) { } diff --git a/multi/pom.xml b/multi/pom.xml new file mode 100644 index 0000000000..98a5d80bbd --- /dev/null +++ b/multi/pom.xml @@ -0,0 +1,146 @@ + + + + 4.0.0 + + + cloud.piranha + project + 24.8.0-SNAPSHOT + + + cloud.piranha + piranha-multi + jar + + Piranha - Multi + + + + + cloud.piranha.feature + piranha-feature-exitonstop + ${project.version} + compile + + + cloud.piranha.feature + piranha-feature-http + ${project.version} + compile + + + cloud.piranha.feature + piranha-feature-https + ${project.version} + compile + + + cloud.piranha.feature + piranha-feature-logging + ${project.version} + compile + + + cloud.piranha.feature + piranha-feature-webapps + ${project.version} + compile + + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.jupiter + junit-jupiter-params + test + + + + + piranha-multi + + + org.codehaus.mojo + build-helper-maven-plugin + + + reserve-network-port + + reserve-network-port + + validate + + + httpPort + httpPort2 + httpsPort2 + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + package + + single + + + false + + src/main/assembly/zip.xml + + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + verify + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + true + cloud.piranha.multi.MultiPiranhaMain + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + ${httpPort} + ${httpPort2} + ${httpsPort2} + + + + + + diff --git a/multi/src/main/assembly/zip.xml b/multi/src/main/assembly/zip.xml new file mode 100644 index 0000000000..e618f8f7c6 --- /dev/null +++ b/multi/src/main/assembly/zip.xml @@ -0,0 +1,35 @@ + + zip + piranha + + zip + tar.gz + + + + ${project.basedir}/src/main/zip + + + + ${project.build.directory}/webapps + webapps + + + + + ${project.build.directory}/piranha-multi.jar + lib + + + + + + ${project.groupId}:${project.artifactId}:jar:* + + lib + true + + + diff --git a/multi/src/main/docker/Dockerfile b/multi/src/main/docker/Dockerfile new file mode 100644 index 0000000000..bc7cd0a79e --- /dev/null +++ b/multi/src/main/docker/Dockerfile @@ -0,0 +1,7 @@ +FROM eclipse-temurin:21 +RUN useradd -m piranha +ADD target/piranha-multi.tar.gz /home/piranha/ +RUN chown -R piranha:piranha /home/piranha +WORKDIR /home/piranha/piranha/bin +USER piranha +CMD ["/bin/bash", "./run.sh"] diff --git a/multi/src/main/java/cloud/piranha/multi/MultiPiranha.java b/multi/src/main/java/cloud/piranha/multi/MultiPiranha.java new file mode 100644 index 0000000000..e9f9a9d421 --- /dev/null +++ b/multi/src/main/java/cloud/piranha/multi/MultiPiranha.java @@ -0,0 +1,346 @@ +/* + * Copyright (c) 2002-2024 Manorrock.com. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package cloud.piranha.multi; + +import cloud.piranha.core.api.Piranha; +import cloud.piranha.core.api.PiranhaConfiguration; +import cloud.piranha.core.api.WebApplicationExtension; +import cloud.piranha.core.impl.DefaultPiranhaConfiguration; +import cloud.piranha.feature.api.FeatureManager; +import cloud.piranha.feature.exitonstop.ExitOnStopFeature; +import cloud.piranha.feature.http.HttpFeature; +import cloud.piranha.feature.https.HttpsFeature; +import cloud.piranha.feature.impl.DefaultFeatureManager; +import cloud.piranha.feature.logging.LoggingFeature; +import cloud.piranha.feature.webapps.WebAppsFeature; +import cloud.piranha.http.api.HttpServer; +import java.io.File; +import java.io.IOException; +import java.lang.System.Logger; +import static java.lang.System.Logger.Level.INFO; +import static java.lang.System.Logger.Level.WARNING; +import java.nio.file.Files; + +/** + * The Multi version of Piranha. + * + *

+ * This version of Piranha supports the following: + *

+ *
    + *
  1. Running with Java modules
  2. + *
  3. Exiting on stop
  4. + *
  5. Exposing a HTTP endpoint
  6. + *
  7. Exposing a HTTPS endpoint
  8. + *
  9. Setting the logging level
  10. + *
  11. Hosting multiple web application
  12. + *
+ * + * @author Manfred Riem (mriem@manorrock.com) + */ +public class MultiPiranha implements Piranha, Runnable { + + /** + * Stores the logger. + */ + private static final Logger LOGGER = System.getLogger(MultiPiranha.class.getName()); + + /** + * Stores the 'tmp/piranha.pid' file constant. + */ + private static final String PID_FILE = "tmp/piranha.pid"; + + /** + * Stores the configuration. + */ + private final PiranhaConfiguration configuration; + + /** + * Stores the feature manager. + */ + private final FeatureManager featureManager; + + /** + * Stores the HTTP feature. + */ + private HttpFeature httpFeature; + + /** + * Stores the HTTP server. + */ + private HttpServer httpServer; + + /** + * Stores the HTTP feature. + */ + private HttpsFeature httpsFeature; + + /** + * Stores the HTTP server. + */ + private HttpServer httpsServer; + + /** + * Stores the started flag. + */ + private boolean started = false; + + /** + * Stores the thread we use. + */ + private Thread thread; + + /** + * Stores the WebAppsFeature. + */ + private WebAppsFeature webAppsFeature; + + /** + * Constructor. + */ + public MultiPiranha() { + configuration = new DefaultPiranhaConfiguration(); + configuration.setBoolean("exitOnStop", false); + configuration.setInteger("httpPort", 8080); + configuration.setInteger("httpsPort", -1); + configuration.setBoolean("jpmsEnabled", false); + configuration.setFile("webAppsDir", new File("webapps")); + featureManager = new DefaultFeatureManager(); + } + + @Override + public PiranhaConfiguration getConfiguration() { + return configuration; + } + + /** + * Are we running? + * + * @return true if we are, false otherwise. + */ + private boolean isRunning() { + boolean result = false; + if (httpServer != null) { + result = httpServer.isRunning(); + } else if (httpsServer != null) { + result = httpsServer.isRunning(); + } + return result; + } + + /** + * Have we started? + * + * @return true if we have, false otherwise. + */ + private boolean isStarted() { + return started; + } + + /** + * Run method. + */ + @Override + public void run() { + long startTime = System.currentTimeMillis(); + + LoggingFeature loggingFeature = new LoggingFeature(); + featureManager.addFeature(loggingFeature); + loggingFeature.setLevel(configuration.getString("loggingLevel")); + loggingFeature.init(); + loggingFeature.start(); + + LOGGER.log(INFO, () -> "Starting Piranha"); + + webAppsFeature = new WebAppsFeature(); + featureManager.addFeature(webAppsFeature); + webAppsFeature.setExtensionClass((Class) configuration.getClass("extensionClass")); + webAppsFeature.setJpmsEnabled(configuration.getBoolean("jpmsEnabled", false)); + webAppsFeature.setWebAppsDir(configuration.getFile("webAppsDir")); + webAppsFeature.init(); + webAppsFeature.start(); + + /* + * Construct, initialize and start HTTP endpoint (if applicable). + */ + if (configuration.getInteger("httpPort") > 0) { + httpFeature = new HttpFeature(); + httpFeature.setHttpServerClass(configuration.getString("httpServerClass")); + httpFeature.setPort(configuration.getInteger("httpPort")); + httpFeature.init(); + httpFeature.getHttpServer().setHttpServerProcessor(webAppsFeature.getHttpServerProcessor()); + httpFeature.start(); + httpServer = httpFeature.getHttpServer(); + } + + /* + * Construct, initialize and start HTTPS endpoint (if applicable). + */ + if (configuration.getInteger("httpsPort") > 0) { + httpsFeature = new HttpsFeature(); + httpsFeature.setHttpsKeystoreFile(configuration.getString("httpsKeystoreFile")); + httpsFeature.setHttpsKeystorePassword(configuration.getString("httpsKeystorePassword")); + httpsFeature.setHttpsServerClass(configuration.getString("httpsServerClass")); + httpsFeature.setHttpsTruststoreFile(configuration.getString("httpsTruststoreFile")); + httpsFeature.setHttpsTruststorePassword(configuration.getString("httpsTruststorePassword")); + httpsFeature.setPort(configuration.getInteger("httpsPort")); + httpsFeature.init(); + httpsFeature.getHttpsServer().setHttpServerProcessor(webAppsFeature.getHttpServerProcessor()); + httpsFeature.start(); + httpServer = httpsFeature.getHttpsServer(); + } + + if (configuration.getBoolean("exitOnStop", false)) { + ExitOnStopFeature exitOnStopFeature = new ExitOnStopFeature(); + featureManager.addFeature(exitOnStopFeature); + } + + long finishTime = System.currentTimeMillis(); + LOGGER.log(INFO, "Started Piranha"); + LOGGER.log(INFO, "It took {0} milliseconds", finishTime - startTime); + + started = true; + + File startedFile = new File("tmp/piranha.started"); + File stoppedFile = new File("tmp/piranha.stopped"); + + if (stoppedFile.exists()) { + try { + Files.delete(stoppedFile.toPath()); + } catch (IOException ioe) { + LOGGER.log(WARNING, "Error while deleting existing piranha.stopped file", ioe); + } + } + + if (!startedFile.exists()) { + try { + startedFile.createNewFile(); + } catch (IOException ioe) { + LOGGER.log(WARNING, "Unable to create piranha.started file", ioe); + } + } + + File pidFile = new File(PID_FILE); + while (isRunning()) { + try { + Thread.sleep(2000); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + } + + if (!pidFile.exists()) { + if (httpServer != null) { + httpServer.stop(); + } + if (httpsServer != null) { + httpsServer.stop(); + } + } + } + + finishTime = System.currentTimeMillis(); + LOGGER.log(INFO, "Stopped Piranha"); + LOGGER.log(INFO, "We ran for {0} milliseconds", finishTime - startTime); + + if (startedFile.exists()) { + try { + Files.delete(startedFile.toPath()); + } catch (IOException ioe) { + LOGGER.log(WARNING, "Error while deleting existing piranha.started file", ioe); + } + } + if (!stoppedFile.exists()) { + try { + stoppedFile.createNewFile(); + } catch (IOException ioe) { + LOGGER.log(WARNING, "Unable to create piranha.stopped file", ioe); + } + } + + featureManager.stop(); + } + + /** + * Set the web applications directory. + * + * @param webAppsDir the web applications directory. + */ + public void setWebAppsDir(File webAppsDir) { + this.configuration.setFile("webAppsDir", webAppsDir); + } + + /** + * Start the server. + */ + public void start() { + File pidFile = new File(PID_FILE); + + if (!pidFile.exists()) { + try { + if (!pidFile.getParentFile().exists()) { + pidFile.getParentFile().mkdirs(); + } + pidFile.createNewFile(); + } catch (IOException ex) { + LOGGER.log(WARNING, "Unable to create PID file"); + } + } else { + LOGGER.log(WARNING, "PID file already exists"); + } + + thread = new Thread(this); + thread.setDaemon(false); + thread.start(); + + while (!isStarted()) { + try { + Thread.sleep(10); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + + /** + * Stop the server. + */ + public void stop() { + File pidFile = new File(PID_FILE); + + if (pidFile.exists()) { + try { + Files.delete(pidFile.toPath()); + } catch (IOException ioe) { + LOGGER.log(WARNING, "Error occurred while deleting PID file", ioe); + } + } + + started = false; + thread = null; + } +} diff --git a/multi/src/main/java/cloud/piranha/multi/MultiPiranhaBuilder.java b/multi/src/main/java/cloud/piranha/multi/MultiPiranhaBuilder.java new file mode 100644 index 0000000000..88f88a0f9e --- /dev/null +++ b/multi/src/main/java/cloud/piranha/multi/MultiPiranhaBuilder.java @@ -0,0 +1,293 @@ +/* + * Copyright (c) 2002-2024 Manorrock.com. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package cloud.piranha.multi; + +import cloud.piranha.core.api.PiranhaBuilder; +import java.lang.System.Logger.Level; + +import static javax.naming.Context.INITIAL_CONTEXT_FACTORY; +import cloud.piranha.core.api.WebApplicationExtension; +import java.io.File; +import static java.lang.System.Logger.Level.WARNING; + +/** + * The builder so you can easily build instances of + * {@link cloud.piranha.multi.MultiPiranha}. + * + * @author Manfred Riem (mriem@manorrock.com) + * @see cloud.piranha.multi.MultiPiranha + */ +public class MultiPiranhaBuilder implements PiranhaBuilder { + + /** + * Stores the logger. + */ + private static final System.Logger LOGGER = System.getLogger(MultiPiranhaBuilder.class.getName()); + + /** + * Stores the Piranha Server instance. + */ + private final MultiPiranha piranha = new MultiPiranha(); + + /** + * Stores the InitialContext factory. + */ + private String initialContextFactory = "com.manorrock.herring.thread.ThreadInitialContextFactory"; + + /** + * Stores the verbose flag. + */ + private boolean verbose = false; + + /** + * Build the server. + * + * @return the server. + */ + @Override + public MultiPiranha build() { + if (verbose) { + showArguments(); + } + System.setProperty(INITIAL_CONTEXT_FACTORY, initialContextFactory); + return piranha; + } + + /** + * Set the exit on stop flag. + * + * @param exitOnStop the exit on stop flag. + * @return the builder. + */ + public MultiPiranhaBuilder exitOnStop(boolean exitOnStop) { + piranha.getConfiguration().setBoolean("exitOnStop", exitOnStop); + return this; + } + + /** + * Set the extension class. + * + * @param extensionClass the extension class. + * @return the builder. + */ + public MultiPiranhaBuilder extensionClass(Class extensionClass) { + piranha.getConfiguration().setClass("extensionClass", extensionClass); + return this; + } + + /** + * Set the extension class. + * + * @param extensionClassName the extension class name. + * @return the builder. + */ + public MultiPiranhaBuilder extensionClass(String extensionClassName) { + try { + extensionClass(Class.forName(extensionClassName) + .asSubclass(WebApplicationExtension.class)); + } catch (ClassNotFoundException cnfe) { + LOGGER.log(WARNING, "Unable to load default extension class", cnfe); + } + return this; + } + + /** + * Set the HTTP server port. + * + * @param httpPort the HTTP server port. + * @return the builder. + */ + public MultiPiranhaBuilder httpPort(int httpPort) { + piranha.getConfiguration().setInteger("httpPort", httpPort); + return this; + } + + /** + * Set the HTTP server class. + * + * @param httpServerClass the HTTP server class. + * @return the builder. + */ + public MultiPiranhaBuilder httpServerClass(String httpServerClass) { + piranha.getConfiguration().setString("httpServerClass", httpServerClass); + return this; + } + + /** + * Set the HTTPS keystore file. + * + * @param httpsKeystoreFile the HTTPS keystore file. + * @return the builder. + */ + public MultiPiranhaBuilder httpsKeystoreFile(String httpsKeystoreFile) { + piranha.getConfiguration().setString("httpsKeystoreFile", httpsKeystoreFile); + return this; + } + + /** + * Set the HTTPS keystore password. + * + * @param httpsKeystorePassword the HTTPS keystore password. + * @return the builder. + */ + public MultiPiranhaBuilder httpsKeystorePassword(String httpsKeystorePassword) { + piranha.getConfiguration().setString("httpsKeystorePassword", httpsKeystorePassword); + return this; + } + + /** + * Set the HTTPS server port. + * + * @param httpsPort the HTTPS server port. + * @return the builder. + */ + public MultiPiranhaBuilder httpsPort(int httpsPort) { + piranha.getConfiguration().setInteger("httpsPort", httpsPort); + return this; + } + + /** + * Set the HTTPS server class. + * + * @param httpsServerClass the HTTPS server class. + * @return the builder. + */ + public MultiPiranhaBuilder httpsServerClass(String httpsServerClass) { + piranha.getConfiguration().setString("httpsServerClass", httpsServerClass); + return this; + } + + /** + * Set the HTTPS truststore file. + * + * @param httpsTruststoreFile the HTTPS truststore file. + * @return the builder. + */ + public MultiPiranhaBuilder httpsTruststoreFile(String httpsTruststoreFile) { + piranha.getConfiguration().setString("httpsTruststoreFile", httpsTruststoreFile); + return this; + } + + /** + * Set the HTTPS truststore password. + * + * @param httpsTruststorePassword the HTTPS truststore password. + * @return the builder. + */ + public MultiPiranhaBuilder httpsTruststorePassword(String httpsTruststorePassword) { + piranha.getConfiguration().setString("httpsTruststorePassword", httpsTruststorePassword); + return this; + } + + /** + * Enable/disable JPMS. + * + * @param jpms the JPMS flag. + * @return the builder. + */ + public MultiPiranhaBuilder jpms(boolean jpms) { + piranha.getConfiguration().setBoolean("jpmsEnabled", jpms); + return this; + } + + /** + * Set the logging level. + * + * @param loggingLevel the logging level. + * @return the builder. + */ + public MultiPiranhaBuilder loggingLevel(String loggingLevel) { + piranha.getConfiguration().setString("loggingLevel", loggingLevel); + return this; + } + + /** + * Show the arguments used. + */ + private void showArguments() { + LOGGER.log(Level.INFO, + """ + + PIRANHA + + Arguments + ========= + + Default extension class : %s + Exit on stop : %s + HTTP port : %s + HTTP server class : %s + HTTPS keystore file : %s + HTTPS keystore password : **** + HTTPS port : %s + HTTPS server class : %s + HTTPS truststore file : %s + HTTPS truststore password : **** + JPMS enabled : %s + Logging level : %s + Web applications dir : %s + + """.formatted( + piranha.getConfiguration().getClass("extensionClass"), + piranha.getConfiguration().getBoolean("exitOnStop", false), + piranha.getConfiguration().getInteger("httpPort"), + piranha.getConfiguration().getString("httpServerClass"), + piranha.getConfiguration().getString("httpsKeystoreFile"), + piranha.getConfiguration().getInteger("httpsPort"), + piranha.getConfiguration().getString("httpsServerClass"), + piranha.getConfiguration().getString("httpsTruststoreFile"), + piranha.getConfiguration().getBoolean("jpmsEnabled", false), + piranha.getConfiguration().getString("loggingLevel"), + piranha.getConfiguration().getFile("webAppsDir") + )); + } + + /** + * Set the verbose flag. + * + * @param verbose the verbose flag. + * @return the builder. + */ + public MultiPiranhaBuilder verbose(boolean verbose) { + this.verbose = verbose; + return this; + } + + /** + * Set the web applications directory. + * + * @param webAppsDir the web applications directory. + * @return the builder. + */ + public MultiPiranhaBuilder webAppsDir(String webAppsDir) { + if (webAppsDir != null) { + piranha.getConfiguration().setFile("webAppsDir", new File(webAppsDir)); + } + return this; + } +} diff --git a/multi/src/main/java/cloud/piranha/multi/MultiPiranhaMain.java b/multi/src/main/java/cloud/piranha/multi/MultiPiranhaMain.java new file mode 100644 index 0000000000..0476e04dc5 --- /dev/null +++ b/multi/src/main/java/cloud/piranha/multi/MultiPiranhaMain.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2002-2024 Manorrock.com. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package cloud.piranha.multi; + +import java.lang.System.Logger.Level; + +/** + * The Main for Piranha Multi. + * + * @author Manfred Riem (mriem@manorrock.com) + */ +public class MultiPiranhaMain { + + /** + * Stores the logger + */ + private static final System.Logger LOGGER = System.getLogger(MultiPiranhaMain.class.getName()); + + /** + * Main method. + * + * @param arguments the arguments. + */ + public static void main(String[] arguments) { + MultiPiranhaBuilder builder = new MultiPiranhaMain().processArguments(arguments); + if (builder != null) { + builder.build().start(); + } else { + showHelp(); + } + } + + /** + * Process the arguments. + * + * @param arguments the arguments. + * @return the builder. + */ + protected MultiPiranhaBuilder processArguments(String[] arguments) { + MultiPiranhaBuilder builder = new MultiPiranhaBuilder() + .exitOnStop(true); + + if (arguments != null) { + for (int i = 0; i < arguments.length; i++) { + if (arguments[i].equals("--extension-class")) { + builder = builder.extensionClass(arguments[i + 1]); + } + if (arguments[i].equals("--help")) { + return null; + } + if (arguments[i].equals("--http-port")) { + builder = builder.httpPort(Integer.parseInt(arguments[i + 1])); + } + if (arguments[i].equals("--http-server-class")) { + builder = builder.httpServerClass(arguments[i + 1]); + } + if (arguments[i].equals("--https-keystore-file")) { + builder = builder.httpsKeystoreFile(arguments[i + 1]); + } + if (arguments[i].equals("--https-keystore-password")) { + builder = builder.httpsKeystorePassword(arguments[i + 1]); + } + if (arguments[i].equals("--https-port")) { + builder = builder.httpsPort(Integer.parseInt(arguments[i + 1])); + } + if (arguments[i].equals("--https-server-class")) { + builder = builder.httpsServerClass(arguments[i + 1]); + } + if (arguments[i].equals("--https-truststore-file")) { + builder = builder.httpsTruststoreFile(arguments[i + 1]); + } + if (arguments[i].equals("--https-truststore-password")) { + builder = builder.httpsTruststorePassword(arguments[i + 1]); + } + if (arguments[i].equals("--jpms")) { + builder = builder.jpms(true); + } + if (arguments[i].equals("--logging-level")) { + builder = builder.loggingLevel(arguments[i + 1]); + } + if (arguments[i].equals("--verbose")) { + builder = builder.verbose(true); + } + if (arguments[i].equals("--webapps-dir")) { + builder = builder.webAppsDir(arguments[i + 1]); + } + } + } + return builder; + } + + /** + * Show help. + */ + protected static void showHelp() { + LOGGER.log(Level.INFO, ""); + LOGGER.log(Level.INFO, + """ + --extension-class - Set the extension to use + --help - Show this help + --http-port - Set the HTTP port (use -1 to disable) + --http-server-class - Set the HTTP server class to use + --https-keystore-file - Set the HTTPS keystore file (applies to + the whole JVM) + --https-keystore-password - Set the HTTPS keystore password + (applies to the whole JVM) + --https-port - Set the HTTPS port (disabled by + default) + --https-server-class - Set the HTTPS server class to use + --https-truststore-file - Set the HTTPS keystore file (applies to + the whole JVM) + --https-truststore-password - Set the HTTPS keystore password + (applies to the whole JVM) + --jpms - Enable Java Platform Module System + --logging-level - Set the logging level + --verbose - Shows the runtime parameters + --webapps-dir - Set the web applications directory + """); + } +} diff --git a/multi/src/main/java/cloud/piranha/multi/MultiWebApplication.java b/multi/src/main/java/cloud/piranha/multi/MultiWebApplication.java new file mode 100644 index 0000000000..ffb293246f --- /dev/null +++ b/multi/src/main/java/cloud/piranha/multi/MultiWebApplication.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2002-2024 Manorrock.com. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package cloud.piranha.multi; + +import cloud.piranha.core.api.WebApplicationServerRequestMapper; +import cloud.piranha.core.impl.DefaultWebApplication; +import jakarta.servlet.ServletContext; +import java.util.Objects; + +/** + * This web application supports finding other contexts using + * {@link ServletContext#getContext(String)}. + */ +public class MultiWebApplication extends DefaultWebApplication { + + /** + * Stores the request mapper. + */ + private final WebApplicationServerRequestMapper requestMapper; + + /** + * Constructor. + * + * @param requestMapper the request mapper. + */ + public MultiWebApplication(WebApplicationServerRequestMapper requestMapper) { + this.requestMapper = Objects.requireNonNull(requestMapper); + } + + @Override + public ServletContext getContext(String uripath) { + return requestMapper.findMapping(uripath); + } +} diff --git a/multi/src/main/java/module-info.java b/multi/src/main/java/module-info.java new file mode 100644 index 0000000000..b3fb0ea88f --- /dev/null +++ b/multi/src/main/java/module-info.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2002-2024 Manorrock.com. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * This module delivers Piranha Multi. + * + * @author Manfred Riem (mriem@manorrock.com) + */ +module cloud.piranha.multi { + + exports cloud.piranha.multi; + opens cloud.piranha.multi; + requires cloud.piranha.feature.exitonstop; + requires cloud.piranha.feature.http; + requires cloud.piranha.feature.https; + requires cloud.piranha.feature.logging; + requires cloud.piranha.feature.webapps; + requires java.logging; + requires java.naming; +} diff --git a/multi/src/main/zip/bin/run.sh b/multi/src/main/zip/bin/run.sh new file mode 100755 index 0000000000..21a8bd38e1 --- /dev/null +++ b/multi/src/main/zip/bin/run.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -m + +exec ./start.sh --run $* diff --git a/multi/src/main/zip/bin/start.cmd b/multi/src/main/zip/bin/start.cmd new file mode 100644 index 0000000000..be1ac74460 --- /dev/null +++ b/multi/src/main/zip/bin/start.cmd @@ -0,0 +1,2 @@ +cd .. +java -jar lib\piranha-multi.jar %* diff --git a/multi/src/main/zip/bin/start.sh b/multi/src/main/zip/bin/start.sh new file mode 100755 index 0000000000..c421b207fe --- /dev/null +++ b/multi/src/main/zip/bin/start.sh @@ -0,0 +1,48 @@ +#!/bin/bash +set -m + +cd .. + +if [ -z ${JAVA_HOME} ]; then + echo Using default java + JAVA_BIN=java +else + echo Using JAVA_HOME: ${JAVA_HOME} + JAVA_BIN=${JAVA_HOME}/bin/java +fi + +if [[ "${PIRANHA_JPMS}" == "true" ]]; then + INIT_OPTIONS="--module-path lib -m cloud.piranha.multi" +else + INIT_OPTIONS="-jar lib/piranha-multi.jar" +fi + + +if [[ "$*" == *"--suspend"* ]]; then + JAVA_ARGS="${JAVA_ARGS} -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:9009" +fi + +# +# Set the SSL debug mode which is useful for debugging SSL connections. +# +# SSL_DEBUG=-Djavax.net.debug=ssl + +CMD="${JAVA_BIN} ${JAVA_ARGS} ${SSL_DEBUG} \ + -Djava.util.logging.config.file=etc/logging.properties \ + ${INIT_OPTIONS} $*" + +echo Starting Piranha using command: ${CMD} + +touch tmp/piranha.pid + +if [[ "$*" == *"--verbose"* ]]; then + ${CMD} +else + if [[ "$*" == *"--run"* ]]; then + echo $$ > tmp/piranha.pid + ${CMD} + else + ${CMD} & + echo $! > tmp/piranha.pid + fi +fi diff --git a/multi/src/main/zip/bin/stop.cmd b/multi/src/main/zip/bin/stop.cmd new file mode 100644 index 0000000000..0e08b27eea --- /dev/null +++ b/multi/src/main/zip/bin/stop.cmd @@ -0,0 +1,2 @@ +cd .. +move tmp\piranha.pid tmp\piranha.pid.old diff --git a/multi/src/main/zip/bin/stop.sh b/multi/src/main/zip/bin/stop.sh new file mode 100755 index 0000000000..84626361ab --- /dev/null +++ b/multi/src/main/zip/bin/stop.sh @@ -0,0 +1,5 @@ +#!/bin/bash +set -m + +cd .. +mv tmp/piranha.pid tmp/piranha.pid.old diff --git a/multi/src/main/zip/etc/keystore.jks b/multi/src/main/zip/etc/keystore.jks new file mode 100644 index 0000000000..04b040728f Binary files /dev/null and b/multi/src/main/zip/etc/keystore.jks differ diff --git a/multi/src/main/zip/etc/logging.properties b/multi/src/main/zip/etc/logging.properties new file mode 100644 index 0000000000..3477f14e6a --- /dev/null +++ b/multi/src/main/zip/etc/logging.properties @@ -0,0 +1,73 @@ +# +# Copyright (c) 2002-2024 Manorrock.com. All Rights Reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# +# Logging handlers, by default the ConsoleHandler and the FileHandler are +# enabled. If you want to add other handlers, see the java.util.logging +# documentation for more information. +# +handlers = java.util.logging.ConsoleHandler java.util.logging.FileHandler + +# +# Global log level +# +.level = INFO + +# +# The configuration for the ConsoleHandler +# +# 1. The log level is set to INFO +# 2. The formatter used is the SimpleFormatter +# +# IMPORTANT NOTE +# +# A Handler (in this case the ConsoleHandler) will only log up up to the level +# specified for its Handler. So be default only INFO messages and higher will +# show up in the log. E.g if you want FINEST messages to show for a particular +# Logger you will have to set the level of the Handler to FINEST as well. +# +# +java.util.logging.ConsoleHandler.level = INFO +java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter + +# +# The configuration for the FileHandler +# +# 1. The log level is set to FINEST +# 2. The formatter used is the SimpleFormatter +# 3. The size of each log file is 100 MB. +# 4. The number of log files to keep is 10 +# 5. The handler is set to append to an existing log file. +# 6. The filename pattern is tmp/piranha-%g.log + +java.util.logging.FileHandler.level = FINEST +java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter +java.util.logging.FileHandler.limit = 10485760 +java.util.logging.FileHandler.count = 10 +java.util.logging.FileHandler.append = true +java.util.logging.FileHandler.pattern = tmp/piranha-%g.log diff --git a/multi/src/main/zip/etc/truststore.jks b/multi/src/main/zip/etc/truststore.jks new file mode 100644 index 0000000000..04b040728f Binary files /dev/null and b/multi/src/main/zip/etc/truststore.jks differ diff --git a/multi/src/main/zip/tmp/README b/multi/src/main/zip/tmp/README new file mode 100644 index 0000000000..c383d2ee99 --- /dev/null +++ b/multi/src/main/zip/tmp/README @@ -0,0 +1,7 @@ + + README + ------ + + This directory is used for temporary files by Piranha. If the server is NOT + running you can safely delete the contents of the directory, but do NOT delete + the directory itself. diff --git a/multi/src/main/zip/webapps/README b/multi/src/main/zip/webapps/README new file mode 100644 index 0000000000..cb1111eb3c --- /dev/null +++ b/multi/src/main/zip/webapps/README @@ -0,0 +1,7 @@ + + README + ------ + + This directory is used for your web applications. If a file ends with the .war + extension, or it is a sub directory it will be considered a web application, + anything else will be ignored. diff --git a/multi/src/test/java/cloud/piranha/multi/MultiPiranhaBuilderTest.java b/multi/src/test/java/cloud/piranha/multi/MultiPiranhaBuilderTest.java new file mode 100644 index 0000000000..29272b13cd --- /dev/null +++ b/multi/src/test/java/cloud/piranha/multi/MultiPiranhaBuilderTest.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2002-2024 Manorrock.com. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package cloud.piranha.multi; + +import java.net.ConnectException; +import java.net.Socket; +import javax.net.SocketFactory; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; +import org.junit.jupiter.api.Test; + +/** + * The JUnit tests for the MultiPiranhaBuilder class. + * + * @author Manfred Riem (mriem@manorrock.com) + */ +class MultiPiranhaBuilderTest { + + /** + * Test httpPort method. + * + * @throws Exception when a serious error occurs. + */ + @Test + void testHttpPort() throws Exception { + MultiPiranha piranha = new MultiPiranhaBuilder() + .httpPort(Integer.parseInt(System.getProperty("httpPort"))) + .build(); + piranha.start(); + Thread.sleep(5000); + try ( Socket socket = new Socket("localhost", Integer.parseInt(System.getProperty("httpPort")))) { + assertNotNull(socket.getOutputStream()); + } + piranha.stop(); + Thread.sleep(5000); + } + + /** + * Test httpsPort method. + * + * @throws Exception when a serious error occurs. + */ + @Test + void testHttpPort2() throws Exception { + MultiPiranha piranha = new MultiPiranhaBuilder() + .httpPort(-1) + .httpsPort(Integer.parseInt(System.getProperty("httpsPort2"))) + .build(); + piranha.start(); + Thread.sleep(5000); + try ( Socket socket = new Socket("localhost", + Integer.parseInt(System.getProperty("httpPort2")))) { + fail(); + } catch (ConnectException e) { + } + piranha.stop(); + Thread.sleep(5000); + } + + /** + * Test httpsPort method. + * + * @throws Exception when a serious error occurs. + */ + @Test + void testHttpsPort2() throws Exception { + MultiPiranha piranha = new MultiPiranhaBuilder() + .httpsKeystoreFile("src/main/zip/etc/keystore.jks") + .httpsKeystorePassword("password") + .httpPort(8228) + .httpsPort(8338) + .build(); + piranha.start(); + Thread.sleep(5000); + SocketFactory factory = SSLSocketFactory.getDefault(); + try ( SSLSocket socket = (SSLSocket) factory.createSocket("localhost", 8338)) { + assertNotNull(socket.getOutputStream()); + assertNotNull(socket.getSSLParameters()); + assertEquals("TLSv1.3", socket.getSSLParameters().getProtocols()[0]); + } + piranha.stop(); + Thread.sleep(5000); + } + + /** + * Test defaultExtensionClass method. + * + * @throws Exception when a serious error occurs. + */ + @Test + void testDefaultExtensionClass() throws Exception { + MultiPiranha piranha = new MultiPiranhaBuilder() + .httpPort(8080) + .verbose(true) + .build(); + piranha.start(); + Thread.sleep(5000); + try ( Socket socket = new Socket("localhost", 8080)) { + assertNotNull(socket.getOutputStream()); + } catch (ConnectException e) { + } + piranha.stop(); + Thread.sleep(5000); + } +} diff --git a/multi/src/test/java/cloud/piranha/multi/MultiPiranhaIT.java b/multi/src/test/java/cloud/piranha/multi/MultiPiranhaIT.java new file mode 100644 index 0000000000..46129bc68b --- /dev/null +++ b/multi/src/test/java/cloud/piranha/multi/MultiPiranhaIT.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2002-2024 Manorrock.com. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package cloud.piranha.multi; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; + +/** + * The JUnit integration tests for ServerPiranha class. + * + * @author Manfred Riem (mriem@manorrock.com) + */ +class MultiPiranhaIT { + + /** + * Extract the zip input stream. + * + * @param zipInput the zip input stream. + * @param filePath the file path. + * @throws IOException when an I/O error occurs. + */ + private void extractZipInputStream(ZipInputStream zipInput, String filePath) throws IOException { + try (BufferedOutputStream bufferOutput = new BufferedOutputStream(new FileOutputStream(filePath))) { + byte[] bytesIn = new byte[8192]; + int read; + while ((read = zipInput.read(bytesIn)) != -1) { + bufferOutput.write(bytesIn, 0, read); + } + } + } + + /** + * Extract the Server zip file. + */ + private void extractServer(File zipFile) { + try (ZipInputStream zipInput = new ZipInputStream(new FileInputStream(zipFile))) { + ZipEntry entry = zipInput.getNextEntry(); + while (entry != null) { + String filePath = "target" + File.separatorChar + entry.getName(); + if (!entry.isDirectory()) { + File file = new File(filePath); + if (!file.getParentFile().exists()) { + file.getParentFile().mkdirs(); + } + extractZipInputStream(zipInput, filePath); + } + zipInput.closeEntry(); + entry = zipInput.getNextEntry(); + } + } catch (IOException ioe) { + } + } + + /** + * Test run method. + * + * @throws Exception when a serious error occurs. + */ + @Test + void testRun() throws Exception { + extractServer(new File("target/piranha-multi.zip")); + ProcessBuilder builder = new ProcessBuilder(); + Process process; + + if (System.getProperty("os.name").toLowerCase().contains("windows")) { + process = builder. + directory(new File("target/piranha/bin")). + command("cmd", "/c", "start.cmd"). + start(); + } else { + process = builder. + directory(new File("target/piranha/bin")). + command("sh", "./start.sh"). + start(); + } + process.waitFor(5, TimeUnit.SECONDS); + Thread.sleep(5000); + + File pidFile = new File("target/piranha/tmp/piranha.pid"); + assertTrue(pidFile.exists()); + + if (System.getProperty("os.name").toLowerCase().contains("windows")) { + process = builder. + directory(new File("target/piranha/bin")). + command("cmd", "/c", "stop.cmd"). + start(); + } else { + process = builder. + directory(new File("target/piranha/bin")). + command("sh", "./stop.sh"). + start(); + } + process.waitFor(5, TimeUnit.SECONDS); + Thread.sleep(5000); + + assertFalse(pidFile.exists()); + } +} diff --git a/multi/tmp/piranha.stopped b/multi/tmp/piranha.stopped new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pom.xml b/pom.xml index 8fe7d9baab..097437d09a 100644 --- a/pom.xml +++ b/pom.xml @@ -52,6 +52,7 @@ feature http maven + multi micro resource single