This is an automated email from the ASF dual-hosted git repository.
jbertram pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/activemq-artemis.git
The following commit(s) were added to refs/heads/main by this push:
new 974bd5f6e1 ARTEMIS-3808 support start/stop embedded web server via
mngmnt
new b8f2358c82 This closes #4061
974bd5f6e1 is described below
commit 974bd5f6e177f747cd100b50063c53fa5bd8fd3c
Author: Justin Bertram <[email protected]>
AuthorDate: Mon May 9 13:05:49 2022 -0500
ARTEMIS-3808 support start/stop embedded web server via mngmnt
It would be useful to be able to cycle the embedded web server if, for
example, one needed to renew the SSL certificates. To support
functionality I made a handful of changes, e.g.:
- Refactoring WebServerComponent so that all the necessary
configuration would happen in the start() method.
- Refactoring WebServerComponentTest to re-use code.
---
.../artemis/api/core/ActiveMQExceptionType.java | 6 +
.../artemis/api/core/ActiveMQTimeoutException.java | 33 +++
.../artemis/marker/WebServerComponentMarker.java | 25 +++
.../api/config/ActiveMQDefaultConfiguration.java | 6 +
.../api/core/management/ActiveMQServerControl.java | 16 ++
.../management/impl/ActiveMQServerControlImpl.java | 85 +++++++-
.../artemis/core/server/ActiveMQMessageBundle.java | 10 +
.../apache/activemq/artemis/ActiveMQWebLogger.java | 20 +-
.../artemis/component/WebServerComponent.java | 223 +++++++++++----------
.../component/WebServerComponentTestAccessor.java | 29 +++
.../activemq/cli/test/WebServerComponentTest.java | 178 ++++++++--------
docs/user-manual/en/versions.md | 6 +
docs/user-manual/en/web-server.md | 9 +-
.../management/ActiveMQServerControlTest.java | 132 ++++++++++++
.../ActiveMQServerControlUsingCoreTest.java | 25 +++
15 files changed, 595 insertions(+), 208 deletions(-)
diff --git
a/artemis-commons/src/main/java/org/apache/activemq/artemis/api/core/ActiveMQExceptionType.java
b/artemis-commons/src/main/java/org/apache/activemq/artemis/api/core/ActiveMQExceptionType.java
index 03d18e9138..9408f1677a 100644
---
a/artemis-commons/src/main/java/org/apache/activemq/artemis/api/core/ActiveMQExceptionType.java
+++
b/artemis-commons/src/main/java/org/apache/activemq/artemis/api/core/ActiveMQExceptionType.java
@@ -273,6 +273,12 @@ public enum ActiveMQExceptionType {
public ActiveMQException createException(String msg) {
return new ActiveMQRoutingException(msg);
}
+ },
+ TIMEOUT_EXCEPTION(223) {
+ @Override
+ public ActiveMQException createException(String msg) {
+ return new ActiveMQTimeoutException(msg);
+ }
};
private static final Map<Integer, ActiveMQExceptionType> TYPE_MAP;
diff --git
a/artemis-commons/src/main/java/org/apache/activemq/artemis/api/core/ActiveMQTimeoutException.java
b/artemis-commons/src/main/java/org/apache/activemq/artemis/api/core/ActiveMQTimeoutException.java
new file mode 100644
index 0000000000..1111ad22c2
--- /dev/null
+++
b/artemis-commons/src/main/java/org/apache/activemq/artemis/api/core/ActiveMQTimeoutException.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.artemis.api.core;
+
+/**
+ * An operation timed out.
+ */
+public final class ActiveMQTimeoutException extends ActiveMQException {
+
+ private static final long serialVersionUID = 0;
+
+ public ActiveMQTimeoutException(String message) {
+ super(ActiveMQExceptionType.TIMEOUT_EXCEPTION, message);
+ }
+
+ public ActiveMQTimeoutException() {
+ super(ActiveMQExceptionType.TIMEOUT_EXCEPTION);
+ }
+}
diff --git
a/artemis-commons/src/main/java/org/apache/activemq/artemis/marker/WebServerComponentMarker.java
b/artemis-commons/src/main/java/org/apache/activemq/artemis/marker/WebServerComponentMarker.java
new file mode 100644
index 0000000000..056c8c9bdc
--- /dev/null
+++
b/artemis-commons/src/main/java/org/apache/activemq/artemis/marker/WebServerComponentMarker.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.activemq.artemis.marker;
+
+/*
+ * This is just a "marker interface" so that the broker can find the
org.apache.activemq.artemis.component.WebServerComponent
+ * for management operations (e.g. start & stop).
+ */
+public interface WebServerComponentMarker {
+}
diff --git
a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/config/ActiveMQDefaultConfiguration.java
b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/config/ActiveMQDefaultConfiguration.java
index 85b8691110..734d58261b 100644
---
a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/config/ActiveMQDefaultConfiguration.java
+++
b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/config/ActiveMQDefaultConfiguration.java
@@ -652,6 +652,8 @@ public final class ActiveMQDefaultConfiguration {
// If SESSION-notifications should be suppressed or not
public static boolean DEFAULT_SUPPRESS_SESSION_NOTIFICATIONS = false;
+ public static final long DEFAULT_EMBEDDED_WEB_SERVER_RESTART_TIMEOUT = 5000;
+
/**
* If true then the ActiveMQ Artemis Server will make use of any Protocol
Managers that are in available on the classpath. If false then only the core
protocol will be available, unless in Embedded mode where users can inject
their own Protocol Managers.
*/
@@ -1786,4 +1788,8 @@ public final class ActiveMQDefaultConfiguration {
return DEFAULT_SUPPRESS_SESSION_NOTIFICATIONS;
}
+ public static long getDefaultEmbeddedWebServerRestartTimeout() {
+ return DEFAULT_EMBEDDED_WEB_SERVER_RESTART_TIMEOUT;
+ }
+
}
diff --git
a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ActiveMQServerControl.java
b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ActiveMQServerControl.java
index ed535d2fd5..402545e024 100644
---
a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ActiveMQServerControl.java
+++
b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ActiveMQServerControl.java
@@ -19,6 +19,7 @@ package org.apache.activemq.artemis.api.core.management;
import javax.management.MBeanOperationInfo;
import java.util.Map;
+import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration;
import
org.apache.activemq.artemis.api.core.ActiveMQAddressDoesNotExistException;
/**
@@ -1971,5 +1972,20 @@ public interface ActiveMQServerControl {
@Parameter(name = "address", desc = "Name of the address to
replay") String address,
@Parameter(name = "target", desc = "Where the replay data
should be sent") String target,
@Parameter(name = "filter", desc = "Filter to apply on message
selection. Null means everything matching the address") String filter) throws
Exception;
+
+ @Operation(desc = "stop the embedded web server", impact =
MBeanOperationInfo.ACTION)
+ void stopEmbeddedWebServer() throws Exception;
+
+ @Operation(desc = "start the embedded web server", impact =
MBeanOperationInfo.ACTION)
+ void startEmbeddedWebServer() throws Exception;
+
+ @Operation(desc = "restart the embedded web server; wait the default " +
ActiveMQDefaultConfiguration.DEFAULT_EMBEDDED_WEB_SERVER_RESTART_TIMEOUT + "
milliseconds to ensure restart completes successfully", impact =
MBeanOperationInfo.ACTION)
+ void restartEmbeddedWebServer() throws Exception;
+
+ @Operation(desc = "restart the embedded web server; wait specified time (in
milliseconds) to ensure restart completes successfully", impact =
MBeanOperationInfo.ACTION)
+ void restartEmbeddedWebServer(@Parameter(name = "timeout", desc = "how long
to wait (in milliseconds) to ensure restart completes successfully") long
timeout) throws Exception;
+
+ @Attribute(desc = "Whether the embedded web server is started")
+ boolean isEmbeddedWebServerStarted();
}
diff --git
a/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/ActiveMQServerControlImpl.java
b/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/ActiveMQServerControlImpl.java
index 58aa64dc80..eaaa121698 100644
---
a/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/ActiveMQServerControlImpl.java
+++
b/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/ActiveMQServerControlImpl.java
@@ -16,10 +16,6 @@
*/
package org.apache.activemq.artemis.core.management.impl;
-import org.apache.activemq.artemis.json.JsonArray;
-import org.apache.activemq.artemis.json.JsonArrayBuilder;
-import org.apache.activemq.artemis.json.JsonObject;
-import org.apache.activemq.artemis.json.JsonObjectBuilder;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanNotificationInfo;
@@ -45,12 +41,16 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration;
import
org.apache.activemq.artemis.api.core.ActiveMQAddressDoesNotExistException;
import org.apache.activemq.artemis.api.core.ActiveMQException;
+import org.apache.activemq.artemis.api.core.ActiveMQIllegalStateException;
import org.apache.activemq.artemis.api.core.JsonUtil;
import org.apache.activemq.artemis.api.core.QueueConfiguration;
import org.apache.activemq.artemis.api.core.RoutingType;
@@ -93,6 +93,7 @@ import
org.apache.activemq.artemis.core.remoting.server.RemotingService;
import org.apache.activemq.artemis.core.security.CheckType;
import org.apache.activemq.artemis.core.security.Role;
import org.apache.activemq.artemis.core.security.impl.SecurityStoreImpl;
+import org.apache.activemq.artemis.core.server.ActiveMQComponent;
import org.apache.activemq.artemis.core.server.ActiveMQMessageBundle;
import org.apache.activemq.artemis.core.server.ActiveMQServer;
import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
@@ -106,6 +107,7 @@ import org.apache.activemq.artemis.core.server.Queue;
import org.apache.activemq.artemis.core.server.ServerConsumer;
import org.apache.activemq.artemis.core.server.ServerProducer;
import org.apache.activemq.artemis.core.server.ServerSession;
+import org.apache.activemq.artemis.core.server.ServiceComponent;
import org.apache.activemq.artemis.core.server.cluster.ClusterConnection;
import org.apache.activemq.artemis.core.server.cluster.ClusterManager;
import org.apache.activemq.artemis.core.server.cluster.ha.HAPolicy;
@@ -127,7 +129,12 @@ import
org.apache.activemq.artemis.core.transaction.TransactionDetail;
import org.apache.activemq.artemis.core.transaction.TransactionDetailFactory;
import org.apache.activemq.artemis.core.transaction.impl.CoreTransactionDetail;
import org.apache.activemq.artemis.core.transaction.impl.XidImpl;
+import org.apache.activemq.artemis.json.JsonArray;
+import org.apache.activemq.artemis.json.JsonArrayBuilder;
+import org.apache.activemq.artemis.json.JsonObject;
+import org.apache.activemq.artemis.json.JsonObjectBuilder;
import org.apache.activemq.artemis.logs.AuditLogger;
+import org.apache.activemq.artemis.marker.WebServerComponentMarker;
import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
import
org.apache.activemq.artemis.spi.core.security.ActiveMQBasicSecurityManager;
import
org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoginModuleConfigurator;
@@ -161,6 +168,8 @@ public class ActiveMQServerControlImpl extends
AbstractControl implements Active
private final Object userLock = new Object();
+ private final Object embeddedWebServerLock = new Object();
+
public ActiveMQServerControlImpl(final PostOffice postOffice,
final Configuration configuration,
final ResourceManager resourceManager,
@@ -4479,5 +4488,73 @@ public class ActiveMQServerControlImpl extends
AbstractControl implements Active
server.replay(startScanDate, endScanDate, address, target, filter);
}
+
+ @Override
+ public void stopEmbeddedWebServer() throws Exception {
+ synchronized (embeddedWebServerLock) {
+ getEmbeddedWebServerComponent().stop(true);
+ }
+ }
+
+ @Override
+ public void startEmbeddedWebServer() throws Exception {
+ synchronized (embeddedWebServerLock) {
+ getEmbeddedWebServerComponent().start();
+ }
+ }
+
+ @Override
+ public void restartEmbeddedWebServer() throws Exception {
+
restartEmbeddedWebServer(ActiveMQDefaultConfiguration.getDefaultEmbeddedWebServerRestartTimeout());
+ }
+
+ @Override
+ public void restartEmbeddedWebServer(long timeout) throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final AtomicReference<Exception> exception = new AtomicReference<>();
+ /*
+ * This needs to be run in its own thread managed by the broker because
if it is run on a thread managed by Jetty
+ * (e.g. if it is invoked from the web console) then the thread will die
before Jetty can be restarted.
+ */
+ server.getThreadPool().execute(() -> {
+ try {
+ synchronized (embeddedWebServerLock) {
+ stopEmbeddedWebServer();
+ startEmbeddedWebServer();
+ }
+ } catch (Exception e) {
+ exception.set(e);
+ } finally {
+ latch.countDown();
+ }
+ });
+ if (!latch.await(timeout, TimeUnit.MILLISECONDS)) {
+ throw
ActiveMQMessageBundle.BUNDLE.embeddedWebServerRestartTimeout(timeout);
+ }
+ if (exception.get() != null) {
+ throw
ActiveMQMessageBundle.BUNDLE.embeddedWebServerRestartFailed(exception.get());
+ }
+ }
+
+ @Override
+ public boolean isEmbeddedWebServerStarted() {
+ try {
+ return getEmbeddedWebServerComponent().isStarted();
+ } catch (Exception e) {
+ if (logger.isTraceEnabled()) {
+ logger.trace(e.getMessage());
+ }
+ return false;
+ }
+ }
+
+ private ServiceComponent getEmbeddedWebServerComponent() throws
ActiveMQIllegalStateException {
+ for (ActiveMQComponent component : server.getExternalComponents()) {
+ if (component instanceof WebServerComponentMarker) {
+ return (ServiceComponent) component;
+ }
+ }
+ throw ActiveMQMessageBundle.BUNDLE.embeddedWebServerNotFound();
+ }
}
diff --git
a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQMessageBundle.java
b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQMessageBundle.java
index da15be51dc..418e183bf7 100644
---
a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQMessageBundle.java
+++
b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQMessageBundle.java
@@ -44,6 +44,7 @@ import
org.apache.activemq.artemis.api.core.ActiveMQRemoteDisconnectException;
import
org.apache.activemq.artemis.api.core.ActiveMQReplicationTimeooutException;
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException;
import org.apache.activemq.artemis.api.core.ActiveMQSessionCreationException;
+import org.apache.activemq.artemis.api.core.ActiveMQTimeoutException;
import
org.apache.activemq.artemis.api.core.ActiveMQUnexpectedRoutingTypeForAddress;
import org.apache.activemq.artemis.api.core.DiscoveryGroupConfiguration;
import org.apache.activemq.artemis.api.core.RoutingType;
@@ -522,4 +523,13 @@ public interface ActiveMQMessageBundle {
@Message(id = 229240, value = "Connection router {0} rejected the
connection", format = Message.Format.MESSAGE_FORMAT)
ActiveMQRemoteDisconnectException connectionRejected(String
connectionRouter);
+
+ @Message(id = 229241, value = "Embedded web server not found")
+ ActiveMQIllegalStateException embeddedWebServerNotFound();
+
+ @Message(id = 229242, value = "Embedded web server not restarted in {0}
milliseconds", format = Message.Format.MESSAGE_FORMAT)
+ ActiveMQTimeoutException embeddedWebServerRestartTimeout(long timeout);
+
+ @Message(id = 229243, value = "Embedded web server restart failed", format
= Message.Format.MESSAGE_FORMAT)
+ ActiveMQException embeddedWebServerRestartFailed(@Cause Exception e);
}
diff --git
a/artemis-web/src/main/java/org/apache/activemq/artemis/ActiveMQWebLogger.java
b/artemis-web/src/main/java/org/apache/activemq/artemis/ActiveMQWebLogger.java
index 59d115d1dd..b89714be56 100644
---
a/artemis-web/src/main/java/org/apache/activemq/artemis/ActiveMQWebLogger.java
+++
b/artemis-web/src/main/java/org/apache/activemq/artemis/ActiveMQWebLogger.java
@@ -44,21 +44,33 @@ public interface ActiveMQWebLogger extends BasicLogger {
ActiveMQWebLogger LOGGER = Logger.getMessageLogger(ActiveMQWebLogger.class,
ActiveMQWebLogger.class.getPackage().getName());
@LogMessage(level = Logger.Level.INFO)
- @Message(id = 241001, value = "HTTP Server started at {0}", format =
Message.Format.MESSAGE_FORMAT)
+ @Message(id = 241001, value = "Embedded web server started at {0}", format
= Message.Format.MESSAGE_FORMAT)
void webserverStarted(String bind);
@LogMessage(level = Logger.Level.INFO)
@Message(id = 241002, value = "Artemis Jolokia REST API available at {0}",
format = Message.Format.MESSAGE_FORMAT)
void jolokiaAvailable(String bind);
- @LogMessage(level = Logger.Level.WARN)
- @Message(id = 244003, value = "Temporary file not deleted on shutdown:
{0}", format = Message.Format.MESSAGE_FORMAT)
- void tmpFileNotDeleted(File tmpdir);
+ @LogMessage(level = Logger.Level.INFO)
+ @Message(id = 241003, value = "Starting embedded web server", format =
Message.Format.MESSAGE_FORMAT)
+ void startingEmbeddedWebServer();
@LogMessage(level = Logger.Level.INFO)
@Message(id = 241004, value = "Artemis Console available at {0}", format =
Message.Format.MESSAGE_FORMAT)
void consoleAvailable(String bind);
+ @LogMessage(level = Logger.Level.INFO)
+ @Message(id = 241005, value = "Stopping embedded web server", format =
Message.Format.MESSAGE_FORMAT)
+ void stoppingEmbeddedWebServer();
+
+ @LogMessage(level = Logger.Level.INFO)
+ @Message(id = 241006, value = "Stopped embedded web server", format =
Message.Format.MESSAGE_FORMAT)
+ void stoppedEmbeddedWebServer();
+
+ @LogMessage(level = Logger.Level.WARN)
+ @Message(id = 244003, value = "Temporary file not deleted on shutdown:
{0}", format = Message.Format.MESSAGE_FORMAT)
+ void tmpFileNotDeleted(File tmpdir);
+
@LogMessage(level = Logger.Level.WARN)
@Message(id = 244005, value = "Web customizer {0} not loaded: {1}", format
= Message.Format.MESSAGE_FORMAT)
void customizerNotLoaded(String customizer, Throwable t);
diff --git
a/artemis-web/src/main/java/org/apache/activemq/artemis/component/WebServerComponent.java
b/artemis-web/src/main/java/org/apache/activemq/artemis/component/WebServerComponent.java
index 8027929c0d..30cb45f6da 100644
---
a/artemis-web/src/main/java/org/apache/activemq/artemis/component/WebServerComponent.java
+++
b/artemis-web/src/main/java/org/apache/activemq/artemis/component/WebServerComponent.java
@@ -33,6 +33,7 @@ import org.apache.activemq.artemis.dto.AppDTO;
import org.apache.activemq.artemis.dto.BindingDTO;
import org.apache.activemq.artemis.dto.ComponentDTO;
import org.apache.activemq.artemis.dto.WebServerDTO;
+import org.apache.activemq.artemis.marker.WebServerComponentMarker;
import org.eclipse.jetty.security.DefaultAuthenticatorFactory;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.CustomRequestLog;
@@ -53,115 +54,81 @@ import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.webapp.WebAppContext;
import org.jboss.logging.Logger;
-public class WebServerComponent implements ExternalComponent {
+public class WebServerComponent implements ExternalComponent,
WebServerComponentMarker {
private static final Logger logger =
Logger.getLogger(WebServerComponent.class);
+ public static final String DIR_ALLOWED =
"org.eclipse.jetty.servlet.Default.dirAllowed";
private Server server;
private HandlerList handlers;
private WebServerDTO webServerConfig;
private final List<String> consoleUrls = new ArrayList<>();
private final List<String> jolokiaUrls = new ArrayList<>();
- private List<WebAppContext> webContexts;
+ private final List<WebAppContext> webContexts = new ArrayList<>();;
private ServerConnector[] connectors;
private Path artemisHomePath;
private Path temporaryWarDir;
+ private String artemisInstance;
+ private String artemisHome;
@Override
public void configure(ComponentDTO config, String artemisInstance, String
artemisHome) throws Exception {
- webServerConfig = (WebServerDTO) config;
+ this.webServerConfig = (WebServerDTO) config;
+ this.artemisInstance = artemisInstance;
+ this.artemisHome = artemisHome;
+
+ temporaryWarDir = Paths.get(artemisInstance != null ? artemisInstance :
".").resolve("tmp").resolve("webapps").toAbsolutePath();
+ if (!Files.exists(temporaryWarDir)) {
+ Files.createDirectories(temporaryWarDir);
+ }
+ }
+
+ @Override
+ public synchronized void start() throws Exception {
+ if (isStarted()) {
+ return;
+ }
+ ActiveMQWebLogger.LOGGER.startingEmbeddedWebServer();
+
server = new Server();
+ handlers = new HandlerList();
HttpConfiguration httpConfiguration = new HttpConfiguration();
- if (webServerConfig.customizer != null) {
+ if (this.webServerConfig.customizer != null) {
try {
- httpConfiguration.addCustomizer((HttpConfiguration.Customizer)
Class.forName(webServerConfig.customizer).getConstructor().newInstance());
+ httpConfiguration.addCustomizer((HttpConfiguration.Customizer)
Class.forName(this.webServerConfig.customizer).getConstructor().newInstance());
} catch (Throwable t) {
-
ActiveMQWebLogger.LOGGER.customizerNotLoaded(webServerConfig.customizer, t);
+
ActiveMQWebLogger.LOGGER.customizerNotLoaded(this.webServerConfig.customizer,
t);
}
}
- List<BindingDTO> bindings = webServerConfig.getBindings();
+ List<BindingDTO> bindings = this.webServerConfig.getBindings();
connectors = new ServerConnector[bindings.size()];
String[] virtualHosts = new String[bindings.size()];
+ this.artemisHomePath = Paths.get(artemisHome != null ? artemisHome :
".");
+ Path homeWarDir =
artemisHomePath.resolve(this.webServerConfig.path).toAbsolutePath();
+ Path instanceWarDir = Paths.get(artemisInstance != null ?
artemisInstance : ".").resolve(this.webServerConfig.path).toAbsolutePath();
+
for (int i = 0; i < bindings.size(); i++) {
BindingDTO binding = bindings.get(i);
URI uri = new URI(binding.uri);
String scheme = uri.getScheme();
- ServerConnector connector;
-
- if ("https".equals(scheme)) {
- SslContextFactory.Server sslFactory = new
SslContextFactory.Server();
- sslFactory.setKeyStorePath(binding.keyStorePath == null ?
artemisInstance + "/etc/keystore.jks" : binding.keyStorePath);
- sslFactory.setKeyStorePassword(binding.getKeyStorePassword() ==
null ? "password" : binding.getKeyStorePassword());
-
- if (binding.getIncludedTLSProtocols() != null) {
-
sslFactory.setIncludeProtocols(binding.getIncludedTLSProtocols());
- }
- if (binding.getExcludedTLSProtocols() != null) {
-
sslFactory.setExcludeProtocols(binding.getExcludedTLSProtocols());
- }
- if (binding.getIncludedCipherSuites() != null) {
-
sslFactory.setIncludeCipherSuites(binding.getIncludedCipherSuites());
- }
- if (binding.getExcludedCipherSuites() != null) {
-
sslFactory.setExcludeCipherSuites(binding.getExcludedCipherSuites());
- }
- if (binding.clientAuth != null) {
- sslFactory.setNeedClientAuth(binding.clientAuth);
- if (binding.clientAuth) {
- sslFactory.setTrustStorePath(binding.trustStorePath);
-
sslFactory.setTrustStorePassword(binding.getTrustStorePassword());
- }
- }
-
- SslConnectionFactory sslConnectionFactory = new
SslConnectionFactory(sslFactory, "HTTP/1.1");
-
- httpConfiguration.addCustomizer(new SecureRequestCustomizer());
- httpConfiguration.setSendServerVersion(false);
- HttpConnectionFactory httpFactory = new
HttpConnectionFactory(httpConfiguration);
-
- connector = new ServerConnector(server, sslConnectionFactory,
httpFactory);
-
- } else {
- httpConfiguration.setSendServerVersion(false);
- ConnectionFactory connectionFactory = new
HttpConnectionFactory(httpConfiguration);
- connector = new ServerConnector(server, connectionFactory);
- }
- connector.setPort(uri.getPort());
- connector.setHost(uri.getHost());
- connector.setName("Connector-" + i);
+ ServerConnector connector = createServerConnector(httpConfiguration,
i, binding, uri, scheme);
connectors[i] = connector;
virtualHosts[i] = "@Connector-" + i;
- }
-
- server.setConnectors(connectors);
-
- handlers = new HandlerList();
- this.artemisHomePath = Paths.get(artemisHome != null ? artemisHome :
".");
- Path homeWarDir =
artemisHomePath.resolve(webServerConfig.path).toAbsolutePath();
- Path instanceWarDir = Paths.get(artemisInstance != null ?
artemisInstance : ".").resolve(webServerConfig.path).toAbsolutePath();
-
- temporaryWarDir = Paths.get(artemisInstance != null ? artemisInstance :
".").resolve("tmp").resolve("webapps").toAbsolutePath();
- if (!Files.exists(temporaryWarDir)) {
- Files.createDirectories(temporaryWarDir);
- }
-
- for (int i = 0; i < bindings.size(); i++) {
- BindingDTO binding = bindings.get(i);
if (binding.apps != null && binding.apps.size() > 0) {
- webContexts = new ArrayList<>();
for (AppDTO app : binding.apps) {
Path dirToUse = homeWarDir;
- if (new File(instanceWarDir.toFile().toString() +
File.separator + app.war).exists()) {
+ if (new File(instanceWarDir.toFile() + File.separator +
app.war).exists()) {
dirToUse = instanceWarDir;
}
- WebAppContext webContext = deployWar(app.url, app.war,
dirToUse, virtualHosts[i]);
-
webContext.setInitParameter("org.eclipse.jetty.servlet.Default.dirAllowed",
"false");
+ WebAppContext webContext = createWebAppContext(app.url,
app.war, dirToUse, virtualHosts[i]);
+ handlers.addHandler(webContext);
+ webContext.setInitParameter(DIR_ALLOWED, "false");
webContexts.add(webContext);
if (app.war.startsWith("console")) {
consoleUrls.add(binding.uri + "/" + app.url);
@@ -171,6 +138,8 @@ public class WebServerComponent implements
ExternalComponent {
}
}
+ server.setConnectors(connectors);
+
ResourceHandler homeResourceHandler = new ResourceHandler();
homeResourceHandler.setResourceBase(homeWarDir.toString());
homeResourceHandler.setDirectoriesListed(false);
@@ -181,7 +150,7 @@ public class WebServerComponent implements
ExternalComponent {
homeContext.setResourceBase(homeWarDir.toString());
homeContext.setHandler(homeResourceHandler);
homeContext.setVirtualHosts(virtualHosts);
-
homeContext.setInitParameter("org.eclipse.jetty.servlet.Default.dirAllowed",
"false");
+ homeContext.setInitParameter(DIR_ALLOWED, "false");
ResourceHandler instanceResourceHandler = new ResourceHandler();
instanceResourceHandler.setResourceBase(instanceWarDir.toString());
@@ -193,12 +162,12 @@ public class WebServerComponent implements
ExternalComponent {
instanceContext.setResourceBase(instanceWarDir.toString());
instanceContext.setHandler(instanceResourceHandler);
instanceContext.setVirtualHosts(virtualHosts);
-
homeContext.setInitParameter("org.eclipse.jetty.servlet.Default.dirAllowed",
"false");
+ homeContext.setInitParameter(DIR_ALLOWED, "false");
DefaultHandler defaultHandler = new DefaultHandler();
defaultHandler.setServeIcon(false);
- if (webServerConfig.requestLog != null) {
+ if (this.webServerConfig.requestLog != null) {
handlers.addHandler(getLogHandler());
}
handlers.addHandler(homeContext);
@@ -206,6 +175,68 @@ public class WebServerComponent implements
ExternalComponent {
handlers.addHandler(defaultHandler); // this should be last
server.setHandler(handlers);
+
+ cleanupTmp();
+ server.start();
+
+ ActiveMQWebLogger.LOGGER.webserverStarted(bindings
+ .stream()
+ .map(binding -> binding.uri)
+
.collect(Collectors.joining(", ")));
+
+ ActiveMQWebLogger.LOGGER.jolokiaAvailable(String.join(", ",
jolokiaUrls));
+ ActiveMQWebLogger.LOGGER.consoleAvailable(String.join(", ",
consoleUrls));
+ }
+
+ private ServerConnector createServerConnector(HttpConfiguration
httpConfiguration,
+ int i,
+ BindingDTO binding,
+ URI uri,
+ String scheme) throws Exception {
+ ServerConnector connector;
+
+ if ("https".equals(scheme)) {
+ SslContextFactory.Server sslFactory = new SslContextFactory.Server();
+ sslFactory.setKeyStorePath(binding.keyStorePath == null ?
artemisInstance + "/etc/keystore.jks" : binding.keyStorePath);
+ sslFactory.setKeyStorePassword(binding.getKeyStorePassword() == null
? "password" : binding.getKeyStorePassword());
+
+ if (binding.getIncludedTLSProtocols() != null) {
+ sslFactory.setIncludeProtocols(binding.getIncludedTLSProtocols());
+ }
+ if (binding.getExcludedTLSProtocols() != null) {
+ sslFactory.setExcludeProtocols(binding.getExcludedTLSProtocols());
+ }
+ if (binding.getIncludedCipherSuites() != null) {
+
sslFactory.setIncludeCipherSuites(binding.getIncludedCipherSuites());
+ }
+ if (binding.getExcludedCipherSuites() != null) {
+
sslFactory.setExcludeCipherSuites(binding.getExcludedCipherSuites());
+ }
+ if (binding.clientAuth != null) {
+ sslFactory.setNeedClientAuth(binding.clientAuth);
+ if (binding.clientAuth) {
+ sslFactory.setTrustStorePath(binding.trustStorePath);
+
sslFactory.setTrustStorePassword(binding.getTrustStorePassword());
+ }
+ }
+
+ SslConnectionFactory sslConnectionFactory = new
SslConnectionFactory(sslFactory, "HTTP/1.1");
+
+ httpConfiguration.addCustomizer(new SecureRequestCustomizer());
+ httpConfiguration.setSendServerVersion(false);
+ HttpConnectionFactory httpFactory = new
HttpConnectionFactory(httpConfiguration);
+
+ connector = new ServerConnector(server, sslConnectionFactory,
httpFactory);
+
+ } else {
+ httpConfiguration.setSendServerVersion(false);
+ ConnectionFactory connectionFactory = new
HttpConnectionFactory(httpConfiguration);
+ connector = new ServerConnector(server, connectionFactory);
+ }
+ connector.setPort(uri.getPort());
+ connector.setHost(uri.getHost());
+ connector.setName("Connector-" + i);
+ return connector;
}
private RequestLogHandler getLogHandler() {
@@ -249,33 +280,6 @@ public class WebServerComponent implements
ExternalComponent {
return requestLogHandler;
}
- @Override
- public void start() throws Exception {
- if (isStarted()) {
- return;
- }
- cleanupTmp();
- server.start();
-
- String bindings = webServerConfig.getBindings()
- .stream()
- .map(binding -> binding.uri)
- .collect(Collectors.joining(", "));
- ActiveMQWebLogger.LOGGER.webserverStarted(bindings);
-
- ActiveMQWebLogger.LOGGER.jolokiaAvailable(String.join(", ",
jolokiaUrls));
- ActiveMQWebLogger.LOGGER.consoleAvailable(String.join(", ",
consoleUrls));
- }
-
- public void internalStop() throws Exception {
- server.stop();
- if (webContexts != null) {
- cleanupWebTemporaryFiles(webContexts);
-
- webContexts.clear();
- }
- }
-
private File getLibFolder() {
Path lib = artemisHomePath.resolve("lib");
File libFolder = new File(lib.toUri());
@@ -283,7 +287,7 @@ public class WebServerComponent implements
ExternalComponent {
}
private void cleanupTmp() {
- if (webContexts == null || webContexts.size() == 0) {
+ if (webContexts.size() == 0) {
//there is no webapp to be deployed (as in some tests)
return;
}
@@ -332,7 +336,7 @@ public class WebServerComponent implements
ExternalComponent {
return -1;
}
- private WebAppContext deployWar(String url, String warFile, Path
warDirectory, String virtualHost) {
+ protected WebAppContext createWebAppContext(String url, String warFile,
Path warDirectory, String virtualHost) {
WebAppContext webapp = new WebAppContext();
if (url.startsWith("/")) {
webapp.setContextPath(url);
@@ -353,7 +357,6 @@ public class WebServerComponent implements
ExternalComponent {
webapp.setVirtualHosts(new String[]{virtualHost});
- handlers.addHandler(webapp);
return webapp;
}
@@ -363,9 +366,17 @@ public class WebServerComponent implements
ExternalComponent {
}
@Override
- public void stop(boolean isShutdown) throws Exception {
- if (isShutdown) {
- internalStop();
+ public synchronized void stop(boolean isShutdown) throws Exception {
+ if (isShutdown && isStarted()) {
+ ActiveMQWebLogger.LOGGER.stoppingEmbeddedWebServer();
+ server.stop();
+ server = null;
+ cleanupWebTemporaryFiles(webContexts);
+ webContexts.clear();
+ jolokiaUrls.clear();
+ consoleUrls.clear();
+ handlers = null;
+ ActiveMQWebLogger.LOGGER.stoppedEmbeddedWebServer();
}
}
diff --git
a/artemis-web/src/main/java/org/apache/activemq/artemis/component/WebServerComponentTestAccessor.java
b/artemis-web/src/main/java/org/apache/activemq/artemis/component/WebServerComponentTestAccessor.java
new file mode 100644
index 0000000000..5cd875a730
--- /dev/null
+++
b/artemis-web/src/main/java/org/apache/activemq/artemis/component/WebServerComponentTestAccessor.java
@@ -0,0 +1,29 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.activemq.artemis.component;
+
+import java.nio.file.Path;
+
+import org.eclipse.jetty.webapp.WebAppContext;
+
+public class WebServerComponentTestAccessor {
+
+ public static WebAppContext createWebAppContext(WebServerComponent
webServerComponent, String url, String warFile, Path warDirectory, String
virtualHost) {
+ return webServerComponent.createWebAppContext(url, warFile,
warDirectory, virtualHost);
+ }
+}
diff --git
a/artemis-web/src/test/java/org/apache/activemq/cli/test/WebServerComponentTest.java
b/artemis-web/src/test/java/org/apache/activemq/cli/test/WebServerComponentTest.java
index f7a08a6d73..09e36cd932 100644
---
a/artemis-web/src/test/java/org/apache/activemq/cli/test/WebServerComponentTest.java
+++
b/artemis-web/src/test/java/org/apache/activemq/cli/test/WebServerComponentTest.java
@@ -25,6 +25,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
+import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -60,6 +61,7 @@ import io.netty.handler.ssl.SslHandler;
import io.netty.util.CharsetUtil;
import org.apache.activemq.artemis.cli.factory.xml.XmlBrokerFactoryHandler;
import org.apache.activemq.artemis.component.WebServerComponent;
+import org.apache.activemq.artemis.component.WebServerComponentTestAccessor;
import org.apache.activemq.artemis.core.remoting.impl.ssl.SSLSupport;
import org.apache.activemq.artemis.core.server.ActiveMQComponent;
import org.apache.activemq.artemis.dto.AppDTO;
@@ -83,16 +85,12 @@ public class WebServerComponentTest extends Assert {
static final String URL = System.getProperty("url",
"http://localhost:8161/WebServerComponentTest.txt");
static final String SECURE_URL = System.getProperty("url",
"https://localhost:8448/WebServerComponentTest.txt");
- private Bootstrap bootstrap;
- private EventLoopGroup group;
private List<ActiveMQComponent> testedComponents;
@Before
public void setupNetty() throws URISyntaxException {
System.setProperty("jetty.base", "./target");
// Configure the client.
- group = new NioEventLoopGroup();
- bootstrap = new Bootstrap();
testedComponents = new ArrayList<>();
}
@@ -133,14 +131,7 @@ public class WebServerComponentTest extends Assert {
// Make the connection attempt.
CountDownLatch latch = new CountDownLatch(1);
final ClientHandler clientHandler = new ClientHandler(latch);
- bootstrap.group(group).channel(NioSocketChannel.class).handler(new
ChannelInitializer() {
- @Override
- protected void initChannel(Channel ch) throws Exception {
- ch.pipeline().addLast(new HttpClientCodec());
- ch.pipeline().addLast(clientHandler);
- }
- });
- Channel ch = bootstrap.connect("localhost", port).sync().channel();
+ Channel ch = getChannel(port, clientHandler);
URI uri = new URI(URL);
// Prepare the HTTP request.
@@ -175,31 +166,31 @@ public class WebServerComponentTest extends Assert {
Assert.assertFalse(webServerComponent.isStarted());
webServerComponent.configure(webServerDTO, "./src/test/resources/",
"./src/test/resources/");
webServerComponent.start();
- final int port = webServerComponent.getPort();
// Make the connection attempt.
- CountDownLatch latch = new CountDownLatch(1);
- final ClientHandler clientHandler = new ClientHandler(latch);
- bootstrap.group(group).channel(NioSocketChannel.class).handler(new
ChannelInitializer() {
- @Override
- protected void initChannel(Channel ch) throws Exception {
- ch.pipeline().addLast(new HttpClientCodec());
- ch.pipeline().addLast(clientHandler);
- }
- });
- Channel ch = bootstrap.connect("localhost", port).sync().channel();
+ verifyConnection(webServerComponent.getPort());
+ Assert.assertTrue(webServerComponent.isStarted());
- URI uri = new URI(URL);
- // Prepare the HTTP request.
- HttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1,
HttpMethod.GET, uri.getRawPath());
- request.headers().set(HttpHeaderNames.HOST, "localhost");
+ //usual stop won't actually stop it
+ webServerComponent.stop();
+ assertTrue(webServerComponent.isStarted());
- // Send the HTTP request.
- ch.writeAndFlush(request);
- assertTrue(latch.await(5, TimeUnit.SECONDS));
- assertEquals("12345", clientHandler.body.toString());
- // Wait for the server to close the connection.
- ch.close();
- ch.eventLoop().shutdownNow();
+ webServerComponent.stop(true);
+ Assert.assertFalse(webServerComponent.isStarted());
+ }
+
+ @Test
+ public void testComponentStopStartBehavior() throws Exception {
+ BindingDTO bindingDTO = new BindingDTO();
+ bindingDTO.uri = "http://localhost:0";
+ WebServerDTO webServerDTO = new WebServerDTO();
+ webServerDTO.setBindings(Collections.singletonList(bindingDTO));
+ webServerDTO.path = "webapps";
+ WebServerComponent webServerComponent = new WebServerComponent();
+ Assert.assertFalse(webServerComponent.isStarted());
+ webServerComponent.configure(webServerDTO, "./src/test/resources/",
"./src/test/resources/");
+ webServerComponent.start();
+ // Make the connection attempt.
+ verifyConnection(webServerComponent.getPort());
Assert.assertTrue(webServerComponent.isStarted());
//usual stop won't actually stop it
@@ -208,6 +199,14 @@ public class WebServerComponentTest extends Assert {
webServerComponent.stop(true);
Assert.assertFalse(webServerComponent.isStarted());
+
+ webServerComponent.start();
+ assertTrue(webServerComponent.isStarted());
+
+ verifyConnection(webServerComponent.getPort());
+
+ webServerComponent.stop(true);
+ Assert.assertFalse(webServerComponent.isStarted());
}
@Test
@@ -257,15 +256,7 @@ public class WebServerComponentTest extends Assert {
CountDownLatch latch = new CountDownLatch(1);
final ClientHandler clientHandler = new ClientHandler(latch);
- bootstrap.group(group).channel(NioSocketChannel.class).handler(new
ChannelInitializer() {
- @Override
- protected void initChannel(Channel ch) throws Exception {
- ch.pipeline().addLast(sslHandler);
- ch.pipeline().addLast(new HttpClientCodec());
- ch.pipeline().addLast(clientHandler);
- }
- });
- Channel ch = bootstrap.connect("localhost", port).sync().channel();
+ Channel ch = getSslChannel(port, sslHandler, clientHandler);
URI uri = new URI(SECURE_URL);
// Prepare the HTTP request.
@@ -334,15 +325,7 @@ public class WebServerComponentTest extends Assert {
CountDownLatch latch = new CountDownLatch(1);
final ClientHandler clientHandler = new ClientHandler(latch);
- bootstrap.group(group).channel(NioSocketChannel.class).handler(new
ChannelInitializer() {
- @Override
- protected void initChannel(Channel ch) throws Exception {
- ch.pipeline().addLast(sslHandler);
- ch.pipeline().addLast(new HttpClientCodec());
- ch.pipeline().addLast(clientHandler);
- }
- });
- Channel ch = bootstrap.connect("localhost", port).sync().channel();
+ Channel ch = getSslChannel(port, sslHandler, clientHandler);
URI uri = new URI(SECURE_URL);
// Prepare the HTTP request.
@@ -420,14 +403,14 @@ public class WebServerComponentTest extends Assert {
public void testServerCleanupBeforeStart() throws Exception {
final String warName = "simple-app.war";
createTestWar(warName);
+ final String url = "simple-app/";
AppDTO app = new AppDTO();
- app.url = "simple-app/";
+ app.url = url;
app.war = warName;
BindingDTO bindingDTO = new BindingDTO();
bindingDTO.uri = "http://localhost:0";
bindingDTO.apps = new ArrayList<>();
- bindingDTO.apps.add(app);
WebServerDTO webServerDTO = new WebServerDTO();
webServerDTO.setBindings(Collections.singletonList(bindingDTO));
webServerDTO.path = "";
@@ -435,15 +418,12 @@ public class WebServerComponentTest extends Assert {
Assert.assertFalse(webServerComponent.isStarted());
testedComponents.add(webServerComponent);
webServerComponent.configure(webServerDTO, "./target", "./target");
- //create some garbage
- List<WebAppContext> contexts = webServerComponent.getWebContexts();
+ WebAppContext ctxt =
WebServerComponentTestAccessor.createWebAppContext(webServerComponent, url,
warName, Paths.get(".").resolve("target").toAbsolutePath(), null);
+ webServerComponent.getWebContexts().add(ctxt);
WebInfConfiguration cfg = new WebInfConfiguration();
- assertEquals(1, contexts.size());
- WebAppContext ctxt = contexts.get(0);
List<File> garbage = new ArrayList<>();
-
cfg.resolveTempDirectory(ctxt);
File tmpdir = ctxt.getTempDirectory();
@@ -463,40 +443,6 @@ public class WebServerComponentTest extends Assert {
for (File file : garbage) {
assertFalse("file exist: " + file.getAbsolutePath(), file.exists());
}
-
- //check the war is working
- final int port = webServerComponent.getPort();
-
- // Make the connection attempt.
- CountDownLatch latch = new CountDownLatch(1);
- final ClientHandler clientHandler = new ClientHandler(latch);
- bootstrap.group(group).channel(NioSocketChannel.class).handler(new
ChannelInitializer() {
- @Override
- protected void initChannel(Channel ch) throws Exception {
- ch.pipeline().addLast(new HttpClientCodec(8192, 8192, 8192,
false));
- ch.pipeline().addLast(clientHandler);
- }
- });
-
- Channel ch = bootstrap.connect("localhost", port).sync().channel();
-
- String warUrl = "http://localhost:" + port + "/" + app.url;
-
- URI uri = new URI(warUrl);
-
- // Prepare the HTTP request.
- HttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1,
HttpMethod.GET, uri.getRawPath());
- request.headers().set(HttpHeaderNames.HOST, "localhost");
-
- // Send the HTTP request.
- ch.writeAndFlush(request);
- assertTrue(latch.await(5, TimeUnit.SECONDS));
-
- assertTrue("content: " + clientHandler.body.toString(),
clientHandler.body.toString().contains("Hello Artemis Test"));
- assertNull(clientHandler.serverHeader);
- // Wait for the server to close the connection.
- ch.close();
- ch.eventLoop().shutdownNow();
Assert.assertTrue(webServerComponent.isStarted());
webServerComponent.stop(true);
Assert.assertFalse(webServerComponent.isStarted());
@@ -574,6 +520,52 @@ public class WebServerComponentTest extends Assert {
}
}
+ private Channel getChannel(int port, ClientHandler clientHandler) throws
InterruptedException {
+ EventLoopGroup group = new NioEventLoopGroup();
+ Bootstrap bootstrap = new Bootstrap();
+ bootstrap.group(group).channel(NioSocketChannel.class).handler(new
ChannelInitializer() {
+ @Override
+ protected void initChannel(Channel ch) throws Exception {
+ ch.pipeline().addLast(new HttpClientCodec());
+ ch.pipeline().addLast(clientHandler);
+ }
+ });
+ return bootstrap.connect("localhost", port).sync().channel();
+ }
+
+ private Channel getSslChannel(int port, SslHandler sslHandler,
ClientHandler clientHandler) throws InterruptedException {
+ EventLoopGroup group = new NioEventLoopGroup();
+ Bootstrap bootstrap = new Bootstrap();
+ bootstrap.group(group).channel(NioSocketChannel.class).handler(new
ChannelInitializer() {
+ @Override
+ protected void initChannel(Channel ch) throws Exception {
+ ch.pipeline().addLast(sslHandler);
+ ch.pipeline().addLast(new HttpClientCodec());
+ ch.pipeline().addLast(clientHandler);
+ }
+ });
+ return bootstrap.connect("localhost", port).sync().channel();
+ }
+
+ private void verifyConnection(int port) throws InterruptedException,
URISyntaxException {
+ CountDownLatch latch = new CountDownLatch(1);
+ final ClientHandler clientHandler = new ClientHandler(latch);
+ Channel ch = getChannel(port, clientHandler);
+
+ URI uri = new URI(URL);
+ // Prepare the HTTP request.
+ HttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1,
HttpMethod.GET, uri.getRawPath());
+ request.headers().set(HttpHeaderNames.HOST, "localhost");
+
+ // Send the HTTP request.
+ ch.writeAndFlush(request);
+ assertTrue(latch.await(5, TimeUnit.SECONDS));
+ assertEquals("12345", clientHandler.body.toString());
+ // Wait for the server to close the connection.
+ ch.close();
+ ch.eventLoop().shutdownNow();
+ }
+
class ClientHandler extends SimpleChannelInboundHandler<HttpObject> {
private CountDownLatch latch;
diff --git a/docs/user-manual/en/versions.md b/docs/user-manual/en/versions.md
index 3f1e4e29aa..67ffc279a0 100644
--- a/docs/user-manual/en/versions.md
+++ b/docs/user-manual/en/versions.md
@@ -8,6 +8,12 @@ This chapter provides the following information for each
release:
- **Note:** Follow the general upgrade procedure outlined in the [Upgrading
the Broker](upgrading.md)
chapter in addition to any version-specific upgrade instructions outlined
here.
+## 2.23.0
+[Full release notes](TBD).
+
+Highlights:
+- New [management operations](web-server.md#management) for the embedded web
server.
+
## 2.21.0
[Full release
notes](https://issues.apache.org/jira/secure/ReleaseNote.jspa?version=12351083&projectId=12315920).
diff --git a/docs/user-manual/en/web-server.md
b/docs/user-manual/en/web-server.md
index 3a01bd45ba..7953b157e4 100644
--- a/docs/user-manual/en/web-server.md
+++ b/docs/user-manual/en/web-server.md
@@ -122,4 +122,11 @@ Set the `customizer` attribute via the `web` element to
enable the [`ForwardedRe
<app url="console" war="console.war"/>
</binding>
</web>
-```
\ No newline at end of file
+```
+
+## Management
+
+The embedded web server can be stopped, started, or restarted via any available
+management interface via the `stopEmbeddedWebServer`,
`starteEmbeddedWebServer`,
+and `restartEmbeddedWebServer` operations on the `ActiveMQServerControl`
+respectively.
\ No newline at end of file
diff --git
a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ActiveMQServerControlTest.java
b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ActiveMQServerControlTest.java
index 78e764995f..e92270db41 100644
---
a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ActiveMQServerControlTest.java
+++
b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ActiveMQServerControlTest.java
@@ -36,9 +36,14 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration;
import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
+import org.apache.activemq.artemis.api.core.ActiveMQIllegalStateException;
+import org.apache.activemq.artemis.api.core.ActiveMQTimeoutException;
import org.apache.activemq.artemis.api.core.JsonUtil;
import org.apache.activemq.artemis.api.core.QueueConfiguration;
import org.apache.activemq.artemis.api.core.RoutingType;
@@ -78,6 +83,7 @@ import
org.apache.activemq.artemis.core.server.BrokerConnection;
import org.apache.activemq.artemis.core.server.Queue;
import org.apache.activemq.artemis.core.server.ServerConsumer;
import org.apache.activemq.artemis.core.server.ServerSession;
+import org.apache.activemq.artemis.core.server.ServiceComponent;
import org.apache.activemq.artemis.core.server.impl.AddressInfo;
import org.apache.activemq.artemis.core.settings.impl.AddressSettings;
import org.apache.activemq.artemis.core.settings.impl.DeletionPolicy;
@@ -88,6 +94,7 @@ import
org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory;
import org.apache.activemq.artemis.jms.client.ActiveMQSession;
import org.apache.activemq.artemis.json.JsonArray;
import org.apache.activemq.artemis.json.JsonObject;
+import org.apache.activemq.artemis.marker.WebServerComponentMarker;
import org.apache.activemq.artemis.nativo.jlibaio.LibaioContext;
import
org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager;
import org.apache.activemq.artemis.spi.core.security.jaas.InVMLoginModule;
@@ -4290,6 +4297,131 @@ public class ActiveMQServerControlTest extends
ManagementTestBase {
}
}
+ @Test
+ public void testManualStopStartEmbeddedWebServer() throws Exception {
+ FakeWebServerComponent fake = new FakeWebServerComponent();
+ server.addExternalComponent(fake, true);
+ Assert.assertTrue(fake.isStarted());
+
+ ActiveMQServerControl serverControl = createManagementControl();
+ serverControl.stopEmbeddedWebServer();
+ Assert.assertFalse(fake.isStarted());
+ serverControl.startEmbeddedWebServer();
+ Assert.assertTrue(fake.isStarted());
+ }
+
+ @Test
+ public void testRestartEmbeddedWebServer() throws Exception {
+ FakeWebServerComponent fake = new FakeWebServerComponent();
+ server.addExternalComponent(fake, true);
+ Assert.assertTrue(fake.isStarted());
+
+ ActiveMQServerControl serverControl = createManagementControl();
+ long time = System.currentTimeMillis();
+ Assert.assertTrue(time >= fake.getStartTime());
+ Assert.assertTrue(time > fake.getStopTime());
+ Thread.sleep(5);
+ serverControl.restartEmbeddedWebServer();
+ Assert.assertTrue(serverControl.isEmbeddedWebServerStarted());
+ Assert.assertTrue(time < fake.getStartTime());
+ Assert.assertTrue(time < fake.getStopTime());
+ }
+
+ @Test
+ public void testRestartEmbeddedWebServerTimeout() throws Exception {
+ final CountDownLatch startDelay = new CountDownLatch(1);
+ FakeWebServerComponent fake = new FakeWebServerComponent(startDelay);
+ server.addExternalComponent(fake, false);
+
+ ActiveMQServerControl serverControl = createManagementControl();
+ try {
+ serverControl.restartEmbeddedWebServer(10);
+ fail();
+ } catch (ActiveMQTimeoutException e) {
+ // expected
+ } finally {
+ startDelay.countDown();
+ }
+ Wait.waitFor(() -> fake.isStarted());
+ }
+
+ @Test
+ public void testRestartEmbeddedWebServerException() throws Exception {
+ final String message = RandomUtil.randomString();
+ final Exception startException = new
ActiveMQIllegalStateException(message);
+ FakeWebServerComponent fake = new FakeWebServerComponent(startException);
+ server.addExternalComponent(fake, false);
+
+ ActiveMQServerControl serverControl = createManagementControl();
+ try {
+ serverControl.restartEmbeddedWebServer(10);
+ fail();
+ } catch (ActiveMQIllegalStateException e) {
+ assertEquals(message, e.getMessage());
+ }
+ }
+
+ class FakeWebServerComponent implements ServiceComponent,
WebServerComponentMarker {
+ AtomicBoolean started = new AtomicBoolean(false);
+ AtomicLong startTime = new AtomicLong(0);
+ AtomicLong stopTime = new AtomicLong(0);
+ CountDownLatch startDelay;
+ Exception startException;
+
+ FakeWebServerComponent(CountDownLatch startDelay) {
+ this.startDelay = startDelay;
+ }
+
+ FakeWebServerComponent(Exception startException) {
+ this.startException = startException;
+ }
+
+ FakeWebServerComponent() {
+ }
+
+ @Override
+ public void start() throws Exception {
+ if (started.get()) {
+ return;
+ }
+ if (startDelay != null) {
+ startDelay.await();
+ }
+ if (startException != null) {
+ throw startException;
+ }
+ startTime.set(System.currentTimeMillis());
+ started.set(true);
+ }
+
+ @Override
+ public void stop() throws Exception {
+ stop(false);
+ }
+
+ @Override
+ public void stop(boolean shutdown) throws Exception {
+ if (!shutdown) {
+ throw new RuntimeException("shutdown flag must be true");
+ }
+ stopTime.set(System.currentTimeMillis());
+ started.set(false);
+ }
+
+ @Override
+ public boolean isStarted() {
+ return started.get();
+ }
+
+ public long getStartTime() {
+ return startTime.get();
+ }
+
+ public long getStopTime() {
+ return stopTime.get();
+ }
+ }
+
protected void scaleDown(ScaleDownHandler handler) throws Exception {
SimpleString address = new SimpleString("testQueue");
HashMap<String, Object> params = new HashMap<>();
diff --git
a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ActiveMQServerControlUsingCoreTest.java
b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ActiveMQServerControlUsingCoreTest.java
index 032e50f6c5..e84fe107ba 100644
---
a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ActiveMQServerControlUsingCoreTest.java
+++
b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ActiveMQServerControlUsingCoreTest.java
@@ -1661,6 +1661,31 @@ public class ActiveMQServerControlUsingCoreTest extends
ActiveMQServerControlTes
String filter) throws Exception {
proxy.invokeOperation("replay", startScan, endScan, address,
target, filter);
}
+
+ @Override
+ public void stopEmbeddedWebServer() throws Exception {
+ proxy.invokeOperation("stopEmbeddedWebServer");
+ }
+
+ @Override
+ public void startEmbeddedWebServer() throws Exception {
+ proxy.invokeOperation("startEmbeddedWebServer");
+ }
+
+ @Override
+ public void restartEmbeddedWebServer() throws Exception {
+ proxy.invokeOperation("restartEmbeddedWebServer");
+ }
+
+ @Override
+ public void restartEmbeddedWebServer(long timeout) throws Exception {
+ proxy.invokeOperation("restartEmbeddedWebServer", timeout);
+ }
+
+ @Override
+ public boolean isEmbeddedWebServerStarted() {
+ return (boolean)
proxy.retrieveAttributeValue("embeddedWebServerStarted");
+ }
};
}