import java.util.List;
import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
import org.argeo.api.cli.CommandArgsException;
import org.argeo.api.cli.CommandsCli;
import org.argeo.api.cli.DescribedCommand;
-import org.argeo.cms.client.WsPing;
+import org.argeo.cms.client.WebSocketEventClient;
+import org.argeo.cms.client.WebSocketPing;
public class CmsCommands extends CommandsCli {
+ final static Option connectOption = Option.builder().option("c").longOpt("connect").desc("server to connect to")
+ .hasArg(true).build();
public CmsCommands(String commandName) {
super(commandName);
addCommand("ping", new Ping());
+ addCommand("event", new Events());
}
@Override
}
class Ping implements DescribedCommand<Void> {
+ @Override
+ public Options getOptions() {
+ Options options = new Options();
+ options.addOption(connectOption);
+ return options;
+ }
+
+ @Override
+ public Void apply(List<String> t) {
+ CommandLine line = toCommandLine(t);
+ String uriArg = line.getOptionValue(connectOption);
+ // TODO make it more robust (trailing /, etc.)
+ URI uri = URI.create(uriArg);
+ if ("".equals(uri.getPath())) {
+ uri = URI.create(uri.toString() + "/cms/status/ping");
+ }
+ new WebSocketPing(uri).run();
+ return null;
+ }
+
+ @Override
+ public String getUsage() {
+ return "[ws|wss]://host:port/";
+ }
+
+ @Override
+ public String getDescription() {
+ return "Test whether an Argeo CMS is available, without auhtentication";
+ }
+
+ }
+
+ class Events implements DescribedCommand<Void> {
+
+ @Override
+ public Options getOptions() {
+ Options options = new Options();
+ options.addOption(connectOption);
+ return options;
+ }
@Override
public Void apply(List<String> t) {
if (remaining.size() == 0) {
throw new CommandArgsException("There must be at least one argument");
}
- String uriArg = remaining.get(0);
+ String topic = remaining.get(0);
+
+ String uriArg = line.getOptionValue(connectOption);
// TODO make it more robust (trailing /, etc.)
URI uri = URI.create(uriArg);
if ("".equals(uri.getPath())) {
- uri = URI.create(uri.toString() + "/cms/status/ping");
+ uri = URI.create(uri.toString() + "/cms/status/event/" + topic);
}
- new WsPing(uri).run();
+ new WebSocketEventClient(uri).run();
return null;
}
@Override
public String getUsage() {
- return "[ws|wss]://host:port/";
+ return "TOPIC";
}
@Override
public String getDescription() {
- return "Test whether an Argeo CMS is available, without auhtentication";
+ return "Listen to events on a topic";
}
}
<service>
<provide interface="com.sun.net.httpserver.HttpHandler"/>
</service>
- <property name="context.path" type="String" value="/cms/status"/>
+ <property name="context.path" type="String" value="/cms/status/"/>
<reference bind="setCmsState" cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
</scr:component>
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpPrincipal;
+/**
+ * An {@link HttpServlet} which integrates an {@link HttpContext} and its
+ * {@link Authenticator} in a servlet container.
+ */
public class HttpContextServlet extends HttpServlet {
private static final long serialVersionUID = 2321612280413662738L;
import com.sun.net.httpserver.HttpPrincipal;
import com.sun.net.httpserver.HttpsExchange;
-public class ServletHttpExchange extends HttpsExchange {
+/** Integrates {@link HttpsExchange} in a servlet container. */
+class ServletHttpExchange extends HttpsExchange {
private final HttpContext httpContext;
private final HttpServletRequest httpServletRequest;
private final HttpServletResponse httpServletResponse;
package org.argeo.cms.websocket.server;
import java.io.IOException;
+import java.nio.channels.ClosedChannelException;
import java.util.Map;
import javax.websocket.OnClose;
+import javax.websocket.OnError;
import javax.websocket.OnOpen;
import javax.websocket.RemoteEndpoint;
import javax.websocket.Session;
import org.argeo.api.cms.CmsEventBus;
import org.argeo.api.cms.CmsEventSubscriber;
+import org.argeo.api.cms.CmsLog;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
-@ServerEndpoint(value = "/event/{topic}", configurator = CmsWebSocketConfigurator.class)
+@ServerEndpoint(value = "/cms/status/event/{topic}", configurator = CmsWebSocketConfigurator.class)
public class EventEndpoint implements CmsEventSubscriber {
+ private final static CmsLog log = CmsLog.getLog(EventEndpoint.class);
private BundleContext bc = FrameworkUtil.getBundle(TestEndpoint.class).getBundleContext();
private RemoteEndpoint.Basic remote;
throw new IllegalStateException(e);
}
}
+
+ @OnError
+ public void onError(Throwable e) {
+ if (e instanceof ClosedChannelException) {
+ // ignore, as it probably means ping was closed on the other side
+ return;
+ }
+ log.error("Cannot process ping", e);
+ }
}
import org.argeo.api.cms.CmsLog;
-@ServerEndpoint(value = "/ping", configurator = PublicWebSocketConfigurator.class)
+@ServerEndpoint(value = "/cms/status/ping", configurator = PublicWebSocketConfigurator.class)
public class PingEndpoint {
private final static CmsLog log = CmsLog.getLog(PingEndpoint.class);
import com.fasterxml.jackson.databind.ObjectMapper;
/** Provides WebSocket access. */
-@ServerEndpoint(value = "/test/{topic}", configurator = CmsWebSocketConfigurator.class)
+@ServerEndpoint(value = "/cms/status/test/{topic}", configurator = CmsWebSocketConfigurator.class)
public class TestEndpoint implements EventHandler {
private final static CmsLog log = CmsLog.getLog(TestEndpoint.class);
import org.eclipse.jetty.servlet.ServletHolder;
import org.osgi.framework.Constants;
+/** A {@link CmsJettyServer} integrating with Equinox HTTP framework. */
public class EquinoxJettyServer extends CmsJettyServer {
private static final String INTERNAL_CONTEXT_CLASSLOADER = "org.eclipse.equinox.http.jetty.internal.ContextClassLoader";
holder.setInitParameter(Constants.SERVICE_VENDOR, "Eclipse.org"); //$NON-NLS-1$
holder.setInitParameter(Constants.SERVICE_DESCRIPTION, "Equinox Jetty-based Http Service"); //$NON-NLS-1$
- // holder.setInitParameter(JettyConstants.CONTEXT_PATH,
- // httpContext.getContextPath());
rootContextHandler.addServlet(holder, "/*");
// post-start
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.concurrent.CompletableFuture;
+import javax.servlet.ServletContext;
import javax.servlet.ServletException;
+import javax.websocket.DeploymentException;
+import javax.websocket.server.ServerContainer;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer;
+import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer.Configurator;
/** A {@link JettyHttpServer} which is compatible with Equinox servlets. */
public class CmsJettyServer extends JettyHttpServer {
private static final String INTERNAL_CONTEXT_CLASSLOADER = "org.eclipse.equinox.http.jetty.internal.ContextClassLoader";
private Path tempDir;
+ private CompletableFuture<ServerContainer> serverContainer = new CompletableFuture<>();
+
protected void addServlets(ServletContextHandler servletContextHandler) throws ServletException {
}
super.start();
}
+ @Override
protected ServletContextHandler createRootContextHandler() {
ServletContextHandler servletContextHandler = new ServletContextHandler();
servletContextHandler.setAttribute(INTERNAL_CONTEXT_CLASSLOADER,
handler.setMaxInactiveInterval(-1);
servletContextHandler.setSessionHandler(handler);
+ JavaxWebSocketServletContainerInitializer.configure(servletContextHandler, new Configurator() {
+
+ @Override
+ public void accept(ServletContext servletContext, ServerContainer serverContainer)
+ throws DeploymentException {
+ CmsJettyServer.this.serverContainer.complete(serverContainer);
+ }
+ });
+
return servletContextHandler;
}
+ @Override
+ protected ServerContainer getRootServerContainer() {
+ return serverContainer.join();
+ }
+
@Override
protected void configureRootContextHandler(ServletContextHandler servletContextHandler) throws ServletException {
addServlets(servletContextHandler);
}
+
+ /*
+ * WEB SOCKET
+ */
+
}
--- /dev/null
+package org.argeo.cms.jetty;
+
+import java.util.AbstractMap;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jetty.server.handler.ContextHandler;
+
+/**
+ * A {@link Map} implementation wrapping the attributes of a Jetty
+ * {@link ContextHandler}.
+ */
+class ContextHandlerAttributes extends AbstractMap<String, Object> {
+ private ContextHandler contextHandler;
+
+ public ContextHandlerAttributes(ContextHandler contextHandler) {
+ super();
+ this.contextHandler = contextHandler;
+ }
+
+ @Override
+ public Set<Entry<String, Object>> entrySet() {
+ Set<Entry<String, Object>> entries = new HashSet<>();
+ for (Enumeration<String> keys = contextHandler.getAttributeNames(); keys.hasMoreElements();) {
+ entries.add(new ContextAttributeEntry(keys.nextElement()));
+ }
+ return entries;
+ }
+
+ @Override
+ public Object put(String key, Object value) {
+ Object previousValue = get(key);
+ contextHandler.setAttribute(key, value);
+ return previousValue;
+ }
+
+ private class ContextAttributeEntry implements Map.Entry<String, Object> {
+ private final String key;
+
+ public ContextAttributeEntry(String key) {
+ this.key = key;
+ }
+
+ @Override
+ public String getKey() {
+ return key;
+ }
+
+ @Override
+ public Object getValue() {
+ return contextHandler.getAttribute(key);
+ }
+
+ @Override
+ public Object setValue(Object value) {
+ Object previousValue = getValue();
+ contextHandler.setAttribute(key, value);
+ return previousValue;
+ }
+
+ }
+}
--- /dev/null
+package org.argeo.cms.jetty;
+
+import java.util.AbstractMap;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.ServletContext;
+import javax.websocket.DeploymentException;
+import javax.websocket.server.ServerContainer;
+
+import org.argeo.cms.servlet.httpserver.HttpContextServlet;
+import org.argeo.cms.websocket.server.WebsocketEndpoints;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.session.SessionHandler;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer;
+import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer.Configurator;
+
+import com.sun.net.httpserver.HttpHandler;
+
+/**
+ * An @{HttpContext} implementation based on a Jetty
+ * {@link ServletContextHandler}.
+ */
+class ContextHandlerHttpContext extends JettyHttpContext {
+ private final ServletContextHandler servletContextHandler;
+ private final ContextHandlerAttributes attributes;
+
+ public ContextHandlerHttpContext(JettyHttpServer httpServer, String path) {
+ super(httpServer, path);
+
+ // Jetty context handler
+ this.servletContextHandler = new ServletContextHandler();
+ servletContextHandler.setContextPath(path);
+ HttpContextServlet servlet = new HttpContextServlet(this);
+ servletContextHandler.addServlet(new ServletHolder(servlet), "/*");
+ SessionHandler sessionHandler = new SessionHandler();
+ // FIXME find a better default
+ sessionHandler.setMaxInactiveInterval(-1);
+ servletContextHandler.setSessionHandler(sessionHandler);
+
+ attributes = new ContextHandlerAttributes(servletContextHandler);
+ }
+
+ @Override
+ public void setHandler(HttpHandler handler) {
+ super.setHandler(handler);
+
+ // web socket
+ if (handler instanceof WebsocketEndpoints) {
+ JavaxWebSocketServletContainerInitializer.configure(servletContextHandler, new Configurator() {
+
+ @Override
+ public void accept(ServletContext servletContext, ServerContainer serverContainer)
+ throws DeploymentException {
+ for (Class<?> clss : ((WebsocketEndpoints) handler).getEndPoints()) {
+ serverContainer.addEndpoint(clss);
+ }
+ }
+ });
+ }
+
+ if (getJettyHttpServer().isStarted())
+ try {
+ servletContextHandler.start();
+ } catch (Exception e) {
+ throw new IllegalStateException("Cannot start context handler", e);
+ }
+ }
+
+ @Override
+ public Map<String, Object> getAttributes() {
+ return attributes;
+ }
+
+ @Override
+ protected ServletContextHandler getServletContextHandler() {
+ return servletContextHandler;
+ }
+
+}
package org.argeo.cms.jetty;
-import java.util.AbstractMap;
import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.HashSet;
import java.util.List;
-import java.util.Map;
import java.util.Objects;
-import java.util.Set;
import javax.servlet.ServletContext;
import javax.websocket.DeploymentException;
import javax.websocket.server.ServerContainer;
-import org.argeo.cms.servlet.httpserver.HttpContextServlet;
import org.argeo.cms.websocket.server.WebsocketEndpoints;
-import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer;
import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer.Configurator;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
-/** Trivial implementation of @{HttpContext}. */
-class JettyHttpContext extends HttpContext {
+/**
+ * An @{HttpContext} implementation based on Jetty. It supports web sockets if
+ * the handler implements {@link WebsocketEndpoints}.
+ */
+abstract class JettyHttpContext extends HttpContext {
private final JettyHttpServer httpServer;
private final String path;
- private final ServletContextHandler contextHandler;
- private final ContextAttributes attributes;
private final List<Filter> filters = new ArrayList<>();
private HttpHandler handler;
public JettyHttpContext(JettyHttpServer httpServer, String path) {
this.httpServer = httpServer;
+ if (!path.endsWith("/"))
+ throw new IllegalArgumentException("Path " + path + " should end with a /");
this.path = path;
-
- // Jetty context handler
- ServletContextHandler servletContextHandler = new ServletContextHandler();
- servletContextHandler.setContextPath(path);
- HttpContextServlet servlet = new HttpContextServlet(this);
- servletContextHandler.addServlet(new ServletHolder(servlet), "/*");
- SessionHandler sessionHandler = new SessionHandler();
- // FIXME find a better default
- sessionHandler.setMaxInactiveInterval(-1);
- servletContextHandler.setSessionHandler(sessionHandler);
- contextHandler = servletContextHandler;
-
- attributes = new ContextAttributes();
}
+ protected abstract ServletContextHandler getServletContextHandler();
+
@Override
public HttpHandler getHandler() {
return handler;
throw new IllegalArgumentException("Handler is already set");
Objects.requireNonNull(handler);
this.handler = handler;
-
- // web socket
- if (handler instanceof WebsocketEndpoints) {
- JavaxWebSocketServletContainerInitializer.configure(contextHandler, new Configurator() {
-
- @Override
- public void accept(ServletContext servletContext, ServerContainer serverContainer)
- throws DeploymentException {
-// CmsWebSocketConfigurator wsEndpointConfigurator = new CmsWebSocketConfigurator();
-
- for (Class<?> clss : ((WebsocketEndpoints) handler).getEndPoints()) {
-// Class<?> clss = websocketEndpoints.get(path);
-// ServerEndpointConfig config = ServerEndpointConfig.Builder.create(clss, path)
-// .configurator(wsEndpointConfigurator).build();
- serverContainer.addEndpoint(clss);
- }
- }
- });
- }
-
- if (httpServer.isStarted())
- try {
- contextHandler.start();
- } catch (Exception e) {
- throw new IllegalStateException("Cannot start context handler", e);
- }
}
@Override
@Override
public HttpServer getServer() {
- return httpServer;
+ return getJettyHttpServer();
}
- @Override
- public Map<String, Object> getAttributes() {
- return attributes;
+ protected JettyHttpServer getJettyHttpServer() {
+ return httpServer;
}
@Override
return authenticator;
}
- ServletContextHandler getContextHandler() {
- return contextHandler;
- }
-
- private class ContextAttributes extends AbstractMap<String, Object> {
- @Override
- public Set<Entry<String, Object>> entrySet() {
- Set<Entry<String, Object>> entries = new HashSet<>();
- for (Enumeration<String> keys = contextHandler.getAttributeNames(); keys.hasMoreElements();) {
- entries.add(new ContextAttributeEntry(keys.nextElement()));
- }
- return entries;
- }
-
- @Override
- public Object put(String key, Object value) {
- Object previousValue = get(key);
- contextHandler.setAttribute(key, value);
- return previousValue;
- }
-
- private class ContextAttributeEntry implements Map.Entry<String, Object> {
- private final String key;
-
- public ContextAttributeEntry(String key) {
- this.key = key;
- }
-
- @Override
- public String getKey() {
- return key;
- }
-
- @Override
- public Object getValue() {
- return contextHandler.getAttribute(key);
- }
-
- @Override
- public Object setValue(Object value) {
- Object previousValue = getValue();
- contextHandler.setAttribute(key, value);
- return previousValue;
- }
-
- }
- }
}
import java.util.concurrent.ThreadPoolExecutor;
import javax.servlet.ServletException;
+import javax.websocket.server.ServerContainer;
import org.argeo.api.cms.CmsLog;
import org.argeo.api.cms.CmsState;
private final Map<String, JettyHttpContext> contexts = new TreeMap<>();
+ private ServletContextHandler rootContextHandler;
protected final ContextHandlerCollection contextHandlerCollection = new ContextHandlerCollection();
private boolean started;
// holder
// context
- ServletContextHandler rootContextHandler = createRootContextHandler();
+ rootContextHandler = createRootContextHandler();
// httpContext.addServlet(holder, "/*");
if (rootContextHandler != null)
configureRootContextHandler(rootContextHandler);
}
}
+ protected void configureConnectors() {
+ HttpConfiguration httpConfiguration = new HttpConfiguration();
+
+ String httpPortStr = getDeployProperty(CmsDeployProperty.HTTP_PORT);
+ String httpsPortStr = getDeployProperty(CmsDeployProperty.HTTPS_PORT);
+ if (httpPortStr != null && httpsPortStr != null)
+ throw new IllegalArgumentException("Either an HTTP or an HTTPS port should be configured, not both");
+ if (httpPortStr == null && httpsPortStr == null)
+ throw new IllegalArgumentException("Neither an HTTP or HTTPS port was configured");
+
+ /// TODO make it more generic
+ String httpHost = getDeployProperty(CmsDeployProperty.HOST);
+
+ // try {
+ if (httpPortStr != null || httpsPortStr != null) {
+ // TODO deal with hostname resolving taking too much time
+// String fallBackHostname = InetAddress.getLocalHost().getHostName();
+
+ boolean httpEnabled = httpPortStr != null;
+ boolean httpsEnabled = httpsPortStr != null;
+
+ if (httpsEnabled) {
+ int httpsPort = Integer.parseInt(httpsPortStr);
+ httpConfiguration.setSecureScheme("https");
+ httpConfiguration.setSecurePort(httpsPort);
+ }
+
+ if (httpEnabled) {
+ int httpPort = Integer.parseInt(httpPortStr);
+ httpConnector = new ServerConnector(server, new HttpConnectionFactory(httpConfiguration));
+ httpConnector.setPort(httpPort);
+ httpConnector.setHost(httpHost);
+ httpConnector.setIdleTimeout(DEFAULT_IDLE_TIMEOUT);
+
+ }
+
+ if (httpsEnabled) {
+ SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
+ // sslContextFactory.setKeyStore(KeyS)
+
+ sslContextFactory.setKeyStoreType(getDeployProperty(CmsDeployProperty.SSL_KEYSTORETYPE));
+ sslContextFactory.setKeyStorePath(getDeployProperty(CmsDeployProperty.SSL_KEYSTORE));
+ sslContextFactory.setKeyStorePassword(getDeployProperty(CmsDeployProperty.SSL_PASSWORD));
+ // sslContextFactory.setKeyManagerPassword(getFrameworkProp(CmsDeployProperty.SSL_KEYPASSWORD));
+ sslContextFactory.setProtocol("TLS");
+
+ sslContextFactory.setTrustStoreType(getDeployProperty(CmsDeployProperty.SSL_TRUSTSTORETYPE));
+ sslContextFactory.setTrustStorePath(getDeployProperty(CmsDeployProperty.SSL_TRUSTSTORE));
+ sslContextFactory.setTrustStorePassword(getDeployProperty(CmsDeployProperty.SSL_TRUSTSTOREPASSWORD));
+
+ String wantClientAuth = getDeployProperty(CmsDeployProperty.SSL_WANTCLIENTAUTH);
+ if (wantClientAuth != null && wantClientAuth.equals(Boolean.toString(true)))
+ sslContextFactory.setWantClientAuth(true);
+ String needClientAuth = getDeployProperty(CmsDeployProperty.SSL_NEEDCLIENTAUTH);
+ if (needClientAuth != null && needClientAuth.equals(Boolean.toString(true)))
+ sslContextFactory.setNeedClientAuth(true);
+
+ // HTTPS Configuration
+ HttpConfiguration https_config = new HttpConfiguration(httpConfiguration);
+ https_config.addCustomizer(new SecureRequestCustomizer());
+ https_config.setUriCompliance(UriCompliance.LEGACY);
+
+ // HTTPS connector
+ httpsConnector = new ServerConnector(server, new SslConnectionFactory(sslContextFactory, "http/1.1"),
+ new HttpConnectionFactory(https_config));
+ int httpsPort = Integer.parseInt(httpsPortStr);
+ httpsConnector.setPort(httpsPort);
+ httpsConnector.setHost(httpHost);
+ }
+ }
+ }
+
@Override
public void stop(int delay) {
// TODO wait for processing to complete
public synchronized HttpContext createContext(String path) {
if (contexts.containsKey(path))
throw new IllegalArgumentException("Context " + path + " already exists");
- JettyHttpContext httpContext = new JettyHttpContext(this, path);
+ if (!path.endsWith("/"))
+ throw new IllegalArgumentException("Path " + path + " should end with a /");
+
+ JettyHttpContext httpContext = new ServletHttpContext(this, path);
contexts.put(path, httpContext);
- contextHandlerCollection.addHandler(httpContext.getContextHandler());
+ contextHandlerCollection.addHandler(httpContext.getServletContextHandler());
return httpContext;
}
if (!contexts.containsKey(path))
throw new IllegalArgumentException("Context " + path + " does not exist");
JettyHttpContext httpContext = contexts.remove(path);
- // TODO stop handler first?
- contextHandlerCollection.removeHandler(httpContext.getContextHandler());
+ if (httpContext instanceof ContextHandlerHttpContext contextHandlerHttpContext) {
+ // TODO stop handler first?
+ contextHandlerCollection.removeHandler(contextHandlerHttpContext.getServletContextHandler());
+ }
}
@Override
return httpsConfigurator;
}
- protected void configureConnectors() {
- HttpConfiguration httpConfiguration = new HttpConfiguration();
-
- String httpPortStr = getDeployProperty(CmsDeployProperty.HTTP_PORT);
- String httpsPortStr = getDeployProperty(CmsDeployProperty.HTTPS_PORT);
- if (httpPortStr != null && httpsPortStr != null)
- throw new IllegalArgumentException("Either an HTTP or an HTTPS port should be configured, not both");
- if (httpPortStr == null && httpsPortStr == null)
- throw new IllegalArgumentException("Neither an HTTP or HTTPS port was configured");
-
- /// TODO make it more generic
- String httpHost = getDeployProperty(CmsDeployProperty.HOST);
-
- // try {
- if (httpPortStr != null || httpsPortStr != null) {
- // TODO deal with hostname resolving taking too much time
-// String fallBackHostname = InetAddress.getLocalHost().getHostName();
-
- boolean httpEnabled = httpPortStr != null;
- boolean httpsEnabled = httpsPortStr != null;
-
- if (httpsEnabled) {
- int httpsPort = Integer.parseInt(httpsPortStr);
- httpConfiguration.setSecureScheme("https");
- httpConfiguration.setSecurePort(httpsPort);
- }
-
- if (httpEnabled) {
- int httpPort = Integer.parseInt(httpPortStr);
- httpConnector = new ServerConnector(server, new HttpConnectionFactory(httpConfiguration));
- httpConnector.setPort(httpPort);
- httpConnector.setHost(httpHost);
- httpConnector.setIdleTimeout(DEFAULT_IDLE_TIMEOUT);
-
- }
-
- if (httpsEnabled) {
-
- SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
- // sslContextFactory.setKeyStore(KeyS)
-
- sslContextFactory.setKeyStoreType(getDeployProperty(CmsDeployProperty.SSL_KEYSTORETYPE));
- sslContextFactory.setKeyStorePath(getDeployProperty(CmsDeployProperty.SSL_KEYSTORE));
- sslContextFactory.setKeyStorePassword(getDeployProperty(CmsDeployProperty.SSL_PASSWORD));
- // sslContextFactory.setKeyManagerPassword(getFrameworkProp(CmsDeployProperty.SSL_KEYPASSWORD));
- sslContextFactory.setProtocol("TLS");
-
- sslContextFactory.setTrustStoreType(getDeployProperty(CmsDeployProperty.SSL_TRUSTSTORETYPE));
- sslContextFactory.setTrustStorePath(getDeployProperty(CmsDeployProperty.SSL_TRUSTSTORE));
- sslContextFactory.setTrustStorePassword(getDeployProperty(CmsDeployProperty.SSL_TRUSTSTOREPASSWORD));
-
- String wantClientAuth = getDeployProperty(CmsDeployProperty.SSL_WANTCLIENTAUTH);
- if (wantClientAuth != null && wantClientAuth.equals(Boolean.toString(true)))
- sslContextFactory.setWantClientAuth(true);
- String needClientAuth = getDeployProperty(CmsDeployProperty.SSL_NEEDCLIENTAUTH);
- if (needClientAuth != null && needClientAuth.equals(Boolean.toString(true)))
- sslContextFactory.setNeedClientAuth(true);
-
- // HTTPS Configuration
- HttpConfiguration https_config = new HttpConfiguration(httpConfiguration);
- https_config.addCustomizer(new SecureRequestCustomizer());
- https_config.setUriCompliance(UriCompliance.LEGACY);
-
- // HTTPS connector
- httpsConnector = new ServerConnector(server, new SslConnectionFactory(sslContextFactory, "http/1.1"),
- new HttpConnectionFactory(https_config));
- int httpsPort = Integer.parseInt(httpsPortStr);
- httpsConnector.setPort(httpsPort);
- httpsConnector.setHost(httpHost);
- }
-
- }
-
- }
-
protected String getDeployProperty(CmsDeployProperty deployProperty) {
return cmsState != null ? cmsState.getDeployProperty(deployProperty.getProperty())
: System.getProperty(deployProperty.getProperty());
private String httpPortsMsg() {
- return (httpConnector != null ? "HTTP " + getHttpPort() + " " : " ")
+ return (httpConnector != null ? "HTTP " + getHttpPort() + " " : "")
+ (httpsConnector != null ? "HTTPS " + getHttpsPort() : "");
}
this.cmsState = cmsState;
}
- public boolean isStarted() {
+ boolean isStarted() {
return started;
}
+ ServletContextHandler getRootContextHandler() {
+ return rootContextHandler;
+ }
+
+ ServerContainer getRootServerContainer() {
+ throw new UnsupportedOperationException();
+ }
+
public static void main(String... args) {
JettyHttpServer httpServer = new JettyHttpServer();
System.setProperty("argeo.http.port", "8080");
--- /dev/null
+package org.argeo.cms.jetty;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.websocket.DeploymentException;
+import javax.websocket.server.ServerContainer;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.servlet.httpserver.HttpContextServlet;
+import org.argeo.cms.websocket.server.WebsocketEndpoints;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+
+import com.sun.net.httpserver.HttpHandler;
+
+/**
+ * A {@link JettyHttpContext} based on registering a servlet to the root handler
+ * of the {@link JettyHttpServer}, in order to integrate the sessions.
+ */
+public class ServletHttpContext extends JettyHttpContext {
+ private final static CmsLog log = CmsLog.getLog(ServletHttpContext.class);
+
+ private Map<String, Object> attributes = Collections.synchronizedMap(new HashMap<>());
+
+ public ServletHttpContext(JettyHttpServer httpServer, String path) {
+ super(httpServer, path);
+
+ ServletContextHandler rootContextHandler = httpServer.getRootContextHandler();
+ HttpContextServlet servlet = new HttpContextServlet(this);
+ rootContextHandler.addServlet(new ServletHolder(servlet), path + "*");
+ }
+
+ @Override
+ public void setHandler(HttpHandler handler) {
+ super.setHandler(handler);
+
+ // web socket
+ if (handler instanceof WebsocketEndpoints) {
+ ServerContainer serverContainer = getJettyHttpServer().getRootServerContainer();
+ for (Class<?> clss : ((WebsocketEndpoints) handler).getEndPoints()) {
+ try {
+ serverContainer.addEndpoint(clss);
+ log.debug(() -> "Added web socket " + clss + " to " + getPath());
+ } catch (DeploymentException e) {
+ log.error("Cannot deploy Web Socket " + clss, e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public Map<String, Object> getAttributes() {
+ return attributes;
+ }
+
+ @Override
+ protected ServletContextHandler getServletContextHandler() {
+ return getJettyHttpServer().getRootContextHandler();
+ }
+
+}
<service>
<provide interface="com.sun.net.httpserver.HttpHandler"/>
</service>
- <property name="context.path" type="String" value="/api/acr" />
+ <property name="context.path" type="String" value="/api/acr/" />
<reference bind="setContentRepository" cardinality="1..1" interface="org.argeo.api.acr.spi.ProvidedRepository" name="ProvidedRepository" policy="static"/>
</scr:component>
import org.argeo.util.http.HttpHeader;
import org.osgi.service.useradmin.Authorization;
-/** Use the HTTP session as the basis for authentication. */
+/** Use a remote session as the basis for authentication. */
public class RemoteSessionLoginModule implements LoginModule {
private final static CmsLog log = CmsLog.getLog(RemoteSessionLoginModule.class);
return false;
// TODO factorize with below
String httpSessionId = httpSession.getId();
-// if (log.isTraceEnabled())
-// log.trace("HTTP login: " + request.getPathInfo() + " #" + httpSessionId);
CmsSessionImpl cmsSession = CmsContextImpl.getCmsContext().getCmsSessionByLocalId(httpSessionId);
if (cmsSession != null && !cmsSession.isAnonymous()) {
authorization = cmsSession.getAuthorization();
authorization = (Authorization) request.getAttribute(RemoteAuthRequest.AUTHORIZATION);
if (authorization == null) {// search by session ID
RemoteAuthSession httpSession = request.getSession();
-// if (httpSession == null) {
-// // TODO make sure this is always safe
-// if (log.isTraceEnabled())
-// log.trace("Create http session");
-// httpSession = request.createSession();
-// }
if (httpSession != null) {
String httpSessionId = httpSession.getId();
-// if (log.isTraceEnabled())
-// log.trace("HTTP login: " + request.getPathInfo() + " #" + httpSessionId);
CmsSessionImpl cmsSession = CmsContextImpl.getCmsContext().getCmsSessionByLocalId(httpSessionId);
if (cmsSession != null && !cmsSession.isAnonymous()) {
authorization = cmsSession.getAuthorization();
}
}
}
-
- // auth token
- // String mail = request.getParameter(LdapAttrs.mail.name());
- // String authPassword = request.getParameter(LdapAttrs.authPassword.name());
- // if (authPassword != null) {
- // sharedState.put(CmsAuthUtils.SHARED_STATE_PWD, authPassword);
- // if (mail != null)
- // sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, mail);
- // }
}
private void extractClientCertificate(RemoteAuthRequest req) {
import java.nio.ByteBuffer;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ExecutionException;
import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
import org.argeo.cms.auth.RemoteAuthUtils;
import org.argeo.util.http.HttpHeader;
/** Tests connectivity to the web socket server. */
-public class WebSocketEventClient {
+public class WebSocketEventClient implements Runnable {
- public static void main(String[] args) throws Exception {
- if (args.length == 0) {
- System.err.println("usage: java " + WebSocketEventClient.class.getName() + " <url>");
- System.exit(1);
- return;
- }
- URI uri = URI.create(args[0]);
- WebSocket.Listener listener = new WebSocket.Listener() {
+ private final URI uri;
- public CompletionStage<?> onText(WebSocket webSocket, CharSequence message, boolean last) {
- System.out.println(message);
- CompletionStage<String> res = CompletableFuture.completedStage(message.toString());
- return res;
- }
+ private WebSocket webSocket;
+
+ public WebSocketEventClient(URI uri) {
+ this.uri = uri;
+ }
- @Override
- public CompletionStage<?> onPong(WebSocket webSocket, ByteBuffer message) {
- // System.out.println("Pong received.");
- return null;
- }
+ @Override
+ public void run() {
+ try {
+ WebSocket.Listener listener = new WebSocket.Listener() {
- };
+ public CompletionStage<?> onText(WebSocket webSocket, CharSequence message, boolean last) {
+ System.out.println(message);
+ CompletionStage<String> res = CompletableFuture.completedStage(message.toString());
+ return res;
+ }
- // SPNEGO
- URL jaasUrl = SpnegoHttpClient.class.getResource("jaas-client-ipa.cfg");
- System.setProperty("java.security.auth.login.config", jaasUrl.toExternalForm());
- LoginContext lc = new LoginContext(SpnegoHttpClient.CLIENT_LOGIN_CONTEXT);
- lc.login();
- String token = RemoteAuthUtils.createGssToken(lc.getSubject(), "HTTP", uri.getHost());
+ @Override
+ public CompletionStage<?> onPong(WebSocket webSocket, ByteBuffer message) {
+ // System.out.println("Pong received.");
+ return null;
+ }
- HttpClient client = SpnegoHttpClient.openHttpClient(lc.getSubject());
- CompletableFuture<WebSocket> ws = client.newWebSocketBuilder()
- .header(HttpHeader.AUTHORIZATION.getHeaderName(), HttpHeader.NEGOTIATE + " " + token)
- .buildAsync(uri, listener);
+ };
- WebSocket webSocket = ws.get();
- webSocket.request(Long.MAX_VALUE);
+ // SPNEGO
+ URL jaasUrl = SpnegoHttpClient.class.getResource("jaas-client-ipa.cfg");
+ System.setProperty("java.security.auth.login.config", jaasUrl.toExternalForm());
+ LoginContext lc = new LoginContext(SpnegoHttpClient.CLIENT_LOGIN_CONTEXT);
+ lc.login();
+ String token = RemoteAuthUtils.createGssToken(lc.getSubject(), "HTTP", uri.getHost());
- Runtime.getRuntime().addShutdownHook(new Thread(() -> webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "")));
+ HttpClient client = SpnegoHttpClient.openHttpClient(lc.getSubject());
+ CompletableFuture<WebSocket> ws = client.newWebSocketBuilder()
+ .header(HttpHeader.AUTHORIZATION.getHeaderName(), HttpHeader.NEGOTIATE + " " + token)
+ .buildAsync(uri, listener);
- while (!webSocket.isInputClosed()) {
- webSocket.sendPing(ByteBuffer.allocate(0));
- Thread.sleep(10000);
+ WebSocket webSocket = ws.get();
+ webSocket.request(Long.MAX_VALUE);
+
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "")));
+
+ while (!webSocket.isInputClosed()) {
+ webSocket.sendPing(ByteBuffer.allocate(0));
+ Thread.sleep(10000);
+ }
+ }catch (InterruptedException e) {
+ if (webSocket != null)
+ webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "");
+ } catch (ExecutionException | LoginException e) {
+ throw new RuntimeException("Cannot listent to " + uri, e.getCause());
}
}
+// public static void main(String[] args) throws Exception {
+// if (args.length == 0) {
+// System.err.println("usage: java " + WebSocketEventClient.class.getName() + " <url>");
+// System.exit(1);
+// return;
+// }
+// URI uri = URI.create(args[0]);
+// }
+
}
--- /dev/null
+package org.argeo.cms.client;
+
+import java.math.RoundingMode;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.WebSocket;
+import java.nio.ByteBuffer;
+import java.text.DecimalFormat;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ExecutionException;
+
+/** Tests connectivity to the web socket server. */
+public class WebSocketPing implements Runnable {
+ private final static int PING_FRAME_SIZE = 125;
+ private final static DecimalFormat decimalFormat = new DecimalFormat("0.0");
+ static {
+ decimalFormat.setRoundingMode(RoundingMode.HALF_UP);
+ }
+
+ private final URI uri;
+ private final UUID uuid;
+
+ private WebSocket webSocket;
+
+ public WebSocketPing(URI uri) {
+ this.uri = uri;
+ this.uuid = UUID.randomUUID();
+ }
+
+ @Override
+ public void run() {
+ try {
+ WebSocket.Listener listener = new WebSocket.Listener() {
+
+ @Override
+ public CompletionStage<?> onPong(WebSocket webSocket, ByteBuffer message) {
+ long msb = message.getLong();
+ long lsb = message.getLong();
+ long end = System.nanoTime();
+ if (msb != uuid.getMostSignificantBits() || lsb != uuid.getLeastSignificantBits())
+ return null; // ignore
+ long begin = message.getLong();
+ double durationNs = end - begin;
+ double durationMs = durationNs / 1000000;
+ int size = message.remaining() + (3 * Long.BYTES);
+ System.out.println(
+ size + " bytes from " + uri + ": time=" + decimalFormat.format(durationMs) + " ms");
+ return null;
+ }
+
+ };
+
+ HttpClient client = HttpClient.newHttpClient();
+ CompletableFuture<WebSocket> ws = client.newWebSocketBuilder().buildAsync(uri, listener);
+ webSocket = ws.get();
+ webSocket.request(Long.MAX_VALUE);
+
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "")));
+
+ while (!webSocket.isInputClosed()) {
+ long begin = System.nanoTime();
+ ByteBuffer buffer = ByteBuffer.allocate(PING_FRAME_SIZE);
+ buffer.putLong(uuid.getMostSignificantBits());
+ buffer.putLong(uuid.getLeastSignificantBits());
+ buffer.putLong(begin);
+ buffer.flip();
+ webSocket.sendPing(buffer);
+ Thread.sleep(1000);
+ }
+ } catch (InterruptedException e) {
+ if (webSocket != null)
+ webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "");
+ } catch (ExecutionException e) {
+ throw new RuntimeException("Cannot ping " + uri, e.getCause());
+ }
+ }
+
+// public static void main(String[] args) throws Exception {
+// if (args.length == 0) {
+// System.err.println("usage: java " + WsPing.class.getName() + " <url>");
+// System.exit(1);
+// return;
+// }
+// URI uri = URI.create(args[0]);
+// new WsPing(uri).run();
+// }
+
+}
+++ /dev/null
-package org.argeo.cms.client;
-
-import java.math.RoundingMode;
-import java.net.URI;
-import java.net.http.HttpClient;
-import java.net.http.WebSocket;
-import java.nio.ByteBuffer;
-import java.text.DecimalFormat;
-import java.util.UUID;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionStage;
-import java.util.concurrent.ExecutionException;
-
-/** Tests connectivity to the web socket server. */
-public class WsPing implements Runnable {
- private final static int PING_FRAME_SIZE = 125;
- private final static DecimalFormat decimalFormat = new DecimalFormat("0.0");
- static {
- decimalFormat.setRoundingMode(RoundingMode.HALF_UP);
- }
-
- private final URI uri;
- private final UUID uuid;
-
- private WebSocket webSocket;
-
- public WsPing(URI uri) {
- this.uri = uri;
- this.uuid = UUID.randomUUID();
- }
-
- @Override
- public void run() {
- try {
- WebSocket.Listener listener = new WebSocket.Listener() {
-
- @Override
- public CompletionStage<?> onPong(WebSocket webSocket, ByteBuffer message) {
- long msb = message.getLong();
- long lsb = message.getLong();
- long end = System.nanoTime();
- if (msb != uuid.getMostSignificantBits() || lsb != uuid.getLeastSignificantBits())
- return null; // ignore
- long begin = message.getLong();
- double durationNs = end - begin;
- double durationMs = durationNs / 1000000;
- int size = message.remaining() + (3 * Long.BYTES);
- System.out.println(
- size + " bytes from " + uri + ": time=" + decimalFormat.format(durationMs) + " ms");
- return null;
- }
-
- };
-
- HttpClient client = HttpClient.newHttpClient();
- CompletableFuture<WebSocket> ws = client.newWebSocketBuilder().buildAsync(uri, listener);
- webSocket = ws.get();
- webSocket.request(Long.MAX_VALUE);
-
- Runtime.getRuntime().addShutdownHook(new Thread(() -> webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "")));
-
- while (!webSocket.isInputClosed()) {
- long begin = System.nanoTime();
- ByteBuffer buffer = ByteBuffer.allocate(PING_FRAME_SIZE);
- buffer.putLong(uuid.getMostSignificantBits());
- buffer.putLong(uuid.getLeastSignificantBits());
- buffer.putLong(begin);
- buffer.flip();
- webSocket.sendPing(buffer);
- Thread.sleep(1000);
- }
- } catch (InterruptedException e) {
- if (webSocket != null)
- webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "");
- } catch (ExecutionException e) {
- throw new RuntimeException("Cannot ping " + uri, e.getCause());
- }
- }
-
-// public static void main(String[] args) throws Exception {
-// if (args.length == 0) {
-// System.err.println("usage: java " + WsPing.class.getName() + " <url>");
-// System.exit(1);
-// return;
-// }
-// URI uri = URI.create(args[0]);
-// new WsPing(uri).run();
-// }
-
-}
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpPrincipal;
+/** An {@link Authenticator} implementation based on CMS authentication. */
public class CmsAuthenticator extends Authenticator {
-// final static String HEADER_AUTHORIZATION = "Authorization";
-// final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
-
-// private final static CmsLog log = CmsLog.getLog(CmsAuthenticator.class);
-
// TODO make it configurable
private final String httpAuthRealm = "Argeo";
private final boolean forceBasic = false;
import org.ietf.jgss.GSSCredential;
import org.osgi.service.useradmin.UserAdmin;
+/** Reference implementation of {@link CmsContext}. */
public class CmsContextImpl implements CmsContext {
private final CmsLog log = CmsLog.getLog(getClass());
private static CompletableFuture<CmsContextImpl> instance = new CompletableFuture<CmsContextImpl>();
-// private static CmsContextImpl instance = null;
private CmsState cmsState;
private CmsDeployment cmsDeployment;
private UserAdmin userAdmin;
private UuidFactory uuidFactory;
private CmsEventBus cmsEventBus;
-// private ProvidedRepository contentRepository;
// i18n
private Locale defaultLocale;
}
}
}, "Check readiness").start();
-
- // checkReadiness();
-
setInstance(this);
}
this.uuidFactory = uuidFactory;
}
-// public ProvidedRepository getContentRepository() {
-// return contentRepository;
-// }
-//
-// public void setContentRepository(ProvidedRepository contentRepository) {
-// this.contentRepository = contentRepository;
-// }
-
@Override
public Locale getDefaultLocale() {
return defaultLocale;
}
private static void setInstance(CmsContextImpl cmsContextImpl) {
-// if (cmsContextImpl != null) {
-// if (instance != null)
-// throw new IllegalStateException("CMS Context is already set");
-// instance = cmsContextImpl;
-// } else {
-// instance = null;
-// }
-// CmsContextImpl.class.notifyAll();
-
if (cmsContextImpl != null) {
if (instance.isDone())
throw new IllegalStateException("CMS Context is already set");
}
private static CmsContextImpl getInstance() {
-// while (instance == null) {
-// try {
-// CmsContextImpl.class.wait();
-// } catch (InterruptedException e) {
-// throw new IllegalStateException("Cannot wait for CMS context instance", e);
-// }
-// }
-// return instance;
-
try {
return instance.get();
} catch (InterruptedException | ExecutionException e) {
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
-/** Implementation of a CMS deployment. */
+/** Reference implementation of {@link CmsDeployment}. */
public class CmsDeploymentImpl implements CmsDeployment {
private final CmsLog log = CmsLog.getLog(getClass());
import org.argeo.api.cms.CmsEventSubscriber;
import org.argeo.api.cms.CmsLog;
+/** {@link CmsEventBus} implementation based on {@link Flow}. */
public class CmsEventBusImpl implements CmsEventBus {
private final CmsLog log = CmsLog.getLog(CmsEventBus.class);
- // CMS events
private Map<String, SubmissionPublisher<Map<String, Object>>> topics = new TreeMap<>();
-// private IdentityHashMap<CmsEventSubscriber, List<CmsEventFlowSubscriber>> subscriptions = new IdentityHashMap<>();
- /*
- * CMS Events
- */
@Override
public void sendEvent(String topic, Map<String, Object> event) {
SubmissionPublisher<Map<String, Object>> publisher = topics.get(topic);
}
}
+ /** A subscriber to a topic. */
static class CmsEventFlowSubscriber implements Flow.Subscriber<Map<String, Object>> {
private String topic;
private CmsEventSubscriber eventSubscriber;