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.
This commit is contained in:
parent
de1542357b
commit
974bd5f6e1
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
server = new Server();
|
||||
|
||||
HttpConfiguration httpConfiguration = new HttpConfiguration();
|
||||
|
||||
if (webServerConfig.customizer != null) {
|
||||
try {
|
||||
httpConfiguration.addCustomizer((HttpConfiguration.Customizer) Class.forName(webServerConfig.customizer).getConstructor().newInstance());
|
||||
} catch (Throwable t) {
|
||||
ActiveMQWebLogger.LOGGER.customizerNotLoaded(webServerConfig.customizer, t);
|
||||
}
|
||||
}
|
||||
|
||||
List<BindingDTO> bindings = webServerConfig.getBindings();
|
||||
connectors = new ServerConnector[bindings.size()];
|
||||
String[] virtualHosts = new String[bindings.size()];
|
||||
|
||||
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);
|
||||
|
||||
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();
|
||||
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 (this.webServerConfig.customizer != null) {
|
||||
try {
|
||||
httpConfiguration.addCustomizer((HttpConfiguration.Customizer) Class.forName(this.webServerConfig.customizer).getConstructor().newInstance());
|
||||
} catch (Throwable t) {
|
||||
ActiveMQWebLogger.LOGGER.customizerNotLoaded(this.webServerConfig.customizer, t);
|
||||
}
|
||||
}
|
||||
|
||||
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 = createServerConnector(httpConfiguration, i, binding, uri, scheme);
|
||||
|
||||
connectors[i] = connector;
|
||||
virtualHosts[i] = "@Connector-" + 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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,8 @@ 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();
|
||||
|
||||
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();
|
||||
verifyConnection(webServerComponent.getPort());
|
||||
Assert.assertTrue(webServerComponent.isStarted());
|
||||
|
||||
//usual stop won't actually stop it
|
||||
|
@ -210,6 +178,37 @@ public class WebServerComponentTest extends Assert {
|
|||
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
|
||||
webServerComponent.stop();
|
||||
assertTrue(webServerComponent.isStarted());
|
||||
|
||||
webServerComponent.stop(true);
|
||||
Assert.assertFalse(webServerComponent.isStarted());
|
||||
|
||||
webServerComponent.start();
|
||||
assertTrue(webServerComponent.isStarted());
|
||||
|
||||
verifyConnection(webServerComponent.getPort());
|
||||
|
||||
webServerComponent.stop(true);
|
||||
Assert.assertFalse(webServerComponent.isStarted());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simpleSecureServer() throws Exception {
|
||||
BindingDTO bindingDTO = new BindingDTO();
|
||||
|
@ -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;
|
||||
|
|
|
@ -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).
|
||||
|
||||
|
|
|
@ -123,3 +123,10 @@ Set the `customizer` attribute via the `web` element to enable the [`ForwardedRe
|
|||
</binding>
|
||||
</web>
|
||||
```
|
||||
|
||||
## 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.
|
|
@ -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<>();
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue