Start supporting JDK HTTP server API
[lgpl/argeo-commons.git] / org.argeo.cms.lib.jetty / src / org / argeo / cms / jetty / JettyHttpServer.java
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
new file mode 100644 (file)
index 0000000..ea97057
--- /dev/null
@@ -0,0 +1,326 @@
+package org.argeo.cms.jetty;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ThreadPoolExecutor;
+
+import javax.servlet.ServletException;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.CmsDeployProperty;
+import org.argeo.util.http.HttpServerUtils;
+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.handler.ContextHandlerCollection;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.ExecutorThreadPool;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.eclipse.jetty.util.thread.ThreadPool;
+
+import com.sun.net.httpserver.HttpContext;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsServer;
+
+public class JettyHttpServer extends HttpsServer {
+       private final static CmsLog log = CmsLog.getLog(JettyHttpServer.class);
+
+       private static final int DEFAULT_IDLE_TIMEOUT = 30000;
+
+       private Server server;
+
+       protected ServerConnector httpConnector;
+       protected ServerConnector httpsConnector;
+
+       private InetSocketAddress address;
+
+       private ThreadPoolExecutor executor;
+
+       private HttpsConfigurator httpsConfigurator;
+
+       private final Map<String, JettyHttpContext> contexts = new TreeMap<>();
+
+       protected final ContextHandlerCollection contextHandlerCollection = new ContextHandlerCollection();
+
+       private boolean started;
+
+       @Override
+       public void bind(InetSocketAddress addr, int backlog) throws IOException {
+               throw new UnsupportedOperationException();
+       }
+
+       @Override
+       public void start() {
+               try {
+
+                       ThreadPool threadPool = null;
+                       if (executor != null) {
+                               threadPool = new ExecutorThreadPool(executor);
+                       } else {
+                               // TODO make it configurable
+                               threadPool = new QueuedThreadPool(10, 1);
+                       }
+
+                       server = new Server(threadPool);
+
+                       configureConnectors();
+
+                       if (httpConnector != null) {
+                               httpConnector.open();
+                               server.addConnector(httpConnector);
+                       }
+
+                       if (httpsConnector != null) {
+                               httpsConnector.open();
+                               server.addConnector(httpsConnector);
+                       }
+
+                       // holder
+
+                       // context
+                       ServletContextHandler rootContextHandler = createRootContextHandler();
+                       // httpContext.addServlet(holder, "/*");
+                       if (rootContextHandler != null)
+                               configureRootContextHandler(rootContextHandler);
+//                     server.setHandler(rootContextHandler);
+
+                       ContextHandlerCollection contextHandlers = new ContextHandlerCollection();
+                       if (rootContextHandler != null && !contexts.containsKey("/"))
+                               contextHandlers.addHandler(rootContextHandler);
+                       for (String contextPath : contexts.keySet()) {
+                               JettyHttpContext ctx = contexts.get(contextPath);
+                               contextHandlers.addHandler(ctx.getContextHandler());
+                       }
+
+                       server.setHandler(contextHandlerCollection);
+
+                       //
+                       // START
+                       server.start();
+                       //
+
+                       Runtime.getRuntime().addShutdownHook(new Thread(() -> stop(), "Jetty shutdown"));
+
+                       log.info(httpPortsMsg());
+                       started = true;
+               } catch (Exception e) {
+                       throw new IllegalStateException("Cannot start Jetty HTTPS server", e);
+               }
+       }
+
+       @Override
+       public void stop(int delay) {
+               // TODO wait for processing to complete
+               stop();
+
+       }
+
+       public void stop() {
+               try {
+                       // serverConnector.close();
+                       server.stop();
+                       // TODO delete temp dir
+                       started = false;
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
+
+       }
+
+       @Override
+       public void setExecutor(Executor executor) {
+               if (!(executor instanceof ThreadPoolExecutor))
+                       throw new IllegalArgumentException("Only " + ThreadPoolExecutor.class.getName() + " are supported");
+               this.executor = (ThreadPoolExecutor) executor;
+       }
+
+       @Override
+       public Executor getExecutor() {
+               return executor;
+       }
+
+       @Override
+       public synchronized HttpContext createContext(String path, HttpHandler handler) {
+               HttpContext httpContext = createContext(path);
+               httpContext.setHandler(handler);
+               return httpContext;
+       }
+
+       @Override
+       public synchronized HttpContext createContext(String path) {
+               if (contexts.containsKey(path))
+                       throw new IllegalArgumentException("Context " + path + " already exists");
+               JettyHttpContext httpContext = new JettyHttpContext(this, path);
+               contexts.put(path, httpContext);
+
+               contextHandlerCollection.addHandler(httpContext.getContextHandler());
+               return httpContext;
+       }
+
+       @Override
+       public synchronized void removeContext(String path) throws IllegalArgumentException {
+               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());
+       }
+
+       @Override
+       public synchronized void removeContext(HttpContext context) {
+               removeContext(context.getPath());
+       }
+
+       @Override
+       public InetSocketAddress getAddress() {
+               return address;
+       }
+
+       @Override
+       public void setHttpsConfigurator(HttpsConfigurator config) {
+               this.httpsConfigurator = config;
+       }
+
+       @Override
+       public HttpsConfigurator getHttpsConfigurator() {
+               return httpsConfigurator;
+       }
+
+       
+       
+       protected void configureConnectors() {
+               HttpConfiguration httpConfiguration = new HttpConfiguration();
+
+               String httpPortStr = getDeployProperty(CmsDeployProperty.HTTP_PORT);
+               String httpsPortStr = getDeployProperty(CmsDeployProperty.HTTPS_PORT);
+
+               /// TODO make it more generic
+               String httpHost = getDeployProperty(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);
+                               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 System.getProperty(deployProperty.getProperty());
+       }
+
+       private String httpPortsMsg() {
+
+               return (httpConnector != null ? "HTTP " + getHttpPort() + " " : " ")
+                               + (httpsConnector != null ? "HTTPS " + getHttpsPort() : "");
+       }
+
+       public Integer getHttpPort() {
+               if (httpConnector == null)
+                       return null;
+               return httpConnector.getLocalPort();
+       }
+
+       public Integer getHttpsPort() {
+               if (httpsConnector == null)
+                       return null;
+               return httpsConnector.getLocalPort();
+       }
+
+       protected ServletContextHandler createRootContextHandler() {
+               return null;
+       }
+
+       protected void configureRootContextHandler(ServletContextHandler servletContextHandler) throws ServletException {
+
+       }
+
+       
+       public boolean isStarted() {
+               return started;
+       }
+
+       public static void main(String... args) {
+               JettyHttpServer httpServer = new JettyHttpServer();
+               System.setProperty("argeo.http.port", "8080");
+               httpServer.createContext("/", (exchange) -> {
+                       exchange.getResponseBody().write("Hello World!".getBytes());
+               });
+               httpServer.start();
+               httpServer.createContext("/sub/context", (exchange) -> {
+                       final String key = "count";
+                       Integer count = (Integer) exchange.getHttpContext().getAttributes().get(key);
+                       if (count == null)
+                               exchange.getHttpContext().getAttributes().put(key, 0);
+                       else
+                               exchange.getHttpContext().getAttributes().put(key, count + 1);
+                       StringBuilder sb = new StringBuilder();
+                       sb.append("Subcontext:");
+                       sb.append(" " + key + "=" + exchange.getHttpContext().getAttributes().get(key));
+                       sb.append(" relativePath=" + HttpServerUtils.relativize(exchange));
+                       exchange.getResponseBody().write(sb.toString().getBytes());
+               });
+       }
+}