From: Mathieu Baudier Date: Mon, 31 Oct 2022 08:46:44 +0000 (+0100) Subject: Improve Jetty integration in order to support consistent HTTP sessions. X-Git-Tag: v2.3.11~56 X-Git-Url: https://git.argeo.org/?p=lgpl%2Fargeo-commons.git;a=commitdiff_plain;h=5b6b49fa655c7b3ae3dcc06d6c504e3d0225684f Improve Jetty integration in order to support consistent HTTP sessions. --- diff --git a/org.argeo.cms.cli/src/org/argeo/cms/cli/CmsCommands.java b/org.argeo.cms.cli/src/org/argeo/cms/cli/CmsCommands.java index ae2b44174..6f2db4ff2 100644 --- a/org.argeo.cms.cli/src/org/argeo/cms/cli/CmsCommands.java +++ b/org.argeo.cms.cli/src/org/argeo/cms/cli/CmsCommands.java @@ -4,16 +4,22 @@ import java.net.URI; 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 @@ -22,6 +28,46 @@ public class CmsCommands extends CommandsCli { } class Ping implements DescribedCommand { + @Override + public Options getOptions() { + Options options = new Options(); + options.addOption(connectOption); + return options; + } + + @Override + public Void apply(List 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 { + + @Override + public Options getOptions() { + Options options = new Options(); + options.addOption(connectOption); + return options; + } @Override public Void apply(List t) { @@ -30,24 +76,26 @@ public class CmsCommands extends CommandsCli { 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"; } } diff --git a/org.argeo.cms.ee/OSGI-INF/statusHandler.xml b/org.argeo.cms.ee/OSGI-INF/statusHandler.xml index 37e6c5fba..b6e9bfd05 100644 --- a/org.argeo.cms.ee/OSGI-INF/statusHandler.xml +++ b/org.argeo.cms.ee/OSGI-INF/statusHandler.xml @@ -4,6 +4,6 @@ - + diff --git a/org.argeo.cms.ee/src/org/argeo/cms/servlet/httpserver/HttpContextServlet.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/httpserver/HttpContextServlet.java index b2f739449..63d59a88d 100644 --- a/org.argeo.cms.ee/src/org/argeo/cms/servlet/httpserver/HttpContextServlet.java +++ b/org.argeo.cms.ee/src/org/argeo/cms/servlet/httpserver/HttpContextServlet.java @@ -15,6 +15,10 @@ import com.sun.net.httpserver.HttpContext; 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; diff --git a/org.argeo.cms.ee/src/org/argeo/cms/servlet/httpserver/ServletHttpExchange.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/httpserver/ServletHttpExchange.java index 5a29fbe84..f5e9c0394 100644 --- a/org.argeo.cms.ee/src/org/argeo/cms/servlet/httpserver/ServletHttpExchange.java +++ b/org.argeo.cms.ee/src/org/argeo/cms/servlet/httpserver/ServletHttpExchange.java @@ -18,7 +18,8 @@ import com.sun.net.httpserver.HttpContext; 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; diff --git a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/EventEndpoint.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/EventEndpoint.java index c71c862d6..defc59efc 100644 --- a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/EventEndpoint.java +++ b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/EventEndpoint.java @@ -1,9 +1,11 @@ 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; @@ -12,11 +14,13 @@ import javax.websocket.server.ServerEndpoint; 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; @@ -47,4 +51,13 @@ public class EventEndpoint implements CmsEventSubscriber { 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); + } } diff --git a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/PingEndpoint.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/PingEndpoint.java index 5ae491acf..dcbce67b1 100644 --- a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/PingEndpoint.java +++ b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/PingEndpoint.java @@ -7,7 +7,7 @@ import javax.websocket.server.ServerEndpoint; 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); diff --git a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/TestEndpoint.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/TestEndpoint.java index 0575726d3..cc608136b 100644 --- a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/TestEndpoint.java +++ b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/TestEndpoint.java @@ -31,7 +31,7 @@ import com.fasterxml.jackson.databind.JsonMappingException; 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); diff --git a/org.argeo.cms.lib.equinox/src/org/argeo/cms/equinox/http/jetty/EquinoxJettyServer.java b/org.argeo.cms.lib.equinox/src/org/argeo/cms/equinox/http/jetty/EquinoxJettyServer.java index 1b1b42961..e6595a05e 100644 --- a/org.argeo.cms.lib.equinox/src/org/argeo/cms/equinox/http/jetty/EquinoxJettyServer.java +++ b/org.argeo.cms.lib.equinox/src/org/argeo/cms/equinox/http/jetty/EquinoxJettyServer.java @@ -21,6 +21,7 @@ import org.eclipse.jetty.servlet.ServletContextHandler; 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"; @@ -31,8 +32,6 @@ public class EquinoxJettyServer extends CmsJettyServer { 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 diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/CmsJettyServer.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/CmsJettyServer.java index 90a800f7e..b0b348d9c 100644 --- a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/CmsJettyServer.java +++ b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/CmsJettyServer.java @@ -3,11 +3,17 @@ package org.argeo.cms.jetty; 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 { @@ -16,6 +22,8 @@ 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 = new CompletableFuture<>(); + protected void addServlets(ServletContextHandler servletContextHandler) throws ServletException { } @@ -29,6 +37,7 @@ public class CmsJettyServer extends JettyHttpServer { super.start(); } + @Override protected ServletContextHandler createRootContextHandler() { ServletContextHandler servletContextHandler = new ServletContextHandler(); servletContextHandler.setAttribute(INTERNAL_CONTEXT_CLASSLOADER, @@ -41,11 +50,30 @@ public class CmsJettyServer extends JettyHttpServer { 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 + */ + } diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerAttributes.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerAttributes.java new file mode 100644 index 000000000..1e64fe075 --- /dev/null +++ b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerAttributes.java @@ -0,0 +1,64 @@ +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 { + private ContextHandler contextHandler; + + public ContextHandlerAttributes(ContextHandler contextHandler) { + super(); + this.contextHandler = contextHandler; + } + + @Override + public Set> entrySet() { + Set> entries = new HashSet<>(); + for (Enumeration 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 { + 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; + } + + } +} diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerHttpContext.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerHttpContext.java new file mode 100644 index 000000000..d6037ba8d --- /dev/null +++ b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerHttpContext.java @@ -0,0 +1,84 @@ +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 getAttributes() { + return attributes; + } + + @Override + protected ServletContextHandler getServletContextHandler() { + return servletContextHandler; + } + +} diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpContext.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpContext.java index 5876d52e8..551e54e05 100644 --- a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpContext.java +++ b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpContext.java @@ -1,23 +1,15 @@ 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; @@ -27,12 +19,13 @@ import com.sun.net.httpserver.HttpContext; 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 filters = new ArrayList<>(); private HttpHandler handler; @@ -40,22 +33,13 @@ class JettyHttpContext extends HttpContext { 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; @@ -67,32 +51,6 @@ class JettyHttpContext extends HttpContext { 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 @@ -102,12 +60,11 @@ class JettyHttpContext extends HttpContext { @Override public HttpServer getServer() { - return httpServer; + return getJettyHttpServer(); } - @Override - public Map getAttributes() { - return attributes; + protected JettyHttpServer getJettyHttpServer() { + return httpServer; } @Override @@ -127,51 +84,4 @@ class JettyHttpContext extends HttpContext { return authenticator; } - ServletContextHandler getContextHandler() { - return contextHandler; - } - - private class ContextAttributes extends AbstractMap { - @Override - public Set> entrySet() { - Set> entries = new HashSet<>(); - for (Enumeration 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 { - 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; - } - - } - } } diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpServer.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpServer.java index fd0ef4cac..11cce74cd 100644 --- a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpServer.java +++ b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpServer.java @@ -8,6 +8,7 @@ import java.util.concurrent.Executor; 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; @@ -52,6 +53,7 @@ public class JettyHttpServer extends HttpsServer { private final Map contexts = new TreeMap<>(); + private ServletContextHandler rootContextHandler; protected final ContextHandlerCollection contextHandlerCollection = new ContextHandlerCollection(); private boolean started; @@ -92,7 +94,7 @@ public class JettyHttpServer extends HttpsServer { // holder // context - ServletContextHandler rootContextHandler = createRootContextHandler(); + rootContextHandler = createRootContextHandler(); // httpContext.addServlet(holder, "/*"); if (rootContextHandler != null) configureRootContextHandler(rootContextHandler); @@ -128,6 +130,78 @@ public class JettyHttpServer extends HttpsServer { } } + 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 @@ -169,10 +243,13 @@ public class JettyHttpServer extends HttpsServer { 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; } @@ -181,8 +258,10 @@ public class JettyHttpServer extends HttpsServer { 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 @@ -208,81 +287,6 @@ public class JettyHttpServer extends HttpsServer { 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()); @@ -290,7 +294,7 @@ public class JettyHttpServer extends HttpsServer { private String httpPortsMsg() { - return (httpConnector != null ? "HTTP " + getHttpPort() + " " : " ") + return (httpConnector != null ? "HTTP " + getHttpPort() + " " : "") + (httpsConnector != null ? "HTTPS " + getHttpsPort() : ""); } @@ -318,10 +322,18 @@ public class JettyHttpServer extends HttpsServer { 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"); diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ServletHttpContext.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ServletHttpContext.java new file mode 100644 index 000000000..33611941d --- /dev/null +++ b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ServletHttpContext.java @@ -0,0 +1,64 @@ +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 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 getAttributes() { + return attributes; + } + + @Override + protected ServletContextHandler getServletContextHandler() { + return getJettyHttpServer().getRootContextHandler(); + } + +} diff --git a/org.argeo.cms/OSGI-INF/cmsAcrHttpHandler.xml b/org.argeo.cms/OSGI-INF/cmsAcrHttpHandler.xml index 6b05a9004..afd4e0aaf 100644 --- a/org.argeo.cms/OSGI-INF/cmsAcrHttpHandler.xml +++ b/org.argeo.cms/OSGI-INF/cmsAcrHttpHandler.xml @@ -4,6 +4,6 @@ - + diff --git a/org.argeo.cms/src/org/argeo/cms/auth/RemoteSessionLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/RemoteSessionLoginModule.java index d2ceb27a5..772531ede 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/RemoteSessionLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/RemoteSessionLoginModule.java @@ -22,7 +22,7 @@ import org.argeo.cms.internal.runtime.CmsStateImpl; 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); @@ -64,8 +64,6 @@ public class RemoteSessionLoginModule implements LoginModule { 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(); @@ -77,16 +75,8 @@ public class RemoteSessionLoginModule implements LoginModule { 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(); @@ -191,15 +181,6 @@ public class RemoteSessionLoginModule implements LoginModule { } } } - - // 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) { diff --git a/org.argeo.cms/src/org/argeo/cms/client/WebSocketEventClient.java b/org.argeo.cms/src/org/argeo/cms/client/WebSocketEventClient.java index 294b485fb..ed20e95b2 100644 --- a/org.argeo.cms/src/org/argeo/cms/client/WebSocketEventClient.java +++ b/org.argeo.cms/src/org/argeo/cms/client/WebSocketEventClient.java @@ -7,59 +7,80 @@ import java.net.http.WebSocket; 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() + " "); - 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 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 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 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 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() + " "); +// System.exit(1); +// return; +// } +// URI uri = URI.create(args[0]); +// } + } diff --git a/org.argeo.cms/src/org/argeo/cms/client/WebSocketPing.java b/org.argeo.cms/src/org/argeo/cms/client/WebSocketPing.java new file mode 100644 index 000000000..808c8de68 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/client/WebSocketPing.java @@ -0,0 +1,90 @@ +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 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() + " "); +// System.exit(1); +// return; +// } +// URI uri = URI.create(args[0]); +// new WsPing(uri).run(); +// } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/client/WsPing.java b/org.argeo.cms/src/org/argeo/cms/client/WsPing.java deleted file mode 100644 index caf679671..000000000 --- a/org.argeo.cms/src/org/argeo/cms/client/WsPing.java +++ /dev/null @@ -1,90 +0,0 @@ -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 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() + " "); -// System.exit(1); -// return; -// } -// URI uri = URI.create(args[0]); -// new WsPing(uri).run(); -// } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/CmsAuthenticator.java b/org.argeo.cms/src/org/argeo/cms/internal/http/CmsAuthenticator.java index 164e9b9b2..5d96244d8 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/http/CmsAuthenticator.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/http/CmsAuthenticator.java @@ -15,12 +15,8 @@ import com.sun.net.httpserver.Authenticator; 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; diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsContextImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsContextImpl.java index c2d2ccf42..bd54b2059 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsContextImpl.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsContextImpl.java @@ -25,19 +25,18 @@ import org.argeo.cms.internal.auth.CmsSessionImpl; 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 instance = new CompletableFuture(); -// 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; @@ -64,9 +63,6 @@ public class CmsContextImpl implements CmsContext { } } }, "Check readiness").start(); - - // checkReadiness(); - setInstance(this); } @@ -178,14 +174,6 @@ public class CmsContextImpl implements CmsContext { this.uuidFactory = uuidFactory; } -// public ProvidedRepository getContentRepository() { -// return contentRepository; -// } -// -// public void setContentRepository(ProvidedRepository contentRepository) { -// this.contentRepository = contentRepository; -// } - @Override public Locale getDefaultLocale() { return defaultLocale; @@ -238,15 +226,6 @@ public class CmsContextImpl implements CmsContext { } 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"); @@ -259,15 +238,6 @@ public class CmsContextImpl implements CmsContext { } 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) { diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsDeploymentImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsDeploymentImpl.java index 421ff661a..e2d1fb97a 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsDeploymentImpl.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsDeploymentImpl.java @@ -18,7 +18,7 @@ import com.sun.net.httpserver.HttpContext; 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()); diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsEventBusImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsEventBusImpl.java index eaa63756d..99f6c1d8d 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsEventBusImpl.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsEventBusImpl.java @@ -10,16 +10,12 @@ import org.argeo.api.cms.CmsEventBus; 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>> topics = new TreeMap<>(); -// private IdentityHashMap> subscriptions = new IdentityHashMap<>(); - /* - * CMS Events - */ @Override public void sendEvent(String topic, Map event) { SubmissionPublisher> publisher = topics.get(topic); @@ -58,6 +54,7 @@ public class CmsEventBusImpl implements CmsEventBus { } } + /** A subscriber to a topic. */ static class CmsEventFlowSubscriber implements Flow.Subscriber> { private String topic; private CmsEventSubscriber eventSubscriber;