Refactor Jetty HTTP server
[lgpl/argeo-commons.git] / org.argeo.cms.lib.jetty / src / org / argeo / cms / jetty / CmsJettyServer.java
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 (file)
index 0000000..39fc66d
--- /dev/null
@@ -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;
+       }
+
+}