/**
* 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.camel.component.websocket;
import java.lang.management.ManagementFactory;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.DispatcherType;
import org.apache.camel.Endpoint;
import org.apache.camel.RuntimeCamelException;
import org.apache.camel.SSLContextParametersAware;
import org.apache.camel.impl.UriEndpointComponent;
import org.apache.camel.spi.Metadata;
import org.apache.camel.util.ObjectHelper;
import org.apache.camel.util.jsse.SSLContextParameters;
import org.eclipse.jetty.jmx.MBeanContainer;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlets.CrossOriginFilter;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.ThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class WebsocketComponent extends UriEndpointComponent implements SSLContextParametersAware {
protected static final Logger LOG = LoggerFactory.getLogger(WebsocketComponent.class);
protected static final HashMap<String, ConnectorRef> CONNECTORS = new HashMap<String, ConnectorRef>();
protected Map<String, WebSocketFactory> socketFactory;
protected Server staticResourcesServer;
protected MBeanContainer mbContainer;
@Metadata(label = "security")
protected SSLContextParameters sslContextParameters;
@Metadata(label = "security", defaultValue = "false")
protected boolean useGlobalSslContextParameters;
@Metadata(label = "advanced")
protected ThreadPool threadPool;
@Metadata(defaultValue = "9292")
protected Integer port = 9292;
@Metadata(label = "advanced")
protected Integer minThreads;
@Metadata(label = "advanced")
protected Integer maxThreads;
@Metadata(label = "advanced")
protected boolean enableJmx;
@Metadata(defaultValue = "0.0.0.0")
protected String host = "0.0.0.0";
@Metadata(label = "consumer")
protected String staticResources;
@Metadata(label = "security", secret = true)
protected String sslKeyPassword;
@Metadata(label = "security", secret = true)
protected String sslPassword;
@Metadata(label = "security", secret = true)
protected String sslKeystore;
/**
* Map for storing servlets. {@link WebsocketComponentServlet} is identified by pathSpec {@link String}.
*/
private Map<String, WebsocketComponentServlet> servlets = new HashMap<String, WebsocketComponentServlet>();
class ConnectorRef {
Server server;
ServerConnector connector;
WebsocketComponentServlet servlet;
MemoryWebsocketStore memoryStore;
int refCount;
ConnectorRef(Server server, ServerConnector connector, WebsocketComponentServlet servlet, MemoryWebsocketStore memoryStore) {
this.server = server;
this.connector = connector;
this.servlet = servlet;
this.memoryStore = memoryStore;
increment();
}
public int increment() {
return ++refCount;
}
public int decrement() {
return --refCount;
}
public int getRefCount() {
return refCount;
}
}
public WebsocketComponent() {
super(WebsocketEndpoint.class);
if (this.socketFactory == null) {
this.socketFactory = new HashMap<String, WebSocketFactory>();
this.socketFactory.put("default", new DefaultWebsocketFactory());
}
}
/**
* Connects the URL specified on the endpoint to the specified processor.
*/
public void connect(WebsocketProducerConsumer prodcon) throws Exception {
Server server = null;
WebsocketEndpoint endpoint = prodcon.getEndpoint();
String connectorKey = getConnectorKey(endpoint);
synchronized (CONNECTORS) {
ConnectorRef connectorRef = CONNECTORS.get(connectorKey);
if (connectorRef == null) {
ServerConnector connector;
// Create Server and add connector
server = createServer();
if (endpoint.isEnableJmx()) {
enableJmx(server);
}
if (endpoint.getSslContextParameters() != null) {
connector = getSslSocketConnector(server, endpoint.getSslContextParameters());
} else {
connector = new ServerConnector(server);
}
if (endpoint.getPort() != null) {
connector.setPort(endpoint.getPort());
} else {
connector.setPort(port);
}
if (endpoint.getHost() != null) {
connector.setHost(endpoint.getHost());
} else {
connector.setHost(host);
}
server.addConnector(connector);
LOG.trace("Jetty Connector added: {}", connector.getName());
// Create ServletContextHandler
ServletContextHandler context = createContext(server, connector, endpoint.getHandlers());
// setup the WebSocketComponentServlet initial parameters
setWebSocketComponentServletInitialParameter(context, endpoint);
server.setHandler(context);
// Apply CORS (http://www.w3.org/TR/cors/)
applyCrossOriginFiltering(endpoint, context);
// Create Static resources
if (endpoint.getStaticResources() != null) {
server = createStaticResourcesServer(server, context, endpoint.getStaticResources());
}
MemoryWebsocketStore memoryStore = new MemoryWebsocketStore();
// Don't provide a Servlet object as Producer/Consumer will create them later on
connectorRef = new ConnectorRef(server, connector, null, memoryStore);
// must enable session before we start
if (endpoint.isSessionSupport()) {
enableSessionSupport(connectorRef.server, connectorKey);
}
LOG.info("Jetty Server starting on host: {}:{}", connector.getHost(), connector.getPort());
connectorRef.memoryStore.start();
connectorRef.server.start();
CONNECTORS.put(connectorKey, connectorRef);
} else {
connectorRef.increment();
}
// check the session support
if (endpoint.isSessionSupport()) {
enableSessionSupport(connectorRef.server, connectorKey);
}
NodeSynchronization sync = new DefaultNodeSynchronization(connectorRef.memoryStore);
WebsocketComponentServlet servlet = addServlet(sync, prodcon, endpoint.getResourceUri());
if (prodcon instanceof WebsocketConsumer) {
WebsocketConsumer consumer = WebsocketConsumer.class.cast(prodcon);
if (servlet.getConsumer() == null) {
servlet.setConsumer(consumer);
}
// register the consumer here
servlet.connect(consumer);
}
if (prodcon instanceof WebsocketProducer) {
WebsocketProducer producer = WebsocketProducer.class.cast(prodcon);
producer.setStore(connectorRef.memoryStore);
}
}
}
/**
* Disconnects the URL specified on the endpoint from the specified
* processor.
*/
public void disconnect(WebsocketProducerConsumer prodcon) throws Exception {
// If the connector is not needed anymore then stop it
WebsocketEndpoint endpoint = prodcon.getEndpoint();
String connectorKey = getConnectorKey(endpoint);
synchronized (CONNECTORS) {
ConnectorRef connectorRef = CONNECTORS.get(connectorKey);
if (connectorRef != null) {
if (connectorRef.decrement() == 0) {
LOG.info("Stopping Jetty Server as the last connector is disconnecting: {}:{}", connectorRef.connector.getHost(), connectorRef.connector.getPort());
servlets.remove(createPathSpec(endpoint.getResourceUri()));
connectorRef.server.removeConnector(connectorRef.connector);
if (connectorRef.connector != null) {
// static server may not have set a connector
connectorRef.connector.stop();
}
connectorRef.server.stop();
connectorRef.memoryStore.stop();
CONNECTORS.remove(connectorKey);
// Camel controls the lifecycle of these entities so remove the
// registered MBeans when Camel is done with the managed objects.
if (mbContainer != null) {
mbContainer.beanRemoved(null, connectorRef.server);
mbContainer.beanRemoved(null, connectorRef.connector);
}
}
if (prodcon instanceof WebsocketConsumer) {
connectorRef.servlet.disconnect((WebsocketConsumer) prodcon);
}
if (prodcon instanceof WebsocketProducer) {
((WebsocketProducer) prodcon).setStore(null);
}
}
}
}
public synchronized MBeanContainer getMbContainer() {
// If null, provide the default implementation.
if (mbContainer == null) {
mbContainer = new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
}
return this.mbContainer;
}
@Override
protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception {
SSLContextParameters sslContextParameters = resolveAndRemoveReferenceParameter(parameters, "sslContextParameters", SSLContextParameters.class);
Boolean enableJmx = getAndRemoveParameter(parameters, "enableJmx", Boolean.class);
String staticResources = getAndRemoveParameter(parameters, "staticResources", String.class);
int port = extractPortNumber(remaining);
String host = extractHostName(remaining);
WebsocketEndpoint endpoint = new WebsocketEndpoint(this, uri, remaining, parameters);
if (enableJmx != null) {
endpoint.setEnableJmx(enableJmx);
} else {
endpoint.setEnableJmx(isEnableJmx());
}
// prefer to use endpoint configured over component configured
if (sslContextParameters == null) {
// fallback to component configured
sslContextParameters = getSslContextParameters();
}
if (sslContextParameters == null) {
sslContextParameters = retrieveGlobalSslContextParameters();
}
// prefer to use endpoint configured over component configured
if (staticResources == null) {
// fallback to component configured
staticResources = getStaticResources();
}
if (staticResources != null) {
endpoint.setStaticResources(staticResources);
}
endpoint.setSslContextParameters(sslContextParameters);
endpoint.setPort(port);
endpoint.setHost(host);
setProperties(endpoint, parameters);
return endpoint;
}
protected void setWebSocketComponentServletInitialParameter(ServletContextHandler context, WebsocketEndpoint endpoint) {
if (endpoint.getBufferSize() != null) {
context.setInitParameter("bufferSize", endpoint.getBufferSize().toString());
}
if (endpoint.getMaxIdleTime() != null) {
context.setInitParameter("maxIdleTime", endpoint.getMaxIdleTime().toString());
}
if (endpoint.getMaxTextMessageSize() != null) {
context.setInitParameter("maxTextMessageSize", endpoint.getMaxTextMessageSize().toString());
}
if (endpoint.getMaxBinaryMessageSize() != null) {
context.setInitParameter("maxBinaryMessageSize", endpoint.getMaxBinaryMessageSize().toString());
}
if (endpoint.getMinVersion() != null) {
context.setInitParameter("minVersion", endpoint.getMinVersion().toString());
}
}
protected Server createServer() throws Exception {
Server server = null;
if (minThreads == null && maxThreads == null && getThreadPool() == null) {
minThreads = 1;
// 1+selectors+acceptors
maxThreads = 1 + Runtime.getRuntime().availableProcessors() * 2;
}
// configure thread pool if min/max given
if (minThreads != null || maxThreads != null) {
if (getThreadPool() != null) {
throw new IllegalArgumentException("You cannot configure both minThreads/maxThreads and a custom threadPool on JettyHttpComponent: " + this);
}
QueuedThreadPool qtp = new QueuedThreadPool();
if (minThreads != null) {
qtp.setMinThreads(minThreads.intValue());
}
if (maxThreads != null) {
qtp.setMaxThreads(maxThreads.intValue());
}
// let the thread names indicate they are from the server
qtp.setName("CamelJettyWebSocketServer");
try {
qtp.start();
} catch (Exception e) {
throw new RuntimeCamelException("Error starting JettyWebSocketServer thread pool: " + qtp, e);
}
server = new Server(qtp);
ContextHandlerCollection collection = new ContextHandlerCollection();
server.setHandler(collection);
}
if (getThreadPool() != null) {
server = new Server(getThreadPool());
ContextHandlerCollection collection = new ContextHandlerCollection();
server.setHandler(collection);
}
return server;
}
protected Server createStaticResourcesServer(Server server, ServletContextHandler context, String home) throws Exception {
context.setContextPath("/");
SessionHandler sh = new SessionHandler();
context.setSessionHandler(sh);
if (home != null) {
String[] resources = home.split(":");
if (LOG.isDebugEnabled()) {
LOG.debug(">>> Protocol found: " + resources[0] + ", and resource: " + resources[1]);
}
if (resources[0].equals("classpath")) {
context.setBaseResource(new JettyClassPathResource(getCamelContext().getClassResolver(), resources[1]));
} else if (resources[0].equals("file")) {
context.setBaseResource(Resource.newResource(resources[1]));
}
DefaultServlet defaultServlet = new DefaultServlet();
ServletHolder holder = new ServletHolder(defaultServlet);
// avoid file locking on windows
// http://stackoverflow.com/questions/184312/how-to-make-jetty-dynamically-load-static-pages
holder.setInitParameter("useFileMappedBuffer", "false");
context.addServlet(holder, "/");
}
server.setHandler(context);
return server;
}
protected Server createStaticResourcesServer(ServletContextHandler context, String host, int port, String home) throws Exception {
Server server = new Server();
HttpConfiguration httpConfig = new HttpConfiguration();
ServerConnector connector = new ServerConnector(server, new HttpConnectionFactory(httpConfig));
connector.setHost(host);
connector.setPort(port);
server.addConnector(connector);
return createStaticResourcesServer(server, context, home);
}
protected WebsocketComponentServlet addServlet(NodeSynchronization sync, WebsocketProducerConsumer prodcon, String resourceUri) throws Exception {
// Get Connector from one of the Jetty Instances to add WebSocket Servlet
WebsocketEndpoint endpoint = prodcon.getEndpoint();
String key = getConnectorKey(endpoint);
ConnectorRef connectorRef = getConnectors().get(key);
WebsocketComponentServlet servlet;
if (connectorRef != null) {
String pathSpec = createPathSpec(resourceUri);
servlet = servlets.get(pathSpec);
if (servlet == null) {
// Retrieve Context
ServletContextHandler context = (ServletContextHandler) connectorRef.server.getHandler();
servlet = createServlet(sync, pathSpec, servlets, context);
connectorRef.servlet = servlet;
LOG.debug("WebSocket servlet added for the following path : " + pathSpec + ", to the Jetty Server : " + key);
}
return servlet;
} else {
throw new Exception("Jetty instance has not been retrieved for : " + key);
}
}
protected WebsocketComponentServlet createServlet(NodeSynchronization sync, String pathSpec, Map<String, WebsocketComponentServlet> servlets, ServletContextHandler handler) {
WebsocketComponentServlet servlet = new WebsocketComponentServlet(sync, pathSpec, socketFactory);
servlets.put(pathSpec, servlet);
ServletHolder servletHolder = new ServletHolder(servlet);
servletHolder.getInitParameters().putAll(handler.getInitParams());
// Jetty 9 parameter bufferSize is now inputBufferSize
servletHolder.setInitParameter("inputBufferSize", handler.getInitParameter("bufferSize"));
handler.addServlet(servletHolder, pathSpec);
return servlet;
}
protected ServletContextHandler createContext(Server server, Connector connector, List<Handler> handlers) throws Exception {
ServletContextHandler context = new ServletContextHandler(server, "/", ServletContextHandler.NO_SECURITY | ServletContextHandler.NO_SESSIONS);
server.addConnector(connector);
if (handlers != null && !handlers.isEmpty()) {
for (Handler handler : handlers) {
if (handler instanceof HandlerWrapper) {
((HandlerWrapper) handler).setHandler(server.getHandler());
server.setHandler(handler);
} else {
HandlerCollection handlerCollection = new HandlerCollection();
handlerCollection.addHandler(server.getHandler());
handlerCollection.addHandler(handler);
server.setHandler(handlerCollection);
}
}
}
return context;
}
private void enableSessionSupport(Server server, String connectorKey) throws Exception {
ServletContextHandler context = server.getChildHandlerByClass(ServletContextHandler.class);
if (context.getSessionHandler() == null) {
SessionHandler sessionHandler = new SessionHandler();
if (context.isStarted()) {
throw new IllegalStateException("Server has already been started. Cannot enabled sessionSupport on " + connectorKey);
} else {
context.setSessionHandler(sessionHandler);
}
}
}
private ServerConnector getSslSocketConnector(Server server, SSLContextParameters sslContextParameters) throws Exception {
ServerConnector sslSocketConnector = null;
if (sslContextParameters != null) {
SslContextFactory sslContextFactory = new WebSocketComponentSslContextFactory();
sslContextFactory.setSslContext(sslContextParameters.createSSLContext(getCamelContext()));
sslSocketConnector = new ServerConnector(server, sslContextFactory);
} else {
SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setKeyStorePassword(sslKeyPassword);
sslContextFactory.setKeyManagerPassword(sslPassword);
if (sslKeystore != null) {
sslContextFactory.setKeyStorePath(sslKeystore);
}
sslSocketConnector = new ServerConnector(server, sslContextFactory);
}
return sslSocketConnector;
}
/**
* Override the key/trust store check method as it does not account for a factory that has
* a pre-configured {@link javax.net.ssl.SSLContext}.
*/
private static final class WebSocketComponentSslContextFactory extends SslContextFactory {
// This method is for Jetty 7.0.x ~ 7.4.x
@SuppressWarnings("unused")
public boolean checkConfig() {
if (getSslContext() == null) {
return checkSSLContextFactoryConfig(this);
} else {
return true;
}
}
}
private static boolean checkSSLContextFactoryConfig(Object instance) {
try {
Method method = instance.getClass().getMethod("checkConfig");
return (Boolean) method.invoke(instance);
} catch (NoSuchMethodException ex) {
// ignore
} catch (IllegalArgumentException e) {
// ignore
} catch (IllegalAccessException e) {
// ignore
} catch (InvocationTargetException e) {
// ignore
}
return false;
}
public static String createPathSpec(String remaining) {
// Is not correct as it does not support to add port in the URI
//return String.format("/%s/*", remaining);
int index = remaining.indexOf("/");
if (index != -1) {
return remaining.substring(index, remaining.length());
} else {
return "/" + remaining;
}
}
private int extractPortNumber(String remaining) {
int index1 = remaining.indexOf(":");
int index2 = remaining.indexOf("/");
if ((index1 != -1) && (index2 != -1)) {
String result = remaining.substring(index1 + 1, index2);
return Integer.parseInt(result);
} else {
return port;
}
}
private String extractHostName(String remaining) {
int index = remaining.indexOf(":");
if (index != -1) {
return remaining.substring(0, index);
} else {
return host;
}
}
private static String getConnectorKey(WebsocketEndpoint endpoint) {
return endpoint.getProtocol() + ":" + endpoint.getHost() + ":" + endpoint.getPort();
}
private void enableJmx(Server server) {
MBeanContainer containerToRegister = getMbContainer();
if (containerToRegister != null) {
LOG.info("Jetty JMX Extensions is enabled");
server.addEventListener(containerToRegister);
// Since we may have many Servers running, don't tie the MBeanContainer
// to a Server lifecycle or we end up closing it while it is still in use.
//server.addBean(mbContainer);
}
}
private void applyCrossOriginFiltering(WebsocketEndpoint endpoint, ServletContextHandler context) {
if (endpoint.isCrossOriginFilterOn()) {
FilterHolder filterHolder = new FilterHolder();
CrossOriginFilter filter = new CrossOriginFilter();
filterHolder.setFilter(filter);
filterHolder.setInitParameter("allowedOrigins", endpoint.getAllowedOrigins());
context.addFilter(filterHolder, endpoint.getFilterPath(), EnumSet.allOf(DispatcherType.class));
}
}
// Properties
// -------------------------------------------------------------------------
public String getStaticResources() {
return staticResources;
}
/**
* Set a resource path for static resources (such as .html files etc).
* <p/>
* The resources can be loaded from classpath, if you prefix with <tt>classpath:</tt>,
* otherwise the resources is loaded from file system or from JAR files.
* <p/>
* For example to load from root classpath use <tt>classpath:.</tt>, or
* <tt>classpath:WEB-INF/static</tt>
* <p/>
* If not configured (eg <tt>null</tt>) then no static resource is in use.
*/
public void setStaticResources(String staticResources) {
this.staticResources = staticResources;
}
public String getHost() {
return host;
}
/**
* The hostname. The default value is <tt>0.0.0.0</tt>
*/
public void setHost(String host) {
this.host = host;
}
public Integer getPort() {
return port;
}
/**
* The port number. The default value is <tt>9292</tt>
*/
public void setPort(Integer port) {
this.port = port;
}
public String getSslKeyPassword() {
return sslKeyPassword;
}
public String getSslPassword() {
return sslPassword;
}
public String getSslKeystore() {
return sslKeystore;
}
/**
* The password for the keystore when using SSL.
*/
public void setSslKeyPassword(String sslKeyPassword) {
this.sslKeyPassword = sslKeyPassword;
}
/**
* The password when using SSL.
*/
public void setSslPassword(String sslPassword) {
this.sslPassword = sslPassword;
}
/**
* The path to the keystore.
*/
public void setSslKeystore(String sslKeystore) {
this.sslKeystore = sslKeystore;
}
/**
* If this option is true, Jetty JMX support will be enabled for this endpoint. See Jetty JMX support for more details.
*/
public void setEnableJmx(boolean enableJmx) {
this.enableJmx = enableJmx;
}
public boolean isEnableJmx() {
return enableJmx;
}
public Integer getMinThreads() {
return minThreads;
}
/**
* To set a value for minimum number of threads in server thread pool. MaxThreads/minThreads or threadPool fields are required due to switch to Jetty9.
* The default values for minThreads is 1.
*/
public void setMinThreads(Integer minThreads) {
this.minThreads = minThreads;
}
public Integer getMaxThreads() {
return maxThreads;
}
/**
* To set a value for maximum number of threads in server thread pool. MaxThreads/minThreads or threadPool fields are required due to switch to Jetty9.
* The default values for maxThreads is 1 + 2 * noCores.
*/
public void setMaxThreads(Integer maxThreads) {
this.maxThreads = maxThreads;
}
public ThreadPool getThreadPool() {
return threadPool;
}
/**
* To use a custom thread pool for the server. MaxThreads/minThreads or threadPool fields are required due to switch to Jetty9.
*/
public void setThreadPool(ThreadPool threadPool) {
this.threadPool = threadPool;
}
public SSLContextParameters getSslContextParameters() {
return sslContextParameters;
}
/**
* To configure security using SSLContextParameters
*/
public void setSslContextParameters(SSLContextParameters sslContextParameters) {
this.sslContextParameters = sslContextParameters;
}
@Override
public boolean isUseGlobalSslContextParameters() {
return this.useGlobalSslContextParameters;
}
/**
* Enable usage of global SSL context parameters.
*/
@Override
public void setUseGlobalSslContextParameters(boolean useGlobalSslContextParameters) {
this.useGlobalSslContextParameters = useGlobalSslContextParameters;
}
public Map<String, WebSocketFactory> getSocketFactory() {
return socketFactory;
}
/**
* To configure a map which contains custom WebSocketFactory for sub protocols. The key in the map is the sub protocol.
* <p/>
* The <tt>default</tt> key is reserved for the default implementation.
*/
public void setSocketFactory(Map<String, WebSocketFactory> socketFactory) {
this.socketFactory = socketFactory;
if (!this.socketFactory.containsKey("default")) {
this.socketFactory.put("default", new DefaultWebsocketFactory());
}
}
public static HashMap<String, ConnectorRef> getConnectors() {
return CONNECTORS;
}
@Override
protected void doStart() throws Exception {
super.doStart();
if (staticResources != null) {
// host and port must be configured
ObjectHelper.notEmpty(host, "host", this);
ObjectHelper.notNull(port, "port", this);
LOG.info("Starting static resources server {}:{} with static resource: {}", new Object[]{host, port, staticResources});
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
staticResourcesServer = createStaticResourcesServer(context, host, port, staticResources);
staticResourcesServer.start();
ServerConnector connector = (ServerConnector) staticResourcesServer.getConnectors()[0];
// must add static resource server to CONNECTORS in case the websocket producers/consumers
// uses the same port number, and therefore we must be part of this
MemoryWebsocketStore memoryStore = new MemoryWebsocketStore();
ConnectorRef ref = new ConnectorRef(staticResourcesServer, connector, null, memoryStore);
String key = "websocket:" + host + ":" + port;
CONNECTORS.put(key, ref);
}
}
@Override
public void doStop() throws Exception {
super.doStop();
if (CONNECTORS.size() > 0) {
for (String connectorKey : CONNECTORS.keySet()) {
ConnectorRef connectorRef = CONNECTORS.get(connectorKey);
if (connectorRef != null && connectorRef.getRefCount() == 0) {
connectorRef.server.removeConnector(connectorRef.connector);
connectorRef.connector.stop();
connectorRef.server.stop();
connectorRef.memoryStore.stop();
connectorRef.servlet = null;
}
CONNECTORS.remove(connectorKey);
}
}
CONNECTORS.clear();
if (staticResourcesServer != null) {
LOG.info("Stopping static resources server {}:{} with static resource: {}", new Object[]{host, port, staticResources});
staticResourcesServer.stop();
staticResourcesServer.destroy();
staticResourcesServer = null;
}
servlets.clear();
}
}