From: Mathieu Baudier Date: Sun, 10 Jul 2022 09:29:54 +0000 (+0200) Subject: Refactor Jetty HTTP server X-Git-Tag: v2.3.10~128 X-Git-Url: http://git.argeo.org/?a=commitdiff_plain;h=4f4eb7a8c86fc86e02b57d30218ecfdd398f71b4;p=lgpl%2Fargeo-commons.git Refactor Jetty HTTP server --- diff --git a/org.argeo.cms.lib.equinox/.project b/org.argeo.cms.lib.equinox/.project index 3be5eb6c2..c551d5dff 100644 --- a/org.argeo.cms.lib.equinox/.project +++ b/org.argeo.cms.lib.equinox/.project @@ -20,6 +20,11 @@ + + org.eclipse.pde.ds.core.builder + + + org.eclipse.pde.PluginNature diff --git a/org.argeo.cms.lib.equinox/OSGI-INF/jettyServiceFactory.xml b/org.argeo.cms.lib.equinox/OSGI-INF/jettyServiceFactory.xml index c2cf1c73b..2eec733bc 100644 --- a/org.argeo.cms.lib.equinox/OSGI-INF/jettyServiceFactory.xml +++ b/org.argeo.cms.lib.equinox/OSGI-INF/jettyServiceFactory.xml @@ -1,6 +1,6 @@ - + diff --git a/org.argeo.cms.lib.equinox/bnd.bnd b/org.argeo.cms.lib.equinox/bnd.bnd index 3d836dfb6..2c83158e2 100644 --- a/org.argeo.cms.lib.equinox/bnd.bnd +++ b/org.argeo.cms.lib.equinox/bnd.bnd @@ -1,4 +1,2 @@ -Fragment-Host: org.eclipse.equinox.http.jetty - Service-Component: \ OSGI-INF/jettyServiceFactory.xml,\ 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 new file mode 100644 index 000000000..d73bdfd1b --- /dev/null +++ b/org.argeo.cms.lib.equinox/src/org/argeo/cms/equinox/http/jetty/EquinoxJettyServer.java @@ -0,0 +1,152 @@ +package org.argeo.cms.equinox.http.jetty; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import javax.servlet.Servlet; +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpSessionEvent; +import javax.servlet.http.HttpSessionIdListener; +import javax.servlet.http.HttpSessionListener; + +import org.argeo.cms.jetty.CmsJettyServer; +import org.eclipse.equinox.http.servlet.HttpServiceServlet; +import org.eclipse.jetty.server.session.SessionHandler; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.osgi.framework.Constants; + +public class EquinoxJettyServer extends CmsJettyServer { + private static final String INTERNAL_CONTEXT_CLASSLOADER = "org.eclipse.equinox.http.jetty.internal.ContextClassLoader"; + + @Override + protected void addServlets(ServletContextHandler servletContextHandler) throws ServletException { + ServletHolder holder = new ServletHolder(new InternalHttpServiceServlet()); + holder.setInitOrder(0); + 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()); + servletContextHandler.addServlet(holder, "/*"); + + // post-start + SessionHandler sessionManager = servletContextHandler.getSessionHandler(); + sessionManager.addEventListener((HttpSessionIdListener) holder.getServlet()); + } + + public static class InternalHttpServiceServlet implements HttpSessionListener, HttpSessionIdListener, Servlet { + private final Servlet httpServiceServlet = new HttpServiceServlet(); + private ClassLoader contextLoader; + private final Method sessionDestroyed; + private final Method sessionIdChanged; + + public InternalHttpServiceServlet() { + Class clazz = httpServiceServlet.getClass(); + + try { + sessionDestroyed = clazz.getMethod("sessionDestroyed", new Class[] { String.class }); //$NON-NLS-1$ + } catch (Exception e) { + throw new IllegalStateException(e); + } + try { + sessionIdChanged = clazz.getMethod("sessionIdChanged", new Class[] { String.class }); //$NON-NLS-1$ + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + @Override + public void init(ServletConfig config) throws ServletException { + ServletContext context = config.getServletContext(); + contextLoader = (ClassLoader) context.getAttribute(INTERNAL_CONTEXT_CLASSLOADER); + + Thread thread = Thread.currentThread(); + ClassLoader current = thread.getContextClassLoader(); + thread.setContextClassLoader(contextLoader); + try { + httpServiceServlet.init(config); + } finally { + thread.setContextClassLoader(current); + } + } + + @Override + public void destroy() { + Thread thread = Thread.currentThread(); + ClassLoader current = thread.getContextClassLoader(); + thread.setContextClassLoader(contextLoader); + try { + httpServiceServlet.destroy(); + } finally { + thread.setContextClassLoader(current); + } + contextLoader = null; + } + + @Override + public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { + Thread thread = Thread.currentThread(); + ClassLoader current = thread.getContextClassLoader(); + thread.setContextClassLoader(contextLoader); + try { + httpServiceServlet.service(req, res); + } finally { + thread.setContextClassLoader(current); + } + } + + @Override + public ServletConfig getServletConfig() { + return httpServiceServlet.getServletConfig(); + } + + @Override + public String getServletInfo() { + return httpServiceServlet.getServletInfo(); + } + + @Override + public void sessionCreated(HttpSessionEvent event) { + // Nothing to do. + } + + @Override + public void sessionDestroyed(HttpSessionEvent event) { + Thread thread = Thread.currentThread(); + ClassLoader current = thread.getContextClassLoader(); + thread.setContextClassLoader(contextLoader); + try { + sessionDestroyed.invoke(httpServiceServlet, event.getSession().getId()); + } catch (IllegalAccessException | IllegalArgumentException e) { + // not likely + } catch (InvocationTargetException e) { + throw new RuntimeException(e.getCause()); + } finally { + thread.setContextClassLoader(current); + } + } + + @Override + public void sessionIdChanged(HttpSessionEvent event, String oldSessionId) { + Thread thread = Thread.currentThread(); + ClassLoader current = thread.getContextClassLoader(); + thread.setContextClassLoader(contextLoader); + try { + sessionIdChanged.invoke(httpServiceServlet, oldSessionId); + } catch (IllegalAccessException | IllegalArgumentException e) { + // not likely + } catch (InvocationTargetException e) { + throw new RuntimeException(e.getCause()); + } finally { + thread.setContextClassLoader(current); + } + } + } + +} diff --git a/org.argeo.cms.lib.equinox/src/org/argeo/cms/servlet/internal/jetty/JettyConfig.java b/org.argeo.cms.lib.equinox/src/org/argeo/cms/servlet/internal/jetty/JettyConfig.java index 97321915a..e7a1ac176 100644 --- a/org.argeo.cms.lib.equinox/src/org/argeo/cms/servlet/internal/jetty/JettyConfig.java +++ b/org.argeo.cms.lib.equinox/src/org/argeo/cms/servlet/internal/jetty/JettyConfig.java @@ -1,5 +1,6 @@ package org.argeo.cms.servlet.internal.jetty; +import java.io.File; import java.util.Dictionary; import java.util.Hashtable; import java.util.Map; @@ -18,6 +19,7 @@ import org.argeo.cms.websocket.javax.server.TestEndpoint; import org.argeo.util.LangUtils; import org.eclipse.equinox.http.jetty.JettyConfigurator; import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; import org.osgi.framework.FrameworkUtil; import org.osgi.framework.ServiceReference; import org.osgi.util.tracker.ServiceTracker; @@ -38,7 +40,7 @@ public class JettyConfig { // We need to start asynchronously so that Jetty bundle get started by lazy init // due to the non-configurable behaviour of its activator ForkJoinPool.commonPool().execute(() -> { - Dictionary properties = getHttpServerConfig(); + Dictionary properties = getHttpServerConfig(); startServer(properties); }); @@ -90,7 +92,7 @@ public class JettyConfig { } - public void startServer(Dictionary properties) { + public void startServer(Dictionary properties) { // Explicitly configures Jetty so that the default server is not started by the // activator of the Equinox Jetty bundle. Map config = LangUtils.dictToStringMap(properties); @@ -105,38 +107,53 @@ public class JettyConfig { } } - long begin = System.currentTimeMillis(); - int tryCount = 60; - try { - while (tryCount > 0) { - try { - // FIXME deal with multiple ids - JettyConfigurator.startServer(CmsConstants.DEFAULT, new Hashtable<>(config)); - - Object httpPort = config.get(JettyHttpConstants.HTTP_PORT); - Object httpsPort = config.get(JettyHttpConstants.HTTPS_PORT); - log.info(httpPortsMsg(httpPort, httpsPort)); - - // Explicitly starts Jetty OSGi HTTP bundle, so that it gets triggered if OSGi - // configuration is not cleaned - FrameworkUtil.getBundle(JettyConfigurator.class).start(); - return; - } catch (IllegalStateException e) { - // e.printStackTrace(); - // Jetty may not be ready - try { - Thread.sleep(1000); - } catch (Exception e1) { - // silent - } - tryCount--; - } - } - long duration = System.currentTimeMillis() - begin; - log.error("Gave up with starting Jetty server after " + (duration / 1000) + " s"); - } catch (Exception e) { - log.error("Cannot start default Jetty server with config " + properties, e); - } + properties.put(Constants.SERVICE_PID, "default"); + File jettyWorkDir = new File(bc.getDataFile(""), "jettywork"); //$NON-NLS-1$ + jettyWorkDir.mkdir(); + +// HttpServerManager serverManager = new HttpServerManager(jettyWorkDir); +// try { +// serverManager.updated("default", properties); +// } catch (ConfigurationException e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } + Object httpPort = config.get(JettyHttpConstants.HTTP_PORT); + Object httpsPort = config.get(JettyHttpConstants.HTTPS_PORT); + log.info(httpPortsMsg(httpPort, httpsPort)); + +// long begin = System.currentTimeMillis(); +// int tryCount = 60; +// try { +// while (tryCount > 0) { +// try { +// // FIXME deal with multiple ids +// JettyConfigurator.startServer(CmsConstants.DEFAULT, new Hashtable<>(config)); +// +// Object httpPort = config.get(JettyHttpConstants.HTTP_PORT); +// Object httpsPort = config.get(JettyHttpConstants.HTTPS_PORT); +// log.info(httpPortsMsg(httpPort, httpsPort)); +// +// // Explicitly starts Jetty OSGi HTTP bundle, so that it gets triggered if OSGi +// // configuration is not cleaned +// FrameworkUtil.getBundle(JettyConfigurator.class).start(); +// return; +// } catch (IllegalStateException e) { +// // e.printStackTrace(); +// // Jetty may not be ready +// try { +// Thread.sleep(1000); +// } catch (Exception e1) { +// // silent +// } +// tryCount--; +// } +// } +// long duration = System.currentTimeMillis() - begin; +// log.error("Gave up with starting Jetty server after " + (duration / 1000) + " s"); +// } catch (Exception e) { +// log.error("Cannot start default Jetty server with config " + properties, e); +// } } diff --git a/org.argeo.cms.lib.equinox/src/org/argeo/cms/servlet/internal/jetty/JettyServiceFactory.java b/org.argeo.cms.lib.equinox/src/org/argeo/cms/servlet/internal/jetty/JettyServiceFactory.java deleted file mode 100644 index 005af743c..000000000 --- a/org.argeo.cms.lib.equinox/src/org/argeo/cms/servlet/internal/jetty/JettyServiceFactory.java +++ /dev/null @@ -1,120 +0,0 @@ -package org.argeo.cms.servlet.internal.jetty; - -import java.util.Dictionary; -import java.util.Hashtable; -import java.util.Map; - -import javax.websocket.DeploymentException; -import javax.websocket.server.ServerContainer; -import javax.websocket.server.ServerEndpointConfig; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.websocket.javax.server.CmsWebSocketConfigurator; -import org.argeo.cms.websocket.javax.server.TestEndpoint; -import org.argeo.util.LangUtils; -import org.eclipse.equinox.http.jetty.JettyConfigurator; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; -import org.osgi.framework.ServiceReference; -import org.osgi.service.cm.ConfigurationException; -import org.osgi.service.cm.ManagedServiceFactory; -import org.osgi.util.tracker.ServiceTracker; - -@Deprecated -public class JettyServiceFactory implements ManagedServiceFactory { - private final static CmsLog log = CmsLog.getLog(JettyServiceFactory.class); - - final static String CMS_JETTY_CUSTOMIZER_CLASS = "org.argeo.equinox.jetty.CmsJettyCustomizer"; - // Argeo specific - final static String WEBSOCKET_ENABLED = "websocket.enabled"; - - private final BundleContext bc = FrameworkUtil.getBundle(JettyServiceFactory.class).getBundleContext(); - - public void start() { - ServiceTracker serverSt = new ServiceTracker( - bc, ServerContainer.class, null) { - - @Override - public ServerContainer addingService(ServiceReference reference) { - ServerContainer serverContainer = super.addingService(reference); - - BundleContext bc = reference.getBundle().getBundleContext(); - ServiceReference srConfigurator = bc - .getServiceReference(ServerEndpointConfig.Configurator.class); - ServerEndpointConfig.Configurator endpointConfigurator = bc.getService(srConfigurator); - ServerEndpointConfig config = ServerEndpointConfig.Builder - .create(TestEndpoint.class, "/ws/test/events/").configurator(endpointConfigurator).build(); - try { - serverContainer.addEndpoint(config); - } catch (DeploymentException e) { - throw new IllegalStateException("Cannot initalise the WebSocket server runtime.", e); - } - return serverContainer; - } - - }; - serverSt.open(); - } - - @Override - public String getName() { - return "Jetty Service Factory"; - } - - @Override - public void updated(String pid, Dictionary properties) throws ConfigurationException { - // Explicitly configures Jetty so that the default server is not started by the - // activator of the Equinox Jetty bundle. - Map config = LangUtils.dictToStringMap(properties); - if (!config.isEmpty()) { - config.put("customizer.class", CMS_JETTY_CUSTOMIZER_CLASS); - - // TODO centralise with Jetty extender - Object webSocketEnabled = config.get(WEBSOCKET_ENABLED); - if (webSocketEnabled != null && webSocketEnabled.toString().equals("true")) { - bc.registerService(ServerEndpointConfig.Configurator.class, new CmsWebSocketConfigurator(), null); - config.put(WEBSOCKET_ENABLED, "true"); - } - } - - int tryCount = 60; - try { - tryGettyJetty: while (tryCount > 0) { - try { - // FIXME deal with multiple ids - JettyConfigurator.startServer(CmsConstants.DEFAULT, new Hashtable<>(config)); - // Explicitly starts Jetty OSGi HTTP bundle, so that it gets triggered if OSGi - // configuration is not cleaned - FrameworkUtil.getBundle(JettyConfigurator.class).start(); - break tryGettyJetty; - } catch (IllegalStateException e) { - // Jetty may not be ready - try { - Thread.sleep(1000); - } catch (Exception e1) { - // silent - } - tryCount--; - } - } - } catch (Exception e) { - log.error("Cannot start default Jetty server with config " + properties, e); - } - - } - - @Override - public void deleted(String pid) { - } - - public void stop() { - try { - JettyConfigurator.stopServer(CmsConstants.DEFAULT); - } catch (Exception e) { - log.error("Cannot stop default Jetty server.", e); - } - - } - -} diff --git a/org.argeo.cms.lib.equinox/src/org/argeo/equinox/jetty/CmsJettyCustomizer.java b/org.argeo.cms.lib.equinox/src/org/argeo/equinox/jetty/CmsJettyCustomizer.java index ab291b545..7be23fc0f 100644 --- a/org.argeo.cms.lib.equinox/src/org/argeo/equinox/jetty/CmsJettyCustomizer.java +++ b/org.argeo.cms.lib.equinox/src/org/argeo/equinox/jetty/CmsJettyCustomizer.java @@ -52,11 +52,11 @@ public class CmsJettyCustomizer extends JettyCustomizer { if (httpsConnector != null) for (ConnectionFactory connectionFactory : httpsConnector.getConnectionFactories()) { if (connectionFactory instanceof SslConnectionFactory) { - SslContextFactory.Server sslConnectionFactory = ((SslConnectionFactory) connectionFactory) + SslContextFactory.Server sslContextFactory = ((SslConnectionFactory) connectionFactory) .getSslContextFactory(); - sslConnectionFactory.setTrustStorePath((String) settings.get(SSL_TRUSTSTORE)); - sslConnectionFactory.setTrustStoreType((String) settings.get(SSL_TRUSTSTORETYPE)); - sslConnectionFactory.setTrustStorePassword((String) settings.get(SSL_TRUSTSTOREPASSWORD)); + sslContextFactory.setTrustStorePath((String) settings.get(SSL_TRUSTSTORE)); + sslContextFactory.setTrustStoreType((String) settings.get(SSL_TRUSTSTORETYPE)); + sslContextFactory.setTrustStorePassword((String) settings.get(SSL_TRUSTSTOREPASSWORD)); } } return super.customizeHttpsConnector(connector, settings); diff --git a/org.argeo.cms.lib.jetty/.settings/org.eclipse.jdt.core.prefs b/org.argeo.cms.lib.jetty/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index 62ef3488c..000000000 --- a/org.argeo.cms.lib.jetty/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,9 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 -org.eclipse.jdt.core.compiler.compliance=17 -org.eclipse.jdt.core.compiler.problem.assertIdentifier=error -org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled -org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning -org.eclipse.jdt.core.compiler.release=enabled -org.eclipse.jdt.core.compiler.source=17 diff --git a/org.argeo.cms.lib.jetty/.settings/org.eclipse.pde.core.prefs b/org.argeo.cms.lib.jetty/.settings/org.eclipse.pde.core.prefs deleted file mode 100644 index e8ff8be0b..000000000 --- a/org.argeo.cms.lib.jetty/.settings/org.eclipse.pde.core.prefs +++ /dev/null @@ -1,4 +0,0 @@ -eclipse.preferences.version=1 -pluginProject.equinox=false -pluginProject.extensions=false -resolve.requirebundle=false diff --git a/org.argeo.cms.lib.jetty/bnd.bnd b/org.argeo.cms.lib.jetty/bnd.bnd index d92601739..171059dfa 100644 --- a/org.argeo.cms.lib.jetty/bnd.bnd +++ b/org.argeo.cms.lib.jetty/bnd.bnd @@ -1,9 +1,5 @@ Import-Package: \ javax.servlet.http,\ -org.eclipse.jetty.util.component;version="[9.4,12)";resolution:=optional,\ -org.eclipse.jetty.http;version="[9.4,12)";resolution:=optional,\ -org.eclipse.jetty.io;version="[9.4,12)";resolution:=optional,\ -org.eclipse.jetty.security;version="[9.4,12)";resolution:=optional,\ -org.eclipse.jetty.server.handler;version="[9.4,12)";resolution:=optional,\ -org.eclipse.jetty.*;version="[9.4,12)";resolution:=optional,\ +org.eclipse.jetty.server.handler,\ +org.eclipse.jetty.util.component,\ * \ No newline at end of file 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 new file mode 100644 index 000000000..39fc66d9d --- /dev/null +++ b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/CmsJettyServer.java @@ -0,0 +1,247 @@ +package org.argeo.cms.jetty; + +import java.nio.file.Files; +import java.nio.file.Path; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.websocket.DeploymentException; +import javax.websocket.server.ServerContainer; +import javax.websocket.server.ServerEndpointConfig; + +import org.argeo.api.cms.CmsLog; +import org.argeo.api.cms.CmsState; +import org.argeo.cms.CmsDeployProperty; +import org.argeo.cms.websocket.javax.server.CmsWebSocketConfigurator; +import org.argeo.cms.websocket.javax.server.TestEndpoint; +import org.eclipse.jetty.http.UriCompliance; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.SecureRequestCustomizer; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.SslConnectionFactory; +import org.eclipse.jetty.server.session.SessionHandler; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.eclipse.jetty.util.thread.QueuedThreadPool; + +import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer; +import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer.Configurator; + +public class CmsJettyServer { + private static final CmsLog log = CmsLog.getLog(CmsJettyServer.class); + + private static final int DEFAULT_IDLE_TIMEOUT = 30000; + private static final String CONTEXT_TEMPDIR = "javax.servlet.context.tempdir"; + + // Equinox compatibility + private static final String INTERNAL_CONTEXT_CLASSLOADER = "org.eclipse.equinox.http.jetty.internal.ContextClassLoader"; + + private Server server; + private Path tempDir; + + private ServerConnector httpConnector; + private ServerConnector httpsConnector; + + // WebSocket + private ServerContainer wsServerContainer; + private ServerEndpointConfig.Configurator wsEndpointConfigurator; + + private CmsState cmsState; + + public void start() { + try { + tempDir = Files.createTempDirectory("jetty"); + + server = new Server(new QueuedThreadPool(10, 1)); + + configure(); + // context.addServlet(new ServletHolder(new RWTServlet()), "/" + entryPoint); + // Required to serve rwt-resources. It is important that this is last. +// ServletHolder holderPwd = new ServletHolder("default", DefaultServlet.class); +// context.addServlet(holderPwd, "/"); + + if (httpConnector != null) { + httpConnector.open(); + server.addConnector(httpConnector); + } + + if (httpsConnector != null) { + httpsConnector.open(); + server.addConnector(httpsConnector); + } + + // holder + + // context + ServletContextHandler httpContext = createHttpContext(); + // httpContext.addServlet(holder, "/*"); + addServlets(httpContext); + enableWebSocket(httpContext); + server.setHandler(httpContext); + + // + // START + server.start(); + // + + Runtime.getRuntime().addShutdownHook(new Thread(() -> stop(), "Jetty shutdown")); + + log.info(httpPortsMsg()); + } catch (Exception e) { + throw new IllegalStateException("Cannot start Jetty HTTPS server", e); + } + } + + protected void addServlets(ServletContextHandler servletContextHandler) throws ServletException { + } + + public Integer getHttpPort() { + if (httpConnector == null) + return null; + return httpConnector.getLocalPort(); + } + + public Integer getHttpsPort() { + if (httpsConnector == null) + return null; + return httpsConnector.getLocalPort(); + } + + public void stop() { + try { + // serverConnector.close(); + server.stop(); + // TODO delete temp dir + } catch (Exception e) { + e.printStackTrace(); + } + + } + + protected void configure() { + HttpConfiguration http_config = new HttpConfiguration(); + + String httpPortStr = getFrameworkProp(CmsDeployProperty.HTTP_PORT); + String httpsPortStr = getFrameworkProp(CmsDeployProperty.HTTPS_PORT); + + /// TODO make it more generic + String httpHost = getFrameworkProp(CmsDeployProperty.HOST); +// String httpsHost = getFrameworkProp( +// JettyConfig.JETTY_PROPERTY_PREFIX + CmsHttpConstants.HTTPS_HOST); + + // try { + if (httpPortStr != null || httpsPortStr != null) { + boolean httpEnabled = httpPortStr != null; + // props.put(JettyHttpConstants.HTTP_ENABLED, httpEnabled); + boolean httpsEnabled = httpsPortStr != null; + // props.put(JettyHttpConstants.HTTPS_ENABLED, httpsEnabled); + if (httpsEnabled) { + int httpsPort = Integer.parseInt(httpsPortStr); + http_config.setSecureScheme("https"); + http_config.setSecurePort(httpsPort); + } + + if (httpEnabled) { + int httpPort = Integer.parseInt(httpPortStr); + httpConnector = new ServerConnector(server, new HttpConnectionFactory(http_config)); + httpConnector.setPort(httpPort); + httpConnector.setHost(httpHost); + httpConnector.setIdleTimeout(DEFAULT_IDLE_TIMEOUT); + } + + if (httpsEnabled) { + + SslContextFactory.Server sslContextFactory = new SslContextFactory.Server(); + // sslContextFactory.setKeyStore(KeyS) + + sslContextFactory.setKeyStoreType(getFrameworkProp(CmsDeployProperty.SSL_KEYSTORETYPE)); + sslContextFactory.setKeyStorePath(getFrameworkProp(CmsDeployProperty.SSL_KEYSTORE)); + sslContextFactory.setKeyStorePassword(getFrameworkProp(CmsDeployProperty.SSL_PASSWORD)); + // sslContextFactory.setKeyManagerPassword(getFrameworkProp(CmsDeployProperty.SSL_KEYPASSWORD)); + sslContextFactory.setProtocol("TLS"); + + sslContextFactory.setTrustStoreType(getFrameworkProp(CmsDeployProperty.SSL_TRUSTSTORETYPE)); + sslContextFactory.setTrustStorePath(getFrameworkProp(CmsDeployProperty.SSL_TRUSTSTORE)); + sslContextFactory.setTrustStorePassword(getFrameworkProp(CmsDeployProperty.SSL_TRUSTSTOREPASSWORD)); + + String wantClientAuth = getFrameworkProp(CmsDeployProperty.SSL_WANTCLIENTAUTH); + if (wantClientAuth != null && wantClientAuth.equals(Boolean.toString(true))) + sslContextFactory.setWantClientAuth(true); + String needClientAuth = getFrameworkProp(CmsDeployProperty.SSL_NEEDCLIENTAUTH); + if (needClientAuth != null && needClientAuth.equals(Boolean.toString(true))) + sslContextFactory.setNeedClientAuth(true); + + // HTTPS Configuration + HttpConfiguration https_config = new HttpConfiguration(http_config); + 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 void enableWebSocket(ServletContextHandler servletContextHandler) { + String webSocketEnabled = getFrameworkProp(CmsDeployProperty.WEBSOCKET_ENABLED); + // web socket + if (webSocketEnabled != null && webSocketEnabled.equals(Boolean.toString(true))) { + JavaxWebSocketServletContainerInitializer.configure(servletContextHandler, new Configurator() { + + @Override + public void accept(ServletContext servletContext, ServerContainer serverContainer) + throws DeploymentException { + wsServerContainer = serverContainer; + + wsEndpointConfigurator = new CmsWebSocketConfigurator(); + + ServerEndpointConfig config = ServerEndpointConfig.Builder + .create(TestEndpoint.class, "/ws/test/events/").configurator(wsEndpointConfigurator) + .build(); + try { + wsServerContainer.addEndpoint(config); + } catch (DeploymentException e) { + throw new IllegalStateException("Cannot initalise the WebSocket server runtime.", e); + } + } + }); + } + } + + protected ServletContextHandler createHttpContext() { + ServletContextHandler httpContext = new ServletContextHandler(); + httpContext.setAttribute(INTERNAL_CONTEXT_CLASSLOADER, Thread.currentThread().getContextClassLoader()); + httpContext.setClassLoader(this.getClass().getClassLoader()); + httpContext.setContextPath("/"); + + httpContext.setAttribute(CONTEXT_TEMPDIR, tempDir.toAbsolutePath().toFile()); + SessionHandler handler = new SessionHandler(); + handler.setMaxInactiveInterval(-1); + httpContext.setSessionHandler(handler); + + return httpContext; + } + + private String httpPortsMsg() { + + return (httpConnector != null ? "HTTP " + getHttpPort() + " " : " ") + + (httpsConnector != null ? "HTTPS " + getHttpsPort() : ""); + } + + private String getFrameworkProp(CmsDeployProperty deployProperty) { + return cmsState.getDeployProperty(deployProperty.getProperty()); + } + + public void setCmsState(CmsState cmsState) { + this.cmsState = cmsState; + } + +} diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/lib/jetty/CmsJettyServer.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/lib/jetty/CmsJettyServer.java deleted file mode 100644 index d197a00f7..000000000 --- a/org.argeo.cms.lib.jetty/src/org/argeo/cms/lib/jetty/CmsJettyServer.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.argeo.cms.lib.jetty; - -import java.nio.file.Path; - -import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.servlet.DefaultServlet; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; -import org.eclipse.jetty.util.thread.QueuedThreadPool; - -public class CmsJettyServer { - private Server server; - private ServerConnector serverConnector; - private Path tempDir; - - public void start() { - server = new Server(new QueuedThreadPool(10, 1)); - serverConnector = new ServerConnector(server); - serverConnector.setPort(0); - server.setConnectors(new Connector[] { serverConnector }); - - ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); - context.setContextPath("/"); - server.setHandler(context); - - //context.addServlet(new ServletHolder(new RWTServlet()), "/" + entryPoint); - - // Required to serve rwt-resources. It is important that this is last. - ServletHolder holderPwd = new ServletHolder("default", DefaultServlet.class); - context.addServlet(holderPwd, "/"); - - try { - server.start(); - } catch (Exception e) { - throw new IllegalStateException("Cannot start Jetty server", e); - } - Runtime.getRuntime().addShutdownHook(new Thread(() -> stop(), "Jetty shutdown")); - } - - public void stop() { - try { - serverConnector.close(); - server.stop(); - // TODO delete temp dir - } catch (Exception e) { - e.printStackTrace(); - } - - } -} diff --git a/swt/rap/org.argeo.cms.swt.rap.cli/src/org/argeo/cms/swt/rap/cli/CmsRapCli.java b/swt/rap/org.argeo.cms.swt.rap.cli/src/org/argeo/cms/swt/rap/cli/CmsRapCli.java index 8d0fff4f3..5191b7718 100644 --- a/swt/rap/org.argeo.cms.swt.rap.cli/src/org/argeo/cms/swt/rap/cli/CmsRapCli.java +++ b/swt/rap/org.argeo.cms.swt.rap.cli/src/org/argeo/cms/swt/rap/cli/CmsRapCli.java @@ -14,6 +14,8 @@ import org.argeo.api.cli.CommandsCli; import org.argeo.api.cli.DescribedCommand; import org.argeo.api.cms.CmsApp; import org.argeo.api.cms.CmsContext; +import org.argeo.api.cms.CmsState; +import org.argeo.cms.jetty.CmsJettyServer; import org.argeo.cms.runtime.StaticCms; import org.argeo.cms.swt.app.CmsUserApp; import org.argeo.cms.web.CmsWebApp; @@ -60,6 +62,7 @@ public class CmsRapCli extends CommandsCli { Path instancePath = Paths.get(dataPath); System.setProperty("osgi.instance.area", instancePath.toUri().toString()); + System.setProperty("argeo.http.port", "0"); StaticCms staticCms = new StaticCms() { @Override @@ -77,16 +80,17 @@ public class CmsRapCli extends CommandsCli { CmsWebApp cmsWebApp = new CmsWebApp(); Component cmsWebAppC = new Component.Builder<>(cmsWebApp) // .addType(ApplicationConfiguration.class) // + .addType(CmsWebApp.class) // .addDependency(cmsAppC.getType(CmsApp.class), cmsWebApp::setCmsApp, null) // .build(register); - RwtRunner rwtRunner = new RwtRunner(); - Component rwtRunnerC = new Component.Builder<>(rwtRunner) // - .addActivation(rwtRunner::init) // - .addDeactivation(rwtRunner::destroy) // - .addType(RwtRunner.class) // - .addDependency(cmsWebAppC.getType(ApplicationConfiguration.class), - rwtRunner::setApplicationConfiguration, null) // + RapJettyServer rwtRunner = new RapJettyServer(); + Component rwtRunnerC = new Component.Builder<>(rwtRunner) // + .addActivation(rwtRunner::start) // + .addDeactivation(rwtRunner::stop) // + .addType(CmsJettyServer.class) // + .addDependency(register.getSingleton(CmsState.class), rwtRunner::setCmsState, null) // + .addDependency(cmsWebAppC.getType(CmsWebApp.class), rwtRunner::setCmsWebApp, null) // .build(register); } } @@ -102,8 +106,9 @@ public class CmsRapCli extends CommandsCli { try { // open browser in app mode Thread.sleep(2000);// wait for RWT to be ready - Runtime.getRuntime().exec("google-chrome --app=http://localhost:" - + staticCms.getComponentRegister().getObject(RwtRunner.class).getEffectivePort() + "/data"); + String browserCommand = "google-chrome --app=http://localhost:" + + staticCms.getComponentRegister().getObject(CmsJettyServer.class).getHttpPort() + "/data"; + Runtime.getRuntime().exec(browserCommand); } catch (InterruptedException | IOException e) { e.printStackTrace(); } diff --git a/swt/rap/org.argeo.cms.swt.rap.cli/src/org/argeo/cms/swt/rap/cli/RapJettyServer.java b/swt/rap/org.argeo.cms.swt.rap.cli/src/org/argeo/cms/swt/rap/cli/RapJettyServer.java new file mode 100644 index 000000000..5b3337ace --- /dev/null +++ b/swt/rap/org.argeo.cms.swt.rap.cli/src/org/argeo/cms/swt/rap/cli/RapJettyServer.java @@ -0,0 +1,59 @@ +package org.argeo.cms.swt.rap.cli; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.ServletException; + +import org.argeo.cms.jetty.CmsJettyServer; +import org.argeo.cms.web.CmsWebApp; +import org.eclipse.jetty.servlet.DefaultServlet; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.rap.rwt.application.ApplicationRunner; +import org.eclipse.rap.rwt.engine.RWTServlet; + +public class RapJettyServer extends CmsJettyServer { + private CmsWebApp cmsWebApp; + + @Override + protected void addServlets(ServletContextHandler servletContextHandler) throws ServletException { + // rwt-resources requires a file system + try { + Path tempDir = Files.createTempDirectory("argeo-rwtRunner"); + servletContextHandler.setBaseResource(Resource.newResource(tempDir.resolve("www").toString())); + } catch (IOException e) { + throw new IllegalStateException("Cannot create temporary directory", e); + } + servletContextHandler.addEventListener(new ServletContextListener() { + ApplicationRunner applicationRunner; + + @Override + public void contextInitialized(ServletContextEvent sce) { + applicationRunner = new ApplicationRunner(cmsWebApp, sce.getServletContext()); + applicationRunner.start(); + } + + @Override + public void contextDestroyed(ServletContextEvent sce) { + applicationRunner.stop(); + } + }); + for (String uiName : cmsWebApp.getCmsApp().getUiNames()) + servletContextHandler.addServlet(new ServletHolder(new RWTServlet()), "/" + uiName); + + // Required to serve rwt-resources. It is important that this is last. + ServletHolder holderPwd = new ServletHolder("default", DefaultServlet.class); + servletContextHandler.addServlet(holderPwd, "/"); + + } + + public void setCmsWebApp(CmsWebApp cmsWebApp) { + this.cmsWebApp = cmsWebApp; + } + +} diff --git a/swt/rap/org.argeo.cms.swt.rap/.project b/swt/rap/org.argeo.cms.swt.rap/.project index 630160402..84a20aeb1 100644 --- a/swt/rap/org.argeo.cms.swt.rap/.project +++ b/swt/rap/org.argeo.cms.swt.rap/.project @@ -1,6 +1,6 @@ - org.argeo.cms.ui.rap + org.argeo.cms.swt.rap diff --git a/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsWebApp.java b/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsWebApp.java index 0270933c0..fec9e2485 100644 --- a/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsWebApp.java +++ b/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsWebApp.java @@ -113,7 +113,7 @@ public class CmsWebApp implements ApplicationConfiguration, ExceptionHandler, Cm // log.debug("Published CMS web app /" + (contextName != null ? contextName : "")); } - CmsApp getCmsApp() { + public CmsApp getCmsApp() { return cmsApp; }